@mintlify/cli 4.0.1510 → 4.0.1512
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -15,6 +15,8 @@ import type { CodeTestReport } from '../src/agent-harness/types.js';
|
|
|
15
15
|
const temporaryDirectories: string[] = [];
|
|
16
16
|
|
|
17
17
|
afterEach(async () => {
|
|
18
|
+
vi.restoreAllMocks();
|
|
19
|
+
vi.unstubAllGlobals();
|
|
18
20
|
await Promise.all(
|
|
19
21
|
temporaryDirectories.splice(0).map((directory) => fs.rm(directory, { recursive: true }))
|
|
20
22
|
);
|
|
@@ -119,6 +121,80 @@ it('keeps every run when history writes overlap', async () => {
|
|
|
119
121
|
);
|
|
120
122
|
}, 60_000);
|
|
121
123
|
|
|
124
|
+
it('retries a temporarily unavailable Windows history lock', async () => {
|
|
125
|
+
vi.stubGlobal('process', { ...process, platform: 'win32' });
|
|
126
|
+
const docsRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mint-test-history-'));
|
|
127
|
+
temporaryDirectories.push(docsRoot);
|
|
128
|
+
const report = makeReport(docsRoot, 'windows-retry', '2026-09-01T12:00:01.000Z');
|
|
129
|
+
const open = vi
|
|
130
|
+
.spyOn(fs, 'open')
|
|
131
|
+
.mockRejectedValueOnce(Object.assign(new Error('Lock deletion pending'), { code: 'EPERM' }));
|
|
132
|
+
|
|
133
|
+
await writeTestRunHistory(docsRoot, report);
|
|
134
|
+
|
|
135
|
+
expect(open).toHaveBeenCalledTimes(2);
|
|
136
|
+
expect(open).toHaveBeenNthCalledWith(
|
|
137
|
+
2,
|
|
138
|
+
path.join(docsRoot, '.mintlify/test/testRuns.lock'),
|
|
139
|
+
'wx'
|
|
140
|
+
);
|
|
141
|
+
expect(JSON.parse(await fs.readFile(testRunsIndexPath(docsRoot), 'utf8'))).toMatchObject({
|
|
142
|
+
runs: [{ runId: report.runId }],
|
|
143
|
+
});
|
|
144
|
+
await expect(
|
|
145
|
+
fs.access(path.join(docsRoot, '.mintlify/test/testRuns.lock'))
|
|
146
|
+
).rejects.toMatchObject({
|
|
147
|
+
code: 'ENOENT',
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it.each([
|
|
152
|
+
['linux', 'EPERM'],
|
|
153
|
+
['win32', 'EACCES'],
|
|
154
|
+
])('propagates %s lock acquisition errors with code %s', async (platform, code) => {
|
|
155
|
+
vi.stubGlobal('process', { ...process, platform });
|
|
156
|
+
const docsRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mint-test-history-'));
|
|
157
|
+
temporaryDirectories.push(docsRoot);
|
|
158
|
+
const report = makeReport(docsRoot, 'denied', '2026-09-01T12:00:01.000Z');
|
|
159
|
+
const error = Object.assign(new Error('Permission denied'), { code });
|
|
160
|
+
const open = vi.spyOn(fs, 'open').mockRejectedValue(error);
|
|
161
|
+
|
|
162
|
+
await expect(writeTestRunHistory(docsRoot, report)).rejects.toBe(error);
|
|
163
|
+
expect(open).toHaveBeenCalledTimes(1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('stops retrying a Windows lock at the acquisition deadline', async () => {
|
|
167
|
+
vi.stubGlobal('process', { ...process, platform: 'win32' });
|
|
168
|
+
const docsRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mint-test-history-'));
|
|
169
|
+
temporaryDirectories.push(docsRoot);
|
|
170
|
+
const report = makeReport(docsRoot, 'timeout', '2026-09-01T12:00:01.000Z');
|
|
171
|
+
vi.spyOn(fs, 'open').mockRejectedValue(
|
|
172
|
+
Object.assign(new Error('Permission denied'), { code: 'EPERM' })
|
|
173
|
+
);
|
|
174
|
+
vi.spyOn(Date, 'now').mockReturnValueOnce(0).mockReturnValueOnce(0).mockReturnValue(40_000);
|
|
175
|
+
|
|
176
|
+
await expect(writeTestRunHistory(docsRoot, report)).rejects.toThrow(
|
|
177
|
+
'Timed out updating mint test run history'
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('does not retry a token write failure after acquiring the Windows lock', async () => {
|
|
182
|
+
vi.stubGlobal('process', { ...process, platform: 'win32' });
|
|
183
|
+
const docsRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mint-test-history-'));
|
|
184
|
+
temporaryDirectories.push(docsRoot);
|
|
185
|
+
const report = makeReport(docsRoot, 'token-failure', '2026-09-01T12:00:01.000Z');
|
|
186
|
+
const error = Object.assign(new Error('Cannot write token'), { code: 'EPERM' });
|
|
187
|
+
const realOpen = fs.open;
|
|
188
|
+
const open = vi.spyOn(fs, 'open').mockImplementationOnce(async (...args) => {
|
|
189
|
+
const handle = await realOpen(...args);
|
|
190
|
+
vi.spyOn(handle, 'writeFile').mockRejectedValueOnce(error);
|
|
191
|
+
return handle;
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await expect(writeTestRunHistory(docsRoot, report)).rejects.toBe(error);
|
|
195
|
+
expect(open).toHaveBeenCalledTimes(1);
|
|
196
|
+
});
|
|
197
|
+
|
|
122
198
|
it('recovers a stale history lock left by an interrupted process', async () => {
|
|
123
199
|
const docsRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'mint-test-history-'));
|
|
124
200
|
temporaryDirectories.push(docsRoot);
|
|
@@ -144,30 +144,34 @@ function acquireIndexLock(docsRoot) {
|
|
|
144
144
|
const token = `${process.pid}:${randomUUID()}`;
|
|
145
145
|
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
146
146
|
while (Date.now() < deadline) {
|
|
147
|
+
let lock;
|
|
147
148
|
try {
|
|
148
|
-
|
|
149
|
-
try {
|
|
150
|
-
yield lock.writeFile(token);
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
yield lock.close();
|
|
154
|
-
}
|
|
155
|
-
return () => __awaiter(this, void 0, void 0, function* () {
|
|
156
|
-
const owner = yield fs.readFile(lockPath, 'utf8').catch(() => undefined);
|
|
157
|
-
if (owner === token)
|
|
158
|
-
yield fs.unlink(lockPath).catch(() => undefined);
|
|
159
|
-
});
|
|
149
|
+
lock = yield fs.open(lockPath, 'wx');
|
|
160
150
|
}
|
|
161
151
|
catch (error) {
|
|
162
|
-
|
|
152
|
+
const code = errorCode(error);
|
|
153
|
+
// Windows can deny opening a lock while another writer's deletion is still pending.
|
|
154
|
+
if (code !== 'EEXIST' && !(process.platform === 'win32' && code === 'EPERM'))
|
|
163
155
|
throw error;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
156
|
+
const stats = yield fs.stat(lockPath).catch(() => undefined);
|
|
157
|
+
if (stats && Date.now() - stats.mtimeMs > STALE_LOCK_MS) {
|
|
158
|
+
yield fs.unlink(lockPath).catch(() => undefined);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
yield delay(LOCK_RETRY_MS);
|
|
168
162
|
continue;
|
|
169
163
|
}
|
|
170
|
-
|
|
164
|
+
try {
|
|
165
|
+
yield lock.writeFile(token);
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
yield lock.close();
|
|
169
|
+
}
|
|
170
|
+
return () => __awaiter(this, void 0, void 0, function* () {
|
|
171
|
+
const owner = yield fs.readFile(lockPath, 'utf8').catch(() => undefined);
|
|
172
|
+
if (owner === token)
|
|
173
|
+
yield fs.unlink(lockPath).catch(() => undefined);
|
|
174
|
+
});
|
|
171
175
|
}
|
|
172
176
|
throw new Error('Timed out updating mint test run history');
|
|
173
177
|
});
|