summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Nate Moore <natemoo-re@users.noreply.github.com> 2022-03-25 16:26:55 -0500
committerGravatar GitHub <noreply@github.com> 2022-03-25 16:26:55 -0500
commit72ef7ae64a6d0fd17077bf6920ce11613116b659 (patch)
tree42f77045b9088f6ce724cce3d2b7759149dc4060
parent07a64be4d3938eff1fb96fa625849deacdc48ec3 (diff)
downloadastro-72ef7ae64a6d0fd17077bf6920ce11613116b659.tar.gz
astro-72ef7ae64a6d0fd17077bf6920ce11613116b659.tar.zst
astro-72ef7ae64a6d0fd17077bf6920ce11613116b659.zip
feat(cli): scaffold out `astro add` command (#2849)
* feat(cli): scaffold out `astro add` command * added first babel transforms * Format output * Added changes confirmation * Error flow * Add dependencies * feat(cli): astro add cleanup pass * feat: add support for tailwind * chore: update lockfile * fix: types * chore: rever @proload/core bump * chore: add changeset * chore: rollback dep update * Added spinners * chore: remove extra deps * Removed extra argument * Use `execa` instead of `exec` * Changed how lines are trimmed within diffLines * refactor: move add to core * refactor: remove old add entrypoint * refactor: simplify wording * feat: improve diff * feat: improve diff and logging, add interactive prompt when no args passed * Formatted files * Added --yes * feat: improve logging for install command * Fixed execa * Added help message to add * refactor: extract consts to own file * feat: remove implicit projectRoot behavior * feat: improve error handling, existing integrations * fix(tailwind): ensure existing tailwind config is not overwritten * refactor: prefer cwd to projectRoot flag * chore: add refactor notes * refactor: throw createPrettyError > implicit bail * refactor: cleanup language * feat(cli): prompt user before generating tailwind config * fix(cli): update config generation to use cwd * fix: resolve root from cwd * chore: update changelog Co-authored-by: JuanM04 <me@juanm04.com>
-rw-r--r--.changeset/brave-rings-jump.md9
-rw-r--r--examples/with-tailwindcss/tailwind.config.cjs7
-rw-r--r--packages/astro/package.json10
-rw-r--r--packages/astro/src/cli/index.ts88
-rw-r--r--packages/astro/src/core/add/babel.ts17
-rw-r--r--packages/astro/src/core/add/consts.ts26
-rw-r--r--packages/astro/src/core/add/imports.ts35
-rw-r--r--packages/astro/src/core/add/index.ts459
-rw-r--r--packages/astro/src/core/add/wrapper.ts11
-rw-r--r--packages/astro/src/core/config.ts23
-rw-r--r--packages/astro/src/core/messages.ts86
-rw-r--r--pnpm-lock.yaml105
12 files changed, 792 insertions, 84 deletions
diff --git a/.changeset/brave-rings-jump.md b/.changeset/brave-rings-jump.md
new file mode 100644
index 000000000..1fbb5c90d
--- /dev/null
+++ b/.changeset/brave-rings-jump.md
@@ -0,0 +1,9 @@
+---
+'astro': minor
+---
+
+Introduce new `astro add` command to automatically configure integrations.
+
+```shell
+npx astro add
+```
diff --git a/examples/with-tailwindcss/tailwind.config.cjs b/examples/with-tailwindcss/tailwind.config.cjs
new file mode 100644
index 000000000..81cb91aee
--- /dev/null
+++ b/examples/with-tailwindcss/tailwind.config.cjs
@@ -0,0 +1,7 @@
+module.exports = {
+ content: [],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}
diff --git a/packages/astro/package.json b/packages/astro/package.json
index 23926f8d0..da89cddd9 100644
--- a/packages/astro/package.json
+++ b/packages/astro/package.json
@@ -79,13 +79,17 @@
"@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/traverse": "^7.17.3",
"@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",
"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",
@@ -99,11 +103,14 @@
"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",
"postcss-load-config": "^3.1.3",
+ "preferred-pm": "^3.0.3",
"prismjs": "^1.27.0",
+ "prompts": "^2.4.2",
"rehype-slug": "^5.0.1",
"resolve": "^1.22.0",
"rollup": "^2.70.1",
@@ -126,16 +133,19 @@
"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",
"@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",
"@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/index.ts b/packages/astro/src/cli/index.ts
index ffa1071cc..fa6fc7547 100644
--- a/packages/astro/src/cli/index.ts
+++ b/packages/astro/src/cli/index.ts
@@ -8,23 +8,23 @@ 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 { 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' | 'dev' | 'build' | 'preview' | 'reload' | 'check';
+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.'],
['preview', 'Preview your build locally before deploying.'],
@@ -32,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 <path>', 'Specify the path to the Astro config file.'],
['--project-root <path>', 'Specify the path to the project root folder.'],
@@ -48,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 */
@@ -93,15 +56,15 @@ 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;
+ 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 'dev' | 'build' | 'preview' | 'check';
+ return cmd as CLICommand;
}
return 'help';
}
@@ -110,11 +73,11 @@ 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];
+ const projectRoot = flags.projectRoot;
switch (cmd) {
case 'help':
- printHelp();
+ printAstroHelp();
return process.exit(0);
case 'version':
await printVersion();
@@ -135,6 +98,8 @@ export async function cli(args: string[]) {
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);
@@ -142,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 });
@@ -150,7 +125,6 @@ export async function cli(args: string[]) {
} catch (err) {
throwAndExit(err);
}
-
return;
}
diff --git a/packages/astro/src/core/add/babel.ts b/packages/astro/src/core/add/babel.ts
new file mode 100644
index 000000000..8ec31cd46
--- /dev/null
+++ b/packages/astro/src/core/add/babel.ts
@@ -0,0 +1,17 @@
+import traverse from '@babel/traverse';
+import generator from '@babel/generator';
+import * as t from '@babel/types';
+import parser from '@babel/parser';
+
+// @ts-ignore @babel/traverse isn't ESM and needs this trick
+export const visit = traverse.default as typeof traverse;
+export { t };
+
+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 { code } = astToText(ast);
+ return code;
+}
+
+export const parse = (code: string) => parser.parse(code, { sourceType: 'unambiguous', plugins: ['typescript'] });
diff --git a/packages/astro/src/core/add/consts.ts b/packages/astro/src/core/add/consts.ts
new file mode 100644
index 000000000..7eb64b4db
--- /dev/null
+++ b/packages/astro/src/core/add/consts.ts
@@ -0,0 +1,26 @@
+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({});`;
+export const TAILWIND_CONFIG_STUB = `module.exports = {
+ content: [],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+}\n`;
diff --git a/packages/astro/src/core/add/imports.ts b/packages/astro/src/core/add/imports.ts
new file mode 100644
index 000000000..bae8c7443
--- /dev/null
+++ b/packages/astro/src/core/add/imports.ts
@@ -0,0 +1,35 @@
+import { t, visit } from './babel.js';
+
+export function ensureImport(root: t.File, 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;
+
+ visit(root, {
+ Program(path) {
+ const declaration = t.importDeclaration(specifiersToFind, importDeclaration.source);
+ const latestImport = path
+ .get('body')
+ .filter((statement) => statement.isImportDeclaration())
+ .pop();
+
+ 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
new file mode 100644
index 000000000..4175cbf2f
--- /dev/null
+++ b/packages/astro/src/core/add/index.ts
@@ -0,0 +1,459 @@
+import type yargs from 'yargs-parser';
+import path from 'path';
+import { existsSync, promises as fs } from 'fs';
+import { execa } from 'execa';
+import { fileURLToPath, pathToFileURL } from 'url';
+import { diffWords } 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 { 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';
+import { ensureImport } from './imports.js';
+import { t, parse, visit, generate } from './babel.js';
+
+export interface AddOptions {
+ logging: LogOptions;
+ flags: yargs.Arguments;
+ cwd?: string;
+}
+
+export interface IntegrationInfo {
+ id: string;
+ packageName: string;
+ dependencies: [name: string, version: string][];
+}
+
+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;
+ }
+ 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 });
+
+ 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([
+ {
+ type: 'multiselect',
+ name: 'frameworks',
+ message: 'What frameworks would you like to enable?',
+ instructions: '\n Space to select. Return to submit',
+ 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: CONSTS.FIRST_PARTY_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) => (CONSTS.ALIASES.has(name) ? CONSTS.ALIASES.get(name)! : name));
+
+ 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', root);
+ await fs.writeFile(fileURLToPath(configURL), CONSTS.CONFIG_STUB, { encoding: 'utf-8' });
+ }
+
+ const integrations = await validateIntegrations(names);
+
+ 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);
+ throw createPrettyError(err as Error);
+ }
+
+ let configResult: UpdateResult | undefined;
+ let installResult: UpdateResult | undefined;
+
+ if (ast) {
+ try {
+ configResult = await updateAstroConfig({ configURL, ast, flags, logging });
+ } catch (err) {
+ debug('add', 'Error updating astro config', err);
+ throw createPrettyError(err as Error);
+ }
+ }
+
+ switch (configResult) {
+ case UpdateResult.cancelled: {
+ info(logging, null, msg.cancelled(`Your configuration has ${bold('NOT')} been updated.`));
+ 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;
+ }
+ }
+
+ installResult = await tryToInstallIntegrations({ integrations, cwd, flags, logging });
+
+ switch (installResult) {
+ case UpdateResult.updated: {
+ const len = integrations.length;
+ if (integrations.find((integration) => integration.id === 'tailwind')) {
+ 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) {
+ 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`);
+ }
+ }
+ 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: {
+ info(logging, null, msg.cancelled(`Dependencies ${bold('NOT')} installed.`, `Be sure to install them manually before continuing!`));
+ return;
+ }
+ case UpdateResult.failure: {
+ throw createPrettyError(new Error(`Unable to install dependencies`));
+ }
+ }
+}
+
+async function parseAstroConfig(configURL: URL): Promise<t.File> {
+ 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;
+}
+
+const toIdent = (name: string) => {
+ if (name.includes('-')) {
+ return name.split('-')[0];
+ }
+ return name;
+};
+
+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/`
+ return err;
+}
+
+async function addIntegration(ast: t.File, integration: IntegrationInfo) {
+ const integrationId = t.identifier(toIdent(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, flags, logging }: { configURL: URL; ast: t.File; flags: yargs.Arguments; logging: LogOptions }): Promise<UpdateResult> {
+ const input = await fs.readFile(fileURLToPath(configURL), { encoding: 'utf-8' });
+ 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;
+ }
+
+ let changes = [];
+ for (const change of diffWords(input, output)) {
+ let lines = change.value
+ .trim()
+ .split('\n')
+ .slice(0, change.count)
+ if (lines.length === 0) continue;
+ if (change.added) {
+ 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
+ .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`;
+
+ 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' });
+ debug('add', `Updated astro config`);
+ return UpdateResult.updated;
+ } else {
+ return UpdateResult.cancelled;
+ }
+}
+
+interface InstallCommand {
+ pm: string;
+ command: string;
+ flags: string[];
+ dependencies: string[];
+}
+async function getInstallIntegrationsCommand({ integrations, cwd = process.cwd() }: { integrations: IntegrationInfo[]; cwd?: string }): Promise<InstallCommand | null> {
+ const pm = await preferredPM(cwd);
+ debug('add', `package manager: ${JSON.stringify(pm)}`);
+ if (!pm) return null;
+
+ let dependencies = integrations
+ .map<[string, string | null][]>((i) => [[i.packageName, null], ...i.dependencies])
+ .flat(1)
+ .filter((dep, i, arr) => arr.findIndex((d) => d[0] === dep[0]) === i)
+ .map(([name, version]) => (version === null ? name : `${name}@${version}`))
+ .sort();
+
+ switch (pm.name) {
+ case 'npm':
+ return { pm: 'npm', command: 'install', flags: ['--save-dev'], dependencies };
+ case 'yarn':
+ return { pm: 'yarn', command: 'add', flags: ['--dev'], dependencies };
+ case 'pnpm':
+ return { pm: 'pnpm', command: 'install', flags: ['--save-dev'], dependencies };
+ default:
+ return null;
+ }
+}
+
+async function tryToInstallIntegrations({
+ integrations,
+ cwd,
+ flags,
+ logging,
+}: {
+ integrations: IntegrationInfo[];
+ cwd?: string;
+ flags: yargs.Arguments;
+ logging: LogOptions;
+}): Promise<UpdateResult> {
+ const installCommand = await getInstallIntegrationsCommand({ integrations, cwd });
+
+ if (installCommand === null) {
+ info(logging, null);
+ return UpdateResult.none;
+ } else {
+ 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,
+ null,
+ `\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 })) {
+ const spinner = ora('Installing dependencies...').start();
+ try {
+ await execa(installCommand.pm, [installCommand.command, ...installCommand.flags, ...installCommand.dependencies], { 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<IntegrationInfo[]> {
+ const spinner = ora('Resolving integrations...').start();
+ const integrationEntries = await Promise.all(
+ integrations.map(async (integration): Promise<IntegrationInfo> => {
+ const parsed = parseIntegrationName(integration);
+ if (!parsed) {
+ spinner.fail();
+ throw new Error(`${integration} does not appear to be a valid package name!`);
+ }
+
+ 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 };
+}
+
+async function askToContinue({ flags }: { flags: yargs.Arguments }): Promise<boolean> {
+ if (flags.yes) return true;
+
+ const response = await prompts({
+ type: 'confirm',
+ name: 'askToContinue',
+ message: 'Continue?',
+ initial: true,
+ });
+
+ return Boolean(response.askToContinue);
+}
diff --git a/packages/astro/src/core/add/wrapper.ts b/packages/astro/src/core/add/wrapper.ts
new file mode 100644
index 000000000..a8f6b3bc8
--- /dev/null
+++ b/packages/astro/src/core/add/wrapper.ts
@@ -0,0 +1,11 @@
+import { t, visit } from './babel.js';
+
+export function wrapDefaultExport(ast: t.File, 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/packages/astro/src/core/config.ts b/packages/astro/src/core/config.ts
index 26ebc0931..7bc121963 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]);
@@ -266,6 +265,28 @@ interface LoadConfigOptions {
flags?: Flags;
}
+/**
+ * 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<URL | undefined> {
+ 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<AstroConfig> {
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 c84a454d1..e3a7741c4 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, bgWhite } from 'kleur/colors';
import { pad, emoji, getLocalAddress, getNetworkLogging } from './dev/util.js';
import os from 'os';
import type { AddressInfo } from 'net';
@@ -87,6 +87,36 @@ 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}` : 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 */
export function portInUse({ port }: { port: number }): string {
return `Port ${port} in use. Trying a new one…`;
@@ -102,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'));
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 20e1d39d9..29351650e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -448,32 +448,39 @@ 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/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
'@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
'@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
'@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
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
@@ -489,11 +496,14 @@ importers:
micromorph: ^0.1.2
mime: ^3.0.0
mocha: ^9.2.2
+ ora: ^6.1.0
parse5: ^6.0.1
path-to-regexp: ^6.2.0
postcss: ^8.4.12
postcss-load-config: ^3.1.3
+ preferred-pm: ^3.0.3
prismjs: ^1.27.0
+ prompts: ^2.4.2
rehype-slug: ^5.0.1
resolve: ^1.22.0
rollup: ^2.70.1
@@ -520,13 +530,17 @@ 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/traverse': 7.17.3
'@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
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
@@ -540,11 +554,14 @@ importers:
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
postcss-load-config: 3.1.3
+ preferred-pm: 3.0.3
prismjs: 1.27.0
+ prompts: 2.4.2
rehype-slug: 5.0.1
resolve: 1.22.0
rollup: 2.70.1
@@ -566,16 +583,19 @@ 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
'@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
'@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
@@ -3821,6 +3841,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:
@@ -3936,6 +3960,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
@@ -4460,6 +4488,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'}
@@ -4473,7 +4507,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==}
@@ -4492,6 +4525,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'}
@@ -4706,6 +4744,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:
@@ -4807,7 +4859,6 @@ packages:
/camelcase/6.3.0:
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
engines: {node: '>=10'}
- dev: true
/caniuse-lite/1.0.30001320:
resolution: {integrity: sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==}
@@ -4939,6 +4990,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}
@@ -5423,7 +5479,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==}
@@ -6244,7 +6299,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==}
@@ -6252,14 +6306,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==}
@@ -6938,7 +6990,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==}
@@ -7294,7 +7345,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==}
@@ -7306,14 +7356,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=}
@@ -8260,28 +8308,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==}
@@ -8298,7 +8342,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==}
@@ -8378,7 +8421,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=}
@@ -8423,14 +8465,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==}
@@ -8520,7 +8560,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=}
@@ -9391,7 +9430,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==}
@@ -9470,7 +9508,6 @@ packages:
engines: {node: '>=8'}
dependencies:
ansi-regex: 5.0.1
- dev: true
/strip-ansi/7.0.1:
resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
@@ -9487,7 +9524,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==}
@@ -9871,7 +9907,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:
@@ -10065,6 +10101,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'}
@@ -10562,7 +10603,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==}
@@ -10597,6 +10637,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'}
@@ -10770,6 +10817,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=}
@@ -10866,7 +10922,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==}