@mintlify/cli 4.0.677 → 4.0.678
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__/migrateMdx.test.ts +221 -0
- package/bin/cli.js +5 -0
- package/bin/migrateMdx.js +375 -0
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/src/cli.tsx +10 -0
- package/src/migrateMdx.tsx +463 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { getConfigObj, getConfigPath } from '@mintlify/prebuild';
|
|
2
|
+
import * as previewing from '@mintlify/previewing';
|
|
3
|
+
import { validateDocsConfig } from '@mintlify/validation';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { outputFile } from 'fs-extra';
|
|
6
|
+
|
|
7
|
+
import { migrateMdx } from '../src/migrateMdx.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('../src/constants.js', () => ({
|
|
10
|
+
HOME_DIR: '/home/test-user',
|
|
11
|
+
CMD_EXEC_PATH: '/project',
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('@mintlify/prebuild', async () => {
|
|
15
|
+
const original = await vi.importActual<typeof import('@mintlify/prebuild')>('@mintlify/prebuild');
|
|
16
|
+
return {
|
|
17
|
+
...original,
|
|
18
|
+
getConfigPath: vi.fn(),
|
|
19
|
+
getConfigObj: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
vi.mock('@mintlify/validation', async () => {
|
|
24
|
+
const original =
|
|
25
|
+
await vi.importActual<typeof import('@mintlify/validation')>('@mintlify/validation');
|
|
26
|
+
return {
|
|
27
|
+
...original,
|
|
28
|
+
validateDocsConfig: vi.fn(),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
vi.mock('fs-extra', () => ({
|
|
33
|
+
outputFile: vi.fn(),
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const addLogSpy = vi.spyOn(previewing, 'addLog');
|
|
37
|
+
|
|
38
|
+
describe('migrateMdx', () => {
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
vi.clearAllMocks();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const mkDirent = (name: string, isDir: boolean) =>
|
|
44
|
+
({ name, isDirectory: () => isDir, isFile: () => !isDir }) as unknown as fs.Dirent;
|
|
45
|
+
|
|
46
|
+
const readdirSpy = vi.spyOn(fs.promises, 'readdir');
|
|
47
|
+
readdirSpy.mockImplementation(async (p: Parameters<typeof fs.promises.readdir>[0]) => {
|
|
48
|
+
const value = typeof p === 'string' ? p : (p as unknown as { toString(): string }).toString();
|
|
49
|
+
if (value === '/project') {
|
|
50
|
+
return [
|
|
51
|
+
mkDirent('api', true),
|
|
52
|
+
mkDirent('webhooks', true),
|
|
53
|
+
mkDirent('openapi.json', false),
|
|
54
|
+
mkDirent('openapi1.json', false),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
if (value === '/project/api') {
|
|
58
|
+
return [mkDirent('pets.mdx', false)];
|
|
59
|
+
}
|
|
60
|
+
if (value === '/project/webhooks') {
|
|
61
|
+
return [mkDirent('newPet.mdx', false)];
|
|
62
|
+
}
|
|
63
|
+
return [] as unknown as fs.Dirent[];
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('logs and exits when docs.json is not found', async () => {
|
|
67
|
+
vi.mocked(getConfigPath).mockResolvedValueOnce(undefined as unknown as string);
|
|
68
|
+
|
|
69
|
+
await migrateMdx();
|
|
70
|
+
|
|
71
|
+
expect(addLogSpy).toHaveBeenCalledWith(
|
|
72
|
+
expect.objectContaining({ props: { message: 'docs.json not found in current directory' } })
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('migrates a path operation MDX page to x-mint and updates docs.json and spec', async () => {
|
|
77
|
+
vi.mocked(getConfigPath).mockResolvedValueOnce('/project/docs.json');
|
|
78
|
+
vi.mocked(getConfigObj).mockResolvedValueOnce({
|
|
79
|
+
navigation: {
|
|
80
|
+
pages: ['api/pets'],
|
|
81
|
+
},
|
|
82
|
+
} as unknown as object);
|
|
83
|
+
vi.mocked(validateDocsConfig).mockResolvedValueOnce({
|
|
84
|
+
success: true,
|
|
85
|
+
data: {
|
|
86
|
+
navigation: {
|
|
87
|
+
pages: ['api/pets'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
} as unknown as ReturnType<typeof validateDocsConfig>);
|
|
91
|
+
|
|
92
|
+
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
93
|
+
existsSyncSpy.mockImplementation((p: fs.PathLike) => {
|
|
94
|
+
const value = String(p);
|
|
95
|
+
return value === '/project/api/pets.mdx' || value === 'openapi.json';
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const readFileSpy = vi.spyOn(fs.promises, 'readFile');
|
|
99
|
+
readFileSpy.mockImplementation(async (p: Parameters<typeof fs.promises.readFile>[0]) => {
|
|
100
|
+
const value = typeof p === 'string' ? p : (p as unknown as { toString(): string }).toString();
|
|
101
|
+
if (value === '/project/api/pets.mdx') {
|
|
102
|
+
return `---\nopenapi: openapi.json GET /pets\n---\n\n# Title\nContent`;
|
|
103
|
+
}
|
|
104
|
+
if (value === '/project/openapi.json') {
|
|
105
|
+
return JSON.stringify({
|
|
106
|
+
openapi: '3.0.0',
|
|
107
|
+
info: { title: 'Test', version: '1.0.0' },
|
|
108
|
+
paths: {
|
|
109
|
+
'/pets': {
|
|
110
|
+
get: {
|
|
111
|
+
summary: 'List pets',
|
|
112
|
+
responses: { '200': { description: 'ok' } },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
throw new Error('Unexpected readFile path: ' + value);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const unlinkSpy = vi.spyOn(fs.promises, 'unlink').mockResolvedValueOnce();
|
|
122
|
+
|
|
123
|
+
await migrateMdx();
|
|
124
|
+
|
|
125
|
+
expect(addLogSpy).toHaveBeenCalledWith(
|
|
126
|
+
expect.objectContaining({ props: { message: 'docs.json updated' } })
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
expect(outputFile).toHaveBeenCalledWith(
|
|
130
|
+
'/project/docs.json',
|
|
131
|
+
expect.stringContaining('openapi.json get /pets')
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const specWriteCall = vi
|
|
135
|
+
.mocked(outputFile)
|
|
136
|
+
.mock.calls.find(
|
|
137
|
+
(c) => typeof c[0] === 'string' && (c[0] as string).includes('openapi.json')
|
|
138
|
+
);
|
|
139
|
+
expect(specWriteCall).toBeTruthy();
|
|
140
|
+
const writtenSpec = JSON.parse(specWriteCall?.[1] as string);
|
|
141
|
+
expect(writtenSpec.paths['/pets'].get['x-mint']).toEqual(
|
|
142
|
+
expect.objectContaining({ href: '/api/pets' })
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(unlinkSpy).toHaveBeenCalledWith('/project/api/pets.mdx');
|
|
146
|
+
|
|
147
|
+
expect(addLogSpy).toHaveBeenCalledWith(
|
|
148
|
+
expect.objectContaining({ props: { message: 'migration complete' } })
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('migrates a webhook MDX page to x-mint on the webhook operation', async () => {
|
|
153
|
+
vi.mocked(getConfigPath).mockResolvedValueOnce('/project/docs.json');
|
|
154
|
+
vi.mocked(getConfigObj).mockResolvedValueOnce({
|
|
155
|
+
navigation: {
|
|
156
|
+
pages: ['webhooks/newPet'],
|
|
157
|
+
},
|
|
158
|
+
} as unknown as object);
|
|
159
|
+
vi.mocked(validateDocsConfig).mockResolvedValueOnce({
|
|
160
|
+
success: true,
|
|
161
|
+
data: {
|
|
162
|
+
navigation: {
|
|
163
|
+
pages: ['webhooks/newPet'],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
} as unknown as ReturnType<typeof validateDocsConfig>);
|
|
167
|
+
|
|
168
|
+
const existsSyncSpy = vi.spyOn(fs, 'existsSync');
|
|
169
|
+
existsSyncSpy.mockImplementation((p: fs.PathLike) => {
|
|
170
|
+
const value = String(p);
|
|
171
|
+
return value === '/project/webhooks/newPet.mdx' || value === 'openapi1.json';
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const readFileSpy = vi.spyOn(fs.promises, 'readFile');
|
|
175
|
+
readFileSpy.mockImplementation(async (p: Parameters<typeof fs.promises.readFile>[0]) => {
|
|
176
|
+
const value = typeof p === 'string' ? p : (p as unknown as { toString(): string }).toString();
|
|
177
|
+
if (value === '/project/webhooks/newPet.mdx') {
|
|
178
|
+
return `---\nopenapi: openapi1.json webhook newPet\n---\n\n# Webhook\nBody`;
|
|
179
|
+
}
|
|
180
|
+
if (value === '/project/openapi1.json') {
|
|
181
|
+
return JSON.stringify({
|
|
182
|
+
openapi: '3.1.0',
|
|
183
|
+
info: { title: 'Test', version: '1.0.0' },
|
|
184
|
+
webhooks: {
|
|
185
|
+
newPet: {
|
|
186
|
+
post: {
|
|
187
|
+
summary: 'Webhook - new pet',
|
|
188
|
+
requestBody: {},
|
|
189
|
+
responses: { '200': { description: 'ok' } },
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
throw new Error('Unexpected readFile path: ' + value);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
vi.spyOn(fs.promises, 'unlink').mockResolvedValueOnce();
|
|
199
|
+
|
|
200
|
+
await migrateMdx();
|
|
201
|
+
|
|
202
|
+
expect(addLogSpy).toHaveBeenCalledWith(
|
|
203
|
+
expect.objectContaining({ props: { message: 'docs.json updated' } })
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const specWriteCall = vi
|
|
207
|
+
.mocked(outputFile)
|
|
208
|
+
.mock.calls.find(
|
|
209
|
+
(c) => typeof c[0] === 'string' && (c[0] as string).includes('openapi1.json')
|
|
210
|
+
);
|
|
211
|
+
expect(specWriteCall).toBeTruthy();
|
|
212
|
+
const writtenSpec = JSON.parse(specWriteCall?.[1] as string);
|
|
213
|
+
expect(writtenSpec.webhooks.newPet.post['x-mint']).toEqual(
|
|
214
|
+
expect.objectContaining({ href: '/webhooks/newPet' })
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(addLogSpy).toHaveBeenCalledWith(
|
|
218
|
+
expect.objectContaining({ props: { message: 'migration complete' } })
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
});
|
package/bin/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import path from 'path';
|
|
|
16
16
|
import yargs from 'yargs';
|
|
17
17
|
import { hideBin } from 'yargs/helpers';
|
|
18
18
|
import { checkPort, checkForMintJson, checkNodeVersion, upgradeConfig, checkForDocsJson, getVersions, suppressConsoleWarnings, terminate, readLocalOpenApiFile, } from './helpers.js';
|
|
19
|
+
import { migrateMdx } from './migrateMdx.js';
|
|
19
20
|
import { update } from './update.js';
|
|
20
21
|
export const cli = ({ packageName = 'mint' }) => {
|
|
21
22
|
render(_jsx(Logs, {}));
|
|
@@ -166,6 +167,10 @@ export const cli = ({ packageName = 'mint' }) => {
|
|
|
166
167
|
yield checkForDocsJson();
|
|
167
168
|
}
|
|
168
169
|
yield upgradeConfig();
|
|
170
|
+
}))
|
|
171
|
+
.command('migrate-mdx', 'migrate MDX OpenAPI endpoint pages to x-mint extensions and docs.json', () => undefined, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
172
|
+
yield migrateMdx();
|
|
173
|
+
yield terminate(0);
|
|
169
174
|
}))
|
|
170
175
|
.command(['version', 'v'], 'display the current version of the CLI and client', () => undefined, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
171
176
|
const { cli, client } = getVersions();
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
11
|
+
import { potentiallyParseOpenApiString } from '@mintlify/common';
|
|
12
|
+
import { getConfigObj, getConfigPath } from '@mintlify/prebuild';
|
|
13
|
+
import { addLog, ErrorLog, SuccessLog } from '@mintlify/previewing';
|
|
14
|
+
import { divisions, validateDocsConfig, } from '@mintlify/validation';
|
|
15
|
+
import fs from 'fs';
|
|
16
|
+
import { outputFile } from 'fs-extra';
|
|
17
|
+
import matter from 'gray-matter';
|
|
18
|
+
import inquirer from 'inquirer';
|
|
19
|
+
import yaml from 'js-yaml';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import { CMD_EXEC_PATH } from './constants.js';
|
|
22
|
+
const specCache = {};
|
|
23
|
+
const candidateSpecCache = {};
|
|
24
|
+
const specLocks = new Map();
|
|
25
|
+
function withSpecLock(specPath, task) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
var _a;
|
|
28
|
+
const key = path.resolve(specPath);
|
|
29
|
+
const previous = (_a = specLocks.get(key)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
30
|
+
let releaseNext;
|
|
31
|
+
const next = new Promise((resolve) => {
|
|
32
|
+
releaseNext = resolve;
|
|
33
|
+
});
|
|
34
|
+
specLocks.set(key, next);
|
|
35
|
+
yield previous;
|
|
36
|
+
try {
|
|
37
|
+
yield task();
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
releaseNext();
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
let inquirerLockQueue = Promise.resolve();
|
|
45
|
+
function withInquirerLock(task) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const previous = inquirerLockQueue;
|
|
48
|
+
let releaseNext;
|
|
49
|
+
const next = new Promise((resolve) => {
|
|
50
|
+
releaseNext = resolve;
|
|
51
|
+
});
|
|
52
|
+
inquirerLockQueue = next;
|
|
53
|
+
yield previous;
|
|
54
|
+
try {
|
|
55
|
+
return yield task();
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
releaseNext();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export function migrateMdx() {
|
|
63
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
64
|
+
const docsConfigPath = yield getConfigPath(CMD_EXEC_PATH, 'docs');
|
|
65
|
+
if (!docsConfigPath) {
|
|
66
|
+
addLog(_jsx(ErrorLog, { message: "docs.json not found in current directory" }));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const docsConfigObj = yield getConfigObj(CMD_EXEC_PATH, 'docs');
|
|
70
|
+
const validationResults = yield validateDocsConfig(docsConfigObj);
|
|
71
|
+
if (!validationResults.success) {
|
|
72
|
+
addLog(_jsx(ErrorLog, { message: "docs.json is invalid" }));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const validatedDocsConfig = validationResults.data;
|
|
76
|
+
const docsConfig = docsConfigObj;
|
|
77
|
+
yield buildCandidateSpecCacheIfNeeded(CMD_EXEC_PATH);
|
|
78
|
+
const updatedNavigation = yield processNav(validatedDocsConfig.navigation);
|
|
79
|
+
docsConfig.navigation = updatedNavigation;
|
|
80
|
+
yield outputFile(docsConfigPath, JSON.stringify(docsConfig, null, 2));
|
|
81
|
+
addLog(_jsx(SuccessLog, { message: "docs.json updated" }));
|
|
82
|
+
for (const specPath in specCache) {
|
|
83
|
+
const specObj = specCache[specPath];
|
|
84
|
+
const ext = path.extname(specPath).toLowerCase();
|
|
85
|
+
const stringified = ext === '.json' ? JSON.stringify(specObj, null, 2) : yaml.dump(specObj);
|
|
86
|
+
yield outputFile(specPath, stringified);
|
|
87
|
+
addLog(_jsx(SuccessLog, { message: `updated ${path.relative(CMD_EXEC_PATH, specPath)}` }));
|
|
88
|
+
}
|
|
89
|
+
addLog(_jsx(SuccessLog, { message: "migration complete" }));
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function processNav(nav) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
let newNav = Object.assign({}, nav);
|
|
95
|
+
if ('pages' in newNav) {
|
|
96
|
+
newNav.pages = yield Promise.all(newNav.pages.map((page) => __awaiter(this, void 0, void 0, function* () {
|
|
97
|
+
if (typeof page === 'object' && page !== null && 'group' in page) {
|
|
98
|
+
return processNav(page);
|
|
99
|
+
}
|
|
100
|
+
if (typeof page === 'string' && !/\s/.test(page)) {
|
|
101
|
+
const mdxCandidatePath = path.join(CMD_EXEC_PATH, `${page}.mdx`);
|
|
102
|
+
if (!fs.existsSync(mdxCandidatePath)) {
|
|
103
|
+
return page;
|
|
104
|
+
}
|
|
105
|
+
const { data, content } = matter(yield fs.promises.readFile(mdxCandidatePath, 'utf-8'));
|
|
106
|
+
const frontmatter = data;
|
|
107
|
+
if (!frontmatter.openapi) {
|
|
108
|
+
return page;
|
|
109
|
+
}
|
|
110
|
+
const parsed = potentiallyParseOpenApiString(frontmatter.openapi);
|
|
111
|
+
if (!parsed) {
|
|
112
|
+
addLog(_jsx(ErrorLog, { message: `invalid openapi frontmatter in ${mdxCandidatePath}: ${frontmatter.openapi}` }));
|
|
113
|
+
return page;
|
|
114
|
+
}
|
|
115
|
+
const { filename, method, endpoint: endpointPath } = parsed;
|
|
116
|
+
let specPath = filename;
|
|
117
|
+
if (specPath && URL.canParse(specPath)) {
|
|
118
|
+
return page;
|
|
119
|
+
}
|
|
120
|
+
if (!specPath) {
|
|
121
|
+
const methodLower = method.toLowerCase();
|
|
122
|
+
const matchingSpecs = yield findMatchingOpenApiSpecs({
|
|
123
|
+
method: methodLower,
|
|
124
|
+
endpointPath,
|
|
125
|
+
}, candidateSpecCache);
|
|
126
|
+
if (matchingSpecs.length === 0) {
|
|
127
|
+
addLog(_jsx(ErrorLog, { message: `no OpenAPI spec found for ${method.toUpperCase()} ${endpointPath} in repository` }));
|
|
128
|
+
return page;
|
|
129
|
+
}
|
|
130
|
+
if (matchingSpecs.length === 1) {
|
|
131
|
+
specPath = path.relative(CMD_EXEC_PATH, matchingSpecs[0]);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
const answer = yield withInquirerLock(() => inquirer.prompt([
|
|
135
|
+
{
|
|
136
|
+
type: 'list',
|
|
137
|
+
name: 'chosen',
|
|
138
|
+
message: `multiple OpenAPI specs found for ${method.toUpperCase()} ${endpointPath}. which one should be used for ${path.relative(CMD_EXEC_PATH, mdxCandidatePath)}?`,
|
|
139
|
+
choices: matchingSpecs.map((p) => ({
|
|
140
|
+
name: path.relative(CMD_EXEC_PATH, p),
|
|
141
|
+
value: path.relative(CMD_EXEC_PATH, p),
|
|
142
|
+
})),
|
|
143
|
+
},
|
|
144
|
+
]));
|
|
145
|
+
specPath = answer.chosen;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const href = `/${page}`;
|
|
149
|
+
const pageName = specPath ? `${specPath} ${method} ${endpointPath}` : frontmatter.openapi;
|
|
150
|
+
delete frontmatter.openapi;
|
|
151
|
+
yield withSpecLock(path.resolve(specPath), () => migrateToXMint({
|
|
152
|
+
specPath,
|
|
153
|
+
method,
|
|
154
|
+
endpointPath,
|
|
155
|
+
frontmatter,
|
|
156
|
+
content,
|
|
157
|
+
href,
|
|
158
|
+
}));
|
|
159
|
+
try {
|
|
160
|
+
yield fs.promises.unlink(mdxCandidatePath);
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
addLog(_jsx(ErrorLog, { message: `failed to delete ${mdxCandidatePath}: ${err.message}` }));
|
|
164
|
+
}
|
|
165
|
+
return pageName;
|
|
166
|
+
}
|
|
167
|
+
return page;
|
|
168
|
+
})));
|
|
169
|
+
}
|
|
170
|
+
for (const division of ['groups', ...divisions]) {
|
|
171
|
+
if (division in newNav) {
|
|
172
|
+
const items = newNav[division];
|
|
173
|
+
newNav = Object.assign(Object.assign({}, newNav), { [division]: yield Promise.all(items.map((item) => processNav(item))) });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return newNav;
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
function migrateToXMint(args) {
|
|
180
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
181
|
+
const { specPath, method, endpointPath, frontmatter, content, href } = args;
|
|
182
|
+
if (!fs.existsSync(specPath)) {
|
|
183
|
+
addLog(_jsx(ErrorLog, { message: `spec file not found: ${specPath}` }));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let specObj;
|
|
187
|
+
if (path.resolve(specPath) in specCache) {
|
|
188
|
+
specObj = specCache[path.resolve(specPath)];
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const pathname = path.join(CMD_EXEC_PATH, specPath);
|
|
192
|
+
const file = yield fs.promises.readFile(pathname, 'utf-8');
|
|
193
|
+
const ext = path.extname(specPath).toLowerCase();
|
|
194
|
+
if (ext === '.json') {
|
|
195
|
+
specObj = JSON.parse(file);
|
|
196
|
+
}
|
|
197
|
+
else if (ext === '.yml' || ext === '.yaml') {
|
|
198
|
+
specObj = yaml.load(file);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
addLog(_jsx(ErrorLog, { message: `unsupported spec file extension: ${specPath}` }));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const methodLower = method.toLowerCase();
|
|
206
|
+
if (!editXMint(specObj, endpointPath, methodLower, {
|
|
207
|
+
metadata: Object.keys(frontmatter).length > 0 ? frontmatter : undefined,
|
|
208
|
+
content: content.length > 0 ? content : undefined,
|
|
209
|
+
href,
|
|
210
|
+
})) {
|
|
211
|
+
addLog(_jsx(ErrorLog, { message: `operation not found in spec: ${method.toUpperCase()} ${endpointPath} in ${specPath}` }));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
specCache[path.resolve(specPath)] = specObj;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
function editXMint(document, path, method, newXMint) {
|
|
218
|
+
if (method === 'webhook') {
|
|
219
|
+
return editWebhookXMint(document, path, newXMint);
|
|
220
|
+
}
|
|
221
|
+
if (!document.paths || !document.paths[path]) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const pathItem = document.paths[path];
|
|
225
|
+
const normalizedMethod = method.toLowerCase();
|
|
226
|
+
if (!pathItem[normalizedMethod]) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
const operation = pathItem[normalizedMethod];
|
|
230
|
+
operation['x-mint'] = newXMint;
|
|
231
|
+
if ('x-mcp' in operation && !('mcp' in operation['x-mint'])) {
|
|
232
|
+
operation['x-mint']['mcp'] = operation['x-mcp'];
|
|
233
|
+
delete operation['x-mcp'];
|
|
234
|
+
}
|
|
235
|
+
return true;
|
|
236
|
+
}
|
|
237
|
+
function editWebhookXMint(document, path, newXMint) {
|
|
238
|
+
var _a;
|
|
239
|
+
const webhookObject = (_a = document.webhooks) === null || _a === void 0 ? void 0 : _a[path];
|
|
240
|
+
if (!webhookObject || typeof webhookObject !== 'object') {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
if (!webhookObject['post']) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
const operation = webhookObject['post'];
|
|
247
|
+
operation['x-mint'] = newXMint;
|
|
248
|
+
if ('x-mcp' in operation && !('mcp' in operation['x-mint'])) {
|
|
249
|
+
operation['x-mint']['mcp'] = operation['x-mcp'];
|
|
250
|
+
delete operation['x-mcp'];
|
|
251
|
+
}
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
function findMatchingOpenApiSpecs(args, docsByPath) {
|
|
255
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
256
|
+
const { method, endpointPath } = args;
|
|
257
|
+
const docsEntries = docsByPath
|
|
258
|
+
? Object.entries(docsByPath)
|
|
259
|
+
: (yield collectOpenApiFiles(CMD_EXEC_PATH)).map((absPath) => [absPath, undefined]);
|
|
260
|
+
const normalizedMethod = method.toLowerCase();
|
|
261
|
+
const endpointVariants = new Set([endpointPath]);
|
|
262
|
+
if (!endpointPath.startsWith('/')) {
|
|
263
|
+
endpointVariants.add(`/${endpointPath}`);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
endpointVariants.add(endpointPath.replace(/^\/+/, ''));
|
|
267
|
+
}
|
|
268
|
+
const matches = [];
|
|
269
|
+
for (const [absPath, maybeDoc] of docsEntries) {
|
|
270
|
+
try {
|
|
271
|
+
const doc = maybeDoc || (yield loadOpenApiDocument(absPath));
|
|
272
|
+
if (!doc)
|
|
273
|
+
continue;
|
|
274
|
+
if (normalizedMethod === 'webhook') {
|
|
275
|
+
const webhooks = doc.webhooks;
|
|
276
|
+
if (!webhooks)
|
|
277
|
+
continue;
|
|
278
|
+
for (const key of Object.keys(webhooks)) {
|
|
279
|
+
if (endpointVariants.has(key)) {
|
|
280
|
+
const pathItem = webhooks[key];
|
|
281
|
+
if (pathItem && typeof pathItem === 'object' && 'post' in pathItem && pathItem.post) {
|
|
282
|
+
matches.push(absPath);
|
|
283
|
+
break;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
if (!doc.paths)
|
|
290
|
+
continue;
|
|
291
|
+
for (const variant of endpointVariants) {
|
|
292
|
+
const pathItem = doc.paths[variant];
|
|
293
|
+
if (!pathItem)
|
|
294
|
+
continue;
|
|
295
|
+
const hasOperation = !!pathItem[normalizedMethod];
|
|
296
|
+
if (hasOperation) {
|
|
297
|
+
matches.push(absPath);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch (_a) { }
|
|
303
|
+
}
|
|
304
|
+
return matches.map((abs) => path.resolve(abs)).filter((v, i, a) => a.indexOf(v) === i);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
function collectOpenApiFiles(rootDir) {
|
|
308
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
309
|
+
const results = [];
|
|
310
|
+
const excludedDirs = new Set([
|
|
311
|
+
'node_modules',
|
|
312
|
+
'.git',
|
|
313
|
+
'dist',
|
|
314
|
+
'build',
|
|
315
|
+
'.next',
|
|
316
|
+
'.vercel',
|
|
317
|
+
'out',
|
|
318
|
+
'coverage',
|
|
319
|
+
'tmp',
|
|
320
|
+
'temp',
|
|
321
|
+
]);
|
|
322
|
+
function walk(currentDir) {
|
|
323
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
324
|
+
const entries = yield fs.promises.readdir(currentDir, { withFileTypes: true });
|
|
325
|
+
for (const entry of entries) {
|
|
326
|
+
const abs = path.join(currentDir, entry.name);
|
|
327
|
+
if (entry.isDirectory()) {
|
|
328
|
+
if (excludedDirs.has(entry.name))
|
|
329
|
+
continue;
|
|
330
|
+
yield walk(abs);
|
|
331
|
+
}
|
|
332
|
+
else if (entry.isFile()) {
|
|
333
|
+
if (/\.(ya?ml|json)$/i.test(entry.name)) {
|
|
334
|
+
results.push(abs);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
yield walk(rootDir);
|
|
341
|
+
return results;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
function loadOpenApiDocument(absPath) {
|
|
345
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
346
|
+
try {
|
|
347
|
+
const file = yield fs.promises.readFile(absPath, 'utf-8');
|
|
348
|
+
const ext = path.extname(absPath).toLowerCase();
|
|
349
|
+
let doc;
|
|
350
|
+
if (ext === '.json') {
|
|
351
|
+
doc = JSON.parse(file);
|
|
352
|
+
}
|
|
353
|
+
else if (ext === '.yml' || ext === '.yaml') {
|
|
354
|
+
doc = yaml.load(file);
|
|
355
|
+
}
|
|
356
|
+
return doc;
|
|
357
|
+
}
|
|
358
|
+
catch (_a) {
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function buildCandidateSpecCacheIfNeeded(rootDir) {
|
|
364
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
365
|
+
if (Object.keys(candidateSpecCache).length > 0)
|
|
366
|
+
return;
|
|
367
|
+
const files = yield collectOpenApiFiles(rootDir);
|
|
368
|
+
yield Promise.all(files.map((abs) => __awaiter(this, void 0, void 0, function* () {
|
|
369
|
+
const doc = yield loadOpenApiDocument(abs);
|
|
370
|
+
if (doc) {
|
|
371
|
+
candidateSpecCache[path.resolve(abs)] = doc;
|
|
372
|
+
}
|
|
373
|
+
})));
|
|
374
|
+
});
|
|
375
|
+
}
|