Skip to content

Commit

Permalink
refactor(cli): drop execa (#4457)
Browse files Browse the repository at this point in the history
* wip: exec refactor

* refactor(cli): drop execa

* chore: tweaks

* fix: watch mode

* refactor: more
  • Loading branch information
tmm authored Dec 12, 2024
1 parent afea6b6 commit 21ec74d
Show file tree
Hide file tree
Showing 16 changed files with 145 additions and 78 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-cows-kiss.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@wagmi/cli": patch
---

Removed internal dependency.
1 change: 0 additions & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
"dotenv-expand": "^10.0.0",
"esbuild": "^0.19.0",
"escalade": "3.2.0",
"execa": "^8.0.1",
"fdir": "^6.1.1",
"nanospinner": "1.2.2",
"pathe": "^1.1.2",
Expand Down
11 changes: 8 additions & 3 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@ cli
.option('-r, --root <path>', '[string] root path to resolve config from')
.option('-w, --watch', '[boolean] watch for changes')
.example((name) => `${name} generate`)
.action(async (options: Generate) => await generate(options))
.action(async (options: Generate) => {
await generate(options)
if (!options.watch) process.exit(0)
})

cli
.command('init', 'create configuration file')
.option('-c, --config <path>', '[string] path to config file')
.option('-r, --root <path>', '[string] root path to resolve config from')
.example((name) => `${name} init`)
.action(async (options: Init) => await init(options))
.action(async (options: Init) => {
await init(options)
process.exit(0)
})

cli.help()
cli.version(version)
Expand All @@ -40,7 +46,6 @@ void (async () => {
} else throw new Error(`Unknown command: ${cli.args.join(' ')}`)
}
await cli.runMatchedCommand()
process.exit(0)
} catch (error) {
logger.error(`\n${(error as Error).message}`)
process.exit(1)
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/generate.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { readFile } from 'node:fs/promises'
import dedent from 'dedent'
import { resolve } from 'pathe'
import { afterEach, beforeEach, expect, test, vi } from 'vitest'

import { readFile } from 'node:fs/promises'
import { createFixture, typecheck, watchConsole } from '../../test/utils.js'
import { generate } from './generate.js'

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,12 @@ export async function generate(options: Generate = {}) {
type Watcher = FSWatcher & { config?: Watch }
const watchers: Watcher[] = []
const watchWriteDelay = 100
const watchOptions: ChokidarOptions = {
const watchOptions = {
atomic: true,
// awaitWriteFinish: true,
ignoreInitial: true,
persistent: true,
}
} satisfies ChokidarOptions

const outNames = new Set<string>()
const isArrayConfig = Array.isArray(resolvedConfigs)
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/plugins/foundry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ test('validates without project', async () => {
await expect(foundry().validate?.()).resolves.toBeUndefined()
})

test('contracts', () => {
expect(
test('contracts', async () => {
await expect(
foundry({
project: resolve(__dirname, '__fixtures__/foundry/'),
exclude: ['Foo.sol/**'],
Expand Down Expand Up @@ -103,7 +103,7 @@ test('contracts without project', async () => {
const spy = vi.spyOn(process, 'cwd')
spy.mockImplementation(() => dir)

expect(
await expect(
foundry({
exclude: ['Foo.sol/**'],
}).contracts?.(),
Expand Down
40 changes: 29 additions & 11 deletions packages/cli/src/plugins/foundry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { execSync, spawn, spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import dedent from 'dedent'
import { execa, execaCommandSync } from 'execa'
import { fdir } from 'fdir'
import { basename, extname, join, resolve } from 'pathe'
import pc from 'picocolors'
Expand Down Expand Up @@ -153,12 +153,19 @@ export function foundry(config: FoundryConfig = {}): FoundryResult {
src: 'src',
}
try {
foundryConfig = FoundryConfigSchema.parse(
JSON.parse(
execaCommandSync(`${forgeExecutable} config --json --root ${project}`)
.stdout,
),
const result = spawnSync(
forgeExecutable,
['config', '--json', '--root', project],
{
encoding: 'utf-8',
shell: true,
},
)
if (result.error) throw result.error
if (result.status !== 0)
throw new Error(`Failed with code ${result.status}`)
if (result.signal) throw new Error('Process terminated by signal')
foundryConfig = FoundryConfigSchema.parse(JSON.parse(result.stdout))
} catch {
} finally {
foundryConfig = {
Expand All @@ -171,8 +178,16 @@ export function foundry(config: FoundryConfig = {}): FoundryResult {

return {
async contracts() {
if (clean) await execa(forgeExecutable, ['clean', '--root', project])
if (build) await execa(forgeExecutable, ['build', '--root', project])
if (clean)
execSync(`${forgeExecutable} clean --root ${project}`, {
encoding: 'utf-8',
stdio: 'pipe',
})
if (build)
execSync(`${forgeExecutable} build --root ${project}`, {
encoding: 'utf-8',
stdio: 'pipe',
})
if (!existsSync(artifactsDirectory))
throw new Error('Artifacts not found.')

Expand All @@ -194,7 +209,10 @@ export function foundry(config: FoundryConfig = {}): FoundryResult {
// Ensure forge is installed
if (clean || build || rebuild)
try {
await execa(forgeExecutable, ['--version'])
execSync(`${forgeExecutable} --version`, {
encoding: 'utf-8',
stdio: 'pipe',
})
} catch (_error) {
throw new Error(dedent`
forge must be installed to use Foundry plugin.
Expand All @@ -210,7 +228,7 @@ export function foundry(config: FoundryConfig = {}): FoundryResult {
project,
)}`,
)
const subprocess = execa(forgeExecutable, [
const subprocess = spawn(forgeExecutable, [
'build',
'--watch',
'--root',
Expand All @@ -223,7 +241,7 @@ export function foundry(config: FoundryConfig = {}): FoundryResult {
process.once('SIGINT', shutdown)
process.once('SIGTERM', shutdown)
function shutdown() {
subprocess?.cancel()
subprocess?.kill()
}
}
: undefined,
Expand Down
16 changes: 12 additions & 4 deletions packages/cli/src/plugins/hardhat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { execSync, spawn } from 'node:child_process'
import { existsSync } from 'node:fs'
import { readFile } from 'node:fs/promises'
import { execa } from 'execa'
import { fdir } from 'fdir'
import { basename, extname, join, resolve } from 'pathe'
import pc from 'picocolors'
Expand Down Expand Up @@ -116,7 +116,11 @@ export function hardhat(config: HardhatConfig): HardhatResult {
const [command, ...options] = (
typeof clean === 'boolean' ? `${packageManager} hardhat clean` : clean
).split(' ')
await execa(command!, options, { cwd: project })
execSync(`${command!} ${options.join(' ')}`, {
cwd: project,
encoding: 'utf-8',
stdio: 'pipe',
})
}
if (build) {
const packageManager = await getPackageManager(true)
Expand All @@ -125,7 +129,11 @@ export function hardhat(config: HardhatConfig): HardhatResult {
? `${packageManager} hardhat compile`
: build
).split(' ')
await execa(command!, options, { cwd: project })
execSync(`${command!} ${options.join(' ')}`, {
cwd: project,
encoding: 'utf-8',
stdio: 'pipe',
})
}
if (!existsSync(artifactsDirectory))
throw new Error('Artifacts not found.')
Expand Down Expand Up @@ -180,7 +188,7 @@ export function hardhat(config: HardhatConfig): HardhatResult {
logger.log(
`${pc.blue('Hardhat')} Detected ${event} at ${basename(path)}`,
)
const subprocess = execa(command!, options, {
const subprocess = spawn(command!, options, {
cwd: project,
})
subprocess.stdout?.on('data', (data) => {
Expand Down
36 changes: 20 additions & 16 deletions packages/cli/src/utils/packages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { execSync } from 'node:child_process'
import { promises as fs } from 'node:fs'
import { resolve } from 'node:path'
import { execa } from 'execa'

export async function getIsPackageInstalled(parameters: {
packageName: string
Expand All @@ -20,12 +20,16 @@ export async function getIsPackageInstalled(parameters: {
}
})()

const { stdout } = await execa(packageManager, command, { cwd })
const result = execSync(`${packageManager} ${command.join(' ')}`, {
cwd,
encoding: 'utf-8',
stdio: 'pipe',
})

// For Bun, we need to check if the package name is in the output
if (packageManager === 'bun') return stdout.includes(packageName)
if (packageManager === 'bun') return result.includes(packageName)

return stdout !== ''
return result !== ''
} catch (_error) {
return false
}
Expand Down Expand Up @@ -69,21 +73,21 @@ async function detect(

const cache = new Map()

function hasGlobalInstallation(pm: PackageManager): Promise<boolean> {
function hasGlobalInstallation(pm: PackageManager): boolean {
const key = `has_global_${pm}`
if (cache.has(key)) {
return Promise.resolve(cache.get(key))
}
if (cache.has(key)) return cache.get(key)

return execa(pm, ['--version'])
.then((res) => {
return /^\d+.\d+.\d+$/.test(res.stdout)
})
.then((value) => {
cache.set(key, value)
return value
try {
const result = execSync(`${pm} --version`, {
encoding: 'utf-8',
stdio: 'pipe',
})
.catch(() => false)
const isGlobal = /^\d+.\d+.\d+$/.test(result)
cache.set(key, isGlobal)
return isGlobal
} catch {
return false
}
}

function getTypeofLockFile(cwd = '.'): Promise<PackageManager | null> {
Expand Down
25 changes: 14 additions & 11 deletions packages/cli/test/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spawnSync } from 'node:child_process'
import { cp, mkdir, symlink, writeFile } from 'node:fs/promises'
import { execa } from 'execa'
import fixtures from 'fixturez'
import { http, HttpResponse } from 'msw'
import * as path from 'pathe'
Expand Down Expand Up @@ -144,16 +144,19 @@ export function watchConsole() {

export async function typecheck(project: string) {
try {
const res = await execa('tsc', [
'--noEmit',
'--target',
'es2021',
'--pretty',
'false',
'-p',
project,
])
return res.stdout
const result = spawnSync(
'tsc',
['--noEmit', '--target', 'es2021', '--pretty', 'false', '-p', project],
{
encoding: 'utf-8',
stdio: 'pipe',
},
)
if (result.error) throw result.error
if (result.status !== 0)
throw new Error(`Failed with code ${result.status}`)
if (result.signal) throw new Error('Process terminated by signal')
return result.stdout
} catch (error) {
throw new Error(
(error as Error).message.replaceAll(
Expand Down
3 changes: 1 addition & 2 deletions packages/create-wagmi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@
"devDependencies": {
"@types/cross-spawn": "^6.0.6",
"@types/node": "^20.12.10",
"@types/prompts": "^2.4.9",
"execa": "^8.0.1"
"@types/prompts": "^2.4.9"
},
"contributors": ["awkweb.eth <t@wevm.dev>", "jxom.eth <j@wevm.dev>"],
"funding": "https://github.com/sponsors/wevm",
Expand Down
Loading

0 comments on commit 21ec74d

Please sign in to comment.