From 48ca3997888e960c6aaec633ab21160540656656 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Thu, 5 Dec 2024 13:01:13 +0000 Subject: [PATCH] fix: correctly copy to clipboard in `astro info` (#12641) * fix: correctly copy to clipboard on mac * Add platform-specific clipboard handling for Linux * Wording * Update .changeset/angry-pumas-act.md Co-authored-by: Emanuele Stoppa * Use console.info --------- Co-authored-by: Emanuele Stoppa --- .changeset/angry-pumas-act.md | 5 ++ packages/astro/src/cli/info/index.ts | 107 +++++++++++++++++++-------- packages/astro/test/cli.test.js | 20 +++++ 3 files changed, 102 insertions(+), 30 deletions(-) create mode 100644 .changeset/angry-pumas-act.md diff --git a/.changeset/angry-pumas-act.md b/.changeset/angry-pumas-act.md new file mode 100644 index 000000000000..5f74fa050db4 --- /dev/null +++ b/.changeset/angry-pumas-act.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Fixes a bug where `astro info --copy` wasn't working correctly on `macOS` systems. diff --git a/packages/astro/src/cli/info/index.ts b/packages/astro/src/cli/info/index.ts index ab24e2213b1a..5f36d477cdda 100644 --- a/packages/astro/src/cli/info/index.ts +++ b/packages/astro/src/cli/info/index.ts @@ -1,4 +1,4 @@ -import { execSync } from 'node:child_process'; +import { spawnSync } from 'node:child_process'; import { arch, platform } from 'node:os'; import * as colors from 'kleur/colors'; import prompts from 'prompts'; @@ -49,56 +49,63 @@ export async function printInfo({ flags }: InfoOptions) { applyPolyfill(); const { userConfig } = await resolveConfig(flagsToAstroInlineConfig(flags), 'info'); const output = await getInfoOutput({ userConfig, print: true }); - - await copyToClipboard(output); + await copyToClipboard(output, flags.copy); } -async function copyToClipboard(text: string) { +export async function copyToClipboard(text: string, force?: boolean) { text = text.trim(); const system = platform(); let command = ''; + let args: Array = []; + if (system === 'darwin') { command = 'pbcopy'; } else if (system === 'win32') { command = 'clip'; } else { // Unix: check if a supported command is installed - const unixCommands = [ - ['xclip', '-sel clipboard -l 1'], - ['wl-copy', '"$0"'], + + const unixCommands: Array<[string, Array]> = [ + ['xclip', ['-sel', 'clipboard', '-l', '1']], + ['wl-copy', []], ]; - for (const [unixCommand, args] of unixCommands) { + for (const [unixCommand, unixArgs] of unixCommands) { try { - const output = execSync(`which ${unixCommand}`, { encoding: 'utf8', stdio: 'pipe' }); - if (output[0] !== '/') { - // Did not find a path. Skip! - continue; + const output = spawnSync('which', [unixCommand], { encoding: 'utf8' }); + if (output.stdout.trim()) { + command = unixCommand; + args = unixArgs; + break; } - command = `${unixCommand} ${args}`; } catch { - // Failed to execute which. Skip! continue; } } - // Did not find supported command. Bail out! - if (!command) return; } - console.log(); - const { shouldCopy } = await prompts({ - type: 'confirm', - name: 'shouldCopy', - message: 'Copy to clipboard?', - initial: true, - }); - if (!shouldCopy) return; + if (!command) { + console.error(colors.red('\nClipboard command not found!')); + console.info('Please manually copy the text above.'); + return; + } - try { - execSync(command.replaceAll('$0', text), { - stdio: 'ignore', - input: text, - encoding: 'utf8', + if (!force) { + const { shouldCopy } = await prompts({ + type: 'confirm', + name: 'shouldCopy', + message: 'Copy to clipboard?', + initial: true, }); + + if (!shouldCopy) return; + } + + try { + const result = spawnSync(command, args, { input: text }); + if (result.error) { + throw result.error; + } + console.info(colors.green('Copied to clipboard!')); } catch { console.error( colors.red(`\nSorry, something went wrong!`) + ` Please copy the text above manually.`, @@ -106,6 +113,46 @@ async function copyToClipboard(text: string) { } } +export function readFromClipboard() { + const system = platform(); + let command = ''; + let args: Array = []; + + if (system === 'darwin') { + command = 'pbpaste'; + } else if (system === 'win32') { + command = 'powershell'; + args = ['-command', 'Get-Clipboard']; + } else { + const unixCommands: Array<[string, Array]> = [ + ['xclip', ['-sel', 'clipboard', '-o']], + ['wl-paste', []], + ]; + for (const [unixCommand, unixArgs] of unixCommands) { + try { + const output = spawnSync('which', [unixCommand], { encoding: 'utf8' }); + if (output.stdout.trim()) { + command = unixCommand; + args = unixArgs; + break; + } + } catch { + continue; + } + } + } + + if (!command) { + throw new Error('Clipboard read command not found!'); + } + + const result = spawnSync(command, args, { encoding: 'utf8' }); + if (result.error) { + throw result.error; + } + return result.stdout.trim(); +} + const PLATFORM_TO_OS: Partial, string>> = { darwin: 'macOS', win32: 'Windows', @@ -140,7 +187,7 @@ function printRow(label: string, value: string | string[], print: boolean) { } plaintext += '\n'; if (print) { - console.log(richtext); + console.info(richtext); } return plaintext; } diff --git a/packages/astro/test/cli.test.js b/packages/astro/test/cli.test.js index 01c2ba6546e9..4bacc3ec22bd 100644 --- a/packages/astro/test/cli.test.js +++ b/packages/astro/test/cli.test.js @@ -7,6 +7,8 @@ import { describe, it } from 'node:test'; import { fileURLToPath } from 'node:url'; import { stripVTControlCharacters } from 'node:util'; import { cli, cliServerLogSetup, loadFixture, parseCliDevStart } from './test-utils.js'; +import { readFromClipboard } from '../dist/cli/info/index.js'; +import { platform } from 'node:process'; describe('astro cli', () => { const cliServerLogSetupWithFixture = (flags, cmd) => { @@ -78,6 +80,24 @@ describe('astro cli', () => { assert.equal(proc.stdout.includes(pkgVersion), true); }); + it('astro info', async () => { + const proc = await cli('info', '--copy'); + const pkgURL = new URL('../package.json', import.meta.url); + const pkgVersion = await fs.readFile(pkgURL, 'utf8').then((data) => JSON.parse(data).version); + assert.ok(proc.stdout.includes(`v${pkgVersion}`)); + assert.equal(proc.exitCode, 0); + + // On Linux we only check if we have Wayland or x11. In Codespaces it falsely reports that it does have x11 + if(platform === 'linux' && ((!process.env.WAYLAND_DISPLAY && !process.env.DISPLAY) || process.env.CODESPACES)) { + assert.ok(proc.stdout.includes('Please manually copy the text above')); + } else { + assert.ok(proc.stdout.includes('Copied to clipboard!')); + const clipboardContent = await readFromClipboard(); + assert.ok(clipboardContent.includes(`v${pkgVersion}`)); + } + + }); + it( 'astro check no errors', {