fix: update the script build

This commit is contained in:
2026-08-15 12:51:34 +07:00
parent 36c7261f92
commit dbea31f4a5
4 changed files with 52 additions and 21 deletions
+3 -7
View File
@@ -28,7 +28,7 @@ export const ConfigPreview: React.FC<ConfigPreviewProps> = ({
const configContent = tool.sampleTemplate(connection, selectedModels) const configContent = tool.sampleTemplate(connection, selectedModels)
const installScript = buildInstallScript(tool, connection, selectedModels, selectedOS, { const installScript = buildInstallScript(tool, connection, selectedModels, selectedOS, {
keySource: 'runtime-env', keySource: connection.apiKey ? 'embedded' : 'runtime-env',
}) })
const handleCopyConfig = async () => { const handleCopyConfig = async () => {
@@ -182,14 +182,10 @@ export const ConfigPreview: React.FC<ConfigPreviewProps> = ({
<div className="flex flex-col gap-2 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20 text-xs text-left"> <div className="flex flex-col gap-2 p-3 rounded-lg bg-amber-500/10 border border-amber-500/20 text-xs text-left">
<div className="flex items-center gap-2 font-semibold text-amber-800 dark:text-amber-300"> <div className="flex items-center gap-2 font-semibold text-amber-800 dark:text-amber-300">
<Key className="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0" /> <Key className="w-4 h-4 text-amber-600 dark:text-amber-400 shrink-0" />
<span>Secure API Key Storage in VS Code</span> <span>API Key Setup for VS Code</span>
</div> </div>
<p className="text-zinc-600 dark:text-zinc-300 leading-relaxed"> <p className="text-zinc-600 dark:text-zinc-300 leading-relaxed">
No API key entered above. The generated config uses{' '} No API key entered above. Enter your API key under <strong>Gateway Connection</strong> to embed it directly into the config, or use the 1-Command installer which will prompt you for your key securely during installation.
<code className="px-1 py-0.5 rounded bg-amber-500/15 font-mono text-[11px] text-amber-700 dark:text-amber-300">
${'{input:9router-api-key}'}
</code>
. When VS Code prompts you for <strong className="font-semibold text-zinc-900 dark:text-zinc-100">9router-api-key</strong>, paste your key.
</p> </p>
</div> </div>
)} )}
+1 -1
View File
@@ -37,7 +37,7 @@ export const tools: Record<ToolId, ToolMeta> = {
} }
const endpointUrl = connection.baseUrl.replace(/\/+$/, '') + '/chat/completions' const endpointUrl = connection.baseUrl.replace(/\/+$/, '') + '/chat/completions'
const apiKeyVal = connection.apiKey ? connection.apiKey : '${input:9router-api-key}' const apiKeyVal = connection.apiKey ? connection.apiKey : '<YOUR_9ROUTER_API_KEY>'
const groups = Array.from(groupsMap.entries()).map(([provider, models]) => { const groups = Array.from(groupsMap.entries()).map(([provider, models]) => {
return { return {
+34
View File
@@ -1,6 +1,7 @@
import { describe, it, expect } from 'bun:test' import { describe, it, expect } from 'bun:test'
import { encodeInstallToken, decodeInstallToken, buildOneClickCommand } from './installLink' import { encodeInstallToken, decodeInstallToken, buildOneClickCommand } from './installLink'
import { renderInstallScript } from './installEndpoint' import { renderInstallScript } from './installEndpoint'
import { buildInstallScript } from './installScript'
import { tools } from '../data/tools' import { tools } from '../data/tools'
describe('Install Link & Endpoint', () => { describe('Install Link & Endpoint', () => {
@@ -62,11 +63,44 @@ describe('Install Link & Endpoint', () => {
expect(shRes.contentType).toContain('text/x-shellscript') expect(shRes.contentType).toContain('text/x-shellscript')
expect(shRes.body).toContain('__NINEROUTER_API_KEY__') expect(shRes.body).toContain('__NINEROUTER_API_KEY__')
expect(shRes.body).toContain('NINEROUTER_API_KEY') expect(shRes.body).toContain('NINEROUTER_API_KEY')
expect(shRes.body).toContain('/dev/tty')
expect(shRes.body).toContain('exit 1')
expect(shRes.body).not.toContain('${input:9router-api-key}')
const ps1Res = renderInstallScript(token, 'ps1') const ps1Res = renderInstallScript(token, 'ps1')
expect(ps1Res.status).toBe(200) expect(ps1Res.status).toBe(200)
expect(ps1Res.contentType).toContain('text/plain') expect(ps1Res.contentType).toContain('text/plain')
expect(ps1Res.body).toContain('__NINEROUTER_API_KEY__') expect(ps1Res.body).toContain('__NINEROUTER_API_KEY__')
expect(ps1Res.body).toContain('exit 1')
expect(ps1Res.body).not.toContain('${input:9router-api-key}')
})
it('builds embedded install script with real API key inline and no prompts', () => {
const scriptUnix = buildInstallScript(
tools.copilot,
{ baseUrl: 'http://localhost:20128/v1', apiKey: 'sk-real-secret-123' },
[{ id: 'cc/claude-sonnet-4.5', name: 'Claude Sonnet 4.5' }],
'unix',
{ keySource: 'embedded' }
)
expect(scriptUnix).not.toBeNull()
expect(scriptUnix!.content).toContain('sk-real-secret-123')
expect(scriptUnix!.content).not.toContain('__NINEROUTER_API_KEY__')
expect(scriptUnix!.content).not.toContain('read -rsp')
const scriptWin = buildInstallScript(
tools.copilot,
{ baseUrl: 'http://localhost:20128/v1', apiKey: 'sk-real-secret-123' },
[{ id: 'cc/claude-sonnet-4.5', name: 'Claude Sonnet 4.5' }],
'windows',
{ keySource: 'embedded' }
)
expect(scriptWin).not.toBeNull()
expect(scriptWin!.content).toContain('sk-real-secret-123')
expect(scriptWin!.content).not.toContain('__NINEROUTER_API_KEY__')
expect(scriptWin!.content).not.toContain('Read-Host')
}) })
it('replaces every API key placeholder in generated runtime scripts', () => { it('replaces every API key placeholder in generated runtime scripts', () => {
+14 -13
View File
@@ -39,10 +39,14 @@ export function buildInstallScript(
keySource === 'runtime-env' keySource === 'runtime-env'
? { ? {
writeConfigSection: `API_KEY="\${NINEROUTER_API_KEY:-}" writeConfigSection: `API_KEY="\${NINEROUTER_API_KEY:-}"
if [[ -z "$API_KEY" ]]; then if [[ -z "$API_KEY" && -r /dev/tty ]]; then
read -rsp "Enter your 9router API key: " API_KEY read -rsp "Enter your 9router API key: " API_KEY < /dev/tty
echo echo
fi fi
if [[ -z "$API_KEY" ]]; then
echo "❌ No 9router API key provided (set NINEROUTER_API_KEY or run in an interactive terminal)." >&2
exit 1
fi
CONFIG=$(cat <<'EOF' CONFIG=$(cat <<'EOF'
${configContent} ${configContent}
@@ -85,11 +89,7 @@ replace_api_key_placeholder() {
done done
} }
if [[ -n "$API_KEY" ]]; then replace_api_key_placeholder "$(json_escape "$API_KEY")"
replace_api_key_placeholder "$(json_escape "$API_KEY")"
else
replace_api_key_placeholder '\${input:9router-api-key}'
fi
printf '%s\\n' "$CONFIG" > "$TARGET_FILE"` printf '%s\\n' "$CONFIG" > "$TARGET_FILE"`
, ,
@@ -165,16 +165,17 @@ if (-not $apiKey) {
$apiKey = [System.Net.NetworkCredential]::new('', $secureApiKey).Password $apiKey = [System.Net.NetworkCredential]::new('', $secureApiKey).Password
} }
if (-not $apiKey) {
Write-Error "No 9router API key provided. Set NINEROUTER_API_KEY or run in an interactive terminal."
exit 1
}
$config = @' $config = @'
${configContent} ${configContent}
'@ '@
if ($apiKey) { $jsonApiKey = ConvertTo-Json -InputObject $apiKey -Compress
$jsonApiKey = ConvertTo-Json -InputObject $apiKey -Compress $config = $config.Replace('"__NINEROUTER_API_KEY__"', $jsonApiKey)
$config = $config.Replace('"__NINEROUTER_API_KEY__"', $jsonApiKey)
} else {
$config = $config.Replace('"__NINEROUTER_API_KEY__"', '"\${input:9router-api-key}"')
}
Set-Content -Path $targetFile -Value $config -Encoding UTF8`, Set-Content -Path $targetFile -Value $config -Encoding UTF8`,
psPostInstallNotes: `Write-Host "" psPostInstallNotes: `Write-Host ""