From a57672550656f1835385b8ee9569697bfb71380e Mon Sep 17 00:00:00 2001 From: Olivers Vitins Date: Sun, 19 May 2024 02:46:32 +0300 Subject: [PATCH] feat(commands): open default editor upon footer/body writing (#24) This commit is being made to implement an ability to create multi-line bodies and footers in RCZ. Default behavior is to present tje user with a prompt inquiring on whether they wish to write a body/footer; the initial value is false as both of these parts of the commit message are used rather rarely. Proposed an accepted during TD meeting 18.05.2024 --- src/commands/commit.ts | 79 ++++++++++++++++++++++++++++++++++++------ src/utils/functions.ts | 19 +++++++++- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/commands/commit.ts b/src/commands/commit.ts index b1c9eab..89baa14 100644 --- a/src/commands/commit.ts +++ b/src/commands/commit.ts @@ -18,7 +18,11 @@ along with RCZ. If not, see . */ import { Command } from "commander"; -import { checkForUpdates, getConfig } from "../utils/functions"; +import { + checkForUpdates, + getConfig, + getDefaultEditor, +} from "../utils/functions"; import { cancel, confirm, @@ -33,6 +37,32 @@ import { import { existsSync } from "fs"; import { join } from "path"; import simpleGit, { ResetMode } from "simple-git"; +import { readFile, writeFile } from "fs/promises"; +import { tmpdir } from "os"; +import { spawn } from "child_process"; + +const getEditorInput = async (defaultValue = "") => { + const tempFilePath = join(tmpdir(), `io.resultium.rcz-${Date.now()}.txt`); + await writeFile(tempFilePath, defaultValue); + + const editor = spawn(getDefaultEditor(), [tempFilePath], { + stdio: "inherit", + }); + + return await new Promise((resolve) => { + editor.on("exit", async (code) => { + if (code !== 0) { + throw new Error(`Editor exited with code ${code}`); + } + + resolve((await readFile(tempFilePath)).toString()); + }); + + editor.on("error", (err) => { + throw err; + }); + }); +}; const command = new Command("commit") .alias("c") @@ -224,27 +254,56 @@ const command = new Command("commit") process.exit(0); } - const body = await text({ - message: `Insert a commit body (motivation or elaboration for the change), can be left empty`, - placeholder: - "improves regex for suspicious character check in router requests", + let body: null | string = null; + const writeBody = await confirm({ + message: `Would you like to insert a commit body (elaboration for the change)?`, + initialValue: false, }); - if (isCancel(body)) { + if (isCancel(writeBody)) { cancel("Commit creation cancelled"); process.exit(0); } - const footer = await text({ - message: `Insert commit footer, can be left empty, e.g. Acked-by: @johndoe`, - placeholder: "Acked-by: @security", + if (writeBody) { + // Stop clack input + process.stdin.pause(); + + body = await getEditorInput( + `e.g. +* Revert "Guard against SMTP smuggling", commit faf23f1, by restoring the setting to its default. +* Revert "[security] SMTP smuggling: update short term fix (#2346)", commmit e931e10, by restoring the setting to its default. +* Set smtpd_forbid_bare_newline=normalize. + +Fixes #444 +`, + ); + + // Resume clack input + process.stdin.resume(); + } + + let footer: null | string = null; + const writeFooter = await confirm({ + message: `Would you like to insert a commit footer?`, + initialValue: false, }); - if (isCancel(footer)) { + if (isCancel(writeFooter)) { cancel("Commit creation cancelled"); process.exit(0); } + if (writeFooter) { + process.stdin.pause(); + footer = await getEditorInput( + `e.g. +Co-authored-by: @john +`, + ); + process.stdin.resume(); + } + const isBreaking = await confirm({ message: "Does this commit have breaking changes?", initialValue: false, diff --git a/src/utils/functions.ts b/src/utils/functions.ts index b31a131..0c911ce 100644 --- a/src/utils/functions.ts +++ b/src/utils/functions.ts @@ -23,7 +23,7 @@ import { join } from "path"; import { cwd } from "process"; import { Config } from "../types"; import { request } from "http"; -import { tmpdir } from "os"; +import { platform, tmpdir } from "os"; import { execSync } from "child_process"; import { gt } from "semver"; import { note } from "@clack/prompts"; @@ -110,3 +110,20 @@ export const checkForUpdates = async () => { if (gt(cachedVersion, localVersion)) note(updateText); } }; + +export const getDefaultEditor = () => { + if (process.env.EDITOR) { + return process.env.EDITOR; + } + + if (process.env.VISUAL) { + return process.env.VISUAL; + } + + // Windows as always being the special kid + if (platform() === "win32") { + return "notepad"; + } else { + return "vi"; + } +};