@mintlify/cli 4.0.1510 → 4.0.1511

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
- const lock = yield fs.open(lockPath, 'wx');
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
- if (errorCode(error) !== 'EEXIST')
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
- const stats = yield fs.stat(lockPath).catch(() => undefined);
166
- if (stats && Date.now() - stats.mtimeMs > STALE_LOCK_MS) {
167
- yield fs.unlink(lockPath).catch(() => undefined);
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
- yield delay(LOCK_RETRY_MS);
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
  });