feat: Shell 类运行时显示 source 命令(继承环境变量)

- CommandCard 支持 primary/secondary 两种样式
- bash/zsh 使用 source <(curl URL),sh 使用 . <(curl URL),fish 使用 curl URL | source
- 非 Shell 类运行时(python3/node/ruby/php)不显示 source 命令

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-29 13:30:54 +08:00
parent ae4d361ebe
commit e3d380f9ab
4 changed files with 76 additions and 8 deletions

View File

@@ -2,9 +2,11 @@ import { useState } from 'react'
interface Props {
command: string
label?: string
variant?: 'primary' | 'secondary'
}
export default function CommandCard({ command }: Props) {
export default function CommandCard({ command, label = '运行命令', variant = 'primary' }: Props) {
const [copied, setCopied] = useState(false)
const handleCopy = () => {
@@ -14,18 +16,38 @@ export default function CommandCard({ command }: Props) {
})
}
const isPrimary = variant === 'primary'
return (
<div className="bg-gray-900 border border-blue-500/30 rounded-lg overflow-hidden">
<div className="flex items-center justify-between px-4 py-2 bg-blue-500/10 border-b border-blue-500/20">
<span className="text-xs text-blue-400 font-medium"></span>
<div className={`rounded-lg overflow-hidden border ${
isPrimary
? 'bg-gray-900 border-blue-500/30'
: 'bg-gray-900/50 border-gray-700'
}`}>
<div className={`flex items-center justify-between px-4 py-2 border-b ${
isPrimary
? 'bg-blue-500/10 border-blue-500/20'
: 'bg-gray-800/50 border-gray-700'
}`}>
<span className={`text-xs font-medium ${
isPrimary ? 'text-blue-400' : 'text-gray-400'
}`}>
{label}
</span>
</div>
<div className="p-4 flex items-center gap-3">
<code className="flex-1 text-sm font-mono text-green-400 break-all select-all">
<code className={`flex-1 text-sm font-mono break-all select-all ${
isPrimary ? 'text-green-400' : 'text-gray-300'
}`}>
{command}
</code>
<button
onClick={handleCopy}
className="shrink-0 px-3 py-1.5 bg-blue-600 hover:bg-blue-700 rounded text-xs font-medium transition-colors"
className={`shrink-0 px-3 py-1.5 rounded text-xs font-medium transition-colors ${
isPrimary
? 'bg-blue-600 hover:bg-blue-700'
: 'bg-gray-700 hover:bg-gray-600'
}`}
>
{copied ? '已复制' : '复制'}
</button>

View File

@@ -1,4 +1,4 @@
import { CreateScriptResponse } from '../types'
import { CreateScriptResponse, isShellRuntime, getSourceCommand } from '../types'
import CommandCard from './CommandCard'
interface Props {
@@ -8,6 +8,8 @@ interface Props {
export default function ResultCard({ result, onReset }: Props) {
const detailUrl = `${window.location.origin}/s/${result.id}`
const showSource = isShellRuntime(result.runtime)
const sourceCommand = showSource ? getSourceCommand(result.url, result.runtime) : null
return (
<div className="space-y-6">
@@ -18,6 +20,19 @@ export default function ResultCard({ result, onReset }: Props) {
<CommandCard command={result.command} />
{showSource && sourceCommand && (
<div>
<p className="text-xs text-gray-500 mb-2">
使 shell
</p>
<CommandCard
command={sourceCommand}
label="继承环境变量"
variant="secondary"
/>
</div>
)}
<div className="bg-gray-800/50 border border-gray-700 rounded-lg p-4 space-y-3">
<div className="flex justify-between text-sm">
<span className="text-gray-400"> ID</span>

View File

@@ -3,7 +3,7 @@ import { useParams, Link } from 'react-router-dom'
import ScriptViewer from '../components/ScriptViewer'
import CommandCard from '../components/CommandCard'
import { getScript } from '../lib/api'
import { ScriptDetail as ScriptDetailType } from '../types'
import { ScriptDetail as ScriptDetailType, isShellRuntime, getSourceCommand } from '../types'
export default function ScriptDetail() {
const { id } = useParams<{ id: string }>()
@@ -41,6 +41,10 @@ export default function ScriptDetail() {
}
const command = `curl ${window.location.origin}/raw/${script.id} | ${script.runtime}`
const showSource = isShellRuntime(script.runtime)
const sourceCommand = showSource
? getSourceCommand(`${window.location.origin}/raw/${script.id}`, script.runtime)
: null
return (
<div>
@@ -64,6 +68,18 @@ export default function ScriptDetail() {
<div className="mt-8">
<CommandCard command={command} />
{showSource && sourceCommand && (
<div className="mt-4">
<p className="text-xs text-gray-500 mb-2">
使 shell
</p>
<CommandCard
command={sourceCommand}
label="继承环境变量"
variant="secondary"
/>
</div>
)}
</div>
<div className="mt-8 text-center">

View File

@@ -37,3 +37,18 @@ export const EXPIRES_OPTIONS: { value: ExpiresIn; label: string }[] = [
{ value: '7d', label: '7 天' },
{ value: '30d', label: '30 天' },
]
const SOURCE_TEMPLATES: Record<string, string> = {
bash: 'source <(curl {url})',
zsh: 'source <(curl {url})',
sh: '. <(curl {url})',
fish: 'curl {url} | source',
}
export function isShellRuntime(runtime: string): boolean {
return runtime in SOURCE_TEMPLATES
}
export function getSourceCommand(url: string, runtime: string): string {
return (SOURCE_TEMPLATES[runtime] ?? 'source <(curl {url})').replace('{url}', url)
}