Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 1 addition & 4 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@
"@astrojs/markdown-remark": "workspace:*",
"@astrojs/telemetry": "workspace:*",
"@capsizecss/unpack": "^3.0.0",
"@clack/prompts": "1.0.0-alpha.6",
"@oslojs/encoding": "^1.1.0",
"@rollup/pluginutils": "^5.2.0",
"acorn": "^8.15.0",
"aria-query": "^5.3.2",
"axobject-query": "^4.1.0",
"boxen": "8.0.1",
"ci-info": "^4.3.0",
"clsx": "^2.1.1",
"common-ancestor-path": "^1.0.1",
Expand Down Expand Up @@ -147,7 +147,6 @@
"p-queue": "^8.1.0",
"package-manager-detector": "^1.3.0",
"picomatch": "^4.0.3",
"prompts": "^2.4.2",
"rehype": "^13.0.2",
"semver": "^7.7.2",
"shiki": "^3.12.0",
Expand All @@ -164,7 +163,6 @@
"vitefu": "^1.1.1",
"xxhash-wasm": "^1.1.0",
"yargs-parser": "^21.1.1",
"yocto-spinner": "^0.2.3",
"zod": "^3.25.76",
"zod-to-json-schema": "^3.24.6",
"zod-to-ts": "^1.2.0"
Expand All @@ -186,7 +184,6 @@
"@types/http-cache-semantics": "^4.0.4",
"@types/js-yaml": "^4.0.9",
"@types/picomatch": "^3.0.2",
"@types/prompts": "^2.4.9",
"@types/semver": "^7.7.0",
"@types/yargs-parser": "^21.0.3",
"astro-scripts": "workspace:*",
Expand Down
84 changes: 37 additions & 47 deletions packages/astro/src/cli/add/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import fsMod, { existsSync, promises as fs } from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import boxen from 'boxen';
import * as clack from '@clack/prompts';
import { diffWords } from 'diff';
import { bold, cyan, dim, green, magenta, red, yellow } from 'kleur/colors';
import { type ASTNode, builders, generateCode, loadFile, type ProxifiedModule } from 'magicast';
import { getDefaultExportOptions } from 'magicast/helpers';
import { detect, resolveCommand } from 'package-manager-detector';
import prompts from 'prompts';
import maxSatisfying from 'semver/ranges/max-satisfying.js';
import type yargsParser from 'yargs-parser';
import yoctoSpinner from 'yocto-spinner';
import {
loadTSConfig,
resolveConfig,
Expand Down Expand Up @@ -363,19 +361,19 @@ export async function add(names: string[], { flags }: AddOptions) {
),
);
if (integrations.find((integration) => integration.integrationName === 'tailwind')) {
const code = boxen(getDiffContent('---\n---', "---\nimport '../styles/global.css'\n---")!, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
title: 'src/layouts/Layout.astro',
});
logger.warn(
'SKIP_FORMAT',
msg.actionRequired(
'You must import your Tailwind stylesheet, e.g. in a shared layout:\n',
),
);
logger.info('SKIP_FORMAT', code + '\n');
clack.box(
getDiffContent('---\n---', "---\nimport '../styles/global.css'\n---")!,
'src/layouts/Layout.astro',
{
rounded: true,
},
);
}
}
}
Expand Down Expand Up @@ -561,18 +559,15 @@ async function updateAstroConfig({
return UpdateResult.none;
}

const message = `\n${boxen(diff, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
title: configURL.pathname.split('/').pop(),
})}\n`;

logger.info(
'SKIP_FORMAT',
`\n ${magenta('Astro will make the following changes to your config file:')}\n${message}`,
`\n ${magenta('Astro will make the following changes to your config file:')}`,
);

clack.box(diff, configURL.pathname.split('/').pop(), {
rounded: true,
});

if (logAdapterInstructions) {
logger.info(
'SKIP_FORMAT',
Expand Down Expand Up @@ -676,20 +671,19 @@ async function tryToInstallIntegrations({
);

const coloredOutput = `${bold(installCommand.command)} ${installCommand.args.join(' ')} ${cyan(installSpecifiers.join(' '))}`;
const message = `\n${boxen(coloredOutput, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
})}\n`;
logger.info(
'SKIP_FORMAT',
`\n ${magenta('Astro will run the following command:')}\n ${dim(
'If you skip this step, you can always run it yourself later',
)}\n${message}`,
)}`,
);
clack.box(coloredOutput, undefined, {
rounded: true,
});

if (await askToContinue({ flags })) {
const spinner = yoctoSpinner({ text: 'Installing dependencies...' }).start();
const spinner = clack.spinner();
spinner.start('Installing dependencies...');
try {
await exec(installCommand.command, [...installCommand.args, ...installSpecifiers], {
nodeOptions: {
Expand All @@ -698,10 +692,10 @@ async function tryToInstallIntegrations({
env: { NODE_ENV: undefined },
},
});
spinner.success();
spinner.stop();
return UpdateResult.updated;
} catch (err: any) {
spinner.error();
spinner.stop(undefined, 2);
logger.debug('add', 'Error installing dependencies', err);
// NOTE: `err.stdout` can be an empty string, so log the full error instead for a more helpful log
console.error('\n', err.stdout || err.message, '\n');
Expand All @@ -716,7 +710,8 @@ async function validateIntegrations(
integrations: string[],
flags: yargsParser.Arguments,
): Promise<IntegrationInfo[]> {
const spinner = yoctoSpinner({ text: 'Resolving packages...' }).start();
const spinner = clack.spinner();
spinner.start('Resolving packages...');
try {
const integrationEntries = await Promise.all(
integrations.map(async (integration): Promise<IntegrationInfo> => {
Expand All @@ -734,17 +729,17 @@ async function validateIntegrations(
const firstPartyPkgCheck = await fetchPackageJson('@astrojs', name, tag);
if (firstPartyPkgCheck instanceof Error) {
if (firstPartyPkgCheck.message) {
spinner.warning(yellow(firstPartyPkgCheck.message));
spinner.message(yellow(firstPartyPkgCheck.message));
}
spinner.warning(yellow(`${bold(integration)} is not an official Astro package.`));
spinner.message(yellow(`${bold(integration)} is not an official Astro package.`));
if (!(await askToContinue({ flags }))) {
throw new Error(
`No problem! Find our official integrations at ${cyan(
'https://astro.build/integrations',
)}`,
);
}
spinner.start('Resolving with third party packages...');
spinner.message('Resolving with third party packages...');
pkgType = 'third-party';
} else {
pkgType = 'first-party';
Expand All @@ -755,7 +750,7 @@ async function validateIntegrations(
const thirdPartyPkgCheck = await fetchPackageJson(scope, name, tag);
if (thirdPartyPkgCheck instanceof Error) {
if (thirdPartyPkgCheck.message) {
spinner.warning(yellow(thirdPartyPkgCheck.message));
spinner.message(yellow(thirdPartyPkgCheck.message));
}
throw new Error(`Unable to fetch ${bold(integration)}. Does the package exist?`);
} else {
Expand Down Expand Up @@ -813,11 +808,11 @@ async function validateIntegrations(
};
}),
);
spinner.success();
spinner.stop();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick comparison of the two states here:

Current CLI This PR
Checkmark icon Resolving packages... diamond icon with no text

The “Resolving packages” message is preserved currently as a reference to the completed task, whereas it looks like Clack’s stop() clears that out. I think it would be nice to preserve a message to refer back to.

Maybe as simple as?

Suggested change
spinner.stop();
spinner.stop('Resolved packages');

return integrationEntries;
} catch (e) {
if (e instanceof Error) {
spinner.error(e.message);
spinner.stop(e.message, 2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Linking bombshell-dev/clack#405 here as a reminder to revisit if that change to the spinner API goes ahead.

process.exit(1);
} else {
throw e;
Expand Down Expand Up @@ -872,18 +867,15 @@ async function updateTSConfig(
return UpdateResult.none;
}

const message = `\n${boxen(diff, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
title: configFileName,
})}\n`;

logger.info(
'SKIP_FORMAT',
`\n ${magenta(`Astro will make the following changes to your ${configFileName}:`)}\n${message}`,
`\n ${magenta(`Astro will make the following changes to your ${configFileName}:`)}`,
);

clack.box(diff, configFileName, {
rounded: true,
});

// Every major framework, apart from Vue and Svelte requires different `jsxImportSource`, as such it's impossible to config
// all of them in the same `tsconfig.json`. However, Vue only need `"jsx": "preserve"` for template intellisense which
// can be compatible with some frameworks (ex: Solid)
Expand Down Expand Up @@ -935,14 +927,12 @@ function parseIntegrationName(spec: string) {
async function askToContinue({ flags }: { flags: Flags }): Promise<boolean> {
if (flags.yes || flags.y) return true;

const response = await prompts({
type: 'confirm',
name: 'askToContinue',
const response = await clack.confirm({
message: 'Continue?',
initial: true,
initialValue: true,
});

return Boolean(response.askToContinue);
return response === true;
}

function getDiffContent(input: string, output: string): string | null {
Expand Down
10 changes: 4 additions & 6 deletions packages/astro/src/cli/info/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { spawn, spawnSync } from 'node:child_process';
import { arch, platform } from 'node:os';
import * as clack from '@clack/prompts';
import * as colors from 'kleur/colors';
import prompts from 'prompts';
import { resolveConfig } from '../../core/config/index.js';
import { ASTRO_VERSION } from '../../core/constants.js';
import { apply as applyPolyfill } from '../../core/polyfill.js';
Expand Down Expand Up @@ -124,14 +124,12 @@ async function copyToClipboard(text: string, force?: boolean) {
}

if (!force) {
const { shouldCopy } = await prompts({
type: 'confirm',
name: 'shouldCopy',
const shouldCopy = await clack.confirm({
message: 'Copy to clipboard?',
initial: true,
initialValue: true,
});

if (!shouldCopy) return;
if (shouldCopy !== true) return;
}
Comment on lines 126 to 133
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documenting the visual changes here:

Current CLI This PR
image image
image image

Looks good I think — best not to add borders to the list logged here to make manual copy/paste easy. Could bold the question perhaps, as noted above?


try {
Expand Down
32 changes: 13 additions & 19 deletions packages/astro/src/cli/install-package.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { createRequire } from 'node:module';
import boxen from 'boxen';
import * as clack from '@clack/prompts';
import ci from 'ci-info';
import { bold, cyan, dim, magenta } from 'kleur/colors';
import { detect, resolveCommand } from 'package-manager-detector';
import prompts from 'prompts';
import yoctoSpinner from 'yocto-spinner';
import type { Logger } from '../core/logger/core.js';
import { exec } from './exec.js';

Expand Down Expand Up @@ -75,34 +73,30 @@ async function installPackage(
packageNames = packageNames.map((name) => `npm:${name}`);
}
const coloredOutput = `${bold(installCommand.command)} ${installCommand.args.join(' ')} ${cyan(packageNames.join(' '))}`;
const message = `\n${boxen(coloredOutput, {
margin: 0.5,
padding: 0.5,
borderStyle: 'round',
})}\n`;
logger.info(
'SKIP_FORMAT',
`\n ${magenta('Astro will run the following command:')}\n ${dim(
'If you skip this step, you can always run it yourself later',
)}\n${message}`,
)}`,
);
clack.box(coloredOutput, undefined, {
rounded: true,
});

let response;
if (options.skipAsk) {
response = true;
} else {
response = (
await prompts({
type: 'confirm',
name: 'askToContinue',
response =
(await clack.confirm({
message: 'Continue?',
initial: true,
})
).askToContinue;
initialValue: true,
})) === true;
}
Comment on lines -78 to 95
Copy link
Member

@delucis delucis Oct 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documenting the visual changes here:

Current CLI This PR
image image

Cancel state:

Current CLI This PR
image image

I quite like how the bold text in the old version helps the “Continue?” question stand out, so could be something for us to create a little set of styled utilities for? For example, something like this would help us use that style consistently:

const confirm = ({ message, ...opts }) =>
  clack.confirm({ message: bold(message), ...opts });

As noted in my main comment, you can see the inconsistency of switching between the Clack style with the left border and the regular logging here.


if (Boolean(response)) {
const spinner = yoctoSpinner({ text: 'Installing dependencies...' }).start();
const spinner = clack.spinner();
spinner.start('Installing dependencies...');
try {
await exec(installCommand.command, [...installCommand.args, ...packageNames], {
nodeOptions: {
Expand All @@ -111,12 +105,12 @@ async function installPackage(
env: { NODE_ENV: undefined },
},
});
spinner.success();
spinner.stop();

return true;
} catch (err) {
logger.debug('add', 'Error installing dependencies', err);
spinner.error();
spinner.stop(undefined, 2);

return false;
}
Expand Down
3 changes: 1 addition & 2 deletions packages/db/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,17 @@
"test:types": "tsc --project test/types/tsconfig.json"
},
"dependencies": {
"@clack/prompts": "1.0.0-alpha.6",
"@libsql/client": "^0.15.15",
"deep-diff": "^1.0.2",
"drizzle-orm": "^0.42.0",
"kleur": "^4.1.5",
"nanoid": "^5.1.6",
"prompts": "^2.4.2",
"yargs-parser": "^21.1.1",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/deep-diff": "^1.0.5",
"@types/prompts": "^2.4.9",
"@types/yargs-parser": "^21.0.3",
"astro": "workspace:*",
"astro-scripts": "workspace:*",
Expand Down
10 changes: 4 additions & 6 deletions packages/db/src/core/cli/commands/push/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as clack from '@clack/prompts';
import type { AstroConfig } from 'astro';
import { sql } from 'drizzle-orm';
import prompts from 'prompts';
import type { Arguments } from 'yargs-parser';
import { MIGRATION_VERSION } from '../../../consts.js';
import { createClient } from '../../../db-client/libsql-node.js';
Expand Down Expand Up @@ -42,14 +42,12 @@ export async function cmd({
}

if (isForceReset) {
const { begin } = await prompts({
type: 'confirm',
name: 'begin',
const begin = await clack.confirm({
message: `Reset your database? All of your data will be erased and your schema created from scratch.`,
initial: false,
initialValue: false,
});

if (!begin) {
if (begin !== true) {
console.log('Canceled.');
process.exit(0);
}
Expand Down
Loading