Skip to content

Commit 56d13b3

Browse files
committed
Fix CI and ts-format.
1 parent 56cb7e8 commit 56d13b3

2 files changed

Lines changed: 87 additions & 42 deletions

File tree

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import { execFileSync } from 'node:child_process'
2-
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
2+
import {
3+
mkdir,
4+
mkdtemp,
5+
readFile,
6+
rm,
7+
symlink,
8+
writeFile,
9+
} from 'node:fs/promises'
310
import { tmpdir } from 'node:os'
411
import path from 'node:path'
512
import { describe, expect, it } from 'vitest'
@@ -31,39 +38,78 @@ describe('formatFiles', () => {
3138
}
3239
})
3340

34-
it('honors Git ignores and exclusions while collecting errors and formatting other files', async () => {
35-
const cwd = await mkdtemp(path.join(tmpdir(), 'purestack-format-'))
41+
const caseInsensitivePaths =
42+
process.platform === 'win32' || process.platform === 'darwin'
43+
44+
it.each(caseInsensitivePaths ? [false, true] : [false])(
45+
'honors Git ignores and exclusions while collecting errors (uppercase path: %s)',
46+
async (uppercase) => {
47+
const directory = await mkdtemp(path.join(tmpdir(), 'purestack-format-'))
48+
const cwd = uppercase ? directory.toUpperCase() : directory
49+
try {
50+
execFileSync('git', ['init', cwd], { stdio: 'ignore' })
51+
await writeFile(path.join(cwd, '.gitignore'), 'ignored.mdx\n')
52+
await writeFile(
53+
path.join(cwd, 'ignored.mdx'),
54+
'<Panel><Badge /></Panel>',
55+
)
56+
await writeFile(
57+
path.join(cwd, 'excluded.mdx'),
58+
'<Panel><Badge /></Panel>',
59+
)
60+
await writeFile(path.join(cwd, 'good.mdx'), '<Panel><Badge /></Panel>')
61+
await writeFile(
62+
path.join(cwd, 'bad.mdx'),
63+
'<Panel title="broken></Panel>',
64+
)
65+
const result = await formatFiles(['.', 'missing.mdx'], {
66+
cwd,
67+
write: true,
68+
exclude: [uppercase ? 'EXCLUDED.mdx' : 'excluded.mdx'],
69+
})
70+
expect(result.checked).toBe(2)
71+
expect(result.changed).toEqual([path.join(cwd, 'good.mdx')])
72+
expect(
73+
result.errors.map((error) => path.basename(error.filePath)).sort(),
74+
).toEqual(['bad.mdx', 'missing.mdx'])
75+
expect(await readFile(path.join(cwd, 'bad.mdx'), 'utf8')).toBe(
76+
'<Panel title="broken></Panel>',
77+
)
78+
expect(await readFile(path.join(cwd, 'ignored.mdx'), 'utf8')).toBe(
79+
'<Panel><Badge /></Panel>',
80+
)
81+
} finally {
82+
await rm(directory, { recursive: true, force: true })
83+
}
84+
},
85+
)
86+
87+
it('honors Git ignores and exclusions through a directory alias', async () => {
88+
const directory = await mkdtemp(path.join(tmpdir(), 'purestack-format-'))
89+
const projects = path.join(directory, 'projects')
90+
const repository = path.join(projects, 'project with spaces')
91+
const alias = path.join(directory, 'projects-alias')
92+
const cwd = path.join(alias, 'project with spaces')
3693
try {
37-
execFileSync('git', ['init', cwd], { stdio: 'ignore' })
38-
await writeFile(path.join(cwd, '.gitignore'), 'ignored.mdx\n')
39-
await writeFile(path.join(cwd, 'ignored.mdx'), '<Panel><Badge /></Panel>')
40-
await writeFile(
41-
path.join(cwd, 'excluded.mdx'),
42-
'<Panel><Badge /></Panel>',
43-
)
44-
await writeFile(path.join(cwd, 'good.mdx'), '<Panel><Badge /></Panel>')
45-
await writeFile(
46-
path.join(cwd, 'bad.mdx'),
47-
'<Panel title="broken></Panel>',
94+
await mkdir(repository, { recursive: true })
95+
execFileSync('git', ['init', repository], { stdio: 'ignore' })
96+
await writeFile(path.join(repository, '.gitignore'), 'ignored.mdx\n')
97+
for (const file of ['good.mdx', 'excluded.mdx', 'ignored.mdx'])
98+
await writeFile(path.join(repository, file), '<Panel><Badge /></Panel>')
99+
await symlink(
100+
projects,
101+
alias,
102+
process.platform === 'win32' ? 'junction' : 'dir',
48103
)
49-
const result = await formatFiles(['.', 'missing.mdx'], {
104+
const result = await formatFiles(['.'], {
50105
cwd,
51-
write: true,
52106
exclude: ['excluded.mdx'],
53107
})
54-
expect(result.checked).toBe(2)
108+
expect(result.errors).toEqual([])
109+
expect(result.checked).toBe(1)
55110
expect(result.changed).toEqual([path.join(cwd, 'good.mdx')])
56-
expect(
57-
result.errors.map((error) => path.basename(error.filePath)).sort(),
58-
).toEqual(['bad.mdx', 'missing.mdx'])
59-
expect(await readFile(path.join(cwd, 'bad.mdx'), 'utf8')).toBe(
60-
'<Panel title="broken></Panel>',
61-
)
62-
expect(await readFile(path.join(cwd, 'ignored.mdx'), 'utf8')).toBe(
63-
'<Panel><Badge /></Panel>',
64-
)
65111
} finally {
66-
await rm(cwd, { recursive: true, force: true })
112+
await rm(directory, { recursive: true, force: true })
67113
}
68114
})
69115
})

‎packages/ts-format/src/files.ts‎

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ import { formatSource } from './index'
66
import type { FormatOptions } from './source'
77

88
const exec = promisify(execFile)
9+
// Windows and macOS file systems ignore letter case by default.
10+
const caseInsensitivePaths =
11+
process.platform === 'win32' || process.platform === 'darwin'
12+
const comparablePath = (value: string) =>
13+
caseInsensitivePaths ? value.toLowerCase() : value
914
const extensions = /\.(?:[cm]?ts|mdx|rmdx)$/i
1015
const ignoredDirectories = new Set([
1116
'.git',
@@ -40,20 +45,22 @@ export async function formatFiles(
4045
) {
4146
const cwd = path.resolve(options.cwd ?? process.cwd())
4247
const excluded = (options.exclude ?? []).map((entry) =>
43-
path.resolve(cwd, entry),
48+
comparablePath(path.resolve(cwd, entry)),
4449
)
4550
const files = new Set<string>()
4651
const errors: FormatFileError[] = []
4752
const changed: string[] = []
4853

4954
function isExcluded(file: string) {
55+
const comparable = comparablePath(file)
5056
return (
5157
path
5258
.relative(cwd, file)
5359
.split(path.sep)
54-
.some((part) => ignoredDirectories.has(part)) ||
60+
.some((part) => ignoredDirectories.has(comparablePath(part))) ||
5561
excluded.some(
56-
(entry) => file === entry || file.startsWith(`${entry}${path.sep}`),
62+
(entry) =>
63+
comparable === entry || comparable.startsWith(`${entry}${path.sep}`),
5764
)
5865
)
5966
}
@@ -84,34 +91,26 @@ export async function formatFiles(
8491
if (!entry.isDirectory() || isExcluded(target)) continue
8592
let gitFiles: string[] | undefined
8693
try {
87-
const { stdout: root } = await exec('git', [
88-
'-C',
89-
target,
90-
'rev-parse',
91-
'--show-toplevel',
92-
])
93-
const repository = root.trim()
94-
const relative =
95-
path.relative(repository, target).split(path.sep).join('/') || '.'
9694
const { stdout } = await exec(
9795
'git',
9896
[
9997
'-C',
100-
repository,
98+
target,
10199
'ls-files',
102100
'--cached',
103101
'--others',
104102
'--exclude-standard',
105103
'-z',
106104
'--',
107-
relative,
105+
'.',
108106
],
109107
{ maxBuffer: 32 * 1024 * 1024 },
110108
)
111109
gitFiles = stdout
112110
.split('\0')
113111
.filter(Boolean)
114-
.map((file) => path.resolve(repository, file))
112+
// Git lists paths relative to -C, so keep the caller's path spelling.
113+
.map((file) => path.resolve(target, file))
115114
} catch {
116115
// Git is optional; a standalone project still has a formatter.
117116
}

0 commit comments

Comments
 (0)