GitRoot

Craft your forge, Build your project, Grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5import { JSON } from "json-as";
  6import {
  7  Commit,
  8  File,
  9  Plugin,
 10  PluginRun,
 11  PluginRunWhen,
 12  PluginWrite,
 13  PluginWriteRight,
 14  PluginWriteRightCan,
 15  Register,
 16  ReportLevel,
 17  Server,
 18} from "plugin/server";
 19import { getInitShContent } from "./embedded-data";
 20
 21export * from "plugin/exports";
 22
 23@json
 24class Conf {
 25  static default(): Map<string, string> {
 26    return new Map<string, string>();
 27  }
 28}
 29
 30const defaultRun: PluginRun = new PluginRun(
 31  "**/*",
 32  ["*"],
 33  [PluginRunWhen.Add, PluginRunWhen.Mod, PluginRunWhen.Del],
 34  [],
 35  new PluginWrite(
 36    [
 37      new PluginWriteRight(".gitroot/init.sh", [
 38        PluginWriteRightCan.Add,
 39        PluginWriteRightCan.Mod,
 40        PluginWriteRightCan.Del,
 41      ]),
 42      new PluginWriteRight(".gitroot/allowed_signers", [
 43        PluginWriteRightCan.Add,
 44        PluginWriteRightCan.Mod,
 45        PluginWriteRightCan.Del,
 46      ]),
 47    ],
 48    [],
 49    [],
 50    [],
 51  ),
 52  Conf.default(),
 53);
 54
 55class Stigma implements Plugin {
 56  private server: Server;
 57  private conf: Conf | null = null;
 58  private goodCommits: Array<Commit> = new Array<Commit>();
 59  private notSignedCommits: Array<Commit> = new Array<Commit>();
 60  private badCommits: Array<Commit> = new Array<Commit>();
 61
 62  constructor(server: Server) {
 63    this.server = server;
 64  }
 65
 66  init(
 67    repoName: string,
 68    confHasChanged: boolean,
 69    serializedConf: string,
 70  ): void {
 71    this.conf = JSON.parse<Conf>(serializedConf);
 72    if (!this.server.worktree().exists(".gitroot/init.sh")) {
 73      this.server
 74        .worktree()
 75        .writeContent(".gitroot/init.sh", getInitShContent());
 76    }
 77    if (!this.server.worktree().exists(".gitroot/allowed_signers")) {
 78      this.server.worktree().writeContent(".gitroot/allowed_signers", "");
 79    }
 80    this.server.commitAllIfNeeded("init stigma plugin");
 81    return;
 82  }
 83
 84  startCommit(commit: Commit): void {
 85    if (commit.isSigned && commit.isValidSignature) {
 86      this.goodCommits.push(commit);
 87    } else if (commit.isSigned === false) {
 88      this.notSignedCommits.push(commit);
 89    } else if (commit.isValidSignature === false) {
 90      this.badCommits.push(commit);
 91    }
 92    return;
 93  }
 94
 95  addFile(file: File): void {
 96    return;
 97  }
 98
 99  modFile(file: File): void {
100    return;
101  }
102
103  delFile(file: File): void {
104    return;
105  }
106
107  endCommit(commit: Commit): void {
108    return;
109  }
110
111  finish(): void {
112    if (this.badCommits.length > 0) {
113      this.server.report(ReportLevel.Error, ["Commits are bad signed!"]);
114    }
115    if (this.notSignedCommits.length > 0) {
116      this.server.report(ReportLevel.Warning, ["Commits are not signed!"]);
117    }
118    if (this.goodCommits.length > 0) {
119      let content =
120        this.server.worktree().readAll(".gitroot/allowed_signers") || "";
121      for (let i = 0; i < this.goodCommits.length; i++) {
122        const c = this.goodCommits[i];
123        if (!content.includes(c.signingKey)) {
124          content = content + `${c.committerEmail} ${c.signingKey}\n`;
125        }
126      }
127      this.server.worktree().writeContent(".gitroot/allowed_signers", content);
128      this.server.commitAllIfNeeded("added signers");
129    }
130    this.conf = null;
131    this.goodCommits = [];
132    this.notSignedCommits = [];
133    this.badCommits = [];
134    return;
135  }
136}
137
138function build(server: Server): Plugin {
139  return new Stigma(server);
140}
141
142export function install(): void {
143  Register([defaultRun], build);
144}