mirror of
https://github.com/iflytek/skillhub.git
synced 2026-10-07 02:57:51 +00:00
fix(ISSUE-64): quote legacy install command versions
Signed-off-by: dongmucat <1127093059@qq.com>
This commit is contained in:
parent
4f193a5eba
commit
9815cd7e91
3 changed files with 79 additions and 5 deletions
|
|
@ -74,6 +74,15 @@ describe('install-command', () => {
|
|||
)
|
||||
})
|
||||
|
||||
it('shell-quotes historical supported versions that include spaces', () => {
|
||||
expect(buildInstallCommand('team-alpha', 'my-skill', 'https://skill.xfyun.cn', '1.0.0 beta')).toBe(
|
||||
"npx clawhub install team-alpha--my-skill --registry https://skill.xfyun.cn --version '1.0.0 beta'",
|
||||
)
|
||||
expect(buildSkillhubInstallCommand('team-alpha', 'my-skill', 'https://skill.xfyun.cn', '1.0.0 beta')).toBe(
|
||||
"npx @astron-team/skillhub@latest install my-skill --namespace team-alpha --registry https://skill.xfyun.cn --version '1.0.0 beta'",
|
||||
)
|
||||
})
|
||||
|
||||
it('builds a one-line SkillHub npx command for the global namespace', () => {
|
||||
expect(buildSkillhubInstallCommand('global', 'my-skill', 'https://skill.xfyun.cn')).toBe(
|
||||
'npx @astron-team/skillhub@latest install my-skill --registry https://skill.xfyun.cn',
|
||||
|
|
@ -92,12 +101,12 @@ describe('install-command', () => {
|
|||
)
|
||||
})
|
||||
|
||||
it('omits unsafe versions from install commands', () => {
|
||||
it('shell-quotes potentially hostile versions instead of silently dropping them', () => {
|
||||
expect(buildInstallCommand('team-alpha', 'my-skill', 'https://skill.xfyun.cn', '0.9.0 && whoami')).toBe(
|
||||
'npx clawhub install team-alpha--my-skill --registry https://skill.xfyun.cn',
|
||||
"npx clawhub install team-alpha--my-skill --registry https://skill.xfyun.cn --version '0.9.0 && whoami'",
|
||||
)
|
||||
expect(buildSkillhubInstallCommand('team-alpha', 'my-skill', 'https://skill.xfyun.cn', '0.9.0 && whoami')).toBe(
|
||||
'npx @astron-team/skillhub@latest install my-skill --namespace team-alpha --registry https://skill.xfyun.cn',
|
||||
"npx @astron-team/skillhub@latest install my-skill --namespace team-alpha --registry https://skill.xfyun.cn --version '0.9.0 && whoami'",
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ interface InstallCommandProps {
|
|||
}
|
||||
|
||||
const SAFE_VERSION_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._+-]*[A-Za-z0-9])?$/
|
||||
const CONTROL_CHARACTER_PATTERN = /\p{Cc}/u
|
||||
const SHELL_SINGLE_QUOTE_ESCAPE = `'"'"'`
|
||||
|
||||
export function buildInstallTarget(namespace: string, slug: string): string {
|
||||
return namespace === 'global' ? slug : `${namespace}--${slug}`
|
||||
|
|
@ -31,11 +33,25 @@ export function getBaseUrl(): string {
|
|||
return `${window.location.protocol}//${window.location.host}`
|
||||
}
|
||||
|
||||
function escapeShellArgument(value: string): string | null {
|
||||
if (!value || CONTROL_CHARACTER_PATTERN.test(value)) {
|
||||
return null
|
||||
}
|
||||
if (SAFE_VERSION_PATTERN.test(value)) {
|
||||
return value
|
||||
}
|
||||
return `'${value.replace(/'/g, SHELL_SINGLE_QUOTE_ESCAPE)}'`
|
||||
}
|
||||
|
||||
function buildVersionedCommand(command: string, version?: string): string {
|
||||
if (!version || !SAFE_VERSION_PATTERN.test(version)) {
|
||||
if (!version) {
|
||||
return command
|
||||
}
|
||||
return `${command} --version ${version}`
|
||||
const escapedVersion = escapeShellArgument(version)
|
||||
if (!escapedVersion) {
|
||||
return command
|
||||
}
|
||||
return `${command} --version ${escapedVersion}`
|
||||
}
|
||||
|
||||
export function buildInstallCommand(namespace: string, slug: string, baseUrl: string, version?: string): string {
|
||||
|
|
|
|||
|
|
@ -392,6 +392,55 @@ describe('SkillDetailPage', () => {
|
|||
expect(html).toContain('install:0.9.0')
|
||||
})
|
||||
|
||||
it('passes historical published versions with spaces through to the install command', () => {
|
||||
detailSearchState = { returnTo: '/dashboard/skills', version: '1.0.0 beta' }
|
||||
useSkillVersionsMock.mockReturnValue({
|
||||
data: [
|
||||
{
|
||||
id: 10,
|
||||
version: '1.0.0',
|
||||
status: 'PUBLISHED',
|
||||
changelog: '',
|
||||
fileCount: 1,
|
||||
totalSize: 12,
|
||||
publishedAt: '2026-03-20T00:00:00Z',
|
||||
downloadAvailable: true,
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
version: '1.0.0 beta',
|
||||
status: 'PUBLISHED',
|
||||
changelog: '',
|
||||
fileCount: 1,
|
||||
totalSize: 12,
|
||||
publishedAt: '2026-03-19T00:00:00Z',
|
||||
downloadAvailable: true,
|
||||
},
|
||||
],
|
||||
})
|
||||
useSkillVersionDetailMock.mockImplementation((_namespace: string, _slug: string, version?: string) => ({
|
||||
data: version === '1.0.0 beta'
|
||||
? {
|
||||
id: 9,
|
||||
version: '1.0.0 beta',
|
||||
status: 'PUBLISHED',
|
||||
changelog: '',
|
||||
fileCount: 1,
|
||||
totalSize: 12,
|
||||
publishedAt: '2026-03-19T00:00:00Z',
|
||||
parsedMetadataJson: '{}',
|
||||
manifestJson: '[]',
|
||||
complianceMappings: [],
|
||||
}
|
||||
: undefined,
|
||||
}))
|
||||
|
||||
const html = renderToStaticMarkup(<SkillDetailPage />)
|
||||
|
||||
expect(useSkillVersionDetailMock).toHaveBeenCalledWith('global', 'demo-skill', '1.0.0 beta', true)
|
||||
expect(html).toContain('install:1.0.0 beta')
|
||||
})
|
||||
|
||||
it('shows the label management panel for a user who can manage the skill lifecycle', () => {
|
||||
useSkillDetailMock.mockReturnValue({
|
||||
data: createSkill({
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue