fix(web): update skill detail install command (#139)

* fix(web): update skill install command on detail page

* fix(web): reuse base url for skill install registry

* test(web): fix install command window mock typing
This commit is contained in:
Xudong Sun 2026-03-20 15:44:09 +08:00 committed by GitHub
parent 21ba6ee938
commit 611d67f845
2 changed files with 80 additions and 11 deletions

View file

@ -0,0 +1,67 @@
import { afterEach, describe, expect, it } from 'vitest'
import { buildInstallCommand, buildInstallTarget, getBaseUrl } from './install-command'
describe('install-command', () => {
const originalWindow = globalThis.window
function setMockWindow(appBaseUrl?: string) {
const location = {
protocol: 'https:',
host: 'fallback.example.com',
} satisfies Pick<Location, 'protocol' | 'host'>
Object.defineProperty(globalThis, 'window', {
configurable: true,
writable: true,
value: {
__SKILLHUB_RUNTIME_CONFIG__: {
appBaseUrl,
},
location,
} satisfies {
location: Pick<Location, 'protocol' | 'host'>
} & {
__SKILLHUB_RUNTIME_CONFIG__: {
appBaseUrl?: string
}
},
})
}
afterEach(() => {
if (originalWindow) {
Object.defineProperty(globalThis, 'window', {
configurable: true,
writable: true,
value: originalWindow,
})
return
}
Reflect.deleteProperty(globalThis, 'window')
})
it('uses the plain slug for the global namespace', () => {
expect(buildInstallTarget('global', 'my-skill')).toBe('my-skill')
expect(buildInstallCommand('global', 'my-skill', 'https://skill.xfyun.cn')).toBe(
'npx clawhub install my-skill --registry https://skill.xfyun.cn',
)
})
it('prefixes non-global namespaces in the install target', () => {
expect(buildInstallTarget('team-alpha', 'my-skill')).toBe('team-alpha--my-skill')
expect(buildInstallCommand('team-alpha', 'my-skill', 'https://skill.xfyun.cn')).toBe(
'npx clawhub install team-alpha--my-skill --registry https://skill.xfyun.cn',
)
})
it('uses the runtime app base url when available', () => {
setMockWindow('https://app.example.com')
expect(getBaseUrl()).toBe('https://app.example.com')
})
it('falls back to the browser origin when the app base url is missing', () => {
setMockWindow()
expect(getBaseUrl()).toBe('https://fallback.example.com')
})
})

View file

@ -9,7 +9,11 @@ interface InstallCommandProps {
version?: string
}
function getAppBaseUrl(): string {
export function buildInstallTarget(namespace: string, slug: string): string {
return namespace === 'global' ? slug : `${namespace}--${slug}`
}
export function getBaseUrl(): string {
if (typeof window === 'undefined') {
return ''
}
@ -20,20 +24,18 @@ function getAppBaseUrl(): string {
return `${window.location.protocol}//${window.location.host}`
}
export function InstallCommand({ slug }: InstallCommandProps) {
export function buildInstallCommand(namespace: string, slug: string, baseUrl: string): string {
const installTarget = buildInstallTarget(namespace, slug)
return `npx clawhub install ${installTarget} --registry ${baseUrl}`
}
export function InstallCommand({ namespace, slug }: InstallCommandProps) {
const { t } = useTranslation()
const [copied, setCopied] = useState(false)
const baseUrl = useMemo(() => getAppBaseUrl(), [])
const baseUrl = useMemo(() => getBaseUrl(), [])
const command = useMemo(() => {
const installCmd = `clawhub install ${slug} `
// 如果是默认的 clawhub.ai 不需要环境变量,否则显示完整配置
if (baseUrl && !baseUrl.includes('clawhub.ai') && !baseUrl.includes('localhost') && !baseUrl.includes('127.0.0.1')) {
return `CLAWHUB_SITE=${baseUrl} CLAWHUB_REGISTRY=${baseUrl} ${installCmd}`
}
return installCmd
}, [baseUrl, slug])
const command = useMemo(() => buildInstallCommand(namespace, slug, baseUrl), [baseUrl, namespace, slug])
const handleCopy = async () => {
try {