WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import { runInNewContext } from 'node:vm'
|
||||
import { loginRoutePreload } from './loginRoutePreload'
|
||||
|
||||
function generate(base = '/') {
|
||||
const chunk = (fileName: string, facadeModuleId: string | null, imports: string[], css: string[] = [], dynamicImports: string[] = []) => ({
|
||||
type: 'chunk', fileName, facadeModuleId, imports, dynamicImports,
|
||||
viteMetadata: { importedCss: new Set(css) },
|
||||
})
|
||||
const bundle = {
|
||||
'assets/main.js': chunk('assets/main.js', '/project/index.html', ['assets/shared.js'], ['assets/main.css'], ['assets/editor.js']),
|
||||
'assets/shared.js': chunk('assets/shared.js', null, [], ['assets/shared.css']),
|
||||
'assets/login.js': chunk('assets/login.js', '/project/src/views/auth/Login.vue', ['assets/main.js', 'assets/password.js'], ['assets/login.css']),
|
||||
'assets/password.js': chunk('assets/password.js', null, ['assets/login.js']),
|
||||
'assets/editor.js': chunk('assets/editor.js', '/project/src/components/editor.vue', [], ['assets/editor.css']),
|
||||
}
|
||||
const plugin = loginRoutePreload() as any
|
||||
plugin.configResolved({ base })
|
||||
return { hook: plugin.transformIndexHtml.handler, context: { path: '/index.html', filename: '/project/index.html', bundle } }
|
||||
}
|
||||
|
||||
function execute(pathname: string, base = '/', supported = true) {
|
||||
const { hook, context } = generate(base)
|
||||
const tags = hook('', context)
|
||||
const links: any[] = []
|
||||
const script = tags[0].children
|
||||
runInNewContext(script, {
|
||||
URL,
|
||||
location: { pathname, href: `https://example.test${pathname}?token=fixture#oidc_error=fixture` },
|
||||
document: {
|
||||
createElement(tag: string) {
|
||||
assert.equal(tag, 'link')
|
||||
return { relList: { supports: (rel: string) => supported && rel === 'modulepreload' }, setAttribute() {} }
|
||||
},
|
||||
head: { appendChild(link: any) { links.push(link) } },
|
||||
},
|
||||
})
|
||||
return links
|
||||
}
|
||||
|
||||
test('downloads only login-specific static resources, despite cycles and lazy imports', () => {
|
||||
const links = execute('/login')
|
||||
assert.deepEqual(links.map(link => link.href), ['/assets/login.js', '/assets/login.css', '/assets/password.js'])
|
||||
assert.deepEqual(links.map(link => [link.rel, link.as]), [['modulepreload', undefined], ['preload', 'style'], ['modulepreload', undefined]])
|
||||
assert.ok(links.every(link => link.crossOrigin === ''))
|
||||
})
|
||||
|
||||
test('login and registration paths permit query and callback hashes without route changes', () => {
|
||||
for (const path of ['/login', '/login/', '/register', '/register/']) assert.equal(execute(path).length, 3, path)
|
||||
})
|
||||
|
||||
test('authenticated, root, embed, legacy, and similar routes do not fetch login resources', () => {
|
||||
for (const path of ['/', '/platform/knowledge-bases', '/embed/fixture', '/admin', '/admin/login', '/login-extra', '/login/nested']) {
|
||||
assert.equal(execute(path).length, 0, path)
|
||||
}
|
||||
})
|
||||
|
||||
test('honors deployment path and CDN base without matching sibling paths', () => {
|
||||
assert.equal(execute('/app/login', '/app/')[0].href, '/app/assets/login.js')
|
||||
assert.equal(execute('/application/login', '/app/').length, 0)
|
||||
assert.equal(execute('/app/register', 'https://cdn.example.test/app/')[0].href, 'https://cdn.example.test/app/assets/login.js')
|
||||
})
|
||||
|
||||
test('relative-base deployments keep normal Vite loading instead of guessing asset paths', () => {
|
||||
assert.equal(execute('/login/', './').length, 0)
|
||||
assert.equal(execute('/login/', '').length, 0)
|
||||
})
|
||||
|
||||
test('unsupported modulepreload preserves stylesheet preloading without fetching scripts as plain preload', () => {
|
||||
assert.deepEqual(execute('/login', '/', false).map(link => link.href), ['/assets/login.css'])
|
||||
})
|
||||
|
||||
test('embed output remains independent and incomplete main build fails visibly', () => {
|
||||
const { hook, context } = generate()
|
||||
assert.equal(hook('', { ...context, path: '/embed.html' }), undefined)
|
||||
assert.throws(() => hook('', { ...context, bundle: {} }), /could not resolve/)
|
||||
})
|
||||
|
||||
test('preload-only script does not evaluate app code, touch storage, or propagate a DOM failure', () => {
|
||||
const { hook, context } = generate()
|
||||
const script = hook('', context)[0].children
|
||||
assert.doesNotMatch(script, /\b(?:import|fetch|localStorage|sessionStorage)\b/)
|
||||
assert.doesNotThrow(() => runInNewContext(script, { URL, location: { pathname: '/login', href: 'https://example.test/login' }, document: { createElement() { throw new Error('unavailable') } } }))
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { OutputBundle, OutputChunk } from 'rollup'
|
||||
import type { Plugin, ResolvedConfig } from 'vite'
|
||||
|
||||
type StyleChunk = OutputChunk & { viteMetadata?: { importedCss: Set<string> } }
|
||||
|
||||
function staticAssets(bundle: OutputBundle, entry: OutputChunk): Set<string> {
|
||||
const assets = new Set<string>()
|
||||
const visit = (chunk: StyleChunk) => {
|
||||
if (assets.has(chunk.fileName)) return
|
||||
assets.add(chunk.fileName)
|
||||
for (const css of chunk.viteMetadata?.importedCss ?? []) assets.add(css)
|
||||
for (const file of chunk.imports) {
|
||||
const dependency = bundle[file]
|
||||
if (dependency?.type === 'chunk') visit(dependency)
|
||||
}
|
||||
}
|
||||
visit(entry)
|
||||
return assets
|
||||
}
|
||||
|
||||
// Download route resources without evaluating the module or applying its CSS.
|
||||
export function loginRoutePreload(): Plugin {
|
||||
let config: ResolvedConfig
|
||||
return {
|
||||
name: 'login-route-preload',
|
||||
apply: 'build',
|
||||
configResolved(resolved) { config = resolved },
|
||||
transformIndexHtml: {
|
||||
order: 'post',
|
||||
handler(_html, context) {
|
||||
if (context.path !== '/index.html' || !context.bundle) return
|
||||
const bundle = context.bundle
|
||||
const chunks = Object.values(bundle).filter((item): item is OutputChunk => item.type === 'chunk')
|
||||
const main = chunks.find(chunk => chunk.facadeModuleId === context.filename)
|
||||
const login = chunks.find(chunk => chunk.facadeModuleId?.replaceAll('\\', '/').endsWith('/src/views/auth/Login.vue'))
|
||||
if (!main || !login) throw new Error('Login route preload could not resolve the main and login chunks')
|
||||
const shared = staticAssets(bundle, main)
|
||||
const files = [...staticAssets(bundle, login)].filter(file => !shared.has(file))
|
||||
const json = (value: unknown) => JSON.stringify(value).replaceAll('<', '\\u003c')
|
||||
return [{
|
||||
tag: 'script',
|
||||
attrs: { 'data-login-route-preload': '' },
|
||||
injectTo: 'head-prepend',
|
||||
children: `(function(){try{
|
||||
var base=${json(config.base)},path=location.pathname;
|
||||
if(base==='./'||base==='')return;
|
||||
var prefix=new URL(base,location.href).pathname;
|
||||
if(path.indexOf(prefix)!==0)return;
|
||||
path=path.slice(prefix.length);
|
||||
if(path!=='login'&&path!=='login/'&&path!=='register'&&path!=='register/')return;
|
||||
${json(files)}.forEach(function(file){
|
||||
var link=document.createElement('link'),css=file.endsWith('.css');
|
||||
if(!css&&(!link.relList||!link.relList.supports('modulepreload')))return;
|
||||
link.rel=css?'preload':'modulepreload';
|
||||
if(css)link.as='style';
|
||||
link.crossOrigin='';link.href=base+file;
|
||||
link.setAttribute('data-login-route-preload','');
|
||||
document.head.appendChild(link);
|
||||
});
|
||||
}catch(_){}})();`,
|
||||
}]
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user