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);
});