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