full-stack-dev-workflow
背景
- 如何轻量地快速搭建一个全栈化的开发环境?兼顾通用性?
- AI 适配全栈开发工作流,如何兼顾成本和性能?
方案
基于 GitHub Codespace + OpenCode(Go 套餐 / 自备 DeepSeek API Key)来搭建通用的全栈开发环境。
OpenCode 终端配置脚本
文末提供了开箱即用的配置脚本,替换 API Key 后在终端直接运行即可。脚本会自动安装 OpenCode CLI、配置 Go 套餐和 DeepSeek API、启用 LSP 并授予最高权限(免去每次询问权限的步骤)。
整体架构
multica 项目全栈开发示例
1. Fork GitHub multica 项目,创建 Codespace:

2. 本地 VS Code 通过 Remote Explorer 远程连接 Codespace,执行配置脚本:

3. 启动 OpenCode,执行 prompt:
现在是在 github 的 codespace 中,帮我本地运行这个项目

4. OpenCode 输出启动成功,在 VS Code Codespace 中配置端口转发:

5. 浏览器访问:

6. 配置验证码登录:
opencode使用prompt: 在 .env 中设置 MULTICA_DEV_VERIFICATION_CODE=888888 使用固定验证码,然后重启后端。

用验证码登录报错后,又用opencode修复:

opencode显示修复成功:

浏览器访问,登录成功,进入 onboarding 页面:

7. 修改项目文案并验证:
opencode使用prompt: 现在已经登录成功,进入页面 /test1/issues 路由页面,帮我简单修改一个文案和增加一个 chrome 控制台能打印出的 log,看看是否生效了

8. 浏览器查看修改结果:

9. 查看所有运行中的项目进程:
opencode使用prompt: 帮我看看刚才总共帮我运行了哪些项目,列出各个项目的功能和状态

