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:
@@ -2,9 +2,11 @@ import { useState } from 'react'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
command: string
|
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 [copied, setCopied] = useState(false)
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
@@ -14,18 +16,38 @@ export default function CommandCard({ command }: Props) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPrimary = variant === 'primary'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-gray-900 border border-blue-500/30 rounded-lg overflow-hidden">
|
<div className={`rounded-lg overflow-hidden border ${
|
||||||
<div className="flex items-center justify-between px-4 py-2 bg-blue-500/10 border-b border-blue-500/20">
|
isPrimary
|
||||||
<span className="text-xs text-blue-400 font-medium">运行命令</span>
|
? '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>
|
||||||
<div className="p-4 flex items-center gap-3">
|
<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}
|
{command}
|
||||||
</code>
|
</code>
|
||||||
<button
|
<button
|
||||||
onClick={handleCopy}
|
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 ? '已复制' : '复制'}
|
{copied ? '已复制' : '复制'}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CreateScriptResponse } from '../types'
|
import { CreateScriptResponse, isShellRuntime, getSourceCommand } from '../types'
|
||||||
import CommandCard from './CommandCard'
|
import CommandCard from './CommandCard'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -8,6 +8,8 @@ interface Props {
|
|||||||
|
|
||||||
export default function ResultCard({ result, onReset }: Props) {
|
export default function ResultCard({ result, onReset }: Props) {
|
||||||
const detailUrl = `${window.location.origin}/s/${result.id}`
|
const detailUrl = `${window.location.origin}/s/${result.id}`
|
||||||
|
const showSource = isShellRuntime(result.runtime)
|
||||||
|
const sourceCommand = showSource ? getSourceCommand(result.url, result.runtime) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -18,6 +20,19 @@ export default function ResultCard({ result, onReset }: Props) {
|
|||||||
|
|
||||||
<CommandCard command={result.command} />
|
<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="bg-gray-800/50 border border-gray-700 rounded-lg p-4 space-y-3">
|
||||||
<div className="flex justify-between text-sm">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-gray-400">脚本 ID</span>
|
<span className="text-gray-400">脚本 ID</span>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useParams, Link } from 'react-router-dom'
|
|||||||
import ScriptViewer from '../components/ScriptViewer'
|
import ScriptViewer from '../components/ScriptViewer'
|
||||||
import CommandCard from '../components/CommandCard'
|
import CommandCard from '../components/CommandCard'
|
||||||
import { getScript } from '../lib/api'
|
import { getScript } from '../lib/api'
|
||||||
import { ScriptDetail as ScriptDetailType } from '../types'
|
import { ScriptDetail as ScriptDetailType, isShellRuntime, getSourceCommand } from '../types'
|
||||||
|
|
||||||
export default function ScriptDetail() {
|
export default function ScriptDetail() {
|
||||||
const { id } = useParams<{ id: string }>()
|
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 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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -64,6 +68,18 @@ export default function ScriptDetail() {
|
|||||||
|
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<CommandCard command={command} />
|
<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>
|
||||||
|
|
||||||
<div className="mt-8 text-center">
|
<div className="mt-8 text-center">
|
||||||
|
|||||||
@@ -37,3 +37,18 @@ export const EXPIRES_OPTIONS: { value: ExpiresIn; label: string }[] = [
|
|||||||
{ value: '7d', label: '7 天' },
|
{ value: '7d', label: '7 天' },
|
||||||
{ value: '30d', label: '30 天' },
|
{ 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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user