Skip to content

Commit dcf5da4

Browse files
pralkarzziebamJounQin
authored
feat: replace execa with tinyexec (#195)
Co-authored-by: ziebam <[email protected]> Co-authored-by: JounQin <[email protected]>
1 parent 76c5371 commit dcf5da4

14 files changed

+269
-222
lines changed

.changeset/dull-badgers-sleep.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"pretty-quick": minor
3+
---
4+
5+
feat: replace `execa` with `tinyexec`

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ node_modules
44
/.yarn/*
55
!/.yarn/plugins
66
!/.yarn/releases
7+
coverage
8+
.eslintcache

__mocks__/execa.ts

Lines changed: 0 additions & 25 deletions
This file was deleted.

__mocks__/tinyexec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const exec = jest.fn().mockReturnValue({
2+
stdout: '',
3+
stderr: '',
4+
// eslint-disable-next-line @typescript-eslint/no-empty-function
5+
kill: () => {},
6+
})

package.json

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,13 @@
5757
"prettier": "^3.0.0"
5858
},
5959
"dependencies": {
60-
"execa": "^5.1.1",
6160
"find-up": "^5.0.0",
6261
"ignore": "^7.0.3",
6362
"mri": "^1.2.0",
64-
"picocolors": "^1.0.0",
65-
"picomatch": "^3.0.1",
66-
"tslib": "^2.6.2"
63+
"picocolors": "^1.1.1",
64+
"picomatch": "^4.0.2",
65+
"tinyexec": "^0.3.2",
66+
"tslib": "^2.8.1"
6767
},
6868
"devDependencies": {
6969
"@1stg/lib-config": "^13.0.0",
@@ -82,14 +82,13 @@
8282
"lint-staged": "^15.4.3",
8383
"mock-fs": "^5.5.0",
8484
"npm-run-all": "^4.1.5",
85-
"prettier": "^3.2.3",
85+
"prettier": "^3.5.3",
8686
"pretty-quick": "link:.",
87-
"simple-git-hooks": "^2.9.0",
88-
"size-limit": "^11.0.2",
87+
"size-limit": "^11.2.0",
8988
"size-limit-preset-node-lib": "^0.3.0",
90-
"ts-jest": "^29.1.1",
89+
"ts-jest": "^29.2.6",
9190
"ts-node": "^10.9.2",
92-
"typescript": "^5.3.3"
91+
"typescript": "^5.8.2"
9392
},
9493
"resolutions": {
9594
"prettier": "^3.5.3",

src/index.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export = async (
3535
}
3636
const directory = scm.rootDirectory
3737

38-
const revision = since || scm.getSinceRevision(directory, { staged, branch })
38+
const revision =
39+
since || (await scm.getSinceRevision(directory, { staged, branch }))
3940

4041
onFoundSinceRevision?.(scm.name, revision)
4142

@@ -49,19 +50,23 @@ export = async (
4950

5051
const isFileSupportedExtension = isSupportedExtension(resolveConfig)
5152

53+
const unfilteredChangedFiles = await scm.getChangedFiles(
54+
directory,
55+
revision,
56+
staged,
57+
)
5258
const changedFiles = await filterAsync(
53-
scm
54-
.getChangedFiles(directory, revision, staged)
59+
unfilteredChangedFiles
5560
.filter(patternMatcher)
5661
.filter(rootIgnorer)
5762
.filter(cwdIgnorer),
5863
isFileSupportedExtension,
5964
)
6065

66+
const unfilteredStagedFiles = await scm.getUnstagedChangedFiles(directory)
6167
const unstagedFiles = staged
6268
? await filterAsync(
63-
scm
64-
.getUnstagedChangedFiles(directory)
69+
unfilteredStagedFiles
6570
.filter(patternMatcher)
6671
.filter(rootIgnorer)
6772
.filter(cwdIgnorer),
@@ -78,14 +83,14 @@ export = async (
7883
await processFiles(directory, changedFiles, {
7984
check,
8085
config,
81-
onWriteFile(file: string) {
82-
onWriteFile?.(file)
86+
onWriteFile: async (file: string) => {
87+
await onWriteFile?.(file)
8388
if (bail) {
8489
failReasons.add('BAIL_ON_WRITE')
8590
}
8691
if (staged && restage) {
8792
if (wasFullyStaged(file)) {
88-
scm.stageFile(directory, file)
93+
await scm.stageFile(directory, file)
8994
} else {
9095
onPartiallyStagedFile?.(file)
9196
failReasons.add('PARTIALLY_STAGED_FILE')

src/processFiles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default async (
4040

4141
if (output !== input) {
4242
fs.writeFileSync(file, output)
43-
onWriteFile?.(relative)
43+
await onWriteFile?.(relative)
4444
}
4545
}
4646
}

src/scms/git.ts

Lines changed: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import fs from 'fs'
22
import path from 'path'
33

4-
import execa from 'execa'
54
import findUp from 'find-up'
5+
import { Output, exec } from 'tinyexec'
66

77
export const name = 'git'
88

@@ -44,26 +44,35 @@ export const detect = (directory: string) => {
4444
}
4545

4646
const runGit = (directory: string, args: string[]) =>
47-
execa.sync('git', args, {
48-
cwd: directory,
47+
exec('git', args, {
48+
nodeOptions: {
49+
cwd: directory,
50+
},
4951
})
5052

51-
const getLines = (execaResult: execa.ExecaSyncReturnValue) =>
52-
execaResult.stdout.split('\n')
53+
const getLines = (tinyexecOutput: Output) => tinyexecOutput.stdout.split('\n')
5354

54-
export const getSinceRevision = (
55+
export const getSinceRevision = async (
5556
directory: string,
5657
{ staged, branch }: { staged?: boolean; branch?: string },
5758
) => {
5859
try {
59-
const revision = staged
60-
? 'HEAD'
61-
: runGit(directory, [
62-
'merge-base',
63-
'HEAD',
64-
branch || 'master',
65-
]).stdout.trim()
66-
return runGit(directory, ['rev-parse', '--short', revision]).stdout.trim()
60+
let revision = 'HEAD'
61+
if (!staged) {
62+
const revisionOutput = await runGit(directory, [
63+
'merge-base',
64+
'HEAD',
65+
branch || 'master',
66+
])
67+
revision = revisionOutput.stdout.trim()
68+
}
69+
70+
const revParseOutput = await runGit(directory, [
71+
'rev-parse',
72+
'--short',
73+
revision,
74+
])
75+
return revParseOutput.stdout.trim()
6776
} catch (err) {
6877
const error = err as Error
6978
if (
@@ -76,14 +85,14 @@ export const getSinceRevision = (
7685
}
7786
}
7887

79-
export const getChangedFiles = (
88+
export const getChangedFiles = async (
8089
directory: string,
8190
revision: string | null,
8291
staged?: boolean | undefined,
8392
) =>
8493
[
8594
...getLines(
86-
runGit(
95+
await runGit(
8796
directory,
8897
[
8998
'diff',
@@ -97,14 +106,17 @@ export const getChangedFiles = (
97106
...(staged
98107
? []
99108
: getLines(
100-
runGit(directory, ['ls-files', '--others', '--exclude-standard']),
109+
await runGit(directory, [
110+
'ls-files',
111+
'--others',
112+
'--exclude-standard',
113+
]),
101114
)),
102115
].filter(Boolean)
103116

104117
export const getUnstagedChangedFiles = (directory: string) => {
105118
return getChangedFiles(directory, null, false)
106119
}
107120

108-
export const stageFile = (directory: string, file: string) => {
121+
export const stageFile = (directory: string, file: string) =>
109122
runGit(directory, ['add', file])
110-
}

src/scms/hg.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from 'path'
22

3-
import execa from 'execa'
43
import findUp from 'find-up'
4+
import { Output, exec } from 'tinyexec'
55

66
export const name = 'hg'
77

@@ -16,33 +16,37 @@ export const detect = (directory: string) => {
1616
}
1717

1818
const runHg = (directory: string, args: string[]) =>
19-
execa.sync('hg', args, {
20-
cwd: directory,
19+
exec('hg', args, {
20+
nodeOptions: {
21+
cwd: directory,
22+
},
2123
})
2224

23-
const getLines = (execaResult: execa.ExecaSyncReturnValue) =>
24-
execaResult.stdout.split('\n')
25+
const getLines = (tinyexecOutput: Output) => tinyexecOutput.stdout.split('\n')
2526

26-
export const getSinceRevision = (
27+
export const getSinceRevision = async (
2728
directory: string,
2829
{ branch }: { branch?: string },
2930
) => {
30-
const revision = runHg(directory, [
31+
const revisionOutput = await runHg(directory, [
3132
'debugancestor',
3233
'tip',
3334
branch || 'default',
34-
]).stdout.trim()
35-
return runHg(directory, ['id', '-i', '-r', revision]).stdout.trim()
35+
])
36+
const revision = revisionOutput.stdout.trim()
37+
38+
const hgOutput = await runHg(directory, ['id', '-i', '-r', revision])
39+
return hgOutput.stdout.trim()
3640
}
3741

38-
export const getChangedFiles = (
42+
export const getChangedFiles = async (
3943
directory: string,
4044
revision: string | null,
4145
_staged?: boolean,
4246
) =>
4347
[
4448
...getLines(
45-
runHg(directory, [
49+
await runHg(directory, [
4650
'status',
4751
'-n',
4852
'-a',
@@ -54,6 +58,5 @@ export const getChangedFiles = (
5458

5559
export const getUnstagedChangedFiles = () => []
5660

57-
export const stageFile = (directory: string, file: string) => {
61+
export const stageFile = (directory: string, file: string) =>
5862
runHg(directory, ['add', file])
59-
}

src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ export interface PrettyQuickOptions {
1515
onPartiallyStagedFile(file: string): void
1616
onExamineFile(relative: string): void
1717
onCheckFile(relative: string, isFormatted: boolean): void
18-
onWriteFile(relative: string): void
18+
onWriteFile(relative: string): Promise<void> | void
1919
}

test/pattern.spec.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import execa from 'execa'
21
import mock from 'mock-fs'
2+
import * as tinyexec from 'tinyexec'
33

44
import prettyQuick from 'pretty-quick'
55

6-
jest.mock('execa')
7-
86
afterEach(() => {
97
mock.restore()
108
jest.clearAllMocks()
@@ -22,9 +20,8 @@ describe('match pattern', () => {
2220
'/src/should-not-be-included/hello/zoo.js': "export const zoo = 'zoo'",
2321
})
2422

25-
// @ts-expect-error -- Need to find a better way to mock this
26-
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
27-
execa.sync.mockImplementation((_command: string, args: string[]) => {
23+
const execSpy = jest.spyOn(tinyexec, 'exec') as jest.Mock
24+
execSpy.mockImplementation((_command: string, args: string[]) => {
2825
switch (args[0]) {
2926
case 'ls-files': {
3027
return { stdout: '' }

0 commit comments

Comments
 (0)