10 Commits

Author SHA1 Message Date
fdbced0453 chore(release): v1.15.0 2024-05-19 02:51:11 +03:00
e8160a6f33 feat(commands): trim body, footer (#24) 2024-05-19 02:49:18 +03:00
a576725506 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
2024-05-19 02:46:32 +03:00
1bce93a24e chore(release): v1.14.0 2024-05-04 23:56:02 +03:00
f767b846d8 feat(commands): add ability to choose files to stage (#22) 2024-05-04 23:55:34 +03:00
0cac22a7e0 perf(changelog): improve generation time by ~2800 times (#23) 2024-05-04 23:01:10 +03:00
6df1021561 chore(release): v1.13.1 2024-05-04 21:56:07 +03:00
87edf8cfad fix(changelog): wrong semver sequence generation (#21)
now made ascending with "Unreleased" on top
2024-05-04 21:55:04 +03:00
47a8b05856 refactor(commands): change head error message
to sound more natively
2024-03-12 18:41:36 +02:00
b6d749beae build: fix version regex to include pre-releases 2024-03-08 23:42:42 +02:00
7 changed files with 182 additions and 71 deletions

View File

@@ -3,20 +3,20 @@ const { execSync } = require("child_process");
const packageFile = fs.readFileSync("./package.json").toString(); const packageFile = fs.readFileSync("./package.json").toString();
const newPackageFile = packageFile.replace( const newPackageFile = packageFile.replace(
/"version": "[0-9]+.[0-9]+.[0-9]+"/, /"version": "[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc|dev)\.[0-9]+)?"/,
`"version": "${__NEW_VERSION__}"` `"version": "${__NEW_VERSION__}"`,
); );
fs.writeFileSync("./package.json", newPackageFile); fs.writeFileSync("./package.json", newPackageFile);
const indexFile = fs.readFileSync("./src/index.ts").toString(); const indexFile = fs.readFileSync("./src/index.ts").toString();
const newIndexFile = indexFile.replace( const newIndexFile = indexFile.replace(
/version\("[0-9]+\.[0-9]+\.[0-9]+"\)/, /version\("[0-9]+\.[0-9]+\.[0-9]+(-(alpha|beta|rc|dev)\.[0-9]+)?"\)/,
`version("${__NEW_VERSION__}")` `version("${__NEW_VERSION__}")`,
); );
fs.writeFileSync("./src/index.ts", newIndexFile); fs.writeFileSync("./src/index.ts", newIndexFile);
execSync( execSync(
`rcz changelog --show-hashes --unreleased-as v${__NEW_VERSION__} > CHANGELOG.md` `rcz changelog --show-hashes --unreleased-as v${__NEW_VERSION__} > CHANGELOG.md`,
); );

View File

@@ -1,9 +1,27 @@
# Changelog # Changelog
Generation of this changelog is based on commits Generation of this changelog is based on commits
## v1.15.0
### Features
- [e8160a6f3] - **commands**: trim body, footer (#24)
- [a57672550] - **commands**: open default editor upon footer/body writing (#24)
## v1.14.0
### Features
- [f767b846d] - **commands**: add ability to choose files to stage (#22)
### Miscellaneous
- [1bce93a24] - **release**: v1.14.0
- [0cac22a7e] - **changelog**: improve generation time by ~2800 times (#23)
## v1.13.1
### Fixes
- [87edf8cfa] - **changelog**: wrong semver sequence generation (#21)
### Miscellaneous
- [6df102156] - **release**: v1.13.1
- [47a8b0585] - **commands**: change head error message
- [b6d749bea] - fix version regex to include pre-releases
## v1.13.0 ## v1.13.0
### Fixes ### Fixes
- [7b499d763] - **commands**: commit crash upon missing HEAD (#19) - [7b499d763] - **commands**: commit crash upon missing HEAD (#19)
### Miscellaneous ### Miscellaneous
- [87a7c4dcc] - **release**: v1.13.0
- [6ce6e88d8] - Merge pull request 'Refactor into multiple command files' (#20) from refactor/#18 into main - [6ce6e88d8] - Merge pull request 'Refactor into multiple command files' (#20) from refactor/#18 into main
## v1.13.0-rc.0 ## v1.13.0-rc.0
### Miscellaneous ### Miscellaneous
@@ -108,13 +126,13 @@ Generation of this changelog is based on commits
- [2670f79e2] - Merge pull request 'Release v1.3.0' (#5) from develop into main - [2670f79e2] - Merge pull request 'Release v1.3.0' (#5) from develop into main
- [3afc2ed07] - Merge branch 'main' into develop - [3afc2ed07] - Merge branch 'main' into develop
- [45458d14e] - **changelog**: generate for v1.3.0 - [45458d14e] - **changelog**: generate for v1.3.0
- [d61c9ecf2] - Merge pull request 'Make commit signing possible' (#3) from develop into main
## v1.3.0 ## v1.3.0
### Features ### Features
- [755da3bb5] - **commands**: add ability to write a footer - [755da3bb5] - **commands**: add ability to write a footer
- [9311be80b] - **commands**: add commit message validation command (#4) - [9311be80b] - **commands**: add commit message validation command (#4)
### Miscellaneous ### Miscellaneous
- [f3c55fac3] - **release**: v1.3.0 - [f3c55fac3] - **release**: v1.3.0
- [d61c9ecf2] - Merge pull request 'Make commit signing possible' (#3) from develop into main
- [8816db86f] - **changelog**: generate - [8816db86f] - **changelog**: generate
## v1.2.0 ## v1.2.0
### Features ### Features

View File

@@ -1,6 +1,6 @@
{ {
"name": "@resultium/rcz", "name": "@resultium/rcz",
"version": "1.13.0", "version": "1.15.0",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"description": "Resultium commit standardization library, inspired by conventional commits", "description": "Resultium commit standardization library, inspired by conventional commits",
"main": "./dist/index.js", "main": "./dist/index.js",

View File

@@ -20,7 +20,7 @@ along with RCZ. If not, see <https://www.gnu.org/licenses/>.
import { Command } from "commander"; import { Command } from "commander";
import simpleGit from "simple-git"; import simpleGit from "simple-git";
import { CommitStack } from "../types"; import { CommitStack } from "../types";
import { sort } from "semver"; import { gt, sort } from "semver";
const command = new Command("changelog") const command = new Command("changelog")
.alias("ch") .alias("ch")
@@ -51,69 +51,50 @@ const command = new Command("changelog")
for (const commit of commits) { for (const commit of commits) {
const tag = const tag =
sort((await simpleGit().tags([`--contains=${commit.hash}`])).all)[0]! || (commit.refs.match(/tag: (\S+)(?:,|$)/)?.[1] ?? lastTag) || unreleased;
unreleased;
const currentCommitStack = parsedCommitStacks.find( let currentCommitStackIndex = parsedCommitStacks.findIndex(
(commitStack) => commitStack.version === tag, (commitStack) => commitStack.version === tag,
) || { );
version: tag || unreleased,
breaking: [],
features: [],
fixes: [],
miscellaneous: [],
};
if (lastTag !== tag) { if (currentCommitStackIndex === -1) {
parsedCommitStacks = [currentCommitStack, ...parsedCommitStacks]; parsedCommitStacks.push({
version: tag || unreleased,
breaking: [],
features: [],
fixes: [],
miscellaneous: [],
});
currentCommitStackIndex = parsedCommitStacks.findIndex(
(commitStack) => commitStack.version === tag,
);
} }
if (commit.message.includes("!:")) { if (commit.message.includes("!:")) {
parsedCommitStacks = [ parsedCommitStacks[currentCommitStackIndex].breaking.push(commit);
{
...currentCommitStack,
breaking: [...currentCommitStack.breaking, commit],
},
...parsedCommitStacks.filter(
(commitStack) => commitStack.version !== tag,
),
];
} else if (commit.message.startsWith("feat")) { } else if (commit.message.startsWith("feat")) {
parsedCommitStacks = [ parsedCommitStacks[currentCommitStackIndex].features.push(commit);
{
...currentCommitStack,
features: [...currentCommitStack.features, commit],
},
...parsedCommitStacks.filter(
(commitStack) => commitStack.version !== tag,
),
];
} else if (commit.message.startsWith("fix")) { } else if (commit.message.startsWith("fix")) {
parsedCommitStacks = [ parsedCommitStacks[currentCommitStackIndex].fixes.push(commit);
{
...currentCommitStack,
fixes: [...currentCommitStack.fixes, commit],
},
...parsedCommitStacks.filter(
(commitStack) => commitStack.version !== tag,
),
];
} else { } else {
parsedCommitStacks = [ parsedCommitStacks[currentCommitStackIndex].miscellaneous.push(commit);
{
...currentCommitStack,
miscellaneous: [...currentCommitStack.miscellaneous, commit],
},
...parsedCommitStacks.filter(
(commitStack) => commitStack.version !== tag,
),
];
} }
lastTag = tag; lastTag = tag;
} }
parsedCommitStacks = parsedCommitStacks.reverse(); // Might be confusing so I will leave mdn docs here
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
parsedCommitStacks = parsedCommitStacks.sort((a, b) => {
if (a.version === unreleased) {
return -1;
} else if (b.version === unreleased) {
return 1;
} else {
return gt(a.version, b.version) ? -1 : 1;
}
});
if (lastOnly) { if (lastOnly) {
parsedCommitStacks = [parsedCommitStacks[0]]; parsedCommitStacks = [parsedCommitStacks[0]];

View File

@@ -18,12 +18,17 @@ along with RCZ. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Command } from "commander"; import { Command } from "commander";
import { checkForUpdates, getConfig } from "../utils/functions"; import {
checkForUpdates,
getConfig,
getDefaultEditor,
} from "../utils/functions";
import { import {
cancel, cancel,
confirm, confirm,
intro, intro,
isCancel, isCancel,
multiselect,
note, note,
outro, outro,
select, select,
@@ -31,7 +36,33 @@ import {
} from "@clack/prompts"; } from "@clack/prompts";
import { existsSync } from "fs"; import { existsSync } from "fs";
import { join } from "path"; import { join } from "path";
import simpleGit from "simple-git"; 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<string>((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") const command = new Command("commit")
.alias("c") .alias("c")
@@ -69,6 +100,37 @@ const command = new Command("commit")
process.exit(0); process.exit(0);
} }
const stageableFiles = (await simpleGit().status()).files;
if (stageAll === false) {
const stagedFiles = (await simpleGit().diff(["--name-only", "--cached"]))
.split("\n")
.filter((file) => file);
const filesToStage = await multiselect({
message: "Which files would you like to stash?",
options: stageableFiles.map((file) => ({
value: file.path,
label: file.path,
hint: file.from,
})),
initialValues: stagedFiles,
required: false,
});
if (isCancel(filesToStage)) {
cancel("Commit creation cancelled");
process.exit(0);
}
await simpleGit().reset(ResetMode.MIXED);
await simpleGit().add(filesToStage);
} else if (stageAll === true) {
note(
stageableFiles.map((file) => file.path).join("\n"),
"Committing following files",
);
}
try { try {
const changedLines = ( const changedLines = (
( (
@@ -94,7 +156,7 @@ const command = new Command("commit")
} }
} }
} catch { } catch {
note("HEAD hasn't been found, skipping commit line amount check"); note("HEAD hasn't been found, skipping commit line sum check");
} }
const type: string | symbol = await select({ const type: string | symbol = await select({
@@ -192,27 +254,60 @@ const command = new Command("commit")
process.exit(0); process.exit(0);
} }
const body = await text({ let body: null | string = null;
message: `Insert a commit body (motivation or elaboration for the change), can be left empty`, const writeBody = await confirm({
placeholder: message: `Would you like to insert a commit body (elaboration for the change)?`,
"improves regex for suspicious character check in router requests", initialValue: false,
}); });
if (isCancel(body)) { if (isCancel(writeBody)) {
cancel("Commit creation cancelled"); cancel("Commit creation cancelled");
process.exit(0); process.exit(0);
} }
const footer = await text({ if (writeBody) {
message: `Insert commit footer, can be left empty, e.g. Acked-by: @johndoe`, // Stop clack input
placeholder: "Acked-by: @security", 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
`,
)
).trim();
// 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"); cancel("Commit creation cancelled");
process.exit(0); process.exit(0);
} }
if (writeFooter) {
process.stdin.pause();
footer = (
await getEditorInput(
`e.g.
Co-authored-by: @john
`,
)
).trim();
process.stdin.resume();
}
const isBreaking = await confirm({ const isBreaking = await confirm({
message: "Does this commit have breaking changes?", message: "Does this commit have breaking changes?",
initialValue: false, initialValue: false,

View File

@@ -29,7 +29,7 @@ import { join } from "path";
program program
.name("rcz") .name("rcz")
.description("Resultium commit standardization command-line interface") .description("Resultium commit standardization command-line interface")
.version("1.13.0"); .version("1.15.0");
const commandFiles = await readdir(join(__dirname, "commands")); const commandFiles = await readdir(join(__dirname, "commands"));

View File

@@ -23,7 +23,7 @@ import { join } from "path";
import { cwd } from "process"; import { cwd } from "process";
import { Config } from "../types"; import { Config } from "../types";
import { request } from "http"; import { request } from "http";
import { tmpdir } from "os"; import { platform, tmpdir } from "os";
import { execSync } from "child_process"; import { execSync } from "child_process";
import { gt } from "semver"; import { gt } from "semver";
import { note } from "@clack/prompts"; import { note } from "@clack/prompts";
@@ -110,3 +110,20 @@ export const checkForUpdates = async () => {
if (gt(cachedVersion, localVersion)) note(updateText); 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";
}
};