Skip to content

Commit aac5114

Browse files
authored
Merge pull request #153 from flashcatcloud/codex/upload-openapi-json-oss
feat: upload slim openapi index manifest
2 parents a6b06db + 4a39aec commit aac5114

2 files changed

Lines changed: 152 additions & 14 deletions

File tree

integration-docs/scripts/upload-openapi.mjs

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
77
const packageRoot = path.resolve(__dirname, '..');
88
const repoRoot = path.resolve(packageRoot, '..');
99
const defaultApiReferenceDir = path.join(repoRoot, 'api-reference');
10+
const docsBaseUrl = 'https://docs.flashcat.cloud';
11+
const httpMethods = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'];
1012

1113
export const requiredEnv = [
1214
'CDN_ACCESS_KEY',
@@ -46,6 +48,36 @@ export function validateJsonFile(filePath) {
4648
JSON.parse(fs.readFileSync(filePath, 'utf8'));
4749
}
4850

51+
export function buildOpenapiReferenceIndex(openapi) {
52+
const result = {};
53+
54+
for (const [apiPath, pathItem] of Object.entries(openapi.paths ?? {})) {
55+
const operation = httpMethods
56+
.map((method) => pathItem?.[method])
57+
.find(Boolean);
58+
const href = operation?.['x-mint']?.href;
59+
const label = operation?.summary || operation?.['x-mint']?.metadata?.sidebarTitle || operation?.operationId;
60+
61+
if (href && label) {
62+
result[apiPath] = {
63+
label,
64+
url: new URL(href, docsBaseUrl).toString()
65+
};
66+
}
67+
}
68+
69+
return result;
70+
}
71+
72+
function buildJsonUploadOptions() {
73+
return {
74+
headers: {
75+
'Content-Type': 'application/json; charset=utf-8',
76+
'Cache-Control': 'public, max-age=300'
77+
}
78+
};
79+
}
80+
4981
async function createOssClient(env = process.env) {
5082
const { default: OSS } = await import('ali-oss');
5183
return new OSS({
@@ -77,6 +109,20 @@ async function refreshCdnCache(cdnRuntime, url) {
77109
console.log(`Refreshed CDN cache: ${url}`);
78110
}
79111

112+
async function uploadJsonAsset({
113+
env,
114+
ossClient,
115+
cdnRuntime,
116+
ossFilePath,
117+
payload,
118+
label
119+
}) {
120+
const result = await ossClient.put(ossFilePath, payload, buildJsonUploadOptions());
121+
const cdnUrl = buildCdnUrl(result.url, env.CDN_ENDPOINT, env.CDN_URL);
122+
console.log(`Uploaded ${label} -> ${cdnUrl}`);
123+
await refreshCdnCache(cdnRuntime, cdnUrl);
124+
}
125+
80126
export async function uploadOpenapiJsonFiles({
81127
apiReferenceDir = defaultApiReferenceDir,
82128
env = process.env,
@@ -103,18 +149,32 @@ export async function uploadOpenapiJsonFiles({
103149
for (const file of files) {
104150
const localFilePath = path.join(apiReferenceDir, file);
105151
const ossFilePath = buildOssFilePath(env.CDN_DIR, file);
106-
const result = await resolvedOssClient.put(ossFilePath, localFilePath, {
107-
headers: {
108-
'Content-Type': 'application/json; charset=utf-8',
109-
'Cache-Control': 'public, max-age=300'
110-
}
152+
const uploadPayload = Buffer.from(`${JSON.stringify(
153+
buildOpenapiReferenceIndex(JSON.parse(fs.readFileSync(localFilePath, 'utf8'))),
154+
null,
155+
2
156+
)}\n`);
157+
await uploadJsonAsset({
158+
env,
159+
ossClient: resolvedOssClient,
160+
cdnRuntime: resolvedCdnRuntime,
161+
ossFilePath,
162+
payload: uploadPayload,
163+
label: file
111164
});
112-
const cdnUrl = buildCdnUrl(result.url, env.CDN_ENDPOINT, env.CDN_URL);
113-
console.log(`Uploaded ${file} -> ${cdnUrl}`);
114-
await refreshCdnCache(resolvedCdnRuntime, cdnUrl);
115165
}
116166

117-
console.log(`Uploaded ${files.length} OpenAPI JSON files from ${apiReferenceDir}`);
167+
const manifestPayload = Buffer.from(`${JSON.stringify({ files }, null, 2)}\n`);
168+
await uploadJsonAsset({
169+
env,
170+
ossClient: resolvedOssClient,
171+
cdnRuntime: resolvedCdnRuntime,
172+
ossFilePath: buildOssFilePath(env.CDN_DIR, 'manifest.json'),
173+
payload: manifestPayload,
174+
label: 'manifest.json'
175+
});
176+
177+
console.log(`Uploaded ${files.length} OpenAPI JSON files and manifest from ${apiReferenceDir}`);
118178
}
119179

120180
async function loadDotenvIfAvailable() {

integration-docs/scripts/upload-openapi.test.mjs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import path from 'node:path';
55
import test from 'node:test';
66

77
import {
8+
buildOpenapiReferenceIndex,
89
buildOssFilePath,
910
listOpenapiJsonFiles,
1011
uploadOpenapiJsonFiles,
@@ -50,12 +51,71 @@ test('validateRequiredEnv reports every missing upload credential', () => {
5051
]);
5152
});
5253

53-
test('uploadOpenapiJsonFiles uploads every JSON file and refreshes each CDN URL', async () => {
54+
test('buildOpenapiReferenceIndex keeps only path label and docs URL', () => {
55+
assert.deepEqual(buildOpenapiReferenceIndex({
56+
paths: {
57+
'/alert/list': {
58+
post: {
59+
summary: '查询告警列表',
60+
'x-mint': {
61+
href: '/zh/api-reference/on-call/alerts/alert-read-list'
62+
}
63+
}
64+
},
65+
'/alert/ignored': {
66+
post: {
67+
summary: '缺少文档链接'
68+
}
69+
}
70+
}
71+
}), {
72+
'/alert/list': {
73+
label: '查询告警列表',
74+
url: 'https://docs.flashcat.cloud/zh/api-reference/on-call/alerts/alert-read-list'
75+
}
76+
});
77+
});
78+
79+
test('uploadOpenapiJsonFiles uploads every JSON file, a manifest, and refreshes each CDN URL', async () => {
5480
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'openapi-upload-'));
5581
const apiReferenceDir = path.join(tempDir, 'api-reference');
5682
fs.mkdirSync(apiReferenceDir);
57-
fs.writeFileSync(path.join(apiReferenceDir, 'openapi.en.json'), '{"openapi":"3.1.0"}\n');
58-
fs.writeFileSync(path.join(apiReferenceDir, 'openapi.zh.json'), '{"openapi":"3.1.0"}\n');
83+
fs.writeFileSync(path.join(apiReferenceDir, 'openapi.en.json'), JSON.stringify({
84+
openapi: '3.1.0',
85+
paths: {
86+
'/alert/list': {
87+
post: {
88+
summary: 'List alerts',
89+
'x-mint': {
90+
href: '/en/api-reference/on-call/alerts/alert-read-list'
91+
},
92+
responses: {
93+
200: {
94+
description: 'Success'
95+
}
96+
}
97+
}
98+
}
99+
}
100+
}));
101+
fs.writeFileSync(path.join(apiReferenceDir, 'openapi.zh.json'), JSON.stringify({
102+
openapi: '3.1.0',
103+
paths: {
104+
'/alert/list': {
105+
post: {
106+
summary: '查询告警列表',
107+
'x-mint': {
108+
href: '/zh/api-reference/on-call/alerts/alert-read-list'
109+
},
110+
responses: {
111+
200: {
112+
description: '成功'
113+
}
114+
}
115+
}
116+
}
117+
}
118+
}));
59119
fs.writeFileSync(path.join(apiReferenceDir, 'ignored.txt'), 'not json\n');
60120

61121
const uploaded = [];
@@ -92,15 +152,33 @@ test('uploadOpenapiJsonFiles uploads every JSON file and refreshes each CDN URL'
92152
uploaded.map((item) => item.ossFilePath),
93153
[
94154
'/docs/api-reference/openapi.en.json',
95-
'/docs/api-reference/openapi.zh.json'
155+
'/docs/api-reference/openapi.zh.json',
156+
'/docs/api-reference/manifest.json'
96157
]
97158
);
98159
assert.deepEqual(
99160
refreshed,
100161
[
101162
'https://docs-cdn.flashcat.cloud/docs/api-reference/openapi.en.json',
102-
'https://docs-cdn.flashcat.cloud/docs/api-reference/openapi.zh.json'
163+
'https://docs-cdn.flashcat.cloud/docs/api-reference/openapi.zh.json',
164+
'https://docs-cdn.flashcat.cloud/docs/api-reference/manifest.json'
103165
]
104166
);
105167
assert.equal(uploaded[0].options.headers['Content-Type'], 'application/json; charset=utf-8');
168+
const zhUpload = uploaded.find((item) => item.ossFilePath.endsWith('/openapi.zh.json'));
169+
assert.ok(zhUpload);
170+
assert.deepEqual(JSON.parse(zhUpload.localFilePath.toString('utf8')), {
171+
'/alert/list': {
172+
label: '查询告警列表',
173+
url: 'https://docs.flashcat.cloud/zh/api-reference/on-call/alerts/alert-read-list'
174+
}
175+
});
176+
const manifestUpload = uploaded.find((item) => item.ossFilePath.endsWith('/manifest.json'));
177+
assert.ok(manifestUpload);
178+
assert.deepEqual(JSON.parse(manifestUpload.localFilePath.toString('utf8')), {
179+
files: [
180+
'openapi.en.json',
181+
'openapi.zh.json'
182+
]
183+
});
106184
});

0 commit comments

Comments
 (0)