Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 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 -76 to 96
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