10. 清理: 不需要的 Codespace 直接在 GitHub Codespaces 或 VS Code 中删除即可。
开发流程总览
相关配置
OpenCode 终端配置脚本
sh
#!/usr/bin/env bash
set -euo pipefail
# ============================================================
# OpenCode Setup Script — safe to copy-paste into terminal
# ============================================================
# Usage: bash <(cat scripts/opencode-config-copy-to-run.sh)
# Or: curl -fsSL <url> | bash
# Or: just copy-paste the whole file into your terminal
#
# Uses heredocs (<<) instead of node -e "..." so that
# multi-line copy-paste into a terminal never breaks.
# ============================================================
# --- Configuration: Set your API keys and defaults here ---
OPENCODE_GO_API_KEY="sk-11uixq122pEm5TVP5vOqeIxsxEhxYzqAMLxHWzwDceedzCa5nvT8PniI9TkFJEIxjt85y"
DEEPSEEK_API_KEY="sk-a03be09222228a15742ff9f10fb83d7962bb5"
DEFAULT_MODEL="opencode-go/deepseek-v4-flash"
LSP_ENABLED="true"
PERMISSION="allow"
# --- Constants ---
AUTH_DIR="$HOME/.local/share/opencode"
AUTH_FILE="$AUTH_DIR/auth.json"
CONFIG_DIR="$HOME/.config/opencode"
CONFIG_FILE="$CONFIG_DIR/opencode.json"
NPM_PACKAGE="opencode-ai"
# --- Color output ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
log_info() { echo -e "${GREEN}[INFO]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
# --- Pre-check: node/npm ---
if ! command -v npm &>/dev/null; then
log_error "npm is required but not found. Please install Node.js first."
exit 1
fi
# --- Step 1: Install opencode CLI if not present ---
log_info "Checking OpenCode CLI..."
if command -v opencode &>/dev/null; then
log_info "OpenCode CLI already installed: $(opencode --version 2>/dev/null || echo 'ok')"
else
log_info "Installing OpenCode CLI via npm..."
npm install -g "$NPM_PACKAGE"
log_info "OpenCode CLI installed successfully"
fi
# --- Step 2: Configure API keys ---
log_info "Configuring API keys..."
mkdir -p "$AUTH_DIR"
if [ -f "$AUTH_FILE" ]; then
log_info "Existing auth.json found, merging keys..."
node <<NODE_SCRIPT
const fs = require('fs');
const existing = JSON.parse(fs.readFileSync('$AUTH_FILE', 'utf8'));
const keysToAdd = {
'opencode-go': { type: 'api', key: '$OPENCODE_GO_API_KEY' },
'deepseek': { type: 'api', key: '$DEEPSEEK_API_KEY' }
};
for (const [k, v] of Object.entries(keysToAdd)) {
if (v.key && !v.key.includes('your-') && !v.key.includes('sk-your-')) {
existing[k] = v;
}
}
fs.writeFileSync('$AUTH_FILE', JSON.stringify(existing, null, 2) + '\n');
NODE_SCRIPT
else
log_info "Creating fresh auth.json..."
node <<NODE_SCRIPT
const fs = require('fs');
const auth = {};
const keysToAdd = {
'opencode-go': { type: 'api', key: '$OPENCODE_GO_API_KEY' },
'deepseek': { type: 'api', key: '$DEEPSEEK_API_KEY' }
};
for (const [k, v] of Object.entries(keysToAdd)) {
if (v.key && !v.key.includes('your-') && !v.key.includes('sk-your-')) {
auth[k] = v;
}
}
fs.writeFileSync('$AUTH_FILE', JSON.stringify(auth, null, 2) + '\n');
NODE_SCRIPT
fi
# --- Step 3: Configure default model ---
log_info "Configuring default model: $DEFAULT_MODEL..."
mkdir -p "$CONFIG_DIR"
if [ -f "$CONFIG_FILE" ]; then
log_info "Existing opencode.json found, merging config..."
node <<NODE_SCRIPT
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('$CONFIG_FILE', 'utf8'));
config['\$schema'] = 'https://opencode.ai/config.json';
config.model = '$DEFAULT_MODEL';
config.lsp = '$LSP_ENABLED' === 'true';
config.permission = '$PERMISSION';
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(config, null, 2) + '\n');
NODE_SCRIPT
else
log_info "Creating fresh opencode.json..."
node <<NODE_SCRIPT
const fs = require('fs');
const config = {
'\$schema': 'https://opencode.ai/config.json',
model: '$DEFAULT_MODEL',
lsp: '$LSP_ENABLED' === 'true',
permission: '$PERMISSION'
};
fs.writeFileSync('$CONFIG_FILE', JSON.stringify(config, null, 2) + '\n');
NODE_SCRIPT
fi
# --- Step 4: Install VS Code extension ---
log_info "Checking VS Code extension 'sst-dev.opencode'..."
if command -v code &>/dev/null; then
if code --list-extensions 2>/dev/null | grep -q "sst-dev.opencode"; then
log_info "VS Code extension 'sst-dev.opencode' already installed"
else
log_info "Installing VS Code extension 'sst-dev.opencode'..."
code --install-extension sst-dev.opencode
log_info "VS Code extension installed successfully"
fi
else
log_warn "'code' command not found. Skipping VS Code extension installation."
fi
# --- Step 5: Verify ---
log_info "Verifying configuration..."
if [ -f "$AUTH_FILE" ]; then
log_info "auth.json written to: $AUTH_FILE"
log_info "Configured providers:"
node -e "const a=require('$AUTH_FILE');Object.keys(a).forEach(k=>console.log(' - '+k))"
else
log_error "Failed to write auth.json"
exit 1
fi
if [ -f "$CONFIG_FILE" ]; then
log_info "opencode.json written to: $CONFIG_FILE"
log_info "Default model: $(node -e "console.log(require('$CONFIG_FILE').model)")"
log_info "LSP enabled: $(node -e "console.log(require('$CONFIG_FILE').lsp)")"
log_info "Permission: $(node -e "console.log(require('$CONFIG_FILE').permission)")"
else
log_error "Failed to write opencode.json"
exit 1
fi
log_info "OpenCode setup complete!"
log_info "Run 'opencode' to start. Default model: $DEFAULT_MODEL"