mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-29 20:12:37 +00:00
fix(desktop): serve index.html for renderer SPA routes on reload
This commit is contained in:
parent
c302846d99
commit
273fa1aeef
6 changed files with 106 additions and 7 deletions
|
|
@ -51,6 +51,14 @@ export function registerRendererScheme(): void {
|
|||
]);
|
||||
}
|
||||
|
||||
async function isRegularFile(filePath: string): Promise<boolean> {
|
||||
try {
|
||||
return (await stat(filePath)).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_RENDERER_BASE = `${RENDERER_SCHEME}://${RENDERER_HOST}/index.html`;
|
||||
|
||||
export function rendererUrl(
|
||||
|
|
@ -114,17 +122,24 @@ export async function handleRendererRequest(
|
|||
if (!filePath.startsWith(root)) {
|
||||
return new Response('forbidden', { status: 403 });
|
||||
}
|
||||
try {
|
||||
const info = await stat(filePath);
|
||||
if (!info.isFile()) {
|
||||
// SPA fallback (mirrors kap-server's webAssets): the renderer's history
|
||||
// routing pushes extensionless navigation URLs (`/sessions/<id>`, `/login`)
|
||||
// that have no file in desktop-dist — serve index.html so a native reload
|
||||
// (cmd+r) lands back in the app. Asset misses with an extension keep their
|
||||
// 404 instead of being fed HTML.
|
||||
let target = filePath;
|
||||
if (!(await isRegularFile(target))) {
|
||||
if (extname(decodedPathname) !== '') {
|
||||
return new Response('not found', { status: 404 });
|
||||
}
|
||||
target = join(root, 'index.html');
|
||||
if (!(await isRegularFile(target))) {
|
||||
return new Response('not found', { status: 404 });
|
||||
}
|
||||
} catch {
|
||||
return new Response('not found', { status: 404 });
|
||||
}
|
||||
const stream = createReadStream(filePath);
|
||||
const stream = createReadStream(target);
|
||||
return new Response(stream as unknown as ResponseBody, {
|
||||
headers: { 'content-type': mimeFor(filePath) },
|
||||
headers: { 'content-type': mimeFor(target) },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,39 @@ describe('handleRendererRequest', () => {
|
|||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
// SPA fallback: the renderer's history routing pushes paths like
|
||||
// `/sessions/<id>` (and `/login` from the auth gate) onto the URL; a native
|
||||
// reload re-requests that path, which has no file in desktop-dist.
|
||||
it('serves index.html for SPA session routes (reload on a deep link)', async () => {
|
||||
const root = await makeRoot();
|
||||
const res = await handleRendererRequest(
|
||||
{ url: 'app://renderer/sessions/abc' } as any,
|
||||
() => root,
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get('content-type')).toBe('text/html; charset=utf-8');
|
||||
expect(await res.text()).toBe('<h1>ok</h1>');
|
||||
});
|
||||
|
||||
it('serves index.html for the auth-gate /login path', async () => {
|
||||
const root = await makeRoot();
|
||||
const res = await handleRendererRequest(
|
||||
{ url: 'app://renderer/login' } as any,
|
||||
() => root,
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(await res.text()).toBe('<h1>ok</h1>');
|
||||
});
|
||||
|
||||
it('404s an extensionless path when index.html itself is missing', async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), 'kimi-renderer-'));
|
||||
const res = await handleRendererRequest(
|
||||
{ url: 'app://renderer/sessions/abc' } as any,
|
||||
() => root,
|
||||
);
|
||||
expect(res.status).toBe(404);
|
||||
});
|
||||
|
||||
it('serves files from the configured desktop-dist root (app://renderer/<path>)', async () => {
|
||||
const root = await makeRoot();
|
||||
await writeFile(join(root, 'assets', 'marker.js'), 'desktop-dist-root');
|
||||
|
|
|
|||
20
apps/web/vitest.config.ts
Normal file
20
apps/web/vitest.config.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { kimiRendererViteConfig } from '@moonshot-ai/vite-preset';
|
||||
|
||||
// Web tests import src code, which pulls in unplugin-icons virtual modules
|
||||
// (`~icons/*`) and `?raw` SVGs. Mirror the web build's plugin set (Vue + the
|
||||
// `kimi` custom icon collection) so those imports resolve under vitest too —
|
||||
// same preset as vite.config.ts, but only the plugins (root/build stay
|
||||
// test-scoped).
|
||||
const preset = kimiRendererViteConfig({
|
||||
root: fileURLToPath(new URL('.', import.meta.url)),
|
||||
iconsDir: fileURLToPath(new URL('./src/icons/kimi', import.meta.url)),
|
||||
});
|
||||
|
||||
export default defineConfig({
|
||||
plugins: preset.plugins,
|
||||
test: {
|
||||
include: ['src/**/*.test.ts'],
|
||||
},
|
||||
});
|
||||
|
|
@ -17,6 +17,8 @@
|
|||
"test": "vitest run"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/ri": "^1.2.10",
|
||||
"@iconify-json/tabler": "^1.2.35",
|
||||
"@types/node": "^22.15.3",
|
||||
"oxlint": "1.59.0",
|
||||
"oxlint-tsgolint": "0.20.0",
|
||||
|
|
|
|||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
|
|
@ -13,6 +13,12 @@ importers:
|
|||
|
||||
.:
|
||||
devDependencies:
|
||||
'@iconify-json/ri':
|
||||
specifier: ^1.2.10
|
||||
version: 1.2.10
|
||||
'@iconify-json/tabler':
|
||||
specifier: ^1.2.35
|
||||
version: 1.2.35
|
||||
'@types/node':
|
||||
specifier: ^22.15.3
|
||||
version: 22.20.1
|
||||
|
|
|
|||
23
vitest.config.ts
Normal file
23
vitest.config.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { defineConfig } from 'vitest/config';
|
||||
|
||||
// Root test entry (`pnpm test`). Runs only code-app's own packages as vitest
|
||||
// projects, each loading its own config (plugin pipelines included). The
|
||||
// `kimi-code/` submodule is intentionally left out: its tests run in its own
|
||||
// repo/CI and need its own environment.
|
||||
export default defineConfig({
|
||||
test: {
|
||||
projects: [
|
||||
// apps/* each carry a vitest.config.ts with their renderer plugin set
|
||||
// (Vue + unplugin-icons), so `~icons/*` and `?raw` imports resolve.
|
||||
'apps/*',
|
||||
// packages/* have no build-time plugin needs; run their tests with the
|
||||
// default pipeline from their own roots.
|
||||
{
|
||||
test: {
|
||||
name: 'packages',
|
||||
include: ['packages/{web-core,web-i18n,web-markdown,web-ui,vite-preset}/{src,test}/**/*.test.ts'],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue