|
1 | 1 | import { spawnSync } from 'node:child_process'; |
2 | | -import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; |
| 2 | +import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; |
3 | 3 | import path from 'node:path'; |
4 | 4 | import { afterEach, beforeEach, expect, test } from 'rstack/test'; |
5 | 5 | import { RSTACK_BIN_PATH } from '#test-helpers'; |
@@ -33,19 +33,19 @@ const writeFixturePlugin = (): void => { |
33 | 33 | ); |
34 | 34 | }; |
35 | 35 |
|
36 | | -const runCLI = (args: string[], input?: string) => { |
| 36 | +const runCLI = (args: string[], input?: string, cwd = projectPath) => { |
37 | 37 | const env: NodeJS.ProcessEnv = { ...process.env, NO_COLOR: '1' }; |
38 | 38 | delete env.FORCE_COLOR; |
39 | 39 |
|
40 | 40 | return spawnSync(process.execPath, [RSTACK_BIN_PATH, ...args], { |
41 | | - cwd: projectPath, |
| 41 | + cwd, |
42 | 42 | encoding: 'utf8', |
43 | 43 | env, |
44 | 44 | input, |
45 | 45 | }); |
46 | 46 | }; |
47 | 47 |
|
48 | | -const runFmt = (args: string[] = []) => runCLI(['fmt', ...args]); |
| 48 | +const runFmt = (args: string[] = [], cwd = projectPath) => runCLI(['fmt', ...args], undefined, cwd); |
49 | 49 |
|
50 | 50 | const runFmtStdin = (args: string[], input: string) => runCLI(['fmt', ...args], input); |
51 | 51 |
|
@@ -164,6 +164,83 @@ test('summarizes write mode when no files change', () => { |
164 | 164 | expect(result.stderr).toBe(''); |
165 | 165 | }); |
166 | 166 |
|
| 167 | +test.each([ |
| 168 | + ['write', []], |
| 169 | + ['check', ['--check']], |
| 170 | + ['list-different', ['--list-different']], |
| 171 | +] as const)('uses the default cache in %s mode', (_, args) => { |
| 172 | + writeProjectFile('index.ts', 'const value = 1;\n'); |
| 173 | + |
| 174 | + const result = runFmt([...args, 'index.ts']); |
| 175 | + |
| 176 | + expect(result.status).toBe(0); |
| 177 | + expect(readProjectFile('.rstack/cache/.gitignore')).toBe('*\n'); |
| 178 | + expect(JSON.parse(readProjectFile('.rstack/cache/fmt-v1.json'))).toMatchObject({ |
| 179 | + version: 1, |
| 180 | + files: { |
| 181 | + 'index.ts': [expect.any(String), expect.any(String), 'clean'], |
| 182 | + }, |
| 183 | + }); |
| 184 | +}); |
| 185 | + |
| 186 | +test('--no-cache bypasses cache reads and writes', () => { |
| 187 | + writeProjectFile('index.ts', 'const value=1'); |
| 188 | + |
| 189 | + const first = runFmt(['--no-cache', 'index.ts']); |
| 190 | + |
| 191 | + expect(first.status).toBe(0); |
| 192 | + expect(existsSync(path.join(projectPath, '.rstack'))).toBe(false); |
| 193 | + |
| 194 | + writeProjectFile('.rstack/cache/fmt-v1.json', 'stale'); |
| 195 | + writeProjectFile('index.ts', 'const value=2'); |
| 196 | + const second = runFmt(['--no-cache', 'index.ts']); |
| 197 | + |
| 198 | + expect(second.status).toBe(0); |
| 199 | + expect(readProjectFile('index.ts')).toBe('const value = 2;\n'); |
| 200 | + expect(readProjectFile('.rstack/cache/fmt-v1.json')).toBe('stale'); |
| 201 | + expect(existsSync(path.join(projectPath, '.rstack/cache/.gitignore'))).toBe(false); |
| 202 | +}); |
| 203 | + |
| 204 | +test('uses an explicit config root cache from a subdirectory', () => { |
| 205 | + const appPath = path.join(projectPath, 'packages/app'); |
| 206 | + writeProjectFile('packages/app/index.ts', 'const value=1'); |
| 207 | + |
| 208 | + const result = runFmt(['index.ts', '--config', '../../rstack.config.ts'], appPath); |
| 209 | + |
| 210 | + expect(result.status).toBe(0); |
| 211 | + expect(readProjectFile('packages/app/index.ts')).toBe('const value = 1;\n'); |
| 212 | + expect(existsSync(path.join(projectPath, '.rstack/cache/fmt-v1.json'))).toBe(true); |
| 213 | + expect(existsSync(path.join(appPath, '.rstack'))).toBe(false); |
| 214 | + expect(JSON.parse(readProjectFile('.rstack/cache/fmt-v1.json'))).toMatchObject({ |
| 215 | + files: { |
| 216 | + 'packages/app/index.ts': [expect.any(String), expect.any(String), 'clean'], |
| 217 | + }, |
| 218 | + }); |
| 219 | +}); |
| 220 | + |
| 221 | +test('recovers from a corrupted cache', () => { |
| 222 | + writeProjectFile('index.ts', 'const value = 1;\n'); |
| 223 | + const first = runFmt(['--check', 'index.ts']); |
| 224 | + writeProjectFile('.rstack/cache/fmt-v1.json', '{'); |
| 225 | + |
| 226 | + const second = runFmt(['--check', 'index.ts']); |
| 227 | + |
| 228 | + expect(second.status).toBe(0); |
| 229 | + expect(normalizeDuration(second.stdout)).toBe(normalizeDuration(first.stdout)); |
| 230 | + expect(second.stderr).toBe(first.stderr); |
| 231 | + expect(JSON.parse(readProjectFile('.rstack/cache/fmt-v1.json'))).toMatchObject({ version: 1 }); |
| 232 | +}); |
| 233 | + |
| 234 | +test('formats without a writable cache directory', () => { |
| 235 | + writeProjectFile('.rstack', 'not a directory'); |
| 236 | + writeProjectFile('index.ts', 'const value=1'); |
| 237 | + |
| 238 | + const result = runFmt(['index.ts']); |
| 239 | + |
| 240 | + expect(result.status).toBe(0); |
| 241 | + expect(readProjectFile('index.ts')).toBe('const value = 1;\n'); |
| 242 | +}); |
| 243 | + |
167 | 244 | test('does not sort package.json by default', () => { |
168 | 245 | writeProjectFile('package.json', packageJsonSource); |
169 | 246 |
|
@@ -461,6 +538,7 @@ test('formats stdin for the given filepath', () => { |
461 | 538 | expect(result.status).toBe(0); |
462 | 539 | expect(result.stdout).toBe('const message = "hello";\n'); |
463 | 540 | expect(result.stderr).toBe(''); |
| 541 | + expect(existsSync(path.join(projectPath, '.rstack'))).toBe(false); |
464 | 542 | }); |
465 | 543 |
|
466 | 544 | test('applies define.fmt options and overrides to stdin', () => { |
@@ -622,6 +700,7 @@ test('returns exit code 2 when no files match', () => { |
622 | 700 | ); |
623 | 701 | expect(result.stderr).not.toContain('\n at '); |
624 | 702 | } |
| 703 | + expect(existsSync(path.join(projectPath, '.rstack'))).toBe(false); |
625 | 704 | }); |
626 | 705 |
|
627 | 706 | test('allows no files to match with --no-error-on-unmatched-pattern', () => { |
|
0 commit comments