From 471fa0186910257defb7ab3fad65907e50699ee9 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 21 Mar 2022 15:49:10 -0500 Subject: [PATCH 01/40] feat(cli): scaffold out `astro add` command --- packages/astro/src/cli/add.ts | 122 ++++++++++++++++++++++++++++ packages/astro/src/cli/index.ts | 23 +++++- packages/astro/src/core/config.ts | 18 ++++ packages/astro/src/core/messages.ts | 7 ++ 4 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 packages/astro/src/cli/add.ts diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts new file mode 100644 index 000000000000..85fe3bb35663 --- /dev/null +++ b/packages/astro/src/cli/add.ts @@ -0,0 +1,122 @@ +import type yargs from 'yargs-parser'; +import path from 'path'; +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { resolveConfigURL } from '../core/config.js'; +import { apply as applyPolyfill } from '../core/polyfill.js'; +import { defaultLogOptions, error, info, debug, LogOptions, warn } from '../core/logger.js'; +import * as msg from '../core/messages.js'; +import { dim, red, cyan } from 'kleur/colors'; +import { parseNpmName } from '../core/util.js'; + +export interface AddOptions { + logging: LogOptions; + cwd?: string; + flags: yargs.Arguments; +} + +const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; + +export async function add(names: string[], { cwd, flags, logging }: AddOptions) { + if (names.length === 0) { + error(logging, null, `\n${red('No integration specified!')}\n${dim('Try using')} astro add ${cyan('[name]')}`); + return; + } + const root = cwd ? path.resolve(cwd) : process.cwd(); + let configURL = await resolveConfigURL({ cwd, flags }); + applyPolyfill(); + if (configURL) { + debug('add', `Found config at ${configURL}`); + } else { + info(logging, 'add', `Unable to locate a config file, generating one for you.`); + configURL = new URL('./astro.config.mjs', `file://${root}/`); + await fs.writeFile(fileURLToPath(configURL), DEFAULT_CONFIG_STUB, { encoding: 'utf-8' }); + } + + const integrations = await validateIntegrations(names); + let ast = await parseAstroConfig(configURL); + for (const integration of integrations) { + ast = await addIntegration(ast, integration, { logging }) + } + await updateAstroConfig(configURL, ast); + + const len = integrations.length; + info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`, `Be sure to re-install your dependencies before continuing!`)); +} + +async function parseAstroConfig(configURL: URL) { + const source = await fs.readFile(fileURLToPath(configURL)).then(res => res.toString()); + // TODO: parse source to AST + // const source = serialize(ast); + return source; +} + +function addIntegration(ast: any, integration: IntegrationInfo, { logging }: { logging: LogOptions }) { + // TODO: handle parsing astro config file, adding + return ast; +} + +async function updateAstroConfig(configURL: URL, ast: any) { + // TODO: serialize AST back to string; + // const source = serialize(ast); + // return await fs.writeFile(fileURLToPath(configURL), source, { encoding: 'utf-8' }); +} + +interface IntegrationInfo { + id: string; + packageName: string; + dependencies: string[]; +} + +export async function validateIntegrations(integrations: string[]): Promise { + const integrationEntries = await Promise.all( + integrations.map((integration) => { + const parsed = parseIntegrationName(integration); + if (!parsed) { + throw new Error(`${integration} does not appear to be a valid package name!`); + } + + let { scope = '', name, tag } = parsed; + // Allow third-party integrations starting with `astro-` namespace + if (!name.startsWith('astro-')) { + scope = `astrojs`; + } + const packageName = `${scope ? `@${scope}/` : ''}${name}`; + return fetch(`https://registry.npmjs.org/${packageName}/${tag}`) + .then((res) => { + if (res.status === 404) { + throw new Error(`Unable to fetch ${packageName}. Does this package exist?`); + } + return res.json(); + }) + .then((res: any) => { + let dependencies: [string, string][] = [[res['name'], `^${res['version']}`]]; + + if (res['peerDependencies']) { + for (const peer in res['peerDependencies']) { + dependencies.push([peer, res['peerDependencies'][peer]]); + } + } + + return { id: integration, packageName, dependencies: dependencies.flat(1) }; + }); + }) + ); + return integrationEntries; +} + +function parseIntegrationName(spec: string) { + const result = 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]; + } + return { scope, name, tag }; +} diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index ffa1071ccc77..b23a8b924fe6 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -11,11 +11,12 @@ import build from '../core/build/index.js'; import devServer from '../core/dev/index.js'; import preview from '../core/preview/index.js'; import { check } from './check.js'; +import { add } from './add.js'; import { formatConfigError, loadConfig } from '../core/config.js'; import { pad } from '../core/dev/util.js'; type Arguments = yargs.Arguments; -type CLICommand = 'help' | 'version' | 'dev' | 'build' | 'preview' | 'reload' | 'check'; +type CLICommand = 'help' | 'version' | 'add' | 'dev' | 'build' | 'preview' | 'reload' | 'check'; /** Display --help flag */ function printHelp() { @@ -25,6 +26,7 @@ function printHelp() { title('Commands'); table( [ + ['add', 'Add an integration to your configuration.'], ['dev', 'Run Astro in development mode.'], ['build', 'Build a pre-compiled production-ready site.'], ['preview', 'Preview your build locally before deploying.'], @@ -99,9 +101,9 @@ function resolveCommand(flags: Arguments): CLICommand { return 'help'; } const cmd = flags._[2] as string; - const supportedCommands = new Set(['dev', 'build', 'preview', 'check']); + const supportedCommands = new Set(['add', 'dev', 'build', 'preview', 'check']); if (supportedCommands.has(cmd)) { - return cmd as 'dev' | 'build' | 'preview' | 'check'; + return cmd as 'add' | 'dev' | 'build' | 'preview' | 'check'; } return 'help'; } @@ -110,7 +112,10 @@ function resolveCommand(flags: Arguments): CLICommand { export async function cli(args: string[]) { const flags = yargs(args); const cmd = resolveCommand(flags); - const projectRoot = flags.projectRoot || flags._[3]; + let projectRoot = flags.projectRoot; + if (!projectRoot && cmd !== 'add') { + projectRoot = flags._[3]; + } switch (cmd) { case 'help': @@ -133,6 +138,16 @@ export async function cli(args: string[]) { logging.level = 'silent'; } + if (cmd === 'add') { + try { + const packages = flags._.slice(3) as string[]; + await add(packages, { cwd: projectRoot, flags, logging }); + process.exit(0); + } catch (err) { + throwAndExit(err); + } + } + let config: AstroConfig; try { config = await loadConfig({ cwd: projectRoot, flags }); diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 26ebc09311b6..dc7bacc5ddf5 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -266,6 +266,24 @@ interface LoadConfigOptions { flags?: Flags; } +/** Resolve the file URL of the user's `astro.config.js|cjs|mjs|ts` file */ +export async function resolveConfigURL(configOptions: LoadConfigOptions): Promise { + const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd(); + const flags = resolveFlags(configOptions.flags || {}); + let userConfigPath: string | undefined; + + if (flags?.config) { + userConfigPath = /^\.*\//.test(flags.config) ? flags.config : `./${flags.config}`; + userConfigPath = fileURLToPath(new URL(userConfigPath, `file://${root}/`)); + } + // Automatically load config file using Proload + // If `userConfigPath` is `undefined`, Proload will search for `astro.config.[cm]?[jt]s` + const config = await load('astro', { mustExist: false, cwd: root, filePath: userConfigPath }); + if (config) { + return pathToFileURL(config.filePath); + } +} + /** Attempt to load an `astro.config.mjs` file */ export async function loadConfig(configOptions: LoadConfigOptions): Promise { const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd(); diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index c84a454d122c..224654824d96 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -87,6 +87,13 @@ export function prerelease({ currentVersion }: { currentVersion: string }) { return [headline, warning, ''].map((msg) => ` ${msg}`).join('\n'); } +export function success(message: string, tip?: string) { + const badge = bgGreen(black(` success `)); + const headline = green(message); + const footer = tip ? `\n ▶ ${tip}` : '' + return ['', badge, headline, footer].map((msg) => ` ${msg}`).join('\n'); +} + /** Display port in use */ export function portInUse({ port }: { port: number }): string { return `Port ${port} in use. Trying a new one…`; From ea0793f13e9622afa5e06cfa840f14f8a7ea40a3 Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Mon, 21 Mar 2022 23:37:30 -0300 Subject: [PATCH 02/40] added first babel transforms --- packages/astro/package.json | 4 ++ packages/astro/src/cli/add.ts | 71 ++++++++++++++++++++----- packages/astro/src/transform/babel.ts | 12 +++++ packages/astro/src/transform/imports.ts | 24 +++++++++ packages/astro/src/transform/index.ts | 3 ++ packages/astro/src/transform/wrapper.ts | 11 ++++ pnpm-lock.yaml | 8 +++ 7 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 packages/astro/src/transform/babel.ts create mode 100644 packages/astro/src/transform/imports.ts create mode 100644 packages/astro/src/transform/index.ts create mode 100644 packages/astro/src/transform/wrapper.ts diff --git a/packages/astro/package.json b/packages/astro/package.json index 23926f8d0b33..9057f9daf019 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -79,6 +79,9 @@ "@astrojs/prism": "0.4.1-next.0", "@astrojs/webapi": "^0.11.0", "@babel/core": "^7.17.8", + "@babel/generator": "^7.17.7", + "@babel/parser": "^7.17.8", + "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.3", "@proload/core": "^0.2.2", "@proload/plugin-tsm": "^0.1.1", @@ -126,6 +129,7 @@ "devDependencies": { "@babel/types": "^7.17.0", "@types/babel__core": "^7.1.19", + "@types/babel__generator": "^7.6.4", "@types/babel__traverse": "^7.14.2", "@types/chai": "^4.3.0", "@types/common-ancestor-path": "^1.0.0", diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index 85fe3bb35663..6d683e9315a3 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -8,6 +8,7 @@ import { defaultLogOptions, error, info, debug, LogOptions, warn } from '../core import * as msg from '../core/messages.js'; import { dim, red, cyan } from 'kleur/colors'; import { parseNpmName } from '../core/util.js'; +import { t, parse, visit, ensureImport, wrapDefaultExport, generate } from '../transform/index.js'; export interface AddOptions { logging: LogOptions; @@ -35,31 +36,73 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) const integrations = await validateIntegrations(names); let ast = await parseAstroConfig(configURL); + + const defineConfig = t.identifier('defineConfig'); + ensureImport(ast, t.importDeclaration([t.importSpecifier(defineConfig, defineConfig)], t.stringLiteral('astro/config'))); + wrapDefaultExport(ast, defineConfig); + for (const integration of integrations) { - ast = await addIntegration(ast, integration, { logging }) + await addIntegration(ast, integration, { logging }); } + await updateAstroConfig(configURL, ast); - + const len = integrations.length; info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`, `Be sure to re-install your dependencies before continuing!`)); } -async function parseAstroConfig(configURL: URL) { - const source = await fs.readFile(fileURLToPath(configURL)).then(res => res.toString()); - // TODO: parse source to AST - // const source = serialize(ast); - return source; +async function parseAstroConfig(configURL: URL): Promise { + const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); + const result = parse(source, { sourceType: 'unambiguous', plugins: ['typescript'] }); + + if (!result) throw new Error('Unknown error parsing astro config'); + if (result.errors.length > 0) throw new Error('Error parsing astro config: ' + JSON.stringify(result.errors)); + + return result.program; } -function addIntegration(ast: any, integration: IntegrationInfo, { logging }: { logging: LogOptions }) { - // TODO: handle parsing astro config file, adding - return ast; +async function addIntegration(ast: t.Program, integration: IntegrationInfo, { logging }: { logging: LogOptions }) { + const integrationId = t.identifier(integration.id); + + ensureImport(ast, t.importDeclaration([t.importDefaultSpecifier(integrationId)], t.stringLiteral(integration.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 integrationsProp = configObject.properties.find((prop) => { + if (prop.type !== 'ObjectProperty') return false; + if (prop.key.type === 'Identifier') { + if (prop.key.name === 'integrations') return true; + } + if (prop.key.type === 'StringLiteral') { + if (prop.key.value === 'integrations') return true; + } + return false; + }) as t.ObjectProperty | undefined; + + const integrationCall = t.callExpression(integrationId, []); + + if (!integrationsProp) { + configObject.properties.push(t.objectProperty(t.identifier('integrations'), t.arrayExpression([integrationCall]))); + return; + } + + if (integrationsProp.value.type !== 'ArrayExpression') return; + + integrationsProp.value.elements.push(integrationCall); + }, + }); } -async function updateAstroConfig(configURL: URL, ast: any) { - // TODO: serialize AST back to string; - // const source = serialize(ast); - // return await fs.writeFile(fileURLToPath(configURL), source, { encoding: 'utf-8' }); +async function updateAstroConfig(configURL: URL, ast: t.Program) { + const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); + const output = generate(ast, {}, source); + await fs.writeFile(fileURLToPath(configURL), output.code, { encoding: 'utf-8' }); } interface IntegrationInfo { diff --git a/packages/astro/src/transform/babel.ts b/packages/astro/src/transform/babel.ts new file mode 100644 index 000000000000..a436e353ba5d --- /dev/null +++ b/packages/astro/src/transform/babel.ts @@ -0,0 +1,12 @@ +import traverse from '@babel/traverse'; +import generator from '@babel/generator'; + +// @ts-ignore @babel/traverse isn't ESM and needs this trick +export const visit = traverse.default as typeof traverse; + +// @ts-ignore @babel/generator isn't ESM and needs this trick +export const generate = generator.default as typeof generator; + +export * as t from '@babel/types'; +export { parse } from '@babel/parser'; +export { default as template } from '@babel/parser'; diff --git a/packages/astro/src/transform/imports.ts b/packages/astro/src/transform/imports.ts new file mode 100644 index 000000000000..497d21a9bd4f --- /dev/null +++ b/packages/astro/src/transform/imports.ts @@ -0,0 +1,24 @@ +import { t, visit } from './babel.js'; + +export function ensureImport(root: t.Program, importDeclaration: t.ImportDeclaration) { + let specifiersToFind = [...importDeclaration.specifiers]; + + visit(root, { + ImportDeclaration(path) { + if (path.node.source.value === importDeclaration.source.value) { + path.node.specifiers.forEach((specifier) => + specifiersToFind.forEach((specifierToFind, i) => { + if (specifier.type !== specifierToFind.type) return; + if (specifier.local.name === specifierToFind.local.name) { + specifiersToFind.splice(i, 1); + } + }) + ); + } + }, + }); + + if (specifiersToFind.length === 0) return; + + root.body.unshift(t.importDeclaration(specifiersToFind, importDeclaration.source)); +} diff --git a/packages/astro/src/transform/index.ts b/packages/astro/src/transform/index.ts new file mode 100644 index 000000000000..fd2f7995b80f --- /dev/null +++ b/packages/astro/src/transform/index.ts @@ -0,0 +1,3 @@ +export * from './babel.js'; +export * from './imports.js'; +export * from './wrapper.js'; diff --git a/packages/astro/src/transform/wrapper.ts b/packages/astro/src/transform/wrapper.ts new file mode 100644 index 000000000000..c75cedcea1ee --- /dev/null +++ b/packages/astro/src/transform/wrapper.ts @@ -0,0 +1,11 @@ +import { t, visit } from './babel.js'; + +export function wrapDefaultExport(ast: t.Program, functionIdentifier: t.Identifier) { + visit(ast, { + ExportDefaultDeclaration(path) { + if (!t.isExpression(path.node.declaration)) return; + if (t.isCallExpression(path.node.declaration) && t.isIdentifier(path.node.declaration.callee) && path.node.declaration.callee.name === functionIdentifier.name) return; + path.node.declaration = t.callExpression(functionIdentifier, [path.node.declaration]); + }, + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 20e1d39d9eef..a5480b6c36c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -448,11 +448,15 @@ importers: '@astrojs/prism': 0.4.1-next.0 '@astrojs/webapi': ^0.11.0 '@babel/core': ^7.17.8 + '@babel/generator': ^7.17.7 + '@babel/parser': ^7.17.8 + '@babel/template': ^7.16.7 '@babel/traverse': ^7.17.3 '@babel/types': ^7.17.0 '@proload/core': ^0.2.2 '@proload/plugin-tsm': ^0.1.1 '@types/babel__core': ^7.1.19 + '@types/babel__generator': ^7.6.4 '@types/babel__traverse': ^7.14.2 '@types/chai': ^4.3.0 '@types/common-ancestor-path': ^1.0.0 @@ -520,6 +524,9 @@ importers: '@astrojs/prism': link:../astro-prism '@astrojs/webapi': link:../webapi '@babel/core': 7.17.8 + '@babel/generator': 7.17.7 + '@babel/parser': 7.17.8 + '@babel/template': 7.16.7 '@babel/traverse': 7.17.3 '@proload/core': 0.2.2 '@proload/plugin-tsm': 0.1.1_@proload+core@0.2.2 @@ -566,6 +573,7 @@ importers: devDependencies: '@babel/types': 7.17.0 '@types/babel__core': 7.1.19 + '@types/babel__generator': 7.6.4 '@types/babel__traverse': 7.14.2 '@types/chai': 4.3.0 '@types/common-ancestor-path': 1.0.0 From 4a955bcbb0edad1024ef11d3229bac6024cbb153 Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 00:23:34 -0300 Subject: [PATCH 03/40] Format output --- packages/astro/package.json | 2 ++ packages/astro/src/cli/add.ts | 15 +++++++------ packages/astro/src/transform/babel.ts | 28 ++++++++++++++++++++----- packages/astro/src/transform/imports.ts | 16 ++++++++++++-- packages/astro/src/transform/wrapper.ts | 2 +- pnpm-lock.yaml | 9 +++++++- 6 files changed, 55 insertions(+), 17 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 9057f9daf019..9dfc5ad54d3d 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -106,6 +106,7 @@ "path-to-regexp": "^6.2.0", "postcss": "^8.4.12", "postcss-load-config": "^3.1.3", + "prettier": "^2.6.0", "prismjs": "^1.27.0", "rehype-slug": "^5.0.1", "resolve": "^1.22.0", @@ -140,6 +141,7 @@ "@types/mime": "^2.0.3", "@types/mocha": "^9.1.0", "@types/parse5": "^6.0.3", + "@types/prettier": "^2.4.4", "@types/resolve": "^1.20.1", "@types/rimraf": "^3.0.2", "@types/send": "^0.17.1", diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index 6d683e9315a3..0d173f753fda 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -51,17 +51,17 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`, `Be sure to re-install your dependencies before continuing!`)); } -async function parseAstroConfig(configURL: URL): Promise { +async function parseAstroConfig(configURL: URL): Promise { const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); - const result = parse(source, { sourceType: 'unambiguous', plugins: ['typescript'] }); + const result = parse(source); if (!result) throw new Error('Unknown error parsing astro config'); if (result.errors.length > 0) throw new Error('Error parsing astro config: ' + JSON.stringify(result.errors)); - return result.program; + return result; } -async function addIntegration(ast: t.Program, integration: IntegrationInfo, { logging }: { logging: LogOptions }) { +async function addIntegration(ast: t.File, integration: IntegrationInfo, { logging }: { logging: LogOptions }) { const integrationId = t.identifier(integration.id); ensureImport(ast, t.importDeclaration([t.importDefaultSpecifier(integrationId)], t.stringLiteral(integration.packageName))); @@ -99,10 +99,9 @@ async function addIntegration(ast: t.Program, integration: IntegrationInfo, { lo }); } -async function updateAstroConfig(configURL: URL, ast: t.Program) { - const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); - const output = generate(ast, {}, source); - await fs.writeFile(fileURLToPath(configURL), output.code, { encoding: 'utf-8' }); +async function updateAstroConfig(configURL: URL, ast: t.File) { + const output = await generate(ast, fileURLToPath(configURL)); + await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); } interface IntegrationInfo { diff --git a/packages/astro/src/transform/babel.ts b/packages/astro/src/transform/babel.ts index a436e353ba5d..902b54425927 100644 --- a/packages/astro/src/transform/babel.ts +++ b/packages/astro/src/transform/babel.ts @@ -1,12 +1,30 @@ import traverse from '@babel/traverse'; import generator from '@babel/generator'; +import * as t from '@babel/types'; +import parser from '@babel/parser'; +import prettier from 'prettier'; // @ts-ignore @babel/traverse isn't ESM and needs this trick export const visit = traverse.default as typeof traverse; -// @ts-ignore @babel/generator isn't ESM and needs this trick -export const generate = generator.default as typeof generator; +export { t }; +export { default as template } from '@babel/template'; -export * as t from '@babel/types'; -export { parse } from '@babel/parser'; -export { default as template } from '@babel/parser'; +export async function generate(ast: t.File, configURL?: string) { + // @ts-ignore @babel/generator isn't ESM and needs this trick + const astToText = generator.default as typeof generator; + + const text = astToText(ast, { retainLines: true }).code; + + const prettierOptions = await prettier.resolveConfig(configURL || process.cwd()); + const formatted = prettier.format(text, { + singleQuote: true, + semi: true, + ...prettierOptions, + parser: 'babel-ts', + }); + + return formatted; +} + +export const parse = (code: string) => parser.parse(code, { sourceType: 'unambiguous', plugins: ['typescript'] }); diff --git a/packages/astro/src/transform/imports.ts b/packages/astro/src/transform/imports.ts index 497d21a9bd4f..8aef09d209c4 100644 --- a/packages/astro/src/transform/imports.ts +++ b/packages/astro/src/transform/imports.ts @@ -1,6 +1,6 @@ import { t, visit } from './babel.js'; -export function ensureImport(root: t.Program, importDeclaration: t.ImportDeclaration) { +export function ensureImport(root: t.File, importDeclaration: t.ImportDeclaration) { let specifiersToFind = [...importDeclaration.specifiers]; visit(root, { @@ -20,5 +20,17 @@ export function ensureImport(root: t.Program, importDeclaration: t.ImportDeclara if (specifiersToFind.length === 0) return; - root.body.unshift(t.importDeclaration(specifiersToFind, importDeclaration.source)); + visit(root, { + Program(path) { + const declaration = t.importDeclaration(specifiersToFind, importDeclaration.source); + const latestImport = path + .get('body') + .filter((statement) => statement.isImportDeclaration()) + .pop(); + + // It's inserted before because of formatting issues + if (latestImport) latestImport.insertBefore(declaration); + else path.unshiftContainer('body', declaration); + }, + }); } diff --git a/packages/astro/src/transform/wrapper.ts b/packages/astro/src/transform/wrapper.ts index c75cedcea1ee..a8f6b3bc847f 100644 --- a/packages/astro/src/transform/wrapper.ts +++ b/packages/astro/src/transform/wrapper.ts @@ -1,6 +1,6 @@ import { t, visit } from './babel.js'; -export function wrapDefaultExport(ast: t.Program, functionIdentifier: t.Identifier) { +export function wrapDefaultExport(ast: t.File, functionIdentifier: t.Identifier) { visit(ast, { ExportDefaultDeclaration(path) { if (!t.isExpression(path.node.declaration)) return; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5480b6c36c7..902b4ece0d31 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -467,6 +467,7 @@ importers: '@types/mime': ^2.0.3 '@types/mocha': ^9.1.0 '@types/parse5': ^6.0.3 + '@types/prettier': ^2.4.4 '@types/resolve': ^1.20.1 '@types/rimraf': ^3.0.2 '@types/send': ^0.17.1 @@ -497,6 +498,7 @@ importers: path-to-regexp: ^6.2.0 postcss: ^8.4.12 postcss-load-config: ^3.1.3 + prettier: ^2.6.0 prismjs: ^1.27.0 rehype-slug: ^5.0.1 resolve: ^1.22.0 @@ -551,6 +553,7 @@ importers: path-to-regexp: 6.2.0 postcss: 8.4.12 postcss-load-config: 3.1.3 + prettier: 2.6.0 prismjs: 1.27.0 rehype-slug: 5.0.1 resolve: 1.22.0 @@ -584,6 +587,7 @@ importers: '@types/mime': 2.0.3 '@types/mocha': 9.1.0 '@types/parse5': 6.0.3 + '@types/prettier': 2.4.4 '@types/resolve': 1.20.1 '@types/rimraf': 3.0.2 '@types/send': 0.17.1 @@ -3944,6 +3948,10 @@ packages: /@types/parse5/6.0.3: resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} + /@types/prettier/2.4.4: + resolution: {integrity: sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA==} + dev: true + /@types/prismjs/1.26.0: resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==} dev: true @@ -8557,7 +8565,6 @@ packages: resolution: {integrity: sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==} engines: {node: '>=10.13.0'} hasBin: true - dev: true /pretty-bytes/5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} From 6616cdaa070866a78b25e5b202e6437b646a97fc Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 00:53:23 -0300 Subject: [PATCH 04/40] Added changes confirmation --- packages/astro/package.json | 3 +++ packages/astro/src/cli/add.ts | 42 ++++++++++++++++++++++++++++++----- pnpm-lock.yaml | 10 +++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 9dfc5ad54d3d..74f9300fd3c0 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -89,6 +89,7 @@ "ci-info": "^3.3.0", "common-ancestor-path": "^1.0.1", "debug": "^4.3.4", + "diff": "^5.0.0", "eol": "^0.9.1", "es-module-lexer": "^0.10.4", "esbuild": "0.14.25", @@ -108,6 +109,7 @@ "postcss-load-config": "^3.1.3", "prettier": "^2.6.0", "prismjs": "^1.27.0", + "prompts": "^2.4.2", "rehype-slug": "^5.0.1", "resolve": "^1.22.0", "rollup": "^2.70.1", @@ -136,6 +138,7 @@ "@types/common-ancestor-path": "^1.0.0", "@types/connect": "^3.4.35", "@types/debug": "^4.1.7", + "@types/diff": "^5.0.2", "@types/estree": "^0.0.51", "@types/html-escaper": "^3.0.0", "@types/mime": "^2.0.3", diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index 0d173f753fda..fb1e4635c76e 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -2,11 +2,13 @@ import type yargs from 'yargs-parser'; import path from 'path'; import fs from 'fs/promises'; import { fileURLToPath } from 'url'; +import { diffLines } from 'diff'; +import prompts from 'prompts'; import { resolveConfigURL } from '../core/config.js'; import { apply as applyPolyfill } from '../core/polyfill.js'; import { defaultLogOptions, error, info, debug, LogOptions, warn } from '../core/logger.js'; import * as msg from '../core/messages.js'; -import { dim, red, cyan } from 'kleur/colors'; +import { dim, red, cyan, green } from 'kleur/colors'; import { parseNpmName } from '../core/util.js'; import { t, parse, visit, ensureImport, wrapDefaultExport, generate } from '../transform/index.js'; @@ -42,10 +44,10 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) wrapDefaultExport(ast, defineConfig); for (const integration of integrations) { - await addIntegration(ast, integration, { logging }); + await addIntegration(ast, integration); } - await updateAstroConfig(configURL, ast); + await updateAstroConfig({ configURL, ast, logging }); const len = integrations.length; info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`, `Be sure to re-install your dependencies before continuing!`)); @@ -61,7 +63,7 @@ async function parseAstroConfig(configURL: URL): Promise { return result; } -async function addIntegration(ast: t.File, integration: IntegrationInfo, { logging }: { logging: LogOptions }) { +async function addIntegration(ast: t.File, integration: IntegrationInfo) { const integrationId = t.identifier(integration.id); ensureImport(ast, t.importDeclaration([t.importDefaultSpecifier(integrationId)], t.stringLiteral(integration.packageName))); @@ -99,9 +101,37 @@ async function addIntegration(ast: t.File, integration: IntegrationInfo, { loggi }); } -async function updateAstroConfig(configURL: URL, ast: t.File) { +async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }) { + const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); const output = await generate(ast, fileURLToPath(configURL)); - await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); + info( + logging, + null, + diffLines(input, output) + .map((change) => { + let lines = change.value.split('\n').slice(0, -1); // remove latest \n + + if (change.added) lines = lines.map((line) => green(`+ ${line}`)); + else if (change.removed) lines = lines.map((line) => red(`- ${line}`)); + else lines = lines.map((line) => ` ${line}`); + + return lines.join('\n'); + }) + .join('\n') + ); + + const response = await prompts({ + type: 'confirm', + name: 'updateConfig', + message: 'This changes will be made to your configuration. Continue?', + initial: true, + }); + + if (response.updateConfig) { + await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); + } else { + info(logging, null, 'No changes were made to the configuration file.'); + } } interface IntegrationInfo { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 902b4ece0d31..021221fad031 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -462,6 +462,7 @@ importers: '@types/common-ancestor-path': ^1.0.0 '@types/connect': ^3.4.35 '@types/debug': ^4.1.7 + '@types/diff': ^5.0.2 '@types/estree': ^0.0.51 '@types/html-escaper': ^3.0.0 '@types/mime': ^2.0.3 @@ -479,6 +480,7 @@ importers: ci-info: ^3.3.0 common-ancestor-path: ^1.0.1 debug: ^4.3.4 + diff: ^5.0.0 eol: ^0.9.1 es-module-lexer: ^0.10.4 esbuild: 0.14.25 @@ -500,6 +502,7 @@ importers: postcss-load-config: ^3.1.3 prettier: ^2.6.0 prismjs: ^1.27.0 + prompts: ^2.4.2 rehype-slug: ^5.0.1 resolve: ^1.22.0 rollup: ^2.70.1 @@ -536,6 +539,7 @@ importers: ci-info: 3.3.0 common-ancestor-path: 1.0.1 debug: 4.3.4 + diff: 5.0.0 eol: 0.9.1 es-module-lexer: 0.10.4 esbuild: 0.14.25 @@ -555,6 +559,7 @@ importers: postcss-load-config: 3.1.3 prettier: 2.6.0 prismjs: 1.27.0 + prompts: 2.4.2 rehype-slug: 5.0.1 resolve: 1.22.0 rollup: 2.70.1 @@ -582,6 +587,7 @@ importers: '@types/common-ancestor-path': 1.0.0 '@types/connect': 3.4.35 '@types/debug': 4.1.7 + '@types/diff': 5.0.2 '@types/estree': 0.0.51 '@types/html-escaper': 3.0.0 '@types/mime': 2.0.3 @@ -3833,6 +3839,10 @@ packages: resolution: {integrity: sha512-CL7y71j2zaDmtPLD5Xq5S1Gv2dFoHl0/GBZm6s39Mj/ls28L3NzAOqf7H4H0/2TNVMgMjMVf9CAFYSjmXhi3bw==} dev: false + /@types/diff/5.0.2: + resolution: {integrity: sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg==} + dev: true + /@types/estree-jsx/0.0.1: resolution: {integrity: sha512-gcLAYiMfQklDCPjQegGn0TBAn9it05ISEsEhlKQUddIk7o2XDokOcTN7HBO8tznM0D9dGezvHEfRZBfZf6me0A==} dependencies: From 8b0b4bf8e8a48451e4ca681b37b130883055344f Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 01:10:16 -0300 Subject: [PATCH 05/40] Error flow --- packages/astro/src/cli/add.ts | 46 +++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index fb1e4635c76e..063d024480c8 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -37,20 +37,45 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) } const integrations = await validateIntegrations(names); - let ast = await parseAstroConfig(configURL); - const defineConfig = t.identifier('defineConfig'); - ensureImport(ast, t.importDeclaration([t.importSpecifier(defineConfig, defineConfig)], t.stringLiteral('astro/config'))); - wrapDefaultExport(ast, defineConfig); - - for (const integration of integrations) { - await addIntegration(ast, integration); + // Add integrations to astro config + // TODO: At the moment, nearly nothing throws an error. We need more errors! + let ast: t.File | null = null; + try { + ast = await parseAstroConfig(configURL); + + debug('add', 'Parsed astro config'); + + const defineConfig = t.identifier('defineConfig'); + ensureImport(ast, t.importDeclaration([t.importSpecifier(defineConfig, defineConfig)], t.stringLiteral('astro/config'))); + wrapDefaultExport(ast, defineConfig); + + debug('add', 'Astro config ensured `defineConfig`'); + + for (const integration of integrations) { + await addIntegration(ast, integration); + debug('add', `Astro config added integration ${integration.id}`); + } + } catch (err) { + debug('add', 'Error parsing/modifying astro config: ', err); + info( + logging, + null, + "Sorry, we couldn't update your configuration automatically. [INSERT HOW TO DO IT MANUALLY --- this link might help: https://next--astro-docs-2.netlify.app/en/guides/integrations-guide/]" + ); } - await updateAstroConfig({ configURL, ast, logging }); + if (ast) { + try { + await updateAstroConfig({ configURL, ast, logging }); - const len = integrations.length; - info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`, `Be sure to re-install your dependencies before continuing!`)); + const len = integrations.length; + info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`, `Be sure to re-install your dependencies before continuing!`)); + } catch (err) { + debug('add', 'Error updating astro config', err); + error(logging, null, 'There has been an error updating the astro config. You might need to update it manually.'); + } + } } async function parseAstroConfig(configURL: URL): Promise { @@ -129,6 +154,7 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti if (response.updateConfig) { await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); + debug('add', `Updated astro config`); } else { info(logging, null, 'No changes were made to the configuration file.'); } From ff5220cd4dde968eb409b0051dbd0e9342b86de7 Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 12:38:33 -0300 Subject: [PATCH 06/40] Add dependencies --- packages/astro/package.json | 1 + packages/astro/src/cli/add.ts | 119 ++++++++++++++++++++++++++-------- pnpm-lock.yaml | 20 +----- 3 files changed, 95 insertions(+), 45 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 74f9300fd3c0..04b584d0a6fa 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -107,6 +107,7 @@ "path-to-regexp": "^6.2.0", "postcss": "^8.4.12", "postcss-load-config": "^3.1.3", + "preferred-pm": "^3.0.3", "prettier": "^2.6.0", "prismjs": "^1.27.0", "prompts": "^2.4.2", diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index 063d024480c8..aea756cda753 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -1,14 +1,16 @@ import type yargs from 'yargs-parser'; import path from 'path'; import fs from 'fs/promises'; +import { exec } from 'child_process'; import { fileURLToPath } from 'url'; import { diffLines } from 'diff'; import prompts from 'prompts'; +import preferredPM from 'preferred-pm'; import { resolveConfigURL } from '../core/config.js'; import { apply as applyPolyfill } from '../core/polyfill.js'; -import { defaultLogOptions, error, info, debug, LogOptions, warn } from '../core/logger.js'; +import { error, info, debug, LogOptions, warn } from '../core/logger.js'; import * as msg from '../core/messages.js'; -import { dim, red, cyan, green } from 'kleur/colors'; +import { dim, red, cyan, green, bold } from 'kleur/colors'; import { parseNpmName } from '../core/util.js'; import { t, parse, visit, ensureImport, wrapDefaultExport, generate } from '../transform/index.js'; @@ -18,6 +20,12 @@ export interface AddOptions { flags: yargs.Arguments; } +export interface IntegrationInfo { + id: string; + packageName: string; + dependencies: [name: string, version: string][]; +} + const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; export async function add(names: string[], { cwd, flags, logging }: AddOptions) { @@ -68,14 +76,17 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) if (ast) { try { await updateAstroConfig({ configURL, ast, logging }); - - const len = integrations.length; - info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`, `Be sure to re-install your dependencies before continuing!`)); } catch (err) { debug('add', 'Error updating astro config', err); error(logging, null, 'There has been an error updating the astro config. You might need to update it manually.'); + return; } } + + await tryToInstallIntegrations({ integrations, cwd, logging }); + + const len = integrations.length; + info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`)); } async function parseAstroConfig(configURL: URL): Promise { @@ -160,15 +171,70 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti } } -interface IntegrationInfo { - id: string; - packageName: string; - dependencies: string[]; +async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise { + const pm = await preferredPM(cwd); + debug('add', `package manager: ${JSON.stringify(pm)}`); + if (!pm) return null; + + let dependenciesList = 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}`)) + .sort() + .join(' '); + + switch (pm.name) { + case 'npm': + return 'npm install --save-dev ' + dependenciesList; + case 'yarn': + return 'yarn add --dev ' + dependenciesList; + case 'pnpm': + return 'pnpm add --save-dev ' + dependenciesList; + default: + return null; + } +} + +async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }) { + const cmd = await getInstallIntegrationsCommand({ integrations, cwd }); + + if (cmd === null) { + info(logging, null); + } else { + info(logging, null, `In order to install the integrations, the following command will be run: \n${bold(cyan(cmd))}`); + const response = await prompts({ + type: 'confirm', + name: 'installDependencies', + message: 'Is this the right command?', + initial: true, + }); + + if (response.installDependencies) { + try { + await new Promise((resolve, reject) => { + exec(cmd, (err, stdout, stderr) => { + if (err) { + reject(stderr); + } else { + resolve(stdout); + } + }); + }); + info(logging, null, 'Dependnecies installed!'); + } catch (err) { + debug('add', 'Error installing dependencies', err); + warn(logging, null, 'There was an error installing the dependencies. Be sure to install them manually before continuing!'); + } + } else { + info(logging, null, 'Be sure to install the dependencies before continuing!'); + } + } } export async function validateIntegrations(integrations: string[]): Promise { const integrationEntries = await Promise.all( - integrations.map((integration) => { + integrations.map(async (integration): Promise => { const parsed = parseIntegrationName(integration); if (!parsed) { throw new Error(`${integration} does not appear to be a valid package name!`); @@ -180,24 +246,23 @@ export async function validateIntegrations(integrations: string[]): Promise { - if (res.status === 404) { - throw new Error(`Unable to fetch ${packageName}. Does this package exist?`); - } - return res.json(); - }) - .then((res: any) => { - let dependencies: [string, string][] = [[res['name'], `^${res['version']}`]]; - - if (res['peerDependencies']) { - for (const peer in res['peerDependencies']) { - dependencies.push([peer, res['peerDependencies'][peer]]); - } - } - return { id: integration, packageName, dependencies: dependencies.flat(1) }; - }); + const result = await fetch(`https://registry.npmjs.org/${packageName}/${tag}`).then((res) => { + if (res.status === 404) { + throw new Error(`Unable to fetch ${packageName}. Does this package exist?`); + } + return res.json(); + }); + + let dependencies: IntegrationInfo['dependencies'] = [[result['name'], `^${result['version']}`]]; + + if (result['peerDependencies']) { + for (const peer in result['peerDependencies']) { + dependencies.push([peer, result['peerDependencies'][peer]]); + } + } + + return { id: integration, packageName, dependencies }; }) ); return integrationEntries; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 021221fad031..ba35ada6c215 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -500,6 +500,7 @@ importers: path-to-regexp: ^6.2.0 postcss: ^8.4.12 postcss-load-config: ^3.1.3 + preferred-pm: ^3.0.3 prettier: ^2.6.0 prismjs: ^1.27.0 prompts: ^2.4.2 @@ -557,6 +558,7 @@ importers: path-to-regexp: 6.2.0 postcss: 8.4.12 postcss-load-config: 3.1.3 + preferred-pm: 3.0.3 prettier: 2.6.0 prismjs: 1.27.0 prompts: 2.4.2 @@ -6270,7 +6272,6 @@ packages: dependencies: locate-path: 5.0.0 path-exists: 4.0.0 - dev: true /find-up/5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -6278,14 +6279,12 @@ packages: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - dev: true /find-yarn-workspace-root2/1.2.16: resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} dependencies: micromatch: 4.0.5 pkg-dir: 4.2.0 - dev: true /flat-cache/3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} @@ -7320,7 +7319,6 @@ packages: js-yaml: 3.14.1 pify: 4.0.1 strip-bom: 3.0.0 - dev: true /local-pkg/0.4.1: resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==} @@ -7332,14 +7330,12 @@ packages: engines: {node: '>=8'} dependencies: p-locate: 4.1.0 - dev: true /locate-path/6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} dependencies: p-locate: 5.0.0 - dev: true /lodash.debounce/4.0.8: resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=} @@ -8286,28 +8282,24 @@ packages: engines: {node: '>=6'} dependencies: p-try: 2.2.0 - dev: true /p-limit/3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 - dev: true /p-locate/4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} dependencies: p-limit: 2.3.0 - dev: true /p-locate/5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} dependencies: p-limit: 3.1.0 - dev: true /p-map/2.1.0: resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} @@ -8324,7 +8316,6 @@ packages: /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - dev: true /pac-proxy-agent/5.0.0: resolution: {integrity: sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ==} @@ -8404,7 +8395,6 @@ packages: /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - dev: true /path-is-absolute/1.0.1: resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} @@ -8449,14 +8439,12 @@ packages: /pify/4.0.1: resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} engines: {node: '>=6'} - dev: true /pkg-dir/4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} dependencies: find-up: 4.1.0 - dev: true /postcss-js/4.0.0_postcss@8.4.12: resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} @@ -8546,7 +8534,6 @@ packages: find-yarn-workspace-root2: 1.2.16 path-exists: 4.0.0 which-pm: 2.0.0 - dev: true /prelude-ls/1.1.2: resolution: {integrity: sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=} @@ -9512,7 +9499,6 @@ packages: /strip-bom/3.0.0: resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=} engines: {node: '>=4'} - dev: true /strip-bom/4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} @@ -10587,7 +10573,6 @@ packages: dependencies: load-yaml-file: 0.2.0 path-exists: 4.0.0 - dev: true /which-typed-array/1.1.7: resolution: {integrity: sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw==} @@ -10891,7 +10876,6 @@ packages: /yocto-queue/0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - dev: true /zod/3.14.2: resolution: {integrity: sha512-iF+wrtzz7fQfkmn60PG6XFxaWBhYYKzp2i+nv24WbLUWb2JjymdkHlzBwP0erpc78WotwP5g9AAu7Sk8GWVVNw==} From a9bc99978ac4686b94a3b1df0ca2c0a93a00ccc3 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 15:34:01 -0500 Subject: [PATCH 07/40] feat(cli): astro add cleanup pass --- packages/astro/package.json | 5 +- packages/astro/src/cli/add.ts | 91 ++++++++++++++++++++++------- packages/astro/src/core/config.ts | 8 +-- packages/astro/src/core/messages.ts | 20 ++++++- 4 files changed, 94 insertions(+), 30 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 04b584d0a6fa..382db26bc2a5 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -83,9 +83,10 @@ "@babel/parser": "^7.17.8", "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.3", - "@proload/core": "^0.2.2", - "@proload/plugin-tsm": "^0.1.1", + "@proload/core": "^0.3.1", + "@proload/plugin-tsm": "^0.2.0", "@web/parse5-utils": "^1.3.0", + "boxen": "^6.2.1", "ci-info": "^3.3.0", "common-ancestor-path": "^1.0.1", "debug": "^4.3.4", diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index aea756cda753..b04ed36aa4bc 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -4,6 +4,7 @@ import fs from 'fs/promises'; import { exec } from 'child_process'; import { fileURLToPath } from 'url'; import { diffLines } from 'diff'; +import boxen from 'boxen'; import prompts from 'prompts'; import preferredPM from 'preferred-pm'; import { resolveConfigURL } from '../core/config.js'; @@ -73,9 +74,12 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) ); } + let configResult: UpdateResult|undefined; + let installResult: UpdateResult|undefined; + if (ast) { try { - await updateAstroConfig({ configURL, ast, logging }); + configResult = await updateAstroConfig({ configURL, ast, logging }); } catch (err) { debug('add', 'Error updating astro config', err); error(logging, null, 'There has been an error updating the astro config. You might need to update it manually.'); @@ -83,10 +87,34 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) } } - await tryToInstallIntegrations({ integrations, cwd, logging }); + switch (configResult) { + case UpdateResult.cancelled: { + info(logging, null, msg.cancelled(`Your configuration has not been updated.`)); + return; + } + case UpdateResult.none: { + info(logging, null, msg.success(`Configuration up-to-date. No changes needed!`)); + break; + } + } + + installResult = await tryToInstallIntegrations({ integrations, cwd, logging }); - const len = integrations.length; - info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project.`)); + switch (installResult) { + case UpdateResult.updated: { + const len = integrations.length; + info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project`)); + return + } + case UpdateResult.cancelled: { + info(logging, null, msg.cancelled(`No dependencies installed.`, `Be sure to install them manually before continuing!`)); + return; + } + case UpdateResult.failure: { + info(logging, null, msg.failure(`There was a problem installing dependencies.`, `Be sure to install them manually before continuing!`)) + process.exit(1); + } + } } async function parseAstroConfig(configURL: URL): Promise { @@ -130,44 +158,64 @@ async function addIntegration(ast: t.File, integration: IntegrationInfo) { return; } - if (integrationsProp.value.type !== 'ArrayExpression') return; + if (integrationsProp.value.type !== 'ArrayExpression') throw new Error('Unable to parse integrations'); + + const existingIntegrationCall = integrationsProp.value.elements.find( + (expr) => t.isCallExpression(expr) && t.isIdentifier(expr.callee) && expr.callee.name === integrationId.name + ); + + if (existingIntegrationCall) return; integrationsProp.value.elements.push(integrationCall); }, }); } -async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }) { +const enum UpdateResult { + none, + updated, + cancelled, + failure, +} + +async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }): Promise { const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); const output = await generate(ast, fileURLToPath(configURL)); - info( - logging, - null, + + if (input === output) { + return UpdateResult.none; + } + + const message = `\n${boxen( diffLines(input, output) .map((change) => { let lines = change.value.split('\n').slice(0, -1); // remove latest \n if (change.added) lines = lines.map((line) => green(`+ ${line}`)); else if (change.removed) lines = lines.map((line) => red(`- ${line}`)); - else lines = lines.map((line) => ` ${line}`); + else lines = lines.map((line) => dim(` ${line}`)); return lines.join('\n'); }) - .join('\n') - ); + .join('\n'), + { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() } + )}\n`; + + info(logging, null, message); const response = await prompts({ type: 'confirm', name: 'updateConfig', - message: 'This changes will be made to your configuration. Continue?', + message: 'These changes will be written to your configuration file.\n Continue?', initial: true, }); if (response.updateConfig) { await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); debug('add', `Updated astro config`); + return UpdateResult.updated; } else { - info(logging, null, 'No changes were made to the configuration file.'); + return UpdateResult.cancelled; } } @@ -190,23 +238,24 @@ async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() case 'yarn': return 'yarn add --dev ' + dependenciesList; case 'pnpm': - return 'pnpm add --save-dev ' + dependenciesList; + return 'pnpm install --save-dev ' + dependenciesList; default: return null; } } -async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }) { +async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }): Promise { const cmd = await getInstallIntegrationsCommand({ integrations, cwd }); if (cmd === null) { info(logging, null); } else { - info(logging, null, `In order to install the integrations, the following command will be run: \n${bold(cyan(cmd))}`); + const message = `\n${boxen(cyan(cmd), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; + info(logging, null, `\n Astro will install these integrations with the following command:${message}`); const response = await prompts({ type: 'confirm', name: 'installDependencies', - message: 'Is this the right command?', + message: 'Run command?', initial: true, }); @@ -221,13 +270,13 @@ async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), log } }); }); - info(logging, null, 'Dependnecies installed!'); + return UpdateResult.updated; } catch (err) { debug('add', 'Error installing dependencies', err); - warn(logging, null, 'There was an error installing the dependencies. Be sure to install them manually before continuing!'); + return UpdateResult.failure; } } else { - info(logging, null, 'Be sure to install the dependencies before continuing!'); + return UpdateResult.cancelled; } } } diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index dc7bacc5ddf5..5bc4874f5c00 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -7,7 +7,7 @@ import path from 'path'; import { pathToFileURL, fileURLToPath } from 'url'; import { mergeConfig as mergeViteConfig } from 'vite'; import { z } from 'zod'; -import load from '@proload/core'; +import load, { resolve } from '@proload/core'; import loadTypeScript from '@proload/plugin-tsm'; import postcssrc from 'postcss-load-config'; import { arraify, isObject } from './util.js'; @@ -278,9 +278,9 @@ export async function resolveConfigURL(configOptions: LoadConfigOptions): Promis } // Automatically load config file using Proload // If `userConfigPath` is `undefined`, Proload will search for `astro.config.[cm]?[jt]s` - const config = await load('astro', { mustExist: false, cwd: root, filePath: userConfigPath }); - if (config) { - return pathToFileURL(config.filePath); + const filePath = await resolve('astro', { mustExist: false, cwd: root, filePath: userConfigPath }); + if (filePath) { + return pathToFileURL(filePath); } } diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index 224654824d96..a4a22660b500 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -3,7 +3,7 @@ */ import stripAnsi from 'strip-ansi'; -import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black } from 'kleur/colors'; +import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black, bgRed } from 'kleur/colors'; import { pad, emoji, getLocalAddress, getNetworkLogging } from './dev/util.js'; import os from 'os'; import type { AddressInfo } from 'net'; @@ -90,8 +90,22 @@ export function prerelease({ currentVersion }: { currentVersion: string }) { export function success(message: string, tip?: string) { const badge = bgGreen(black(` success `)); const headline = green(message); - const footer = tip ? `\n ▶ ${tip}` : '' - return ['', badge, headline, footer].map((msg) => ` ${msg}`).join('\n'); + const footer = tip ? `\n ▶ ${tip}` : undefined; + return ['', badge, headline, footer].filter(v => v !== undefined).map((msg) => ` ${msg}`).join('\n'); +} + +export function failure(message: string, tip?: string) { + const badge = bgRed(black(` error `)); + const headline = red(message); + const footer = tip ? `\n ▶ ${tip}` : undefined; + return ['', badge, headline, footer].filter(v => v !== undefined).map((msg) => ` ${msg}`).join('\n'); +} + +export function cancelled(message: string, tip?: string) { + const badge = bgYellow(black(` cancelled `)); + const headline = yellow(message); + const footer = tip ? `\n ▶ ${tip}` : undefined; + return ['', badge, headline, footer].filter(v => v !== undefined).map((msg) => ` ${msg}`).join('\n'); } /** Display port in use */ From e79c461c2419496e6a808d7c013fb0363b921f5f Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 15:47:57 -0500 Subject: [PATCH 08/40] feat: add support for tailwind --- packages/astro/src/cli/add.ts | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index b04ed36aa4bc..5616331a8511 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -9,9 +9,9 @@ import prompts from 'prompts'; import preferredPM from 'preferred-pm'; import { resolveConfigURL } from '../core/config.js'; import { apply as applyPolyfill } from '../core/polyfill.js'; -import { error, info, debug, LogOptions, warn } from '../core/logger.js'; +import { error, info, debug, LogOptions } from '../core/logger.js'; import * as msg from '../core/messages.js'; -import { dim, red, cyan, green, bold } from 'kleur/colors'; +import { dim, red, cyan, green, bold, magenta } from 'kleur/colors'; import { parseNpmName } from '../core/util.js'; import { t, parse, visit, ensureImport, wrapDefaultExport, generate } from '../transform/index.js'; @@ -103,6 +103,16 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) switch (installResult) { case UpdateResult.updated: { const len = integrations.length; + if (integrations.find(integration => integration.id === 'tailwind')) { + const DEFAULT_TAILWIND_CONFIG = `module.exports = { + content: [], + theme: { + extend: {}, + }, + plugins: [], +}\n` + await fs.writeFile(fileURLToPath(new URL('./tailwind.config.mjs', configURL)), DEFAULT_TAILWIND_CONFIG); + } info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project`)); return } @@ -201,12 +211,12 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() } )}\n`; - info(logging, null, message); + info(logging, null, `\n ${magenta('Astro will update your configuration with these changes...')}\n${message}`); const response = await prompts({ type: 'confirm', name: 'updateConfig', - message: 'These changes will be written to your configuration file.\n Continue?', + message: 'Continue?', initial: true, }); @@ -251,11 +261,11 @@ async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), log info(logging, null); } else { const message = `\n${boxen(cyan(cmd), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; - info(logging, null, `\n Astro will install these integrations with the following command:${message}`); + info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n${message}`); const response = await prompts({ type: 'confirm', name: 'installDependencies', - message: 'Run command?', + message: 'Continue?', initial: true, }); From abbe3e28e2b31695ae5116dfc6ad9d264ce9aa7b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 16:02:47 -0500 Subject: [PATCH 09/40] chore: update lockfile --- pnpm-lock.yaml | 95 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba35ada6c215..ce47af775c4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -453,8 +453,8 @@ importers: '@babel/template': ^7.16.7 '@babel/traverse': ^7.17.3 '@babel/types': ^7.17.0 - '@proload/core': ^0.2.2 - '@proload/plugin-tsm': ^0.1.1 + '@proload/core': ^0.3.1 + '@proload/plugin-tsm': ^0.2.0 '@types/babel__core': ^7.1.19 '@types/babel__generator': ^7.6.4 '@types/babel__traverse': ^7.14.2 @@ -475,6 +475,7 @@ importers: '@types/yargs-parser': ^21.0.0 '@web/parse5-utils': ^1.3.0 astro-scripts: workspace:* + boxen: ^6.2.1 chai: ^4.3.6 cheerio: ^1.0.0-rc.10 ci-info: ^3.3.0 @@ -534,9 +535,10 @@ importers: '@babel/parser': 7.17.8 '@babel/template': 7.16.7 '@babel/traverse': 7.17.3 - '@proload/core': 0.2.2 - '@proload/plugin-tsm': 0.1.1_@proload+core@0.2.2 + '@proload/core': 0.3.1 + '@proload/plugin-tsm': 0.2.0_@proload+core@0.3.1 '@web/parse5-utils': 1.3.0 + boxen: 6.2.1 ci-info: 3.3.0 common-ancestor-path: 1.0.1 debug: 4.3.4 @@ -559,7 +561,7 @@ importers: postcss: 8.4.12 postcss-load-config: 3.1.3 preferred-pm: 3.0.3 - prettier: 2.6.0 + prettier: 2.6.1 prismjs: 1.27.0 prompts: 2.4.2 rehype-slug: 5.0.1 @@ -3590,12 +3592,20 @@ packages: escalade: 3.1.1 dev: false - /@proload/plugin-tsm/0.1.1_@proload+core@0.2.2: - resolution: {integrity: sha512-qfGegg6I3YBCZDjYR9xb41MTc2EfL0sQQmw49Z/yi9OstIpUa/67MBy4AuNhoyG9FuOXia9gPoeBk5pGnBOGtA==} + /@proload/core/0.3.1: + resolution: {integrity: sha512-u902sdjipQ6WjpV6rxcF0CnQP6Z6Gd54MBPuMbZ5amCcdb/meWY6UtCQSLIJmG+zbXtf8Hwzf6ePBey158QAQQ==} + dependencies: + deepmerge: 4.2.2 + escalade: 3.1.1 + resolve-pkg: 2.0.0 + dev: false + + /@proload/plugin-tsm/0.2.0_@proload+core@0.3.1: + resolution: {integrity: sha512-+ew0NTCE6a+MxyMdQOW/efaul+PzLoNuhe9tpplr7uFHA697OJ1Uy2TtLQa/oxq0nChVngnLBX+6iBRRH6XkSg==} peerDependencies: - '@proload/core': ^0.2.1 + '@proload/core': ^0.3.0 dependencies: - '@proload/core': 0.2.2 + '@proload/core': 0.3.1 tsm: 2.2.1 dev: false @@ -4488,6 +4498,12 @@ packages: '@algolia/transporter': 4.13.0 dev: false + /ansi-align/3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + dependencies: + string-width: 4.2.3 + dev: false + /ansi-colors/4.1.1: resolution: {integrity: sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==} engines: {node: '>=6'} @@ -4501,7 +4517,6 @@ packages: /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-regex/6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} @@ -4520,6 +4535,11 @@ packages: dependencies: color-convert: 2.0.1 + /ansi-styles/6.1.0: + resolution: {integrity: sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ==} + engines: {node: '>=12'} + dev: false + /anymatch/3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} engines: {node: '>= 8'} @@ -4734,6 +4754,20 @@ packages: /boolbase/1.0.0: resolution: {integrity: sha1-aN/1++YMUes3cl6p4+0xDcwed24=} + /boxen/6.2.1: + resolution: {integrity: sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + ansi-align: 3.0.1 + camelcase: 6.3.0 + chalk: 4.1.2 + cli-boxes: 3.0.0 + string-width: 5.1.2 + type-fest: 2.12.1 + widest-line: 4.0.1 + wrap-ansi: 8.0.1 + dev: false + /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -4835,7 +4869,6 @@ packages: /camelcase/6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - dev: true /caniuse-lite/1.0.30001320: resolution: {integrity: sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==} @@ -4967,6 +5000,11 @@ packages: engines: {node: '>=6'} dev: true + /cli-boxes/3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + dev: false + /cli-cursor/4.0.0: resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5451,7 +5489,6 @@ packages: /emoji-regex/8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex/9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -6963,7 +7000,6 @@ packages: /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-generator-function/1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} @@ -8927,7 +8963,13 @@ packages: /resolve-from/5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - dev: true + + /resolve-pkg/2.0.0: + resolution: {integrity: sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==} + engines: {node: '>=8'} + dependencies: + resolve-from: 5.0.0 + dev: false /resolve/1.22.0: resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} @@ -9403,7 +9445,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string-width/5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} @@ -9482,7 +9523,6 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-ansi/7.0.1: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} @@ -9882,7 +9922,7 @@ packages: engines: {node: '>=12'} hasBin: true dependencies: - esbuild: 0.14.25 + esbuild: 0.14.27 dev: false /tsutils/3.21.0_typescript@4.6.3: @@ -10076,6 +10116,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest/2.12.1: + resolution: {integrity: sha512-AiknQSEqKVGDDjtZqeKrUoTlcj7FKhupmnVUgz6KoOKtvMwRGE6hUNJ/nVear+h7fnUPO1q/htSkYKb1pyntkQ==} + engines: {node: '>=12.20'} + dev: false + /typescript/4.6.3: resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} engines: {node: '>=4.2.0'} @@ -10607,6 +10652,13 @@ packages: string-width: 1.0.2 dev: true + /widest-line/4.0.1: + resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + dev: false + /word-wrap/1.2.3: resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} engines: {node: '>=0.10.0'} @@ -10780,6 +10832,15 @@ packages: strip-ansi: 6.0.1 dev: true + /wrap-ansi/8.0.1: + resolution: {integrity: sha512-QFF+ufAqhoYHvoHdajT/Po7KoXVBPXS2bgjIam5isfWJPfIOnQZ50JtUiVvCv/sjgacf3yRrt2ZKUZ/V4itN4g==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.1.0 + string-width: 5.1.2 + strip-ansi: 7.0.1 + dev: false + /wrappy/1.0.2: resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} From c8949dadffeb42cc7f591cb581aa050d20ed8f8e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 16:04:54 -0500 Subject: [PATCH 10/40] fix: types --- packages/astro/src/cli/add.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index 5616331a8511..14bf781f85d7 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -259,6 +259,7 @@ async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), log if (cmd === null) { info(logging, null); + return UpdateResult.none; } else { const message = `\n${boxen(cyan(cmd), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n${message}`); From 94e4e3463198b1828c22fdea877da6d47a9aa58f Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 16:31:03 -0500 Subject: [PATCH 11/40] chore: rever @proload/core bump --- packages/astro/package.json | 4 ++-- packages/astro/src/core/config.ts | 8 ++++---- pnpm-lock.yaml | 32 +++++++++---------------------- 3 files changed, 15 insertions(+), 29 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 382db26bc2a5..dc42e435a9f1 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -83,8 +83,8 @@ "@babel/parser": "^7.17.8", "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.3", - "@proload/core": "^0.3.1", - "@proload/plugin-tsm": "^0.2.0", + "@proload/core": "^0.2.0", + "@proload/plugin-tsm": "^0.1.0", "@web/parse5-utils": "^1.3.0", "boxen": "^6.2.1", "ci-info": "^3.3.0", diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 5bc4874f5c00..dc7bacc5ddf5 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -7,7 +7,7 @@ import path from 'path'; import { pathToFileURL, fileURLToPath } from 'url'; import { mergeConfig as mergeViteConfig } from 'vite'; import { z } from 'zod'; -import load, { resolve } from '@proload/core'; +import load from '@proload/core'; import loadTypeScript from '@proload/plugin-tsm'; import postcssrc from 'postcss-load-config'; import { arraify, isObject } from './util.js'; @@ -278,9 +278,9 @@ export async function resolveConfigURL(configOptions: LoadConfigOptions): Promis } // Automatically load config file using Proload // If `userConfigPath` is `undefined`, Proload will search for `astro.config.[cm]?[jt]s` - const filePath = await resolve('astro', { mustExist: false, cwd: root, filePath: userConfigPath }); - if (filePath) { - return pathToFileURL(filePath); + const config = await load('astro', { mustExist: false, cwd: root, filePath: userConfigPath }); + if (config) { + return pathToFileURL(config.filePath); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce47af775c4c..f6a9fc48f87b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -453,8 +453,8 @@ importers: '@babel/template': ^7.16.7 '@babel/traverse': ^7.17.3 '@babel/types': ^7.17.0 - '@proload/core': ^0.3.1 - '@proload/plugin-tsm': ^0.2.0 + '@proload/core': ^0.2.0 + '@proload/plugin-tsm': ^0.1.0 '@types/babel__core': ^7.1.19 '@types/babel__generator': ^7.6.4 '@types/babel__traverse': ^7.14.2 @@ -535,8 +535,8 @@ importers: '@babel/parser': 7.17.8 '@babel/template': 7.16.7 '@babel/traverse': 7.17.3 - '@proload/core': 0.3.1 - '@proload/plugin-tsm': 0.2.0_@proload+core@0.3.1 + '@proload/core': 0.2.2 + '@proload/plugin-tsm': 0.1.1_@proload+core@0.2.2 '@web/parse5-utils': 1.3.0 boxen: 6.2.1 ci-info: 3.3.0 @@ -3592,20 +3592,12 @@ packages: escalade: 3.1.1 dev: false - /@proload/core/0.3.1: - resolution: {integrity: sha512-u902sdjipQ6WjpV6rxcF0CnQP6Z6Gd54MBPuMbZ5amCcdb/meWY6UtCQSLIJmG+zbXtf8Hwzf6ePBey158QAQQ==} - dependencies: - deepmerge: 4.2.2 - escalade: 3.1.1 - resolve-pkg: 2.0.0 - dev: false - - /@proload/plugin-tsm/0.2.0_@proload+core@0.3.1: - resolution: {integrity: sha512-+ew0NTCE6a+MxyMdQOW/efaul+PzLoNuhe9tpplr7uFHA697OJ1Uy2TtLQa/oxq0nChVngnLBX+6iBRRH6XkSg==} + /@proload/plugin-tsm/0.1.1_@proload+core@0.2.2: + resolution: {integrity: sha512-qfGegg6I3YBCZDjYR9xb41MTc2EfL0sQQmw49Z/yi9OstIpUa/67MBy4AuNhoyG9FuOXia9gPoeBk5pGnBOGtA==} peerDependencies: - '@proload/core': ^0.3.0 + '@proload/core': ^0.2.1 dependencies: - '@proload/core': 0.3.1 + '@proload/core': 0.2.2 tsm: 2.2.1 dev: false @@ -8963,13 +8955,7 @@ packages: /resolve-from/5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - - /resolve-pkg/2.0.0: - resolution: {integrity: sha512-+1lzwXehGCXSeryaISr6WujZzowloigEofRB+dj75y9RRa/obVcYgbHJd53tdYw8pvZj8GojXaaENws8Ktw/hQ==} - engines: {node: '>=8'} - dependencies: - resolve-from: 5.0.0 - dev: false + dev: true /resolve/1.22.0: resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} From 234c4b14e0e777df03d283da5631a3704eaec62d Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 16:43:44 -0500 Subject: [PATCH 12/40] chore: add changeset --- .changeset/brave-rings-jump.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/brave-rings-jump.md diff --git a/.changeset/brave-rings-jump.md b/.changeset/brave-rings-jump.md new file mode 100644 index 000000000000..42964b834cc7 --- /dev/null +++ b/.changeset/brave-rings-jump.md @@ -0,0 +1,9 @@ +--- +'astro': patch +--- + +Introduce new `astro add` command to automatically configure integrations. + +```shell +astro add preact tailwind +``` From a0555579eb8f2519dd4f077f2f0aca9d5626daa7 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 16:46:50 -0500 Subject: [PATCH 13/40] chore: rollback dep update --- packages/astro/package.json | 4 ++-- pnpm-lock.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index dc42e435a9f1..148c03d57a4e 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -83,8 +83,8 @@ "@babel/parser": "^7.17.8", "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.3", - "@proload/core": "^0.2.0", - "@proload/plugin-tsm": "^0.1.0", + "@proload/core": "^0.2.2", + "@proload/plugin-tsm": "^0.1.1", "@web/parse5-utils": "^1.3.0", "boxen": "^6.2.1", "ci-info": "^3.3.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6a9fc48f87b..ba418718ec56 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -453,8 +453,8 @@ importers: '@babel/template': ^7.16.7 '@babel/traverse': ^7.17.3 '@babel/types': ^7.17.0 - '@proload/core': ^0.2.0 - '@proload/plugin-tsm': ^0.1.0 + '@proload/core': ^0.2.2 + '@proload/plugin-tsm': ^0.1.1 '@types/babel__core': ^7.1.19 '@types/babel__generator': ^7.6.4 '@types/babel__traverse': ^7.14.2 From 787f129f8855d41544b81dd4d313462fb66c283d Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 19:11:26 -0300 Subject: [PATCH 14/40] Added spinners --- packages/astro/package.json | 1 + packages/astro/src/cli/add.ts | 34 +++++++++++++++++++++++++--------- pnpm-lock.yaml | 2 ++ 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 148c03d57a4e..24590441ee5e 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -104,6 +104,7 @@ "magic-string": "^0.25.9", "micromorph": "^0.1.2", "mime": "^3.0.0", + "ora": "^6.1.0", "parse5": "^6.0.1", "path-to-regexp": "^6.2.0", "postcss": "^8.4.12", diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index 14bf781f85d7..700421660b1e 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -7,6 +7,7 @@ import { diffLines } from 'diff'; import boxen from 'boxen'; import prompts from 'prompts'; import preferredPM from 'preferred-pm'; +import ora from 'ora'; import { resolveConfigURL } from '../core/config.js'; import { apply as applyPolyfill } from '../core/polyfill.js'; import { error, info, debug, LogOptions } from '../core/logger.js'; @@ -74,8 +75,8 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) ); } - let configResult: UpdateResult|undefined; - let installResult: UpdateResult|undefined; + let configResult: UpdateResult | undefined; + let installResult: UpdateResult | undefined; if (ast) { try { @@ -103,25 +104,25 @@ export async function add(names: string[], { cwd, flags, logging }: AddOptions) switch (installResult) { case UpdateResult.updated: { const len = integrations.length; - if (integrations.find(integration => integration.id === 'tailwind')) { - const DEFAULT_TAILWIND_CONFIG = `module.exports = { + if (integrations.find((integration) => integration.id === 'tailwind')) { + const DEFAULT_TAILWIND_CONFIG = `module.exports = { content: [], theme: { extend: {}, }, plugins: [], -}\n` - await fs.writeFile(fileURLToPath(new URL('./tailwind.config.mjs', configURL)), DEFAULT_TAILWIND_CONFIG); +}\n`; + await fs.writeFile(fileURLToPath(new URL('./tailwind.config.mjs', configURL)), DEFAULT_TAILWIND_CONFIG); } info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project`)); - return + return; } case UpdateResult.cancelled: { info(logging, null, msg.cancelled(`No dependencies installed.`, `Be sure to install them manually before continuing!`)); return; } case UpdateResult.failure: { - info(logging, null, msg.failure(`There was a problem installing dependencies.`, `Be sure to install them manually before continuing!`)) + info(logging, null, msg.failure(`There was a problem installing dependencies.`, `Be sure to install them manually before continuing!`)); process.exit(1); } } @@ -254,7 +255,15 @@ async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() } } -async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }): Promise { +async function tryToInstallIntegrations({ + integrations, + cwd = process.cwd(), + logging, +}: { + integrations: IntegrationInfo[]; + cwd?: string; + logging: LogOptions; +}): Promise { const cmd = await getInstallIntegrationsCommand({ integrations, cwd }); if (cmd === null) { @@ -271,6 +280,7 @@ async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), log }); if (response.installDependencies) { + const spinner = ora('Installing dependencies...').start(); try { await new Promise((resolve, reject) => { exec(cmd, (err, stdout, stderr) => { @@ -281,9 +291,11 @@ async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), log } }); }); + spinner.succeed(); return UpdateResult.updated; } catch (err) { debug('add', 'Error installing dependencies', err); + spinner.fail(); return UpdateResult.failure; } } else { @@ -293,10 +305,12 @@ async function tryToInstallIntegrations({ integrations, cwd = process.cwd(), log } export async function validateIntegrations(integrations: string[]): Promise { + const spinner = ora('Resolving integrations...').start(); const integrationEntries = await Promise.all( integrations.map(async (integration): Promise => { const parsed = parseIntegrationName(integration); if (!parsed) { + spinner.fail(); throw new Error(`${integration} does not appear to be a valid package name!`); } @@ -309,6 +323,7 @@ export async function validateIntegrations(integrations: string[]): Promise { if (res.status === 404) { + spinner.fail(); throw new Error(`Unable to fetch ${packageName}. Does this package exist?`); } return res.json(); @@ -325,6 +340,7 @@ export async function validateIntegrations(integrations: string[]): Promise Date: Thu, 24 Mar 2022 17:08:34 -0500 Subject: [PATCH 15/40] chore: remove extra deps --- packages/astro/package.json | 2 -- packages/astro/src/transform/babel.ts | 19 +++---------------- pnpm-lock.yaml | 5 +---- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/packages/astro/package.json b/packages/astro/package.json index 24590441ee5e..da89cddd9a14 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -81,7 +81,6 @@ "@babel/core": "^7.17.8", "@babel/generator": "^7.17.7", "@babel/parser": "^7.17.8", - "@babel/template": "^7.16.7", "@babel/traverse": "^7.17.3", "@proload/core": "^0.2.2", "@proload/plugin-tsm": "^0.1.1", @@ -110,7 +109,6 @@ "postcss": "^8.4.12", "postcss-load-config": "^3.1.3", "preferred-pm": "^3.0.3", - "prettier": "^2.6.0", "prismjs": "^1.27.0", "prompts": "^2.4.2", "rehype-slug": "^5.0.1", diff --git a/packages/astro/src/transform/babel.ts b/packages/astro/src/transform/babel.ts index 902b54425927..8ec31cd46da4 100644 --- a/packages/astro/src/transform/babel.ts +++ b/packages/astro/src/transform/babel.ts @@ -2,29 +2,16 @@ import traverse from '@babel/traverse'; import generator from '@babel/generator'; import * as t from '@babel/types'; import parser from '@babel/parser'; -import prettier from 'prettier'; // @ts-ignore @babel/traverse isn't ESM and needs this trick export const visit = traverse.default as typeof traverse; - export { t }; -export { default as template } from '@babel/template'; -export async function generate(ast: t.File, configURL?: string) { +export async function generate(ast: t.File) { // @ts-ignore @babel/generator isn't ESM and needs this trick const astToText = generator.default as typeof generator; - - const text = astToText(ast, { retainLines: true }).code; - - const prettierOptions = await prettier.resolveConfig(configURL || process.cwd()); - const formatted = prettier.format(text, { - singleQuote: true, - semi: true, - ...prettierOptions, - parser: 'babel-ts', - }); - - return formatted; + const { code } = astToText(ast); + return code; } export const parse = (code: string) => parser.parse(code, { sourceType: 'unambiguous', plugins: ['typescript'] }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 17b607f3b7be..29351650e257 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -450,7 +450,6 @@ importers: '@babel/core': ^7.17.8 '@babel/generator': ^7.17.7 '@babel/parser': ^7.17.8 - '@babel/template': ^7.16.7 '@babel/traverse': ^7.17.3 '@babel/types': ^7.17.0 '@proload/core': ^0.2.2 @@ -503,7 +502,6 @@ importers: postcss: ^8.4.12 postcss-load-config: ^3.1.3 preferred-pm: ^3.0.3 - prettier: ^2.6.0 prismjs: ^1.27.0 prompts: ^2.4.2 rehype-slug: ^5.0.1 @@ -534,7 +532,6 @@ importers: '@babel/core': 7.17.8 '@babel/generator': 7.17.7 '@babel/parser': 7.17.8 - '@babel/template': 7.16.7 '@babel/traverse': 7.17.3 '@proload/core': 0.2.2 '@proload/plugin-tsm': 0.1.1_@proload+core@0.2.2 @@ -563,7 +560,6 @@ importers: postcss: 8.4.12 postcss-load-config: 3.1.3 preferred-pm: 3.0.3 - prettier: 2.6.1 prismjs: 1.27.0 prompts: 2.4.2 rehype-slug: 5.0.1 @@ -8592,6 +8588,7 @@ packages: resolution: {integrity: sha512-8UVbTBYGwN37Bs9LERmxCPjdvPxlEowx2urIL6urHzdb3SDq4B/Z6xLFCblrSnE4iKWcS6ziJ3aOYrc1kz/E2A==} engines: {node: '>=10.13.0'} hasBin: true + dev: true /pretty-bytes/5.6.0: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} From 17a0b7c10363516aacf6e2e2e947d0211c68f222 Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 20:22:13 -0300 Subject: [PATCH 16/40] Removed extra argument --- packages/astro/src/cli/add.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index 700421660b1e..d87ef4322d23 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -191,7 +191,7 @@ const enum UpdateResult { async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }): Promise { const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); - const output = await generate(ast, fileURLToPath(configURL)); + const output = await generate(ast); if (input === output) { return UpdateResult.none; From 880c405625ad68a1d57116fbe8ce2f3ad586002e Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 20:30:43 -0300 Subject: [PATCH 17/40] Use `execa` instead of `exec` --- packages/astro/src/cli/add.ts | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index d87ef4322d23..d77916bab01d 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -1,7 +1,7 @@ import type yargs from 'yargs-parser'; import path from 'path'; import fs from 'fs/promises'; -import { exec } from 'child_process'; +import { execaCommand } from 'execa'; import { fileURLToPath } from 'url'; import { diffLines } from 'diff'; import boxen from 'boxen'; @@ -255,22 +255,14 @@ async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() } } -async function tryToInstallIntegrations({ - integrations, - cwd = process.cwd(), - logging, -}: { - integrations: IntegrationInfo[]; - cwd?: string; - logging: LogOptions; -}): Promise { - const cmd = await getInstallIntegrationsCommand({ integrations, cwd }); +async function tryToInstallIntegrations({ integrations, cwd, logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }): Promise { + const installCommand = await getInstallIntegrationsCommand({ integrations, cwd }); - if (cmd === null) { + if (installCommand === null) { info(logging, null); return UpdateResult.none; } else { - const message = `\n${boxen(cyan(cmd), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; + const message = `\n${boxen(cyan(installCommand), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n${message}`); const response = await prompts({ type: 'confirm', @@ -282,15 +274,7 @@ async function tryToInstallIntegrations({ if (response.installDependencies) { const spinner = ora('Installing dependencies...').start(); try { - await new Promise((resolve, reject) => { - exec(cmd, (err, stdout, stderr) => { - if (err) { - reject(stderr); - } else { - resolve(stdout); - } - }); - }); + await execaCommand(installCommand, { cwd }); spinner.succeed(); return UpdateResult.updated; } catch (err) { From 9ec534deb70ea7f666202d5bbd2cbdb873e9c02d Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Thu, 24 Mar 2022 23:49:45 -0300 Subject: [PATCH 18/40] Changed how lines are trimmed within diffLines --- packages/astro/src/cli/add.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts index d77916bab01d..a701282dc4ea 100644 --- a/packages/astro/src/cli/add.ts +++ b/packages/astro/src/cli/add.ts @@ -12,7 +12,7 @@ import { resolveConfigURL } from '../core/config.js'; import { apply as applyPolyfill } from '../core/polyfill.js'; import { error, info, debug, LogOptions } from '../core/logger.js'; import * as msg from '../core/messages.js'; -import { dim, red, cyan, green, bold, magenta } from 'kleur/colors'; +import { dim, red, cyan, green, magenta } from 'kleur/colors'; import { parseNpmName } from '../core/util.js'; import { t, parse, visit, ensureImport, wrapDefaultExport, generate } from '../transform/index.js'; @@ -200,7 +200,7 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti const message = `\n${boxen( diffLines(input, output) .map((change) => { - let lines = change.value.split('\n').slice(0, -1); // remove latest \n + let lines = change.value.split('\n').slice(0, change.count); // remove possible \n if (change.added) lines = lines.map((line) => green(`+ ${line}`)); else if (change.removed) lines = lines.map((line) => red(`- ${line}`)); From 0401ec684ba23e611b47f0edbcfa355aca9da835 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 22:35:11 -0500 Subject: [PATCH 19/40] refactor: move add to core --- packages/astro/src/cli/index.ts | 2 +- .../src/{transform => core/add}/babel.ts | 0 .../src/{transform => core/add}/imports.ts | 0 packages/astro/src/core/add/index.ts | 347 ++++++++++++++++++ .../src/{transform => core/add}/wrapper.ts | 0 packages/astro/src/transform/index.ts | 3 - 6 files changed, 348 insertions(+), 4 deletions(-) rename packages/astro/src/{transform => core/add}/babel.ts (100%) rename packages/astro/src/{transform => core/add}/imports.ts (100%) create mode 100644 packages/astro/src/core/add/index.ts rename packages/astro/src/{transform => core/add}/wrapper.ts (100%) delete mode 100644 packages/astro/src/transform/index.ts diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index b23a8b924fe6..3f04095a3487 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -8,10 +8,10 @@ import yargs from 'yargs-parser'; import { z } from 'zod'; import { defaultLogDestination } from '../core/logger.js'; import build from '../core/build/index.js'; +import add from '../core/add/index.js'; import devServer from '../core/dev/index.js'; import preview from '../core/preview/index.js'; import { check } from './check.js'; -import { add } from './add.js'; import { formatConfigError, loadConfig } from '../core/config.js'; import { pad } from '../core/dev/util.js'; diff --git a/packages/astro/src/transform/babel.ts b/packages/astro/src/core/add/babel.ts similarity index 100% rename from packages/astro/src/transform/babel.ts rename to packages/astro/src/core/add/babel.ts diff --git a/packages/astro/src/transform/imports.ts b/packages/astro/src/core/add/imports.ts similarity index 100% rename from packages/astro/src/transform/imports.ts rename to packages/astro/src/core/add/imports.ts diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts new file mode 100644 index 000000000000..033abec2f7be --- /dev/null +++ b/packages/astro/src/core/add/index.ts @@ -0,0 +1,347 @@ +import type yargs from 'yargs-parser'; +import path from 'path'; +import fs from 'fs/promises'; +import { execaCommand } from 'execa'; +import { fileURLToPath } from 'url'; +import { diffLines } from 'diff'; +import boxen from 'boxen'; +import prompts from 'prompts'; +import preferredPM from 'preferred-pm'; +import ora from 'ora'; +import { resolveConfigURL } from '../config.js'; +import { apply as applyPolyfill } from '../polyfill.js'; +import { error, info, debug, LogOptions } from '../logger.js'; +import * as msg from '../messages.js'; +import { dim, red, cyan, green, magenta } from 'kleur/colors'; +import { parseNpmName } from '../util.js'; +import { wrapDefaultExport } from './wrapper.js'; +import { ensureImport } from './imports.js'; +import { t, parse, visit, generate } from './babel.js'; + +export interface AddOptions { + logging: LogOptions; + cwd?: string; + flags: yargs.Arguments; +} + +export interface IntegrationInfo { + id: string; + packageName: string; + dependencies: [name: string, version: string][]; +} + +const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; + +export default async function add(names: string[], { cwd, flags, logging }: AddOptions) { + if (names.length === 0) { + error(logging, null, `\n${red('No integration specified!')}\n${dim('Try using')} astro add ${cyan('[name]')}`); + return; + } + const root = cwd ? path.resolve(cwd) : process.cwd(); + let configURL = await resolveConfigURL({ cwd, flags }); + applyPolyfill(); + if (configURL) { + debug('add', `Found config at ${configURL}`); + } else { + info(logging, 'add', `Unable to locate a config file, generating one for you.`); + configURL = new URL('./astro.config.mjs', `file://${root}/`); + await fs.writeFile(fileURLToPath(configURL), DEFAULT_CONFIG_STUB, { encoding: 'utf-8' }); + } + + const integrations = await validateIntegrations(names); + + // Add integrations to astro config + // TODO: At the moment, nearly nothing throws an error. We need more errors! + let ast: t.File | null = null; + try { + ast = await parseAstroConfig(configURL); + + debug('add', 'Parsed astro config'); + + const defineConfig = t.identifier('defineConfig'); + ensureImport(ast, t.importDeclaration([t.importSpecifier(defineConfig, defineConfig)], t.stringLiteral('astro/config'))); + wrapDefaultExport(ast, defineConfig); + + debug('add', 'Astro config ensured `defineConfig`'); + + for (const integration of integrations) { + await addIntegration(ast, integration); + debug('add', `Astro config added integration ${integration.id}`); + } + } catch (err) { + debug('add', 'Error parsing/modifying astro config: ', err); + info( + logging, + null, + "Sorry, we couldn't update your configuration automatically. [INSERT HOW TO DO IT MANUALLY --- this link might help: https://next--astro-docs-2.netlify.app/en/guides/integrations-guide/]" + ); + } + + let configResult: UpdateResult | undefined; + let installResult: UpdateResult | undefined; + + if (ast) { + try { + configResult = await updateAstroConfig({ configURL, ast, logging }); + } catch (err) { + debug('add', 'Error updating astro config', err); + error(logging, null, 'There has been an error updating the astro config. You might need to update it manually.'); + return; + } + } + + switch (configResult) { + case UpdateResult.cancelled: { + info(logging, null, msg.cancelled(`Your configuration has not been updated.`)); + return; + } + case UpdateResult.none: { + info(logging, null, msg.success(`Configuration up-to-date. No changes needed!`)); + break; + } + } + + installResult = await tryToInstallIntegrations({ integrations, cwd, logging }); + + switch (installResult) { + case UpdateResult.updated: { + const len = integrations.length; + if (integrations.find((integration) => integration.id === 'tailwind')) { + const DEFAULT_TAILWIND_CONFIG = `module.exports = { + content: [], + theme: { + extend: {}, + }, + plugins: [], +}\n`; + await fs.writeFile(fileURLToPath(new URL('./tailwind.config.mjs', configURL)), DEFAULT_TAILWIND_CONFIG); + } + info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project`)); + return; + } + case UpdateResult.cancelled: { + info(logging, null, msg.cancelled(`No dependencies installed.`, `Be sure to install them manually before continuing!`)); + return; + } + case UpdateResult.failure: { + info(logging, null, msg.failure(`There was a problem installing dependencies.`, `Be sure to install them manually before continuing!`)); + process.exit(1); + } + } +} + +async function parseAstroConfig(configURL: URL): Promise { + const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); + const result = parse(source); + + if (!result) throw new Error('Unknown error parsing astro config'); + if (result.errors.length > 0) throw new Error('Error parsing astro config: ' + JSON.stringify(result.errors)); + + return result; +} + +async function addIntegration(ast: t.File, integration: IntegrationInfo) { + const integrationId = t.identifier(integration.id); + + ensureImport(ast, t.importDeclaration([t.importDefaultSpecifier(integrationId)], t.stringLiteral(integration.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 integrationsProp = configObject.properties.find((prop) => { + if (prop.type !== 'ObjectProperty') return false; + if (prop.key.type === 'Identifier') { + if (prop.key.name === 'integrations') return true; + } + if (prop.key.type === 'StringLiteral') { + if (prop.key.value === 'integrations') return true; + } + return false; + }) as t.ObjectProperty | undefined; + + const integrationCall = t.callExpression(integrationId, []); + + if (!integrationsProp) { + configObject.properties.push(t.objectProperty(t.identifier('integrations'), t.arrayExpression([integrationCall]))); + return; + } + + if (integrationsProp.value.type !== 'ArrayExpression') throw new Error('Unable to parse integrations'); + + const existingIntegrationCall = integrationsProp.value.elements.find( + (expr) => t.isCallExpression(expr) && t.isIdentifier(expr.callee) && expr.callee.name === integrationId.name + ); + + if (existingIntegrationCall) return; + + integrationsProp.value.elements.push(integrationCall); + }, + }); +} + +const enum UpdateResult { + none, + updated, + cancelled, + failure, +} + +async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }): Promise { + const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); + const output = await generate(ast); + + if (input === output) { + return UpdateResult.none; + } + + const message = `\n${boxen( + diffLines(input, output) + .map((change) => { + let lines = change.value.split('\n').slice(0, change.count); // remove possible \n + + if (change.added) lines = lines.map((line) => green(`+ ${line}`)); + else if (change.removed) lines = lines.map((line) => red(`- ${line}`)); + else lines = lines.map((line) => dim(` ${line}`)); + + return lines.join('\n'); + }) + .join('\n'), + { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() } + )}\n`; + + info(logging, null, `\n ${magenta('Astro will update your configuration with these changes...')}\n${message}`); + + const response = await prompts({ + type: 'confirm', + name: 'updateConfig', + message: 'Continue?', + initial: true, + }); + + if (response.updateConfig) { + await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); + debug('add', `Updated astro config`); + return UpdateResult.updated; + } else { + return UpdateResult.cancelled; + } +} + +async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise { + const pm = await preferredPM(cwd); + debug('add', `package manager: ${JSON.stringify(pm)}`); + if (!pm) return null; + + let dependenciesList = 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}`)) + .sort() + .join(' '); + + switch (pm.name) { + case 'npm': + return 'npm install --save-dev ' + dependenciesList; + case 'yarn': + return 'yarn add --dev ' + dependenciesList; + case 'pnpm': + return 'pnpm install --save-dev ' + dependenciesList; + default: + return null; + } +} + +async function tryToInstallIntegrations({ integrations, cwd, logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }): Promise { + const installCommand = await getInstallIntegrationsCommand({ integrations, cwd }); + + if (installCommand === null) { + info(logging, null); + return UpdateResult.none; + } else { + const message = `\n${boxen(cyan(installCommand), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; + info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n${message}`); + const response = await prompts({ + type: 'confirm', + name: 'installDependencies', + message: 'Continue?', + initial: true, + }); + + if (response.installDependencies) { + const spinner = ora('Installing dependencies...').start(); + try { + await execaCommand(installCommand, { cwd }); + spinner.succeed(); + return UpdateResult.updated; + } catch (err) { + debug('add', 'Error installing dependencies', err); + spinner.fail(); + return UpdateResult.failure; + } + } else { + return UpdateResult.cancelled; + } + } +} + +export async function validateIntegrations(integrations: string[]): Promise { + const spinner = ora('Resolving integrations...').start(); + const integrationEntries = await Promise.all( + integrations.map(async (integration): Promise => { + const parsed = parseIntegrationName(integration); + if (!parsed) { + spinner.fail(); + throw new Error(`${integration} does not appear to be a valid package name!`); + } + + let { scope = '', name, tag } = parsed; + // Allow third-party integrations starting with `astro-` namespace + if (!name.startsWith('astro-')) { + scope = `astrojs`; + } + const packageName = `${scope ? `@${scope}/` : ''}${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?`); + } + return res.json(); + }); + + let dependencies: IntegrationInfo['dependencies'] = [[result['name'], `^${result['version']}`]]; + + if (result['peerDependencies']) { + for (const peer in result['peerDependencies']) { + dependencies.push([peer, result['peerDependencies'][peer]]); + } + } + + return { id: integration, packageName, dependencies }; + }) + ); + spinner.succeed(); + return integrationEntries; +} + +function parseIntegrationName(spec: string) { + const result = 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]; + } + return { scope, name, tag }; +} diff --git a/packages/astro/src/transform/wrapper.ts b/packages/astro/src/core/add/wrapper.ts similarity index 100% rename from packages/astro/src/transform/wrapper.ts rename to packages/astro/src/core/add/wrapper.ts diff --git a/packages/astro/src/transform/index.ts b/packages/astro/src/transform/index.ts deleted file mode 100644 index fd2f7995b80f..000000000000 --- a/packages/astro/src/transform/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './babel.js'; -export * from './imports.js'; -export * from './wrapper.js'; From b168fab9396e7b080e23d98a25eaf79107d1886a Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 22:37:53 -0500 Subject: [PATCH 20/40] refactor: remove old add entrypoint --- packages/astro/src/cli/add.ts | 345 ---------------------------------- 1 file changed, 345 deletions(-) delete mode 100644 packages/astro/src/cli/add.ts diff --git a/packages/astro/src/cli/add.ts b/packages/astro/src/cli/add.ts deleted file mode 100644 index a701282dc4ea..000000000000 --- a/packages/astro/src/cli/add.ts +++ /dev/null @@ -1,345 +0,0 @@ -import type yargs from 'yargs-parser'; -import path from 'path'; -import fs from 'fs/promises'; -import { execaCommand } from 'execa'; -import { fileURLToPath } from 'url'; -import { diffLines } from 'diff'; -import boxen from 'boxen'; -import prompts from 'prompts'; -import preferredPM from 'preferred-pm'; -import ora from 'ora'; -import { resolveConfigURL } from '../core/config.js'; -import { apply as applyPolyfill } from '../core/polyfill.js'; -import { error, info, debug, LogOptions } from '../core/logger.js'; -import * as msg from '../core/messages.js'; -import { dim, red, cyan, green, magenta } from 'kleur/colors'; -import { parseNpmName } from '../core/util.js'; -import { t, parse, visit, ensureImport, wrapDefaultExport, generate } from '../transform/index.js'; - -export interface AddOptions { - logging: LogOptions; - cwd?: string; - flags: yargs.Arguments; -} - -export interface IntegrationInfo { - id: string; - packageName: string; - dependencies: [name: string, version: string][]; -} - -const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; - -export async function add(names: string[], { cwd, flags, logging }: AddOptions) { - if (names.length === 0) { - error(logging, null, `\n${red('No integration specified!')}\n${dim('Try using')} astro add ${cyan('[name]')}`); - return; - } - const root = cwd ? path.resolve(cwd) : process.cwd(); - let configURL = await resolveConfigURL({ cwd, flags }); - applyPolyfill(); - if (configURL) { - debug('add', `Found config at ${configURL}`); - } else { - info(logging, 'add', `Unable to locate a config file, generating one for you.`); - configURL = new URL('./astro.config.mjs', `file://${root}/`); - await fs.writeFile(fileURLToPath(configURL), DEFAULT_CONFIG_STUB, { encoding: 'utf-8' }); - } - - const integrations = await validateIntegrations(names); - - // Add integrations to astro config - // TODO: At the moment, nearly nothing throws an error. We need more errors! - let ast: t.File | null = null; - try { - ast = await parseAstroConfig(configURL); - - debug('add', 'Parsed astro config'); - - const defineConfig = t.identifier('defineConfig'); - ensureImport(ast, t.importDeclaration([t.importSpecifier(defineConfig, defineConfig)], t.stringLiteral('astro/config'))); - wrapDefaultExport(ast, defineConfig); - - debug('add', 'Astro config ensured `defineConfig`'); - - for (const integration of integrations) { - await addIntegration(ast, integration); - debug('add', `Astro config added integration ${integration.id}`); - } - } catch (err) { - debug('add', 'Error parsing/modifying astro config: ', err); - info( - logging, - null, - "Sorry, we couldn't update your configuration automatically. [INSERT HOW TO DO IT MANUALLY --- this link might help: https://next--astro-docs-2.netlify.app/en/guides/integrations-guide/]" - ); - } - - let configResult: UpdateResult | undefined; - let installResult: UpdateResult | undefined; - - if (ast) { - try { - configResult = await updateAstroConfig({ configURL, ast, logging }); - } catch (err) { - debug('add', 'Error updating astro config', err); - error(logging, null, 'There has been an error updating the astro config. You might need to update it manually.'); - return; - } - } - - switch (configResult) { - case UpdateResult.cancelled: { - info(logging, null, msg.cancelled(`Your configuration has not been updated.`)); - return; - } - case UpdateResult.none: { - info(logging, null, msg.success(`Configuration up-to-date. No changes needed!`)); - break; - } - } - - installResult = await tryToInstallIntegrations({ integrations, cwd, logging }); - - switch (installResult) { - case UpdateResult.updated: { - const len = integrations.length; - if (integrations.find((integration) => integration.id === 'tailwind')) { - const DEFAULT_TAILWIND_CONFIG = `module.exports = { - content: [], - theme: { - extend: {}, - }, - plugins: [], -}\n`; - await fs.writeFile(fileURLToPath(new URL('./tailwind.config.mjs', configURL)), DEFAULT_TAILWIND_CONFIG); - } - info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project`)); - return; - } - case UpdateResult.cancelled: { - info(logging, null, msg.cancelled(`No dependencies installed.`, `Be sure to install them manually before continuing!`)); - return; - } - case UpdateResult.failure: { - info(logging, null, msg.failure(`There was a problem installing dependencies.`, `Be sure to install them manually before continuing!`)); - process.exit(1); - } - } -} - -async function parseAstroConfig(configURL: URL): Promise { - const source = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); - const result = parse(source); - - if (!result) throw new Error('Unknown error parsing astro config'); - if (result.errors.length > 0) throw new Error('Error parsing astro config: ' + JSON.stringify(result.errors)); - - return result; -} - -async function addIntegration(ast: t.File, integration: IntegrationInfo) { - const integrationId = t.identifier(integration.id); - - ensureImport(ast, t.importDeclaration([t.importDefaultSpecifier(integrationId)], t.stringLiteral(integration.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 integrationsProp = configObject.properties.find((prop) => { - if (prop.type !== 'ObjectProperty') return false; - if (prop.key.type === 'Identifier') { - if (prop.key.name === 'integrations') return true; - } - if (prop.key.type === 'StringLiteral') { - if (prop.key.value === 'integrations') return true; - } - return false; - }) as t.ObjectProperty | undefined; - - const integrationCall = t.callExpression(integrationId, []); - - if (!integrationsProp) { - configObject.properties.push(t.objectProperty(t.identifier('integrations'), t.arrayExpression([integrationCall]))); - return; - } - - if (integrationsProp.value.type !== 'ArrayExpression') throw new Error('Unable to parse integrations'); - - const existingIntegrationCall = integrationsProp.value.elements.find( - (expr) => t.isCallExpression(expr) && t.isIdentifier(expr.callee) && expr.callee.name === integrationId.name - ); - - if (existingIntegrationCall) return; - - integrationsProp.value.elements.push(integrationCall); - }, - }); -} - -const enum UpdateResult { - none, - updated, - cancelled, - failure, -} - -async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }): Promise { - const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); - const output = await generate(ast); - - if (input === output) { - return UpdateResult.none; - } - - const message = `\n${boxen( - diffLines(input, output) - .map((change) => { - let lines = change.value.split('\n').slice(0, change.count); // remove possible \n - - if (change.added) lines = lines.map((line) => green(`+ ${line}`)); - else if (change.removed) lines = lines.map((line) => red(`- ${line}`)); - else lines = lines.map((line) => dim(` ${line}`)); - - return lines.join('\n'); - }) - .join('\n'), - { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() } - )}\n`; - - info(logging, null, `\n ${magenta('Astro will update your configuration with these changes...')}\n${message}`); - - const response = await prompts({ - type: 'confirm', - name: 'updateConfig', - message: 'Continue?', - initial: true, - }); - - if (response.updateConfig) { - await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); - debug('add', `Updated astro config`); - return UpdateResult.updated; - } else { - return UpdateResult.cancelled; - } -} - -async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise { - const pm = await preferredPM(cwd); - debug('add', `package manager: ${JSON.stringify(pm)}`); - if (!pm) return null; - - let dependenciesList = 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}`)) - .sort() - .join(' '); - - switch (pm.name) { - case 'npm': - return 'npm install --save-dev ' + dependenciesList; - case 'yarn': - return 'yarn add --dev ' + dependenciesList; - case 'pnpm': - return 'pnpm install --save-dev ' + dependenciesList; - default: - return null; - } -} - -async function tryToInstallIntegrations({ integrations, cwd, logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }): Promise { - const installCommand = await getInstallIntegrationsCommand({ integrations, cwd }); - - if (installCommand === null) { - info(logging, null); - return UpdateResult.none; - } else { - const message = `\n${boxen(cyan(installCommand), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; - info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n${message}`); - const response = await prompts({ - type: 'confirm', - name: 'installDependencies', - message: 'Continue?', - initial: true, - }); - - if (response.installDependencies) { - const spinner = ora('Installing dependencies...').start(); - try { - await execaCommand(installCommand, { cwd }); - spinner.succeed(); - return UpdateResult.updated; - } catch (err) { - debug('add', 'Error installing dependencies', err); - spinner.fail(); - return UpdateResult.failure; - } - } else { - return UpdateResult.cancelled; - } - } -} - -export async function validateIntegrations(integrations: string[]): Promise { - const spinner = ora('Resolving integrations...').start(); - const integrationEntries = await Promise.all( - integrations.map(async (integration): Promise => { - const parsed = parseIntegrationName(integration); - if (!parsed) { - spinner.fail(); - throw new Error(`${integration} does not appear to be a valid package name!`); - } - - let { scope = '', name, tag } = parsed; - // Allow third-party integrations starting with `astro-` namespace - if (!name.startsWith('astro-')) { - scope = `astrojs`; - } - const packageName = `${scope ? `@${scope}/` : ''}${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?`); - } - return res.json(); - }); - - let dependencies: IntegrationInfo['dependencies'] = [[result['name'], `^${result['version']}`]]; - - if (result['peerDependencies']) { - for (const peer in result['peerDependencies']) { - dependencies.push([peer, result['peerDependencies'][peer]]); - } - } - - return { id: integration, packageName, dependencies }; - }) - ); - spinner.succeed(); - return integrationEntries; -} - -function parseIntegrationName(spec: string) { - const result = 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]; - } - return { scope, name, tag }; -} From 6b945747a77c85d7795d3bc8f92f613eb1cb9ec2 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 24 Mar 2022 22:38:52 -0500 Subject: [PATCH 21/40] refactor: simplify wording --- packages/astro/src/core/add/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 033abec2f7be..816c4772ec97 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -96,7 +96,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO return; } case UpdateResult.none: { - info(logging, null, msg.success(`Configuration up-to-date. No changes needed!`)); + info(logging, null, msg.success(`Configuration up-to-date.`)); break; } } From 28033574861a7f372bcf3180c15abe3026b90196 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 10:07:32 -0500 Subject: [PATCH 22/40] feat: improve diff --- packages/astro/src/core/add/imports.ts | 3 +-- packages/astro/src/core/add/index.ts | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/astro/src/core/add/imports.ts b/packages/astro/src/core/add/imports.ts index 8aef09d209c4..bae8c7443781 100644 --- a/packages/astro/src/core/add/imports.ts +++ b/packages/astro/src/core/add/imports.ts @@ -28,8 +28,7 @@ export function ensureImport(root: t.File, importDeclaration: t.ImportDeclaratio .filter((statement) => statement.isImportDeclaration()) .pop(); - // It's inserted before because of formatting issues - if (latestImport) latestImport.insertBefore(declaration); + if (latestImport) latestImport.insertAfter(declaration); else path.unshiftContainer('body', declaration); }, }); diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 816c4772ec97..e2890080c0ae 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -193,7 +193,11 @@ const enum UpdateResult { async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }): Promise { const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); - const output = await generate(ast); + let output = await generate(ast); + const comment = '// https://astro.build/config'; + const defaultExport = 'export default defineConfig'; + output = output.replace(` ${comment}`, ''); + output = output.replace(`${defaultExport}`, `\n${comment}\n${defaultExport}`); if (input === output) { return UpdateResult.none; From f6bb9ee819327dfe42d3d7e7297bafa44277a3b0 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 11:00:18 -0500 Subject: [PATCH 23/40] feat: improve diff and logging, add interactive prompt when no args passed --- packages/astro/src/core/add/index.ts | 116 +++++++++++++++++++++------ 1 file changed, 91 insertions(+), 25 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index e2890080c0ae..c4455efe3682 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -3,7 +3,7 @@ import path from 'path'; import fs from 'fs/promises'; import { execaCommand } from 'execa'; import { fileURLToPath } from 'url'; -import { diffLines } from 'diff'; +import { diffWords } from 'diff'; import boxen from 'boxen'; import prompts from 'prompts'; import preferredPM from 'preferred-pm'; @@ -12,7 +12,7 @@ import { resolveConfigURL } from '../config.js'; import { apply as applyPolyfill } from '../polyfill.js'; import { error, info, debug, LogOptions } from '../logger.js'; import * as msg from '../messages.js'; -import { dim, red, cyan, green, magenta } from 'kleur/colors'; +import { dim, red, cyan, green, magenta, bold, reset } from 'kleur/colors'; import { parseNpmName } from '../util.js'; import { wrapDefaultExport } from './wrapper.js'; import { ensureImport } from './imports.js'; @@ -30,13 +30,60 @@ export interface IntegrationInfo { dependencies: [name: string, version: string][]; } +const DEFAULT_FRAMEWORKS = [ + { value: 'react', title: 'React' }, + { value: 'preact', title: 'Preact' }, + { value: 'vue', title: 'Vue' }, + { value: 'svelte', title: 'Svelte' }, + { value: 'solid-js', title: 'Solid' }, + { value: 'lit', title: 'Lit' }, +] +const DEFAULT_ADDONS = [ + { value: 'tailwind', title: 'Tailwind' }, + { value: 'turbolinks', title: 'Turbolinks' }, + { value: 'partytown', title: 'Partytown' }, + { value: 'sitemap', title: 'Sitemap' }, +] + +const ALIASES = new Map([ + ['solid', 'solid-js'], + ['tailwindcss', 'tailwind'], +]) +const INSIGNIFICANT_CHARS = new Set([',', ']', '}']) + const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; export default async function add(names: string[], { cwd, flags, logging }: AddOptions) { if (names.length === 0) { - error(logging, null, `\n${red('No integration specified!')}\n${dim('Try using')} astro add ${cyan('[name]')}`); - return; + const response = await prompts([{ + type: 'multiselect', + name: 'frameworks', + message: 'What frameworks would you like to enable?', + instructions: '\n Space to select. Return to submit', + choices: DEFAULT_FRAMEWORKS, + }, { + type: 'multiselect', + name: 'addons', + message: 'What additional integrations would you like to enable?', + instructions: '\n Space to select. Return to submit', + choices: DEFAULT_ADDONS, + }]); + + if (!response.frameworks && !response.addons) { + info(logging, null, msg.cancelled(`Integrations skipped.`, `You can always run ${cyan('astro add')} later!`)); + return; + } + const selected = [(response.frameworks ?? []), (response.addons ?? [])].flat(1); + if (selected.length === 0) { + error(logging, null, `\n${red('No integrations specified!')}\n${dim('Try running')} astro add again.`); + return; + } + names = selected; } + + // Some packages might have a common alias! We normalize those here. + names = names.map(name => ALIASES.has(name) ? ALIASES.get(name)! : name); + const root = cwd ? path.resolve(cwd) : process.cwd(); let configURL = await resolveConfigURL({ cwd, flags }); applyPolyfill(); @@ -92,7 +139,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO switch (configResult) { case UpdateResult.cancelled: { - info(logging, null, msg.cancelled(`Your configuration has not been updated.`)); + info(logging, null, msg.cancelled(`Your configuration has ${bold('NOT')} been updated.`)); return; } case UpdateResult.none: { @@ -140,8 +187,15 @@ async function parseAstroConfig(configURL: URL): Promise { return result; } +const toIdent = (name: string) => { + if (name.includes('-')) { + return name.split('-')[0]; + } + return name; +} + async function addIntegration(ast: t.File, integration: IntegrationInfo) { - const integrationId = t.identifier(integration.id); + const integrationId = t.identifier(toIdent(integration.id)); ensureImport(ast, t.importDeclaration([t.importDefaultSpecifier(integrationId)], t.stringLiteral(integration.packageName))); @@ -203,18 +257,23 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti return UpdateResult.none; } - const message = `\n${boxen( - diffLines(input, output) - .map((change) => { - let lines = change.value.split('\n').slice(0, change.count); // remove possible \n - - if (change.added) lines = lines.map((line) => green(`+ ${line}`)); - else if (change.removed) lines = lines.map((line) => red(`- ${line}`)); - else lines = lines.map((line) => dim(` ${line}`)); + let changes = []; + for (const change of diffWords(input, output)) { + let lines = change.value.trim().split('\n').slice(0, change.count).filter(x => x && !INSIGNIFICANT_CHARS.has(x)); + if (lines.length === 0) continue; + if (change.added) { + if (INSIGNIFICANT_CHARS.has(change.value.trim())) continue; + changes.push(change.value); + } + } + let diffed = output; + for (let newContent of changes) { + const coloredOutput = newContent.split('\n').map(ln => ln ? green(ln) : '').join('\n'); + diffed = diffed.replace(newContent, coloredOutput); + } - return lines.join('\n'); - }) - .join('\n'), + const message = `\n${boxen( + diffed, { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() } )}\n`; @@ -236,12 +295,18 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti } } -async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise { +interface InstallCommand { + pm: string; + command: string; + flags: string[]; + dependencies: string; +} +async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise { const pm = await preferredPM(cwd); debug('add', `package manager: ${JSON.stringify(pm)}`); if (!pm) return null; - let dependenciesList = integrations + 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) @@ -251,11 +316,11 @@ async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() switch (pm.name) { case 'npm': - return 'npm install --save-dev ' + dependenciesList; + return { pm: 'npm', command: 'install', flags: ['--save-dev'], dependencies }; case 'yarn': - return 'yarn add --dev ' + dependenciesList; + return { pm: 'yarn', command: 'add', flags: ['--dev'], dependencies }; case 'pnpm': - return 'pnpm install --save-dev ' + dependenciesList; + return { pm: 'pnpm', command: 'install', flags: ['--save-dev'], dependencies }; default: return null; } @@ -268,8 +333,9 @@ async function tryToInstallIntegrations({ integrations, cwd, logging }: { integr info(logging, null); return UpdateResult.none; } else { - const message = `\n${boxen(cyan(installCommand), { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; - info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n${message}`); + const coloredOutput = `${cyan(`${installCommand.pm} ${installCommand.command} ${installCommand.flags.join(' ')}`)} ${installCommand.dependencies}` + const message = `\n${boxen(coloredOutput, { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; + info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n ${dim('If you skip this step, you can always run it yourself later')}\n${message}`); const response = await prompts({ type: 'confirm', name: 'installDependencies', @@ -280,7 +346,7 @@ async function tryToInstallIntegrations({ integrations, cwd, logging }: { integr if (response.installDependencies) { const spinner = ora('Installing dependencies...').start(); try { - await execaCommand(installCommand, { cwd }); + await execaCommand(`${installCommand.pm} ${installCommand.command} ${installCommand.flags} ${installCommand.dependencies}`, { cwd }); spinner.succeed(); return UpdateResult.updated; } catch (err) { From 8610d78ab85dce30a2b1b06109d2e0d2021772fc Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Fri, 25 Mar 2022 13:36:08 -0300 Subject: [PATCH 24/40] Formatted files --- packages/astro/src/cli/index.ts | 16 +++---- packages/astro/src/core/add/index.ts | 67 ++++++++++++++++------------ packages/astro/src/core/config.ts | 2 +- packages/astro/src/core/messages.ts | 15 +++++-- 4 files changed, 60 insertions(+), 40 deletions(-) diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 3f04095a3487..bd95b990c45d 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -103,7 +103,7 @@ function resolveCommand(flags: Arguments): CLICommand { const cmd = flags._[2] as string; const supportedCommands = new Set(['add', 'dev', 'build', 'preview', 'check']); if (supportedCommands.has(cmd)) { - return cmd as 'add' | 'dev' | 'build' | 'preview' | 'check'; + return cmd as CLICommand; } return 'help'; } @@ -139,13 +139,13 @@ export async function cli(args: string[]) { } if (cmd === 'add') { - try { - const packages = flags._.slice(3) as string[]; - await add(packages, { cwd: projectRoot, flags, logging }); - process.exit(0); - } catch (err) { - throwAndExit(err); - } + try { + const packages = flags._.slice(3) as string[]; + await add(packages, { cwd: projectRoot, flags, logging }); + process.exit(0); + } catch (err) { + throwAndExit(err); + } } let config: AstroConfig; diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index c4455efe3682..038296b2dec8 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -37,43 +37,46 @@ const DEFAULT_FRAMEWORKS = [ { value: 'svelte', title: 'Svelte' }, { value: 'solid-js', title: 'Solid' }, { value: 'lit', title: 'Lit' }, -] +]; const DEFAULT_ADDONS = [ { value: 'tailwind', title: 'Tailwind' }, { value: 'turbolinks', title: 'Turbolinks' }, { value: 'partytown', title: 'Partytown' }, { value: 'sitemap', title: 'Sitemap' }, -] +]; const ALIASES = new Map([ ['solid', 'solid-js'], ['tailwindcss', 'tailwind'], -]) -const INSIGNIFICANT_CHARS = new Set([',', ']', '}']) +]); +const INSIGNIFICANT_CHARS = new Set([',', ']', '}']); const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; export default async function add(names: string[], { cwd, flags, logging }: AddOptions) { if (names.length === 0) { - const response = await prompts([{ - type: 'multiselect', - name: 'frameworks', - message: 'What frameworks would you like to enable?', - instructions: '\n Space to select. Return to submit', - choices: DEFAULT_FRAMEWORKS, - }, { - type: 'multiselect', - name: 'addons', - message: 'What additional integrations would you like to enable?', - instructions: '\n Space to select. Return to submit', - choices: DEFAULT_ADDONS, - }]); + const response = await prompts([ + { + type: 'multiselect', + name: 'frameworks', + message: 'What frameworks would you like to enable?', + instructions: '\n Space to select. Return to submit', + choices: DEFAULT_FRAMEWORKS, + }, + { + type: 'multiselect', + name: 'addons', + message: 'What additional integrations would you like to enable?', + instructions: '\n Space to select. Return to submit', + choices: DEFAULT_ADDONS, + }, + ]); if (!response.frameworks && !response.addons) { info(logging, null, msg.cancelled(`Integrations skipped.`, `You can always run ${cyan('astro add')} later!`)); return; } - const selected = [(response.frameworks ?? []), (response.addons ?? [])].flat(1); + const selected = [response.frameworks ?? [], response.addons ?? []].flat(1); if (selected.length === 0) { error(logging, null, `\n${red('No integrations specified!')}\n${dim('Try running')} astro add again.`); return; @@ -82,7 +85,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO } // Some packages might have a common alias! We normalize those here. - names = names.map(name => ALIASES.has(name) ? ALIASES.get(name)! : name); + names = names.map((name) => (ALIASES.has(name) ? ALIASES.get(name)! : name)); const root = cwd ? path.resolve(cwd) : process.cwd(); let configURL = await resolveConfigURL({ cwd, flags }); @@ -192,7 +195,7 @@ const toIdent = (name: string) => { return name.split('-')[0]; } return name; -} +}; async function addIntegration(ast: t.File, integration: IntegrationInfo) { const integrationId = t.identifier(toIdent(integration.id)); @@ -259,7 +262,11 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti let changes = []; for (const change of diffWords(input, output)) { - let lines = change.value.trim().split('\n').slice(0, change.count).filter(x => x && !INSIGNIFICANT_CHARS.has(x)); + let lines = change.value + .trim() + .split('\n') + .slice(0, change.count) + .filter((x) => x && !INSIGNIFICANT_CHARS.has(x)); if (lines.length === 0) continue; if (change.added) { if (INSIGNIFICANT_CHARS.has(change.value.trim())) continue; @@ -268,14 +275,14 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti } let diffed = output; for (let newContent of changes) { - const coloredOutput = newContent.split('\n').map(ln => ln ? green(ln) : '').join('\n'); + const coloredOutput = newContent + .split('\n') + .map((ln) => (ln ? green(ln) : '')) + .join('\n'); diffed = diffed.replace(newContent, coloredOutput); } - const message = `\n${boxen( - diffed, - { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() } - )}\n`; + const message = `\n${boxen(diffed, { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() })}\n`; info(logging, null, `\n ${magenta('Astro will update your configuration with these changes...')}\n${message}`); @@ -333,9 +340,13 @@ async function tryToInstallIntegrations({ integrations, cwd, logging }: { integr info(logging, null); return UpdateResult.none; } else { - const coloredOutput = `${cyan(`${installCommand.pm} ${installCommand.command} ${installCommand.flags.join(' ')}`)} ${installCommand.dependencies}` + const coloredOutput = `${cyan(`${installCommand.pm} ${installCommand.command} ${installCommand.flags.join(' ')}`)} ${installCommand.dependencies}`; const message = `\n${boxen(coloredOutput, { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; - info(logging, null, `\n ${magenta('Astro will run the following command to install...')}\n ${dim('If you skip this step, you can always run it yourself later')}\n${message}`); + info( + logging, + null, + `\n ${magenta('Astro will run the following command to install...')}\n ${dim('If you skip this step, you can always run it yourself later')}\n${message}` + ); const response = await prompts({ type: 'confirm', name: 'installDependencies', diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index dc7bacc5ddf5..23233958d2ce 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -267,7 +267,7 @@ interface LoadConfigOptions { } /** Resolve the file URL of the user's `astro.config.js|cjs|mjs|ts` file */ -export async function resolveConfigURL(configOptions: LoadConfigOptions): Promise { +export async function resolveConfigURL(configOptions: LoadConfigOptions): Promise { const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd(); const flags = resolveFlags(configOptions.flags || {}); let userConfigPath: string | undefined; diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index a4a22660b500..b678d91c29a7 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -91,21 +91,30 @@ export function success(message: string, tip?: string) { const badge = bgGreen(black(` success `)); const headline = green(message); const footer = tip ? `\n ▶ ${tip}` : undefined; - return ['', badge, headline, footer].filter(v => v !== undefined).map((msg) => ` ${msg}`).join('\n'); + return ['', badge, headline, footer] + .filter((v) => v !== undefined) + .map((msg) => ` ${msg}`) + .join('\n'); } export function failure(message: string, tip?: string) { const badge = bgRed(black(` error `)); const headline = red(message); const footer = tip ? `\n ▶ ${tip}` : undefined; - return ['', badge, headline, footer].filter(v => v !== undefined).map((msg) => ` ${msg}`).join('\n'); + return ['', badge, headline, footer] + .filter((v) => v !== undefined) + .map((msg) => ` ${msg}`) + .join('\n'); } export function cancelled(message: string, tip?: string) { const badge = bgYellow(black(` cancelled `)); const headline = yellow(message); const footer = tip ? `\n ▶ ${tip}` : undefined; - return ['', badge, headline, footer].filter(v => v !== undefined).map((msg) => ` ${msg}`).join('\n'); + return ['', badge, headline, footer] + .filter((v) => v !== undefined) + .map((msg) => ` ${msg}`) + .join('\n'); } /** Display port in use */ From 59030bda9d8487348aa1e3e4b12dad95091d32cd Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Fri, 25 Mar 2022 13:44:58 -0300 Subject: [PATCH 25/40] Added --yes --- packages/astro/src/core/add/index.ts | 50 +++++++++++++++++----------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 038296b2dec8..57889ef75131 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -132,7 +132,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO if (ast) { try { - configResult = await updateAstroConfig({ configURL, ast, logging }); + configResult = await updateAstroConfig({ configURL, ast, flags, logging }); } catch (err) { debug('add', 'Error updating astro config', err); error(logging, null, 'There has been an error updating the astro config. You might need to update it manually.'); @@ -151,7 +151,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO } } - installResult = await tryToInstallIntegrations({ integrations, cwd, logging }); + installResult = await tryToInstallIntegrations({ integrations, cwd, flags, logging }); switch (installResult) { case UpdateResult.updated: { @@ -248,7 +248,7 @@ const enum UpdateResult { failure, } -async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOptions; configURL: URL; ast: t.File }): Promise { +async function updateAstroConfig({ configURL, ast, flags, logging }: { configURL: URL; ast: t.File; flags: yargs.Arguments; logging: LogOptions }): Promise { const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' }); let output = await generate(ast); const comment = '// https://astro.build/config'; @@ -286,14 +286,7 @@ async function updateAstroConfig({ configURL, ast, logging }: { logging: LogOpti info(logging, null, `\n ${magenta('Astro will update your configuration with these changes...')}\n${message}`); - const response = await prompts({ - type: 'confirm', - name: 'updateConfig', - message: 'Continue?', - initial: true, - }); - - if (response.updateConfig) { + if (await askToContinue({ flags })) { await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); debug('add', `Updated astro config`); return UpdateResult.updated; @@ -333,7 +326,17 @@ async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() } } -async function tryToInstallIntegrations({ integrations, cwd, logging }: { integrations: IntegrationInfo[]; cwd?: string; logging: LogOptions }): Promise { +async function tryToInstallIntegrations({ + integrations, + cwd, + flags, + logging, +}: { + integrations: IntegrationInfo[]; + cwd?: string; + flags: yargs.Arguments; + logging: LogOptions; +}): Promise { const installCommand = await getInstallIntegrationsCommand({ integrations, cwd }); if (installCommand === null) { @@ -347,14 +350,8 @@ async function tryToInstallIntegrations({ integrations, cwd, logging }: { integr null, `\n ${magenta('Astro will run the following command to install...')}\n ${dim('If you skip this step, you can always run it yourself later')}\n${message}` ); - const response = await prompts({ - type: 'confirm', - name: 'installDependencies', - message: 'Continue?', - initial: true, - }); - - if (response.installDependencies) { + + if (await askToContinue({ flags })) { const spinner = ora('Installing dependencies...').start(); try { await execaCommand(`${installCommand.pm} ${installCommand.command} ${installCommand.flags} ${installCommand.dependencies}`, { cwd }); @@ -426,3 +423,16 @@ function parseIntegrationName(spec: string) { } return { scope, name, tag }; } + +async function askToContinue({ flags }: { flags: yargs.Arguments }): Promise { + if (flags.yes) return true; + + const response = await prompts({ + type: 'confirm', + name: 'askToContinue', + message: 'Continue?', + initial: true, + }); + + return Boolean(response.askToContinue); +} From c9421169397e197c585de426f0d37a892c991849 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 12:00:54 -0500 Subject: [PATCH 26/40] feat: improve logging for install command --- packages/astro/src/core/add/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 57889ef75131..58279034cd85 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -343,7 +343,7 @@ async function tryToInstallIntegrations({ info(logging, null); return UpdateResult.none; } else { - const coloredOutput = `${cyan(`${installCommand.pm} ${installCommand.command} ${installCommand.flags.join(' ')}`)} ${installCommand.dependencies}`; + const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command} ${installCommand.flags.join(' ')} ${cyan(installCommand.dependencies)}` const message = `\n${boxen(coloredOutput, { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; info( logging, From e035d102feae2963c3be3c3555566d40bdbd3ceb Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Fri, 25 Mar 2022 14:18:25 -0300 Subject: [PATCH 27/40] Fixed execa --- packages/astro/src/core/add/index.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 58279034cd85..53bd8b4fa20d 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -1,7 +1,7 @@ import type yargs from 'yargs-parser'; import path from 'path'; import fs from 'fs/promises'; -import { execaCommand } from 'execa'; +import { execa } from 'execa'; import { fileURLToPath } from 'url'; import { diffWords } from 'diff'; import boxen from 'boxen'; @@ -299,7 +299,7 @@ interface InstallCommand { pm: string; command: string; flags: string[]; - dependencies: string; + dependencies: string[]; } async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise { const pm = await preferredPM(cwd); @@ -311,8 +311,7 @@ async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() .flat(1) .filter((dep, i, arr) => arr.findIndex((d) => d[0] === dep[0]) === i) .map(([name, version]) => (version === null ? name : `${name}@${version}`)) - .sort() - .join(' '); + .sort(); switch (pm.name) { case 'npm': @@ -343,7 +342,7 @@ async function tryToInstallIntegrations({ info(logging, null); return UpdateResult.none; } else { - const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command} ${installCommand.flags.join(' ')} ${cyan(installCommand.dependencies)}` + const coloredOutput = `${bold(installCommand.pm)} ${installCommand.command} ${installCommand.flags.join(' ')} ${cyan(installCommand.dependencies.join(' '))}`; const message = `\n${boxen(coloredOutput, { margin: 0.5, padding: 0.5, borderStyle: 'round' })}\n`; info( logging, @@ -354,7 +353,7 @@ async function tryToInstallIntegrations({ if (await askToContinue({ flags })) { const spinner = ora('Installing dependencies...').start(); try { - await execaCommand(`${installCommand.pm} ${installCommand.command} ${installCommand.flags} ${installCommand.dependencies}`, { cwd }); + await execa(installCommand.pm, [installCommand.command, ...installCommand.flags, ...installCommand.dependencies], { cwd }); spinner.succeed(); return UpdateResult.updated; } catch (err) { From a43bf5ec8fc44677494f3c829ba0f864aeed5d54 Mon Sep 17 00:00:00 2001 From: JuanM04 Date: Fri, 25 Mar 2022 14:31:10 -0300 Subject: [PATCH 28/40] Added help message to add --- packages/astro/src/cli/index.ts | 69 ++++++---------------------- packages/astro/src/core/add/index.ts | 15 +++++- packages/astro/src/core/messages.ts | 56 +++++++++++++++++++++- 3 files changed, 84 insertions(+), 56 deletions(-) diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index bd95b990c45d..09cd78020629 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -13,19 +13,17 @@ import devServer from '../core/dev/index.js'; import preview from '../core/preview/index.js'; import { check } from './check.js'; import { formatConfigError, loadConfig } from '../core/config.js'; -import { pad } from '../core/dev/util.js'; +import { printHelp } from '../core/messages.js'; type Arguments = yargs.Arguments; type CLICommand = 'help' | 'version' | 'add' | 'dev' | 'build' | 'preview' | 'reload' | 'check'; /** Display --help flag */ -function printHelp() { - linebreak(); - headline('astro', 'Futuristic web development tool.'); - linebreak(); - title('Commands'); - table( - [ +function printAstroHelp() { + printHelp({ + commandName: 'astro', + headline: 'Futuristic web development tool.', + commands: [ ['add', 'Add an integration to your configuration.'], ['dev', 'Run Astro in development mode.'], ['build', 'Build a pre-compiled production-ready site.'], @@ -34,12 +32,7 @@ function printHelp() { ['--version', 'Show the version number and exit.'], ['--help', 'Show this help message.'], ], - { padding: 28, prefix: ' astro ' } - ); - linebreak(); - title('Flags'); - table( - [ + flags: [ ['--host [optional IP]', 'Expose server on network'], ['--config ', 'Specify the path to the Astro config file.'], ['--project-root ', 'Specify the path to the project root folder.'], @@ -50,39 +43,7 @@ function printHelp() { ['--verbose', 'Enable verbose logging'], ['--silent', 'Disable logging'], ], - { padding: 28, prefix: ' ' } - ); - - // Logging utils - function linebreak() { - console.log(); - } - - function headline(name: string, tagline: string) { - console.log(` ${colors.bgGreen(colors.black(` ${name} `))} ${colors.green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${tagline}`); - } - function title(label: string) { - console.log(` ${colors.bgWhite(colors.black(` ${label} `))}`); - } - function table(rows: [string, string][], opts: { padding: number; prefix: string }) { - const split = rows.some((row) => { - const message = `${opts.prefix}${' '.repeat(opts.padding)}${row[1]}`; - return message.length > process.stdout.columns; - }); - for (const row of rows) { - row.forEach((col, i) => { - if (i === 0) { - process.stdout.write(`${opts.prefix}${colors.bold(pad(`${col}`, opts.padding - opts.prefix.length))}`); - } else { - if (split) { - process.stdout.write('\n '); - } - process.stdout.write(colors.dim(col) + '\n'); - } - }); - } - return ''; - } + }); } /** Display --version flag */ @@ -95,13 +56,13 @@ async function printVersion() { /** Determine which command the user requested */ function resolveCommand(flags: Arguments): CLICommand { - if (flags.version) { - return 'version'; - } else if (flags.help) { - return 'help'; - } const cmd = flags._[2] as string; - const supportedCommands = new Set(['add', 'dev', 'build', 'preview', 'check']); + if (cmd === 'add') return 'add'; + + if (flags.version) return 'version'; + else if (flags.help) return 'help'; + + const supportedCommands = new Set(['dev', 'build', 'preview', 'check']); if (supportedCommands.has(cmd)) { return cmd as CLICommand; } @@ -119,7 +80,7 @@ export async function cli(args: string[]) { switch (cmd) { case 'help': - printHelp(); + printAstroHelp(); return process.exit(0); case 'version': await printVersion(); diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 53bd8b4fa20d..274ab3687975 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -11,8 +11,9 @@ import ora from 'ora'; import { resolveConfigURL } from '../config.js'; import { apply as applyPolyfill } from '../polyfill.js'; import { error, info, debug, LogOptions } from '../logger.js'; +import { printHelp } from '../messages.js'; import * as msg from '../messages.js'; -import { dim, red, cyan, green, magenta, bold, reset } from 'kleur/colors'; +import { dim, red, cyan, green, magenta, bold } from 'kleur/colors'; import { parseNpmName } from '../util.js'; import { wrapDefaultExport } from './wrapper.js'; import { ensureImport } from './imports.js'; @@ -54,6 +55,18 @@ const INSIGNIFICANT_CHARS = new Set([',', ']', '}']); const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; export default async function add(names: string[], { cwd, flags, logging }: AddOptions) { + if (flags.help) { + printHelp({ + commandName: 'astro add', + usage: '[FLAGS] [INTEGRATIONS...]', + flags: [ + ['--yes', 'Add the integration without user interaction.'], + ['--help', 'Show this help message.'], + ], + }); + return; + } + if (names.length === 0) { const response = await prompts([ { diff --git a/packages/astro/src/core/messages.ts b/packages/astro/src/core/messages.ts index b678d91c29a7..e3a7741c44fe 100644 --- a/packages/astro/src/core/messages.ts +++ b/packages/astro/src/core/messages.ts @@ -3,7 +3,7 @@ */ import stripAnsi from 'strip-ansi'; -import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black, bgRed } from 'kleur/colors'; +import { bold, dim, red, green, underline, yellow, bgYellow, cyan, bgGreen, black, bgRed, bgWhite } from 'kleur/colors'; import { pad, emoji, getLocalAddress, getNetworkLogging } from './dev/util.js'; import os from 'os'; import type { AddressInfo } from 'net'; @@ -132,3 +132,57 @@ export function err(error: Error): string { stack = stack.slice(split).replace(/^\n+/, ''); return `${message}\n${dim(stack)}`; } + +export function printHelp({ + commandName, + headline, + usage, + commands, + flags, +}: { + commandName: string; + headline?: string; + usage?: string; + commands?: [command: string, help: string][]; + flags?: [flag: string, help: string][]; +}) { + const linebreak = () => ''; + const title = (label: string) => ` ${bgWhite(black(` ${label} `))}`; + const table = (rows: [string, string][], opts: { padding: number; prefix: string }) => { + const split = rows.some((row) => { + const message = `${opts.prefix}${' '.repeat(opts.padding)}${row[1]}`; + return message.length > process.stdout.columns; + }); + + let raw = ''; + + for (const row of rows) { + raw += `${opts.prefix}${bold(pad(`${row[0]}`, opts.padding - opts.prefix.length))}`; + if (split) raw += '\n '; + raw += dim(row[1]) + '\n'; + } + + return raw.slice(0, -1); // remove latest \n + }; + + let message = []; + + if (headline) { + message.push(linebreak(), ` ${bgGreen(black(` ${commandName} `))} ${green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${headline}`); + } + + if (usage) { + message.push(linebreak(), ` ${green(commandName)} ${bold(usage)}`); + } + + if (commands) { + message.push(linebreak(), title('Commands'), table(commands, { padding: 28, prefix: ' astro ' })); + } + + if (flags) { + message.push(linebreak(), title('Flags'), table(flags, { padding: 28, prefix: ' ' })); + } + + // eslint-disable-next-line no-console + console.log(message.join('\n')); +} From 638dc73b46ca02c03104834a509c4fc1e9c59acf Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 13:06:54 -0500 Subject: [PATCH 29/40] refactor: extract consts to own file --- packages/astro/src/core/add/consts.ts | 19 ++++++++++++++++ packages/astro/src/core/add/index.ts | 32 +++++---------------------- 2 files changed, 24 insertions(+), 27 deletions(-) create mode 100644 packages/astro/src/core/add/consts.ts diff --git a/packages/astro/src/core/add/consts.ts b/packages/astro/src/core/add/consts.ts new file mode 100644 index 000000000000..dfc20a3d78ec --- /dev/null +++ b/packages/astro/src/core/add/consts.ts @@ -0,0 +1,19 @@ +export const FIRST_PARTY_FRAMEWORKS = [ + { value: 'react', title: 'React' }, + { value: 'preact', title: 'Preact' }, + { value: 'vue', title: 'Vue' }, + { value: 'svelte', title: 'Svelte' }, + { value: 'solid-js', title: 'Solid' }, + { value: 'lit', title: 'Lit' }, +]; +export const FIRST_PARTY_ADDONS = [ + { value: 'tailwind', title: 'Tailwind' }, + { value: 'turbolinks', title: 'Turbolinks' }, + { value: 'partytown', title: 'Partytown' }, + { value: 'sitemap', title: 'Sitemap' }, +]; +export const ALIASES = new Map([ + ['solid', 'solid-js'], + ['tailwindcss', 'tailwind'], +]); +export const CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 274ab3687975..2aaa76060d26 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -13,6 +13,7 @@ import { apply as applyPolyfill } from '../polyfill.js'; import { error, info, debug, LogOptions } from '../logger.js'; import { printHelp } from '../messages.js'; import * as msg from '../messages.js'; +import * as CONSTS from './consts.js'; import { dim, red, cyan, green, magenta, bold } from 'kleur/colors'; import { parseNpmName } from '../util.js'; import { wrapDefaultExport } from './wrapper.js'; @@ -31,29 +32,6 @@ export interface IntegrationInfo { dependencies: [name: string, version: string][]; } -const DEFAULT_FRAMEWORKS = [ - { value: 'react', title: 'React' }, - { value: 'preact', title: 'Preact' }, - { value: 'vue', title: 'Vue' }, - { value: 'svelte', title: 'Svelte' }, - { value: 'solid-js', title: 'Solid' }, - { value: 'lit', title: 'Lit' }, -]; -const DEFAULT_ADDONS = [ - { value: 'tailwind', title: 'Tailwind' }, - { value: 'turbolinks', title: 'Turbolinks' }, - { value: 'partytown', title: 'Partytown' }, - { value: 'sitemap', title: 'Sitemap' }, -]; - -const ALIASES = new Map([ - ['solid', 'solid-js'], - ['tailwindcss', 'tailwind'], -]); -const INSIGNIFICANT_CHARS = new Set([',', ']', '}']); - -const DEFAULT_CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; - export default async function add(names: string[], { cwd, flags, logging }: AddOptions) { if (flags.help) { printHelp({ @@ -74,14 +52,14 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO name: 'frameworks', message: 'What frameworks would you like to enable?', instructions: '\n Space to select. Return to submit', - choices: DEFAULT_FRAMEWORKS, + choices: CONSTS.FIRST_PARTY_FRAMEWORKS, }, { type: 'multiselect', name: 'addons', message: 'What additional integrations would you like to enable?', instructions: '\n Space to select. Return to submit', - choices: DEFAULT_ADDONS, + choices: CONSTS.FIRST_PARTY_ADDONS, }, ]); @@ -98,7 +76,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO } // Some packages might have a common alias! We normalize those here. - names = names.map((name) => (ALIASES.has(name) ? ALIASES.get(name)! : name)); + names = names.map((name) => (CONSTS.ALIASES.has(name) ? CONSTS.ALIASES.get(name)! : name)); const root = cwd ? path.resolve(cwd) : process.cwd(); let configURL = await resolveConfigURL({ cwd, flags }); @@ -108,7 +86,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO } else { info(logging, 'add', `Unable to locate a config file, generating one for you.`); configURL = new URL('./astro.config.mjs', `file://${root}/`); - await fs.writeFile(fileURLToPath(configURL), DEFAULT_CONFIG_STUB, { encoding: 'utf-8' }); + await fs.writeFile(fileURLToPath(configURL), CONSTS.CONFIG_STUB, { encoding: 'utf-8' }); } const integrations = await validateIntegrations(names); From 03fda0e2487d63f4f1f9acf609787415397045c9 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 13:07:14 -0500 Subject: [PATCH 30/40] feat: remove implicit projectRoot behavior --- packages/astro/src/cli/index.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 09cd78020629..d4fe4c012254 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -73,10 +73,7 @@ function resolveCommand(flags: Arguments): CLICommand { export async function cli(args: string[]) { const flags = yargs(args); const cmd = resolveCommand(flags); - let projectRoot = flags.projectRoot; - if (!projectRoot && cmd !== 'add') { - projectRoot = flags._[3]; - } + const projectRoot = flags.projectRoot; switch (cmd) { case 'help': From 7187bfcc6ecb0b09532e4738a72e499fafc63d90 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 13:42:34 -0500 Subject: [PATCH 31/40] feat: improve error handling, existing integrations --- packages/astro/src/cli/index.ts | 2 +- packages/astro/src/core/add/index.ts | 65 +++++++++++++++++++--------- packages/astro/src/core/config.ts | 1 - 3 files changed, 46 insertions(+), 22 deletions(-) diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index d4fe4c012254..3df653997000 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -99,7 +99,7 @@ export async function cli(args: string[]) { if (cmd === 'add') { try { const packages = flags._.slice(3) as string[]; - await add(packages, { cwd: projectRoot, flags, logging }); + await add(packages, { projectRoot, flags, logging }); process.exit(0); } catch (err) { throwAndExit(err); diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 2aaa76060d26..9846a9d99efd 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -1,5 +1,6 @@ import type yargs from 'yargs-parser'; import path from 'path'; +import { existsSync } from 'fs'; import fs from 'fs/promises'; import { execa } from 'execa'; import { fileURLToPath } from 'url'; @@ -22,7 +23,7 @@ import { t, parse, visit, generate } from './babel.js'; export interface AddOptions { logging: LogOptions; - cwd?: string; + projectRoot?: URL; flags: yargs.Arguments; } @@ -32,7 +33,7 @@ export interface IntegrationInfo { dependencies: [name: string, version: string][]; } -export default async function add(names: string[], { cwd, flags, logging }: AddOptions) { +export default async function add(names: string[], { projectRoot, flags, logging }: AddOptions) { if (flags.help) { printHelp({ commandName: 'astro add', @@ -45,6 +46,17 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO return; } + const cwd = projectRoot ? path.resolve(fileURLToPath(projectRoot)) : process.cwd(); + let configURL: URL | undefined; + + // TODO: improve error handling for invalid configs + configURL = await resolveConfigURL({ cwd, flags }); + + if (configURL?.pathname.endsWith('package.json')) { + throw new Error(`Unable to use astro add with package.json#astro configuration! Try migrating to \`astro.config.mjs\` and try again.`); + } + applyPolyfill(); + if (names.length === 0) { const response = await prompts([ { @@ -78,21 +90,16 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO // Some packages might have a common alias! We normalize those here. names = names.map((name) => (CONSTS.ALIASES.has(name) ? CONSTS.ALIASES.get(name)! : name)); - const root = cwd ? path.resolve(cwd) : process.cwd(); - let configURL = await resolveConfigURL({ cwd, flags }); - applyPolyfill(); if (configURL) { debug('add', `Found config at ${configURL}`); } else { info(logging, 'add', `Unable to locate a config file, generating one for you.`); - configURL = new URL('./astro.config.mjs', `file://${root}/`); + configURL = new URL('./astro.config.mjs', projectRoot); await fs.writeFile(fileURLToPath(configURL), CONSTS.CONFIG_STUB, { encoding: 'utf-8' }); } const integrations = await validateIntegrations(names); - // Add integrations to astro config - // TODO: At the moment, nearly nothing throws an error. We need more errors! let ast: t.File | null = null; try { ast = await parseAstroConfig(configURL); @@ -111,11 +118,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO } } catch (err) { debug('add', 'Error parsing/modifying astro config: ', err); - info( - logging, - null, - "Sorry, we couldn't update your configuration automatically. [INSERT HOW TO DO IT MANUALLY --- this link might help: https://next--astro-docs-2.netlify.app/en/guides/integrations-guide/]" - ); + return bail(err as Error); } let configResult: UpdateResult | undefined; @@ -126,8 +129,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO configResult = await updateAstroConfig({ configURL, ast, flags, logging }); } catch (err) { debug('add', 'Error updating astro config', err); - error(logging, null, 'There has been an error updating the astro config. You might need to update it manually.'); - return; + return bail(err as Error); } } @@ -137,6 +139,17 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO return; } case UpdateResult.none: { + const pkgURL = new URL('./package.json', configURL); + if (existsSync(fileURLToPath(pkgURL))) { + const { dependencies = {}, devDependencies = {} } = await fs.readFile(fileURLToPath(pkgURL)).then(res => JSON.parse(res.toString())); + const deps = Object.keys(Object.assign(dependencies, devDependencies)); + const missingDeps = integrations.filter(integration => !deps.includes(integration.packageName)); + if (missingDeps.length === 0) { + info(logging, null, msg.success(`Configuration up-to-date.`)); + return; + } + } + info(logging, null, msg.success(`Configuration up-to-date.`)); break; } @@ -161,12 +174,12 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO return; } case UpdateResult.cancelled: { - info(logging, null, msg.cancelled(`No dependencies installed.`, `Be sure to install them manually before continuing!`)); + info(logging, null, msg.cancelled(`Dependencies ${bold('NOT')} installed.`, `Be sure to install them manually before continuing!`)); return; } case UpdateResult.failure: { - info(logging, null, msg.failure(`There was a problem installing dependencies.`, `Be sure to install them manually before continuing!`)); - process.exit(1); + bail(new Error(`Unable to install dependencies`)); + return; } } } @@ -188,6 +201,15 @@ const toIdent = (name: string) => { return name; }; +function bail(err: Error) { + err.message = `Astro could not update your astro.config.js file safely. +Reason: ${err.message} + +You will need to add these integration(s) manually. +Documentation: https://next--astro-docs-2.netlify.app/en/guides/integrations-guide/` + throw err; +} + async function addIntegration(ast: t.File, integration: IntegrationInfo) { const integrationId = t.identifier(toIdent(integration.id)); @@ -257,13 +279,16 @@ async function updateAstroConfig({ configURL, ast, flags, logging }: { configURL .trim() .split('\n') .slice(0, change.count) - .filter((x) => x && !INSIGNIFICANT_CHARS.has(x)); if (lines.length === 0) continue; if (change.added) { - if (INSIGNIFICANT_CHARS.has(change.value.trim())) continue; + if (!change.value.trim()) continue; changes.push(change.value); } } + if (changes.length === 0) { + return UpdateResult.none; + } + let diffed = output; for (let newContent of changes) { const coloredOutput = newContent diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index 23233958d2ce..fb7c0da31b1c 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -11,7 +11,6 @@ import load from '@proload/core'; import loadTypeScript from '@proload/plugin-tsm'; import postcssrc from 'postcss-load-config'; import { arraify, isObject } from './util.js'; -import ssgAdapter from '../adapter-ssg/index.js'; load.use([loadTypeScript]); From 35a7dcc41858511714fe70a1932a8ac0db6d22b2 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 14:24:36 -0500 Subject: [PATCH 32/40] fix(tailwind): ensure existing tailwind config is not overwritten --- examples/with-tailwindcss/tailwind.config.cjs | 7 +++++++ packages/astro/src/core/add/consts.ts | 7 +++++++ packages/astro/src/core/add/index.ts | 19 +++++++++++-------- 3 files changed, 25 insertions(+), 8 deletions(-) create mode 100644 examples/with-tailwindcss/tailwind.config.cjs diff --git a/examples/with-tailwindcss/tailwind.config.cjs b/examples/with-tailwindcss/tailwind.config.cjs new file mode 100644 index 000000000000..81cb91aeecf3 --- /dev/null +++ b/examples/with-tailwindcss/tailwind.config.cjs @@ -0,0 +1,7 @@ +module.exports = { + content: [], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/packages/astro/src/core/add/consts.ts b/packages/astro/src/core/add/consts.ts index dfc20a3d78ec..7eb64b4db539 100644 --- a/packages/astro/src/core/add/consts.ts +++ b/packages/astro/src/core/add/consts.ts @@ -17,3 +17,10 @@ export const ALIASES = new Map([ ['tailwindcss', 'tailwind'], ]); export const CONFIG_STUB = `import { defineConfig } from 'astro/config';\n\nexport default defineConfig({});`; +export const TAILWIND_CONFIG_STUB = `module.exports = { + content: [], + theme: { + extend: {}, + }, + plugins: [], +}\n`; diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 9846a9d99efd..48d3fa778201 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -161,14 +161,17 @@ export default async function add(names: string[], { projectRoot, flags, logging case UpdateResult.updated: { const len = integrations.length; if (integrations.find((integration) => integration.id === 'tailwind')) { - const DEFAULT_TAILWIND_CONFIG = `module.exports = { - content: [], - theme: { - extend: {}, - }, - plugins: [], -}\n`; - await fs.writeFile(fileURLToPath(new URL('./tailwind.config.mjs', configURL)), DEFAULT_TAILWIND_CONFIG); + const possibleConfigFiles = ['./tailwind.config.cjs', './tailwind.config.mjs', './tailwind.config.js'].map(p => fileURLToPath(new URL(p, configURL))); + let alreadyConfigured = false; + for (const possibleConfigPath of possibleConfigFiles) { + if (existsSync(possibleConfigPath)) { + alreadyConfigured = true; + break; + } + } + if (!alreadyConfigured) { + await fs.writeFile(fileURLToPath(new URL('./tailwind.config.cjs', configURL)), CONSTS.TAILWIND_CONFIG_STUB); + } } info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project`)); return; From 3d69ef65a07b4750425936fdbe329e0ba2e7783f Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 14:49:25 -0500 Subject: [PATCH 33/40] refactor: prefer cwd to projectRoot flag --- packages/astro/src/core/add/index.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 48d3fa778201..a0315ab31df9 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -1,7 +1,5 @@ import type yargs from 'yargs-parser'; -import path from 'path'; -import { existsSync } from 'fs'; -import fs from 'fs/promises'; +import { existsSync, promises as fs } from 'fs'; import { execa } from 'execa'; import { fileURLToPath } from 'url'; import { diffWords } from 'diff'; @@ -23,8 +21,8 @@ import { t, parse, visit, generate } from './babel.js'; export interface AddOptions { logging: LogOptions; - projectRoot?: URL; flags: yargs.Arguments; + cwd?: string; } export interface IntegrationInfo { @@ -33,7 +31,7 @@ export interface IntegrationInfo { dependencies: [name: string, version: string][]; } -export default async function add(names: string[], { projectRoot, flags, logging }: AddOptions) { +export default async function add(names: string[], { cwd, flags, logging }: AddOptions) { if (flags.help) { printHelp({ commandName: 'astro add', @@ -45,10 +43,7 @@ export default async function add(names: string[], { projectRoot, flags, logging }); return; } - - const cwd = projectRoot ? path.resolve(fileURLToPath(projectRoot)) : process.cwd(); let configURL: URL | undefined; - // TODO: improve error handling for invalid configs configURL = await resolveConfigURL({ cwd, flags }); From f6896b85d936f4db39d3a92b5e308521f1a19178 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 14:49:34 -0500 Subject: [PATCH 34/40] chore: add refactor notes --- packages/astro/src/cli/index.ts | 23 ++++++++++++----------- packages/astro/src/core/config.ts | 6 +++++- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 3df653997000..fa6fc7547aac 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -96,18 +96,10 @@ export async function cli(args: string[]) { logging.level = 'silent'; } - if (cmd === 'add') { - try { - const packages = flags._.slice(3) as string[]; - await add(packages, { projectRoot, flags, logging }); - process.exit(0); - } catch (err) { - throwAndExit(err); - } - } - let config: AstroConfig; try { + // Note: ideally, `loadConfig` would return the config AND its filePath + // For now, `add` has to resolve the config again internally config = await loadConfig({ cwd: projectRoot, flags }); } catch (err) { throwAndExit(err); @@ -115,6 +107,16 @@ export async function cli(args: string[]) { } switch (cmd) { + case 'add': { + try { + const packages = flags._.slice(3) as string[]; + await add(packages, { cwd: projectRoot, flags, logging }); + process.exit(0); + } catch (err) { + throwAndExit(err); + } + return; + } case 'dev': { try { await devServer(config, { logging }); @@ -123,7 +125,6 @@ export async function cli(args: string[]) { } catch (err) { throwAndExit(err); } - return; } diff --git a/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts index fb7c0da31b1c..7bc121963758 100644 --- a/packages/astro/src/core/config.ts +++ b/packages/astro/src/core/config.ts @@ -265,7 +265,11 @@ interface LoadConfigOptions { flags?: Flags; } -/** Resolve the file URL of the user's `astro.config.js|cjs|mjs|ts` file */ +/** + * Resolve the file URL of the user's `astro.config.js|cjs|mjs|ts` file + * Note: currently the same as loadConfig but only returns the `filePath` + * instead of the resolved config + */ export async function resolveConfigURL(configOptions: LoadConfigOptions): Promise { const root = configOptions.cwd ? path.resolve(configOptions.cwd) : process.cwd(); const flags = resolveFlags(configOptions.flags || {}); From 573ba5b640a7fb69c9e33b0a9d5d56d0ef15bbd0 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 14:51:51 -0500 Subject: [PATCH 35/40] refactor: throw createPrettyError > implicit bail --- packages/astro/src/core/add/index.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index a0315ab31df9..c96a84e25eae 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -113,7 +113,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO } } catch (err) { debug('add', 'Error parsing/modifying astro config: ', err); - return bail(err as Error); + throw createPrettyError(err as Error); } let configResult: UpdateResult | undefined; @@ -124,7 +124,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO configResult = await updateAstroConfig({ configURL, ast, flags, logging }); } catch (err) { debug('add', 'Error updating astro config', err); - return bail(err as Error); + throw createPrettyError(err as Error); } } @@ -176,8 +176,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO return; } case UpdateResult.failure: { - bail(new Error(`Unable to install dependencies`)); - return; + throw createPrettyError(new Error(`Unable to install dependencies`)); } } } @@ -199,13 +198,13 @@ const toIdent = (name: string) => { return name; }; -function bail(err: Error) { +function createPrettyError(err: Error) { err.message = `Astro could not update your astro.config.js file safely. Reason: ${err.message} You will need to add these integration(s) manually. Documentation: https://next--astro-docs-2.netlify.app/en/guides/integrations-guide/` - throw err; + return err; } async function addIntegration(ast: t.File, integration: IntegrationInfo) { From 2be9a7bf295fd1fe64059c98b706537cc2d1a819 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 14:52:49 -0500 Subject: [PATCH 36/40] refactor: cleanup language --- packages/astro/src/core/add/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index c96a84e25eae..1e4e82a461a6 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -297,7 +297,7 @@ async function updateAstroConfig({ configURL, ast, flags, logging }: { configURL const message = `\n${boxen(diffed, { margin: 0.5, padding: 0.5, borderStyle: 'round', title: configURL.pathname.split('/').pop() })}\n`; - info(logging, null, `\n ${magenta('Astro will update your configuration with these changes...')}\n${message}`); + info(logging, null, `\n ${magenta('Astro will make the following changes to your config file:')}\n${message}`); if (await askToContinue({ flags })) { await fs.writeFile(fileURLToPath(configURL), output, { encoding: 'utf-8' }); @@ -360,7 +360,7 @@ async function tryToInstallIntegrations({ info( logging, null, - `\n ${magenta('Astro will run the following command to install...')}\n ${dim('If you skip this step, you can always run it yourself later')}\n${message}` + `\n ${magenta('Astro will run the following command:')}\n ${dim('If you skip this step, you can always run it yourself later')}\n${message}` ); if (await askToContinue({ flags })) { From 6aca7e6e2750890cdca93d5a1dc4d231a4d5712b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 15:03:24 -0500 Subject: [PATCH 37/40] feat(cli): prompt user before generating tailwind config --- packages/astro/src/core/add/index.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 1e4e82a461a6..3e29bebcc80f 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -165,10 +165,17 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO } } if (!alreadyConfigured) { - await fs.writeFile(fileURLToPath(new URL('./tailwind.config.cjs', configURL)), CONSTS.TAILWIND_CONFIG_STUB); + info(logging, null, `\n ${magenta(`Astro will generate a minimal ${bold('./tailwind.config.cjs')} file.`)}\n`); + if (await askToContinue({ flags })) { + await fs.writeFile(fileURLToPath(new URL('./tailwind.config.cjs', configURL)), CONSTS.TAILWIND_CONFIG_STUB, { encoding: 'utf-8' }); + debug('add', `Generated default ./tailwind.config.cjs file`); + } + } else { + debug('add', `Using existing Tailwind configuration`); } } - info(logging, null, msg.success(`Added ${len} integration${len === 1 ? '' : 's'} to your project`)); + const list = integrations.map(integration => ` - ${integration.packageName}`).join('\n') + info(logging, null, msg.success(`Added the following integration${len === 1 ? '' : 's'} to your project:\n${list}`)); return; } case UpdateResult.cancelled: { From 164b735670e025b7edf65b6824efcb3eecaad982 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 15:06:40 -0500 Subject: [PATCH 38/40] fix(cli): update config generation to use cwd --- packages/astro/src/core/add/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 3e29bebcc80f..561e54e1388d 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -89,7 +89,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO debug('add', `Found config at ${configURL}`); } else { info(logging, 'add', `Unable to locate a config file, generating one for you.`); - configURL = new URL('./astro.config.mjs', projectRoot); + configURL = new URL('./astro.config.mjs', cwd); await fs.writeFile(fileURLToPath(configURL), CONSTS.CONFIG_STUB, { encoding: 'utf-8' }); } From d5882224c7fcc7b3678709747f45126c1a97ad31 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 16:08:37 -0500 Subject: [PATCH 39/40] fix: resolve root from cwd --- packages/astro/src/core/add/index.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/astro/src/core/add/index.ts b/packages/astro/src/core/add/index.ts index 561e54e1388d..4175cbf2f72a 100644 --- a/packages/astro/src/core/add/index.ts +++ b/packages/astro/src/core/add/index.ts @@ -1,7 +1,8 @@ import type yargs from 'yargs-parser'; +import path from 'path'; import { existsSync, promises as fs } from 'fs'; import { execa } from 'execa'; -import { fileURLToPath } from 'url'; +import { fileURLToPath, pathToFileURL } from 'url'; import { diffWords } from 'diff'; import boxen from 'boxen'; import prompts from 'prompts'; @@ -44,6 +45,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO return; } let configURL: URL | undefined; + const root = pathToFileURL(cwd ? path.resolve(cwd) : process.cwd()); // TODO: improve error handling for invalid configs configURL = await resolveConfigURL({ cwd, flags }); @@ -89,7 +91,7 @@ export default async function add(names: string[], { cwd, flags, logging }: AddO debug('add', `Found config at ${configURL}`); } else { info(logging, 'add', `Unable to locate a config file, generating one for you.`); - configURL = new URL('./astro.config.mjs', cwd); + configURL = new URL('./astro.config.mjs', root); await fs.writeFile(fileURLToPath(configURL), CONSTS.CONFIG_STUB, { encoding: 'utf-8' }); } From 6e0eed5c14a25f54da220c8256be59a49b8cf9fd Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Fri, 25 Mar 2022 16:10:10 -0500 Subject: [PATCH 40/40] chore: update changelog --- .changeset/brave-rings-jump.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/brave-rings-jump.md b/.changeset/brave-rings-jump.md index 42964b834cc7..1fbb5c90d633 100644 --- a/.changeset/brave-rings-jump.md +++ b/.changeset/brave-rings-jump.md @@ -1,9 +1,9 @@ --- -'astro': patch +'astro': minor --- Introduce new `astro add` command to automatically configure integrations. ```shell -astro add preact tailwind +npx astro add ```