chore: 初始化仓库

This commit is contained in:
Lydanne
2026-02-15 22:02:21 +08:00
commit 08d011d63f
381 changed files with 87202 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
import { Command, CommandRunner, Option } from "nest-commander";
import { t } from "@spaceflow/core";
import { CiShellService } from "./ci-shell.service";
export interface CiShellOptions {
dryRun: boolean;
}
@Command({
name: "ci-shell",
description: t("ci-shell:description"),
arguments: "<command>",
argsDescription: {
command: t("ci-shell:argsDescription.command"),
},
})
export class CiShellCommand extends CommandRunner {
constructor(protected readonly ciShellService: CiShellService) {
super();
}
async run(passedParams: string[], options: CiShellOptions): Promise<void> {
const command = passedParams.join(" ");
if (!command) {
console.error(t("ci-shell:noCommand"));
process.exit(1);
}
console.log(`DRY-RUN mode: ${options.dryRun ? "enabled" : "disabled"}`);
try {
const context = this.ciShellService.getContextFromEnv(options);
await this.ciShellService.execute(context, command);
} catch (error) {
console.error(
t("common.executionFailed", { error: error instanceof Error ? error.message : error }),
);
process.exit(1);
}
}
@Option({
flags: "-d, --dry-run",
description: t("common.options.dryRun"),
})
parseDryRun(val: boolean): boolean {
return val;
}
}

View File

@@ -0,0 +1,11 @@
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { GitProviderModule, ciConfig } from "@spaceflow/core";
import { CiShellCommand } from "./ci-shell.command";
import { CiShellService } from "./ci-shell.service";
@Module({
imports: [ConfigModule.forFeature(ciConfig), GitProviderModule.forFeature()],
providers: [CiShellCommand, CiShellService],
})
export class CiShellModule {}

View File

@@ -0,0 +1,149 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { execSync } from "child_process";
import { GitProviderService, BranchProtection, CiConfig } from "@spaceflow/core";
import { CiShellOptions } from "./ci-shell.command";
export interface CiShellContext extends CiShellOptions {
owner: string;
repo: string;
branch: string;
}
export interface CiShellResult {
success: boolean;
message: string;
protection?: BranchProtection | null;
}
@Injectable()
export class CiShellService {
constructor(
protected readonly gitProvider: GitProviderService,
protected readonly configService: ConfigService,
) {}
getContextFromEnv(options: CiShellOptions): CiShellContext {
this.gitProvider.validateConfig();
const ciConf = this.configService.get<CiConfig>("ci");
const repository = ciConf?.repository;
const branch = ciConf?.refName;
if (!repository) {
throw new Error("缺少配置 ci.repository (环境变量 GITHUB_REPOSITORY)");
}
if (!branch) {
throw new Error("缺少配置 ci.refName (环境变量 GITHUB_REF_NAME)");
}
const [owner, repo] = repository.split("/");
if (!owner || !repo) {
throw new Error(`ci.repository 格式不正确,期望 "owner/repo",实际: "${repository}"`);
}
return {
owner,
repo,
branch,
...options,
};
}
async execute(context: CiShellContext, command: string): Promise<void> {
try {
// 1. 锁定分支
await this.handleBegin(context);
try {
// 2. 执行命令
console.log(`🏃 正在执行命令...`);
console.log(`> ${command}`);
if (context.dryRun) {
console.log(`🔍 [DRY-RUN] 跳过命令执行`);
} else {
execSync(command, { stdio: "inherit" });
}
console.log("✅ 命令执行成功");
} catch (error) {
console.error("❌ 命令执行失败:", error);
// 出错时也要尝试解锁
await this.handleEnd(context);
process.exit(1);
}
// 3. 解锁分支
await this.handleEnd(context);
} catch (error) {
console.error("执行失败:", error instanceof Error ? error.message : error);
process.exit(1);
}
}
protected async handleBegin(context: CiShellContext): Promise<CiShellResult> {
const { owner, repo, branch, dryRun } = context;
if (dryRun) {
console.log(`🔍 [DRY-RUN] 将锁定分支: ${owner}/${repo}#${branch}`);
return {
success: true,
message: "DRY-RUN: 分支锁定已跳过",
protection: null,
};
}
console.log(`🔒 正在锁定分支: ${owner}/${repo}#${branch}`);
const protection = await this.gitProvider.lockBranch(owner, repo, branch);
console.log(`✅ 分支已锁定`);
console.log(` 规则名称: ${protection.rule_name || protection.branch_name}`);
console.log(` 允许推送: ${protection.enable_push ? "是" : "否"}`);
return {
success: true,
message: "分支锁定完成",
protection,
};
}
protected async handleEnd(context: CiShellContext): Promise<CiShellResult> {
const { owner, repo, branch, dryRun } = context;
if (dryRun) {
console.log(`🔍 [DRY-RUN] 将解锁分支: ${owner}/${repo}#${branch}`);
return {
success: true,
message: "DRY-RUN: 分支解锁已跳过",
protection: null,
};
}
console.log(`🔓 正在解锁分支: ${owner}/${repo}#${branch}`);
const protection = await this.gitProvider.unlockBranch(owner, repo, branch);
if (protection) {
console.log(`✅ 分支已解锁`);
console.log(` 规则名称: ${protection.rule_name || protection.branch_name}`);
console.log(` 允许推送: ${protection.enable_push ? "是" : "否"}`);
return {
success: true,
message: "分支解锁完成",
protection,
};
} else {
console.log(`✅ 分支本身没有保护规则,无需解锁`);
return {
success: true,
message: "分支本身没有保护规则,无需解锁",
protection: null,
};
}
}
}

View File

@@ -0,0 +1,24 @@
import "./locales";
import { SpaceflowExtension, SpaceflowExtensionMetadata, t } from "@spaceflow/core";
import { CiShellModule } from "./ci-shell.module";
export class CiShellExtension implements SpaceflowExtension {
getMetadata(): SpaceflowExtensionMetadata {
return {
name: "ci-shell",
commands: ["ci-shell"],
configKey: "ci-shell",
version: "1.0.0",
description: t("ci-shell:extensionDescription"),
};
}
getModule() {
return CiShellModule;
}
}
export default CiShellExtension;
export * from "./ci-shell.command";
export * from "./ci-shell.service";
export * from "./ci-shell.module";

View File

@@ -0,0 +1,6 @@
{
"description": "Execute shell commands between branch lock/unlock",
"argsDescription.command": "Shell command to execute",
"noCommand": "❌ Please provide a shell command to execute",
"extensionDescription": "CI shell command for executing shell commands between branch lock/unlock"
}

View File

@@ -0,0 +1,11 @@
import { addLocaleResources } from "@spaceflow/core";
import zhCN from "./zh-cn/ci-shell.json";
import en from "./en/ci-shell.json";
/** ci-shell 命令 i18n 资源 */
export const ciShellLocales: Record<string, Record<string, string>> = {
"zh-CN": zhCN,
en,
};
addLocaleResources("ci-shell", ciShellLocales);

View File

@@ -0,0 +1,6 @@
{
"description": "在分支锁定/解锁之间执行 Shell 命令",
"argsDescription.command": "要执行的 Shell 命令",
"noCommand": "❌ 请提供要执行的 Shell 命令",
"extensionDescription": "CI Shell 命令,用于在分支锁定/解锁之间执行 Shell 命令"
}