diff --git a/.changeset/four-beans-remember.md b/.changeset/four-beans-remember.md new file mode 100644 index 000000000000..cdef7197d15c --- /dev/null +++ b/.changeset/four-beans-remember.md @@ -0,0 +1,5 @@ +--- +'astro': patch +--- + +Replaces `execa` with `tinyexec` internally diff --git a/benchmark/bench/cli-startup.js b/benchmark/bench/cli-startup.js index aa8880554e0c..6f25554997cd 100644 --- a/benchmark/bench/cli-startup.js +++ b/benchmark/bench/cli-startup.js @@ -1,5 +1,5 @@ import { fileURLToPath } from 'node:url'; -import { execaCommand } from 'execa'; +import { exec } from 'tinyexec'; import { markdownTable } from 'markdown-table'; import { astroBin, calculateStat } from './_util.js'; @@ -14,11 +14,11 @@ export async function run(projectDir, outputFile) { const root = fileURLToPath(projectDir); console.log('Benchmarking `astro --help`...'); - const helpStat = await benchmarkCommand(`node ${astroBin} --help`, root); + const helpStat = await benchmarkCommand('node', [astroBin, '--help'], root); console.log('Done'); - console.log('Benchmarking `astro info`...'); - const infoStat = await benchmarkCommand(`node ${astroBin} info`, root); + console.log('Benchmarking `astro preferences list`...'); + const infoStat = await benchmarkCommand('node', [astroBin, 'preferences', 'list'], root); console.log('Done'); console.log('Result preview:'); @@ -35,16 +35,17 @@ export async function run(projectDir, outputFile) { /** * @param {string} command + * @param {string[]} args * @param {string} root * @returns {Promise} */ -async function benchmarkCommand(command, root) { +async function benchmarkCommand(command, args, root) { /** @type {number[]} */ const durations = []; for (let i = 0; i < 10; i++) { const start = performance.now(); - await execaCommand(command, { cwd: root }); + await exec(command, args, { nodeOptions: { cwd: root } }); durations.push(performance.now() - start); } diff --git a/benchmark/bench/memory.js b/benchmark/bench/memory.js index 3a437b3fe357..f1846204f8b9 100644 --- a/benchmark/bench/memory.js +++ b/benchmark/bench/memory.js @@ -1,4 +1,4 @@ -import { execaCommand } from 'execa'; +import { exec } from 'tinyexec'; import { markdownTable } from 'markdown-table'; import fs from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; @@ -18,11 +18,13 @@ export async function run(projectDir, outputFile) { const outputFilePath = fileURLToPath(outputFile); console.log('Building and benchmarking...'); - await execaCommand(`node --expose-gc --max_old_space_size=10000 ${astroBin} build --silent`, { - cwd: root, - stdio: 'inherit', - env: { - ASTRO_TIMER_PATH: outputFilePath, + await exec('node', ['--expose-gc', '--max_old_space_size=10000', astroBin, 'build'], { + nodeOptions: { + cwd: root, + stdio: 'inherit', + env: { + ASTRO_TIMER_PATH: outputFilePath, + }, }, }); diff --git a/benchmark/bench/render.js b/benchmark/bench/render.js index 20c9abb0f3a3..aee04f2b5bed 100644 --- a/benchmark/bench/render.js +++ b/benchmark/bench/render.js @@ -1,4 +1,4 @@ -import { execaCommand } from 'execa'; +import { exec } from 'tinyexec'; import { markdownTable } from 'markdown-table'; import fs from 'node:fs/promises'; import http from 'node:http'; @@ -20,15 +20,19 @@ export async function run(projectDir, outputFile) { const root = fileURLToPath(projectDir); console.log('Building...'); - await execaCommand(`${astroBin} build`, { - cwd: root, - stdio: 'inherit', + await exec(astroBin, ['build'], { + nodeOptions: { + cwd: root, + stdio: 'inherit', + }, }); console.log('Previewing...'); - const previewProcess = execaCommand(`${astroBin} preview --port ${port}`, { - cwd: root, - stdio: 'inherit', + const previewProcess = exec(astroBin, ['preview', '--port', port], { + nodeOptions: { + cwd: root, + stdio: 'inherit', + }, }); console.log('Waiting for server ready...'); diff --git a/benchmark/bench/server-stress.js b/benchmark/bench/server-stress.js index ca165e239a3a..18b31c71c59e 100644 --- a/benchmark/bench/server-stress.js +++ b/benchmark/bench/server-stress.js @@ -1,5 +1,5 @@ import autocannon from 'autocannon'; -import { execaCommand } from 'execa'; +import { exec } from 'tinyexec'; import { markdownTable } from 'markdown-table'; import fs from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; @@ -19,9 +19,11 @@ export async function run(projectDir, outputFile) { const root = fileURLToPath(projectDir); console.log('Building...'); - await execaCommand(`${astroBin} build`, { - cwd: root, - stdio: 'inherit', + await exec(astroBin, ['build'], { + nodeOptions: { + cwd: root, + stdio: 'inherit', + }, }); console.log('Previewing...'); diff --git a/benchmark/package.json b/benchmark/package.json index d56a6d1b0673..60c8621d2eb2 100644 --- a/benchmark/package.json +++ b/benchmark/package.json @@ -12,11 +12,11 @@ "@benchmark/timer": "workspace:*", "astro": "workspace:*", "autocannon": "^7.15.0", - "execa": "^8.0.1", "markdown-table": "^3.0.3", "mri": "^1.2.0", "port-authority": "^2.0.1", "pretty-bytes": "^6.1.1", - "sharp": "^0.33.3" + "sharp": "^0.33.3", + "tinyexec": "^0.3.0" } } diff --git a/packages/astro/package.json b/packages/astro/package.json index d8a9c7c0240b..f2b232eccb60 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -151,7 +151,6 @@ "es-module-lexer": "^1.5.4", "esbuild": "^0.21.5", "estree-walker": "^3.0.3", - "execa": "^8.0.1", "fast-glob": "^3.3.2", "flattie": "^1.1.1", "github-slugger": "^2.0.0", @@ -176,6 +175,7 @@ "shiki": "^1.14.1", "string-width": "^7.2.0", "strip-ansi": "^7.1.0", + "tinyexec": "^0.3.0", "tsconfck": "^3.1.1", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3", @@ -214,6 +214,7 @@ "astro-scripts": "workspace:*", "cheerio": "1.0.0", "eol": "^0.9.1", + "execa": "^8.0.1", "expect-type": "^0.20.0", "mdast-util-mdx": "^3.0.0", "mdast-util-mdx-jsx": "^3.1.3", diff --git a/packages/astro/src/cli/add/index.ts b/packages/astro/src/cli/add/index.ts index 91d5b54352c6..3a889c387721 100644 --- a/packages/astro/src/cli/add/index.ts +++ b/packages/astro/src/cli/add/index.ts @@ -3,12 +3,12 @@ import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import boxen from 'boxen'; import { diffWords } from 'diff'; -import { execa } from 'execa'; import { bold, cyan, dim, green, magenta, red, yellow } from 'kleur/colors'; import ora from 'ora'; import preferredPM from 'preferred-pm'; import prompts from 'prompts'; import maxSatisfying from 'semver/ranges/max-satisfying.js'; +import { exec } from 'tinyexec'; import { loadTSConfig, resolveConfig, @@ -659,7 +659,7 @@ async function tryToInstallIntegrations({ if (await askToContinue({ flags })) { const spinner = ora('Installing dependencies...').start(); try { - await execa( + await exec( installCommand.pm, [ installCommand.command, @@ -668,10 +668,12 @@ async function tryToInstallIntegrations({ ...installCommand.dependencies, ], { - cwd, - // reset NODE_ENV to ensure install command run in dev mode - env: { NODE_ENV: undefined }, - }, + nodeOptions: { + cwd, + // reset NODE_ENV to ensure install command run in dev mode + env: { NODE_ENV: undefined }, + }, + } ); spinner.succeed(); return UpdateResult.updated; diff --git a/packages/astro/src/cli/docs/open.ts b/packages/astro/src/cli/docs/open.ts index 3913ccec4f57..6f2fe4c82117 100644 --- a/packages/astro/src/cli/docs/open.ts +++ b/packages/astro/src/cli/docs/open.ts @@ -1,5 +1,4 @@ -import type { ExecaChildProcess } from 'execa'; -import { execa } from 'execa'; +import { type Result, exec } from 'tinyexec'; /** * Credit: Azhar22 @@ -26,7 +25,7 @@ const getPlatformSpecificCommand = (): [string] | [string, string[]] => { } }; -export async function openInBrowser(url: string): Promise { +export async function openInBrowser(url: string): Promise { const [command, args = []] = getPlatformSpecificCommand(); - return execa(command, [...args, encodeURI(url)]); + return exec(command, [...args, encodeURI(url)]); } diff --git a/packages/astro/src/cli/install-package.ts b/packages/astro/src/cli/install-package.ts index 637390ef3c2c..46440b385fb9 100644 --- a/packages/astro/src/cli/install-package.ts +++ b/packages/astro/src/cli/install-package.ts @@ -1,11 +1,11 @@ import { createRequire } from 'node:module'; import boxen from 'boxen'; import ci from 'ci-info'; -import { execa } from 'execa'; import { bold, cyan, dim, magenta } from 'kleur/colors'; import ora from 'ora'; import preferredPM from 'preferred-pm'; import prompts from 'prompts'; +import { exec } from 'tinyexec'; import whichPm from 'which-pm'; import type { Logger } from '../core/logger/core.js'; @@ -141,10 +141,10 @@ async function installPackage( if (Boolean(response)) { const spinner = ora('Installing dependencies...').start(); try { - await execa( + await exec( installCommand.pm, [installCommand.command, ...installCommand.flags, ...installCommand.dependencies], - { cwd: cwd }, + { nodeOptions: { cwd: cwd } } ); spinner.succeed(); @@ -203,8 +203,8 @@ async function getRegistry(): Promise { const fallback = 'https://registry.npmjs.org'; const packageManager = (await preferredPM(process.cwd()))?.name || 'npm'; try { - const { stdout } = await execa(packageManager, ['config', 'get', 'registry']); - _registry = stdout?.trim()?.replace(/\/$/, '') || fallback; + const { stdout } = await exec(packageManager, ['config', 'get', 'registry']); + _registry = stdout.trim()?.replace(/\/$/, '') || fallback; // Detect cases where the shell command returned a non-URL (e.g. a warning) if (!new URL(_registry).host) _registry = fallback; } catch { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7bae5f21d368..ac8d67daa5c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,9 +78,6 @@ importers: autocannon: specifier: ^7.15.0 version: 7.15.0 - execa: - specifier: ^8.0.1 - version: 8.0.1 markdown-table: specifier: ^3.0.3 version: 3.0.3 @@ -96,6 +93,9 @@ importers: sharp: specifier: ^0.33.3 version: 0.33.3 + tinyexec: + specifier: ^0.3.0 + version: 0.3.0 benchmark/packages/timer: dependencies: @@ -642,9 +642,6 @@ importers: estree-walker: specifier: ^3.0.3 version: 3.0.3 - execa: - specifier: ^8.0.1 - version: 8.0.1 fast-glob: specifier: ^3.3.2 version: 3.3.2 @@ -717,6 +714,9 @@ importers: strip-ansi: specifier: ^7.1.0 version: 7.1.0 + tinyexec: + specifier: ^0.3.0 + version: 0.3.0 tsconfck: specifier: ^3.1.1 version: 3.1.1(typescript@5.5.4) @@ -821,6 +821,9 @@ importers: eol: specifier: ^0.9.1 version: 0.9.1 + execa: + specifier: ^8.0.1 + version: 8.0.1 expect-type: specifier: ^0.20.0 version: 0.20.0 @@ -6007,9 +6010,6 @@ importers: esbuild-plugin-copy: specifier: ^2.1.1 version: 2.1.1(esbuild@0.21.5) - execa: - specifier: ^8.0.1 - version: 8.0.1 fast-glob: specifier: ^3.3.2 version: 3.3.2 @@ -6019,6 +6019,9 @@ importers: p-limit: specifier: ^6.1.0 version: 6.1.0 + tinyexec: + specifier: ^0.3.0 + version: 0.3.0 tsconfck: specifier: ^3.1.1 version: 3.1.1(typescript@5.5.4) @@ -11020,6 +11023,9 @@ packages: tinybench@2.8.0: resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + tinyexec@0.3.0: + resolution: {integrity: sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==} + tinypool@1.0.0: resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==} engines: {node: ^18.0.0 || >=20.0.0} @@ -17560,6 +17566,8 @@ snapshots: tinybench@2.8.0: {} + tinyexec@0.3.0: {} + tinypool@1.0.0: {} tinyrainbow@1.2.0: {} diff --git a/scripts/package.json b/scripts/package.json index 35bf517a6602..bbf3e970606e 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -10,10 +10,10 @@ "dependencies": { "esbuild": "^0.21.5", "esbuild-plugin-copy": "^2.1.1", - "execa": "^8.0.1", "fast-glob": "^3.3.2", "kleur": "^4.1.5", "p-limit": "^6.1.0", + "tinyexec": "^0.3.0", "tsconfck": "^3.1.1" } } diff --git a/scripts/smoke/cleanup.js b/scripts/smoke/cleanup.js index 3b03951f938f..1bb398d9e10e 100644 --- a/scripts/smoke/cleanup.js +++ b/scripts/smoke/cleanup.js @@ -2,7 +2,7 @@ // @ts-check -import { execa } from 'execa'; +import { exec } from 'tinyexec'; import { promises as fs } from 'node:fs'; import { fileURLToPath } from 'node:url'; @@ -36,7 +36,9 @@ async function run() { console.log('🤖', 'Resetting', 'pnpm'); - await execa('pnpm', ['install'], { cwd: fileURLToPath(rootDir), stdout: 'inherit', stderr: 'inherit' }); + await exec('pnpm', ['install'], { + nodeOptions: { cwd: fileURLToPath(rootDir), stdio: ['pipe', 'inherit', 'inherit'] }, + }); } /* Functionality diff --git a/scripts/smoke/index.js b/scripts/smoke/index.js index 7ab78e286eb3..49887cd2ee53 100644 --- a/scripts/smoke/index.js +++ b/scripts/smoke/index.js @@ -2,7 +2,7 @@ // @ts-check -import { execa } from 'execa'; +import { exec } from 'tinyexec'; import { promises as fs } from 'node:fs'; import { fileURLToPath } from 'node:url'; @@ -32,10 +32,12 @@ async function run() { console.log(''); const directories = [...(await getChildDirectories(smokeDir)), ...(await getChildDirectories(exampleDir))]; + /** @type {Partial} */ + const execOptions = { nodeOptions: { cwd: fileURLToPath(rootDir), stdio: 'inherit' }}; console.log('🤖', 'Preparing', 'pnpm'); - - await execa('pnpm', ['install', '--frozen-lockfile=false'], { cwd: fileURLToPath(rootDir), stdio: 'inherit' }); + + await exec('pnpm', ['install', '--frozen-lockfile=false'], execOptions); for (const directory of directories) { const name = directory.pathname.split('/').at(-1) ?? ""; @@ -43,9 +45,9 @@ async function run() { console.log('🤖', 'Testing', name); try { - await execa('pnpm', ['install', '--ignore-scripts', '--frozen-lockfile=false'].filter(x => x), { cwd: fileURLToPath(directory), stdio: 'inherit' }); - await execa('pnpm', ['astro', 'telemetry', 'disable']); - await execa('pnpm', ['run', 'build'], { cwd: fileURLToPath(directory), stdio: 'inherit' }); + await exec('pnpm', ['install', '--ignore-scripts', '--frozen-lockfile=false'], execOptions); + await exec('pnpm', ['astro', 'telemetry', 'disable']); + await exec('pnpm', ['run', 'build'], execOptions); } catch (err) { console.log(err); process.exit(1);