const fs = require("node:fs"); const path = require("node:path"); const { execFileSync } = require("node:child_process"); // Run against the v0.5.0 checkout after building shared, ssh-client, server and web. // All repositories and application data are temporary; SSH only listens on loopback. const os = require("node:os"); const project = path.resolve(process.argv[2] || process.cwd()); const root = fs.mkdtempSync(path.join(os.tmpdir(), "alune-readme-v0.5.0-")); process.env.ALUNE_DATA_DIR = path.join(root, "app-data"); process.env.GIT_CONFIG_GLOBAL = path.join(root, "empty.gitconfig"); process.env.GIT_CONFIG_NOSYSTEM = "1"; fs.writeFileSync(process.env.GIT_CONFIG_GLOBAL, ""); const { startServer } = require(project + "/apps/server/dist/bootstrap"); const { RepositoryService } = require( project + "/apps/server/dist/repository/repository.service", ); const { ConnectionService } = require( project + "/apps/server/dist/connection/connection.service", ); const { startSSHServer } = require( project + "/packages/ssh-client/tests/helpers/ssh-server.cjs", ); const git = (cwd, ...args) => execFileSync("git", ["-C", cwd, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], }); const write = (repo, file, content) => { fs.mkdirSync(path.dirname(path.join(repo, file)), { recursive: true }); fs.writeFileSync(path.join(repo, file), content); }; const commit = (repo, message) => { git(repo, "add", "."); git(repo, "commit", "-qm", message); }; const workspaceBefore = `import { useState } from 'react'; import { RepositoryTree } from './RepositoryTree'; import { FilePreview } from './FilePreview'; export function Workspace() { const [selectedFile, setSelectedFile] = useState('README.md'); return (
); } `; const workspaceAfter = `import { useState } from 'react'; import { RepositoryTree } from './RepositoryTree'; import { FilePreview } from './FilePreview'; import { useCodeAppearance } from '../settings/codeAppearance'; export function Workspace() { const [selectedFile, setSelectedFile] = useState('README.md'); const appearance = useCodeAppearance(); return (
); } `; async function main() { const repo = path.join(root, "alune"); fs.mkdirSync(repo); git(repo, "init", "-q", "-b", "main"); git(repo, "config", "user.name", "Alune Demo"); git(repo, "config", "user.email", "demo@example.invalid"); write(repo, "README.md", "# Alune\n\n本地与 SSH 远程 Git 工作区。\n"); write(repo, "src/components/Workspace.tsx", workspaceBefore); write( repo, "src/settings/codeAppearance.ts", 'export const defaultAppearance = {\n theme: "catppuccin-latte",\n fontFamily: "SFMono-Regular",\n fontSize: 13,\n};\n', ); write( repo, "src/styles/workspace.css", ".workspace {\n display: grid;\n grid-template-columns: 236px 1fr;\n height: 100%;\n}\n", ); write( repo, "package.json", JSON.stringify( { name: "alune-demo", version: "0.5.0", private: true }, null, 2, ) + "\n", ); commit(repo, "feat(workspace): 统一管理本地与 SSH 仓库"); git(repo, "checkout", "-qb", "feature/network-proxy"); write( repo, "src/settings/networkProxy.ts", 'export const networkProxy = {\n enabled: false,\n protocol: "http",\n host: "127.0.0.1",\n port: 7890,\n};\n', ); commit(repo, "feat(proxy): 添加 HTTP、HTTPS 与 SOCKS5 代理设置"); git(repo, "checkout", "-q", "main"); write( repo, "src/settings/accessTokens.ts", "export interface AccessToken {\n id: string;\n name: string;\n host: string;\n}\n", ); commit(repo, "feat(repository): 按仓库远端选择命名访问令牌"); git( repo, "merge", "--no-ff", "feature/network-proxy", "-m", "feat(proxy): 接入网络代理与远端 Git 转发", ); write( repo, "docs/code-themes.md", "# 代码阅读\n\n支持 Catppuccin 主题、自定义字体及 JSON 主题导入。\n", ); commit(repo, "feat(appearance): 支持代码主题与字体配置"); git(repo, "tag", "v0.5.0"); const remote = path.join(root, "origin.git"); git(root, "clone", "-q", "--bare", repo, remote); git(repo, "remote", "add", "origin", remote); git(repo, "fetch", "-q", "origin"); git(repo, "branch", "--set-upstream-to=origin/main", "main"); git(repo, "checkout", "-qb", "feature/code-reading"); const worktree = path.join(root, "alune-review"); git(repo, "worktree", "add", "-qb", "review/0.5.0", worktree, "main"); write(repo, "src/components/Workspace.tsx", workspaceAfter); write( repo, "src/settings/codeAppearance.ts", 'export const defaultAppearance = {\n lightTheme: "catppuccin-latte",\n darkTheme: "catppuccin-mocha",\n fontFamily: "JetBrains Mono",\n fontSize: 14,\n};\n', ); git(repo, "add", "src/settings/codeAppearance.ts"); write( repo, "README.md", "# Alune\n\n本地与 SSH 远程 Git 工作区。\n\n## 代码阅读\n\n在设置中选择主题、字体与字号。\n", ); git(repo, "add", "README.md"); write( repo, "src/styles/workspace.css", ".workspace {\n display: grid;\n grid-template-columns: 236px minmax(0, 1fr);\n height: 100%;\n overflow: hidden;\n}\n", ); write( repo, "docs/keyboard-shortcuts.md", "# 工作区快捷键\n\n- Cmd/Ctrl+B:显示或隐藏仓库导航\n- Cmd/Ctrl+,:打开设置\n- Alt+←/→:移动仓库标签\n", ); const design = path.join(root, "design-system"); const api = path.join(root, "api-service"); git(root, "clone", "-q", remote, design); git(root, "clone", "-q", remote, api); const ssh = await startSSHServer(); const app = await startServer({ host: "127.0.0.1", port: 0, webRoot: project + "/apps/web/dist", }); const repositories = app.get(RepositoryService); const connections = app.get(ConnectionService); const local = await repositories.addLocal(repo); const second = await repositories.addLocal(design); const connection = await connections.create({ name: "开发服务器(演示)", ...ssh.options, authType: "password", }); const sshRepo = await repositories.add(connection.id, api); const url = await app.getUrl(); const state = { root, url, local, second, sshRepo, connectionId: connection.id, worktree, }; fs.writeFileSync( path.join(root, "fixture-state.json"), JSON.stringify(state, null, 2), ); console.log("README_FIXTURE " + JSON.stringify(state)); let closing = false; const close = async () => { if (closing) return; closing = true; await app.close(); app.get("DATABASE").close(); await ssh.close(); fs.rmSync(root, { recursive: true, force: true }); process.exit(0); }; process.once("SIGINT", close); process.once("SIGTERM", close); } main().catch((error) => { console.error(error); process.exit(1); });