@mintlify/cli 4.0.1193 → 4.0.1195
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.
- package/__test__/helpers.test.ts +24 -1
- package/__test__/update.test.ts +15 -6
- package/bin/helpers.js +21 -7
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/bin/update.js +4 -3
- package/package.json +7 -7
- package/src/helpers.tsx +25 -4
- package/src/update.tsx +19 -3
package/__test__/helpers.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LOCAL_LINKED_CLI_VERSION } from '@mintlify/previewing';
|
|
2
2
|
|
|
3
|
-
import { getCliVersion } from '../src/helpers.js';
|
|
3
|
+
import { assertValidPackageName, getCliVersion } from '../src/helpers.js';
|
|
4
4
|
|
|
5
5
|
describe('getCliVersion', () => {
|
|
6
6
|
const originalTestMode = process.env.CLI_TEST_MODE;
|
|
@@ -27,3 +27,26 @@ describe('getCliVersion', () => {
|
|
|
27
27
|
}
|
|
28
28
|
);
|
|
29
29
|
});
|
|
30
|
+
|
|
31
|
+
describe('assertValidPackageName', () => {
|
|
32
|
+
it.each(['mint', 'mintlify', '@mintlify/cli', 'some-pkg.name~2'])(
|
|
33
|
+
'accepts valid npm package name %s',
|
|
34
|
+
(name) => {
|
|
35
|
+
expect(() => assertValidPackageName(name)).not.toThrow();
|
|
36
|
+
}
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
it.each([
|
|
40
|
+
'mint; rm -rf /',
|
|
41
|
+
'mint && calc.exe',
|
|
42
|
+
'mint | whoami',
|
|
43
|
+
'mint`whoami`',
|
|
44
|
+
'mint$(whoami)',
|
|
45
|
+
'mint @latest',
|
|
46
|
+
'../mint',
|
|
47
|
+
'Mint',
|
|
48
|
+
'',
|
|
49
|
+
])('rejects %s', (name) => {
|
|
50
|
+
expect(() => assertValidPackageName(name)).toThrow('Invalid package name');
|
|
51
|
+
});
|
|
52
|
+
});
|
package/__test__/update.test.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import * as previewing from '@mintlify/previewing';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getLatestCliVersion,
|
|
5
|
+
getVersions,
|
|
6
|
+
execFileAsync,
|
|
7
|
+
windowsCompatibleSpawnOptions,
|
|
8
|
+
} from '../src/helpers.js';
|
|
4
9
|
import { update } from '../src/update.js';
|
|
5
10
|
|
|
6
11
|
vi.mock('@mintlify/previewing', async () => {
|
|
@@ -19,7 +24,7 @@ vi.mock('../src/helpers.js', async () => {
|
|
|
19
24
|
await vi.importActual<typeof import('../src/helpers.js')>('../src/helpers.js');
|
|
20
25
|
return {
|
|
21
26
|
...originalModule,
|
|
22
|
-
|
|
27
|
+
execFileAsync: vi.fn(),
|
|
23
28
|
getLatestCliVersion: vi.fn(),
|
|
24
29
|
getVersions: vi.fn().mockReturnValue({
|
|
25
30
|
cli: '1.0.0',
|
|
@@ -46,12 +51,16 @@ describe('update', () => {
|
|
|
46
51
|
client: '1.0.0',
|
|
47
52
|
});
|
|
48
53
|
vi.mocked(getLatestCliVersion).mockReturnValue('2.0.0');
|
|
49
|
-
vi.mocked(
|
|
54
|
+
vi.mocked(execFileAsync).mockResolvedValue({ stdout: '', stderr: '' });
|
|
50
55
|
vi.mocked(previewing.downloadTargetMint).mockResolvedValue();
|
|
51
56
|
|
|
52
57
|
await update({ packageName: 'mintlify' });
|
|
53
58
|
|
|
54
|
-
expect(
|
|
59
|
+
expect(execFileAsync).toHaveBeenCalledWith(
|
|
60
|
+
'npm',
|
|
61
|
+
['install', '-g', 'mintlify@latest', '--silent'],
|
|
62
|
+
windowsCompatibleSpawnOptions
|
|
63
|
+
);
|
|
55
64
|
expect(previewing.downloadTargetMint).toHaveBeenCalledWith({
|
|
56
65
|
targetVersion: '2.0.0',
|
|
57
66
|
existingVersion: '1.0.0',
|
|
@@ -74,7 +83,7 @@ describe('update', () => {
|
|
|
74
83
|
|
|
75
84
|
await update({ packageName: 'mintlify' });
|
|
76
85
|
|
|
77
|
-
expect(
|
|
86
|
+
expect(execFileAsync).not.toHaveBeenCalled();
|
|
78
87
|
expect(previewing.downloadTargetMint).not.toHaveBeenCalled();
|
|
79
88
|
expect(addLogSpy).toHaveBeenCalledWith(
|
|
80
89
|
expect.objectContaining({ props: { message: 'already up to date' } })
|
|
@@ -87,7 +96,7 @@ describe('update', () => {
|
|
|
87
96
|
client: '1.0.0',
|
|
88
97
|
});
|
|
89
98
|
vi.mocked(getLatestCliVersion).mockReturnValue('2.0.0');
|
|
90
|
-
vi.mocked(
|
|
99
|
+
vi.mocked(execFileAsync).mockRejectedValue(new Error('Update failed'));
|
|
91
100
|
|
|
92
101
|
await update({ packageName: 'mintlify' });
|
|
93
102
|
|
package/bin/helpers.js
CHANGED
|
@@ -16,7 +16,7 @@ import detect from 'detect-port';
|
|
|
16
16
|
import fse from 'fs-extra';
|
|
17
17
|
import inquirer from 'inquirer';
|
|
18
18
|
import yaml from 'js-yaml';
|
|
19
|
-
import {
|
|
19
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
20
20
|
import { readFileSync } from 'node:fs';
|
|
21
21
|
import fs from 'node:fs/promises';
|
|
22
22
|
import { createRequire } from 'node:module';
|
|
@@ -149,11 +149,24 @@ export const getVersions = (packageName) => {
|
|
|
149
149
|
const client = getClientVersion().trim();
|
|
150
150
|
return { cli, client };
|
|
151
151
|
};
|
|
152
|
+
// On Windows, npm/pnpm entrypoints are .cmd shims that Node refuses to spawn
|
|
153
|
+
// without a shell (EINVAL since the CVE-2024-27980 fix). Everywhere else we
|
|
154
|
+
// keep the shell disabled so arguments can't be interpreted as shell syntax.
|
|
155
|
+
export const windowsCompatibleSpawnOptions = {
|
|
156
|
+
shell: process.platform === 'win32',
|
|
157
|
+
};
|
|
158
|
+
// npm package name grammar (optionally scoped). Rejecting anything else before
|
|
159
|
+
// it reaches a package-manager spawn keeps the win32 shell path (above) safe
|
|
160
|
+
// from shell metacharacters.
|
|
161
|
+
const VALID_PACKAGE_NAME_PATTERN = /^(@[a-z0-9~-][a-z0-9._~-]*\/)?[a-z0-9~-][a-z0-9._~-]*$/;
|
|
162
|
+
export const assertValidPackageName = (packageName) => {
|
|
163
|
+
if (!VALID_PACKAGE_NAME_PATTERN.test(packageName)) {
|
|
164
|
+
throw new Error(`Invalid package name: ${packageName}`);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
152
167
|
export const getLatestCliVersion = (packageName) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
156
|
-
}).trim();
|
|
168
|
+
assertValidPackageName(packageName);
|
|
169
|
+
return execFileSync('npm', ['view', packageName, 'version', '--silent'], Object.assign({ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }, windowsCompatibleSpawnOptions)).trim();
|
|
157
170
|
};
|
|
158
171
|
export const suppressConsoleWarnings = () => {
|
|
159
172
|
// Ignore tailwind warnings and punycode deprecation warning
|
|
@@ -191,13 +204,14 @@ export const terminate = (code) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
191
204
|
yield shutdownPostHog();
|
|
192
205
|
process.exit(code);
|
|
193
206
|
});
|
|
194
|
-
export const
|
|
207
|
+
export const execFileAsync = promisify(execFile);
|
|
195
208
|
export function isAI() {
|
|
196
209
|
return (!process.stdin.isTTY || process.env.CLAUDECODE === '1' || process.env.TERM_PROGRAM === 'claude');
|
|
197
210
|
}
|
|
198
211
|
export const detectPackageManager = (_a) => __awaiter(void 0, [_a], void 0, function* ({ packageName }) {
|
|
212
|
+
assertValidPackageName(packageName);
|
|
199
213
|
try {
|
|
200
|
-
const { stdout: packagePath } = yield
|
|
214
|
+
const { stdout: packagePath } = yield execFileAsync('which', [packageName]);
|
|
201
215
|
if (packagePath.includes('pnpm')) {
|
|
202
216
|
return 'pnpm';
|
|
203
217
|
}
|