Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): scaffold out astro add command #2849

Merged
merged 40 commits into from
Mar 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
471fa01
feat(cli): scaffold out `astro add` command
Mar 21, 2022
ea0793f
added first babel transforms
JuanM04 Mar 22, 2022
4a955bc
Format output
JuanM04 Mar 24, 2022
6616cda
Added changes confirmation
JuanM04 Mar 24, 2022
8b0b4bf
Error flow
JuanM04 Mar 24, 2022
ff5220c
Add dependencies
JuanM04 Mar 24, 2022
a9bc999
feat(cli): astro add cleanup pass
Mar 24, 2022
e79c461
feat: add support for tailwind
Mar 24, 2022
abbe3e2
chore: update lockfile
Mar 24, 2022
c8949da
fix: types
Mar 24, 2022
94e4e34
chore: rever @proload/core bump
Mar 24, 2022
234c4b1
chore: add changeset
Mar 24, 2022
a055557
chore: rollback dep update
Mar 24, 2022
787f129
Added spinners
JuanM04 Mar 24, 2022
728521c
chore: remove extra deps
Mar 24, 2022
17a0b7c
Removed extra argument
JuanM04 Mar 24, 2022
880c405
Use `execa` instead of `exec`
JuanM04 Mar 24, 2022
9ec534d
Changed how lines are trimmed within diffLines
JuanM04 Mar 25, 2022
0401ec6
refactor: move add to core
Mar 25, 2022
b168fab
refactor: remove old add entrypoint
Mar 25, 2022
6b94574
refactor: simplify wording
Mar 25, 2022
2803357
feat: improve diff
Mar 25, 2022
f6bb9ee
feat: improve diff and logging, add interactive prompt when no args p…
Mar 25, 2022
8610d78
Formatted files
JuanM04 Mar 25, 2022
59030bd
Added --yes
JuanM04 Mar 25, 2022
c942116
feat: improve logging for install command
Mar 25, 2022
e035d10
Fixed execa
JuanM04 Mar 25, 2022
a43bf5e
Added help message to add
JuanM04 Mar 25, 2022
638dc73
refactor: extract consts to own file
Mar 25, 2022
03fda0e
feat: remove implicit projectRoot behavior
Mar 25, 2022
7187bfc
feat: improve error handling, existing integrations
Mar 25, 2022
35a7dcc
fix(tailwind): ensure existing tailwind config is not overwritten
Mar 25, 2022
3d69ef6
refactor: prefer cwd to projectRoot flag
Mar 25, 2022
f6896b8
chore: add refactor notes
Mar 25, 2022
573ba5b
refactor: throw createPrettyError > implicit bail
Mar 25, 2022
2be9a7b
refactor: cleanup language
Mar 25, 2022
6aca7e6
feat(cli): prompt user before generating tailwind config
Mar 25, 2022
164b735
fix(cli): update config generation to use cwd
Mar 25, 2022
d588222
fix: resolve root from cwd
Mar 25, 2022
6e0eed5
chore: update changelog
Mar 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/brave-rings-jump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'astro': minor
---

Introduce new `astro add` command to automatically configure integrations.

```shell
npx astro add
```
7 changes: 7 additions & 0 deletions examples/with-tailwindcss/tailwind.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
content: [],
theme: {
extend: {},
},
plugins: [],
}
10 changes: 10 additions & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
88 changes: 31 additions & 57 deletions packages/astro/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,31 @@ 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.'],
['check', 'Check your project for errors.'],
['--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.'],
Expand All @@ -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 */
Expand All @@ -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';
}
Expand All @@ -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();
Expand All @@ -135,13 +98,25 @@ 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);
return;
}

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 });
Expand All @@ -150,7 +125,6 @@ export async function cli(args: string[]) {
} catch (err) {
throwAndExit(err);
}

return;
}

Expand Down
17 changes: 17 additions & 0 deletions packages/astro/src/core/add/babel.ts
Original file line number Diff line number Diff line change
@@ -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'] });
26 changes: 26 additions & 0 deletions packages/astro/src/core/add/consts.ts
Original file line number Diff line number Diff line change
@@ -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`;
35 changes: 35 additions & 0 deletions packages/astro/src/core/add/imports.ts
Original file line number Diff line number Diff line change
@@ -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);
},
});
}
Loading