feat: enhance response serialization for compact mode
- Added support for preserving native response.compaction payloads when compact serialization is requested in `serializeResponsesFinalPayload`. - Implemented serialization of compact mode as `response.compaction` instead of an ordinary response object. - Introduced a new type `ResponsesFinalSerializationMode` to manage serialization modes. - Refactored `serializeResponsesFinalPayload` to handle different serialization modes and usage payloads. - Added tests to ensure correct behavior for compact serialization and preservation of upstream payloads. - Updated favicon assets and documentation configuration for VitePress.
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { existsSync, globSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import config from './config';
|
||||
|
||||
const getAliasEntry = (aliasConfig: unknown, specifier: string) => {
|
||||
if (!aliasConfig) return undefined;
|
||||
|
||||
if (Array.isArray(aliasConfig)) {
|
||||
return aliasConfig.find(
|
||||
(entry) =>
|
||||
entry &&
|
||||
typeof entry === 'object' &&
|
||||
'find' in entry &&
|
||||
((typeof entry.find === 'string' && entry.find === specifier) ||
|
||||
(entry.find instanceof RegExp && entry.find.test(specifier))),
|
||||
);
|
||||
}
|
||||
|
||||
if (typeof aliasConfig === 'object' && aliasConfig !== null && specifier in aliasConfig) {
|
||||
return {
|
||||
find: specifier,
|
||||
replacement: String((aliasConfig as Record<string, unknown>)[specifier]),
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const getAlias = (aliasConfig: unknown, specifier: string): string | undefined => {
|
||||
const aliasEntry = getAliasEntry(aliasConfig, specifier);
|
||||
return aliasEntry && typeof aliasEntry === 'object' && 'replacement' in aliasEntry ? String(aliasEntry.replacement) : undefined;
|
||||
};
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../..', import.meta.url));
|
||||
|
||||
const getExpectedEntry = (hoistedRelativePath: string, pnpmPattern: string) => {
|
||||
const hoistedEntry = resolve(repoRoot, hoistedRelativePath);
|
||||
if (existsSync(hoistedEntry)) return hoistedEntry;
|
||||
|
||||
const [pnpmEntry] = globSync(resolve(repoRoot, pnpmPattern));
|
||||
return pnpmEntry;
|
||||
};
|
||||
|
||||
describe('docs vitepress config', () => {
|
||||
it('ships copied main-app favicon assets for docs', () => {
|
||||
expect(existsSync(resolve(repoRoot, 'docs/public/favicon.png'))).toBe(true);
|
||||
expect(existsSync(resolve(repoRoot, 'docs/public/favicon-64.png'))).toBe(true);
|
||||
expect(existsSync(resolve(repoRoot, 'docs/public/favicon.ico'))).toBe(true);
|
||||
});
|
||||
|
||||
it('ships a fallback favicon.ico for browsers that probe the default path', () => {
|
||||
expect(existsSync(resolve(repoRoot, 'docs/public/favicon.ico'))).toBe(true);
|
||||
});
|
||||
|
||||
it('declares the main-app favicon assets in docs head tags', () => {
|
||||
const iconLinks =
|
||||
config.head?.filter(
|
||||
(entry) =>
|
||||
entry[0] === 'link' &&
|
||||
typeof entry[1] === 'object' &&
|
||||
entry[1] !== null &&
|
||||
'rel' in entry[1] &&
|
||||
(entry[1].rel === 'icon' || entry[1].rel === 'shortcut icon'),
|
||||
) ?? [];
|
||||
|
||||
expect(iconLinks.some((entry) => typeof entry[1] === 'object' && entry[1] !== null && 'href' in entry[1] && entry[1].href === '/favicon.png')).toBe(true);
|
||||
expect(iconLinks.some((entry) => typeof entry[1] === 'object' && entry[1] !== null && 'href' in entry[1] && entry[1].href === '/favicon-64.png')).toBe(true);
|
||||
expect(iconLinks.some((entry) => typeof entry[1] === 'object' && entry[1] !== null && 'href' in entry[1] && entry[1].href === '/favicon.ico')).toBe(true);
|
||||
});
|
||||
|
||||
it('aliases dayjs to the ESM entry for mermaid browser compatibility', () => {
|
||||
const aliasConfig = config.vite?.resolve?.alias;
|
||||
const alias = getAlias(aliasConfig, 'dayjs');
|
||||
const aliasEntry = getAliasEntry(aliasConfig, 'dayjs');
|
||||
|
||||
expect(alias).toBe(getExpectedEntry('node_modules/dayjs/esm/index.js', 'node_modules/.pnpm/dayjs@*/node_modules/dayjs/esm/index.js'));
|
||||
expect(alias && existsSync(alias)).toBe(true);
|
||||
expect(Array.isArray(aliasConfig)).toBe(true);
|
||||
expect(aliasEntry && typeof aliasEntry === 'object' && 'find' in aliasEntry && aliasEntry.find instanceof RegExp).toBe(true);
|
||||
expect(aliasEntry && typeof aliasEntry === 'object' && 'find' in aliasEntry && aliasEntry.find instanceof RegExp && aliasEntry.find.test('dayjs/plugin/duration.js')).toBe(false);
|
||||
});
|
||||
|
||||
it('aliases sanitize-url to a browser-loadable source entry', () => {
|
||||
const alias = getAlias(config.vite?.resolve?.alias, '@braintree/sanitize-url');
|
||||
|
||||
expect(alias).toBe(
|
||||
getExpectedEntry(
|
||||
'node_modules/@braintree/sanitize-url/src/index.ts',
|
||||
'node_modules/.pnpm/@braintree+sanitize-url@*/node_modules/@braintree/sanitize-url/src/index.ts',
|
||||
),
|
||||
);
|
||||
expect(alias && existsSync(alias)).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,14 +1,56 @@
|
||||
import { existsSync, globSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { defineConfig } from 'vitepress';
|
||||
import { withMermaid } from 'vitepress-plugin-mermaid';
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../..', import.meta.url));
|
||||
const resolveDependencyEntry = (hoistedRelativePath: string, pnpmPattern: string) => {
|
||||
const hoistedEntry = resolve(repoRoot, hoistedRelativePath);
|
||||
if (existsSync(hoistedEntry)) return hoistedEntry;
|
||||
|
||||
const [pnpmEntry] = globSync(resolve(repoRoot, pnpmPattern));
|
||||
return pnpmEntry;
|
||||
};
|
||||
|
||||
const dayjsEsmEntry = resolveDependencyEntry(
|
||||
'node_modules/dayjs/esm/index.js',
|
||||
'node_modules/.pnpm/dayjs@*/node_modules/dayjs/esm/index.js',
|
||||
);
|
||||
const sanitizeUrlSourceEntry = resolveDependencyEntry(
|
||||
'node_modules/@braintree/sanitize-url/src/index.ts',
|
||||
'node_modules/.pnpm/@braintree+sanitize-url@*/node_modules/@braintree/sanitize-url/src/index.ts',
|
||||
);
|
||||
|
||||
if (!dayjsEsmEntry) {
|
||||
throw new Error('Unable to resolve the dayjs ESM entry required by vitepress-plugin-mermaid.');
|
||||
}
|
||||
|
||||
if (!sanitizeUrlSourceEntry) {
|
||||
throw new Error('Unable to resolve the sanitize-url source entry required by vitepress-plugin-mermaid.');
|
||||
}
|
||||
|
||||
export default withMermaid(
|
||||
defineConfig({
|
||||
lang: 'zh-CN',
|
||||
title: 'Metapi 文档',
|
||||
description: 'Metapi 使用文档、FAQ 与维护协作指南',
|
||||
head: [
|
||||
['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon.png' }],
|
||||
['link', { rel: 'icon', type: 'image/png', sizes: '64x64', href: '/favicon-64.png' }],
|
||||
['link', { rel: 'shortcut icon', href: '/favicon.ico' }],
|
||||
],
|
||||
cleanUrls: true,
|
||||
lastUpdated: true,
|
||||
ignoreDeadLinks: true,
|
||||
vite: {
|
||||
resolve: {
|
||||
alias: [
|
||||
{ find: /^dayjs$/, replacement: dayjsEsmEntry },
|
||||
{ find: /^@braintree\/sanitize-url$/, replacement: sanitizeUrlSourceEntry },
|
||||
],
|
||||
},
|
||||
},
|
||||
themeConfig: {
|
||||
siteTitle: 'Metapi Docs',
|
||||
logo: '/logos/logo-icon-512.png',
|
||||
|
||||
Reference in New Issue
Block a user