|
1 | | -import { |
2 | | - existsSync, |
3 | | - mkdtempSync, |
4 | | - readFileSync, |
5 | | - symlinkSync, |
6 | | - writeFileSync, |
7 | | -} from 'node:fs'; |
| 1 | +import { existsSync, mkdtempSync, readFileSync, symlinkSync, writeFileSync } from 'node:fs'; |
8 | 2 | import { tmpdir } from 'node:os'; |
9 | 3 | import path from 'node:path'; |
10 | 4 | import { describe, expect, it, vi } from 'vitest'; |
@@ -1055,74 +1049,74 @@ describe('runInstall — default AgentFs (real disk)', () => { |
1055 | 1049 | it.skipIf(!symlinkCapable)( |
1056 | 1050 | 'refuses to write through a symlinked parent dir (real disk) — exit 5', |
1057 | 1051 | async () => { |
1058 | | - const tmpRoot = mkdtempSync(path.join(tmpdir(), 'agent-test-symlink-parent-')); |
1059 | | - const outside = mkdtempSync(path.join(tmpdir(), 'agent-test-outside-')); |
1060 | | - // `.claude` is a real symlink to a directory outside the project root. |
1061 | | - symlinkSync(outside, path.join(tmpRoot, '.claude'), 'dir'); |
1062 | | - const { deps } = makeCapture(); |
1063 | | - |
1064 | | - let thrown: unknown; |
1065 | | - try { |
1066 | | - await runInstall( |
1067 | | - { |
1068 | | - profile: 'default', |
1069 | | - output: 'text', |
1070 | | - debug: false, |
1071 | | - dryRun: false, |
1072 | | - target: ['claude'], |
1073 | | - force: false, |
1074 | | - dir: tmpRoot, |
1075 | | - }, |
1076 | | - { ...deps }, |
1077 | | - ); |
1078 | | - } catch (err) { |
1079 | | - thrown = err; |
1080 | | - } |
| 1052 | + const tmpRoot = mkdtempSync(path.join(tmpdir(), 'agent-test-symlink-parent-')); |
| 1053 | + const outside = mkdtempSync(path.join(tmpdir(), 'agent-test-outside-')); |
| 1054 | + // `.claude` is a real symlink to a directory outside the project root. |
| 1055 | + symlinkSync(outside, path.join(tmpRoot, '.claude'), 'dir'); |
| 1056 | + const { deps } = makeCapture(); |
| 1057 | + |
| 1058 | + let thrown: unknown; |
| 1059 | + try { |
| 1060 | + await runInstall( |
| 1061 | + { |
| 1062 | + profile: 'default', |
| 1063 | + output: 'text', |
| 1064 | + debug: false, |
| 1065 | + dryRun: false, |
| 1066 | + target: ['claude'], |
| 1067 | + force: false, |
| 1068 | + dir: tmpRoot, |
| 1069 | + }, |
| 1070 | + { ...deps }, |
| 1071 | + ); |
| 1072 | + } catch (err) { |
| 1073 | + thrown = err; |
| 1074 | + } |
1081 | 1075 |
|
1082 | | - expect(thrown).toBeInstanceOf(CLIError); |
1083 | | - expect((thrown as CLIError).exitCode).toBe(5); |
1084 | | - // Nothing was created through the symlink, outside --dir. |
1085 | | - expect(existsSync(path.join(outside, 'skills'))).toBe(false); |
| 1076 | + expect(thrown).toBeInstanceOf(CLIError); |
| 1077 | + expect((thrown as CLIError).exitCode).toBe(5); |
| 1078 | + // Nothing was created through the symlink, outside --dir. |
| 1079 | + expect(existsSync(path.join(outside, 'skills'))).toBe(false); |
1086 | 1080 | }, |
1087 | 1081 | ); |
1088 | 1082 |
|
1089 | 1083 | it.skipIf(!symlinkCapable)( |
1090 | 1084 | 'refuses to overwrite a symlinked target file (real disk) with --force — exit 5', |
1091 | 1085 | async () => { |
1092 | | - const tmpRoot = mkdtempSync(path.join(tmpdir(), 'agent-test-symlink-target-')); |
1093 | | - const outsideDir = mkdtempSync(path.join(tmpdir(), 'agent-test-outside-target-')); |
1094 | | - const { path: relPath } = renderForTarget('claude'); |
1095 | | - const abs = path.resolve(tmpRoot, relPath); |
1096 | | - const nodeFs = await import('node:fs/promises'); |
1097 | | - await nodeFs.mkdir(path.dirname(abs), { recursive: true }); |
1098 | | - // SKILL.md is a real symlink to a file outside the project root. |
1099 | | - const outsideFile = path.join(outsideDir, 'secret.txt'); |
1100 | | - await nodeFs.writeFile(outsideFile, 'SECRET', 'utf8'); |
1101 | | - symlinkSync(outsideFile, abs, 'file'); |
1102 | | - const { deps } = makeCapture(); |
1103 | | - |
1104 | | - let thrown: unknown; |
1105 | | - try { |
1106 | | - await runInstall( |
1107 | | - { |
1108 | | - profile: 'default', |
1109 | | - output: 'text', |
1110 | | - debug: false, |
1111 | | - dryRun: false, |
1112 | | - target: ['claude'], |
1113 | | - force: true, |
1114 | | - dir: tmpRoot, |
1115 | | - }, |
1116 | | - { ...deps }, |
1117 | | - ); |
1118 | | - } catch (err) { |
1119 | | - thrown = err; |
1120 | | - } |
| 1086 | + const tmpRoot = mkdtempSync(path.join(tmpdir(), 'agent-test-symlink-target-')); |
| 1087 | + const outsideDir = mkdtempSync(path.join(tmpdir(), 'agent-test-outside-target-')); |
| 1088 | + const { path: relPath } = renderForTarget('claude'); |
| 1089 | + const abs = path.resolve(tmpRoot, relPath); |
| 1090 | + const nodeFs = await import('node:fs/promises'); |
| 1091 | + await nodeFs.mkdir(path.dirname(abs), { recursive: true }); |
| 1092 | + // SKILL.md is a real symlink to a file outside the project root. |
| 1093 | + const outsideFile = path.join(outsideDir, 'secret.txt'); |
| 1094 | + await nodeFs.writeFile(outsideFile, 'SECRET', 'utf8'); |
| 1095 | + symlinkSync(outsideFile, abs, 'file'); |
| 1096 | + const { deps } = makeCapture(); |
| 1097 | + |
| 1098 | + let thrown: unknown; |
| 1099 | + try { |
| 1100 | + await runInstall( |
| 1101 | + { |
| 1102 | + profile: 'default', |
| 1103 | + output: 'text', |
| 1104 | + debug: false, |
| 1105 | + dryRun: false, |
| 1106 | + target: ['claude'], |
| 1107 | + force: true, |
| 1108 | + dir: tmpRoot, |
| 1109 | + }, |
| 1110 | + { ...deps }, |
| 1111 | + ); |
| 1112 | + } catch (err) { |
| 1113 | + thrown = err; |
| 1114 | + } |
1121 | 1115 |
|
1122 | | - expect(thrown).toBeInstanceOf(CLIError); |
1123 | | - expect((thrown as CLIError).exitCode).toBe(5); |
1124 | | - // The outside file was NOT overwritten (nor clobbered via the .bak path). |
1125 | | - expect(readFileSync(outsideFile, 'utf8')).toBe('SECRET'); |
| 1116 | + expect(thrown).toBeInstanceOf(CLIError); |
| 1117 | + expect((thrown as CLIError).exitCode).toBe(5); |
| 1118 | + // The outside file was NOT overwritten (nor clobbered via the .bak path). |
| 1119 | + expect(readFileSync(outsideFile, 'utf8')).toBe('SECRET'); |
1126 | 1120 | }, |
1127 | 1121 | ); |
1128 | 1122 | }); |
|
0 commit comments