-
Conventional Commits Cheatsheet
# Conventional Commit Messages <sub><img src="https://img.icons8.com/dusk/1600/commit-git.png" height="64" /></sub>
See how [a minor change](#examples) to your commit message style can make a difference.
> [!TIP]
> Have a look at **[git-conventional-commits](https://github.com/qoomon/git-conventional-commits)** , a CLI util to ensure these conventions, determine version and generate changelogs
## Commit Message Formats
### Default
<pre>
<b><a href="#types"><type></a></b></font>(<b><a href="#scopes"><optional scope></a></b>): <b><a href="#description"><description></a></b>
<sub>empty separator line</sub>
<b><a href="#body"><optional body></a></b>
<sub>empty separator line</sub>
<b><a href="#footer"><optional footer></a></b>
</pre>
### Merge Commit
<pre>
Merge branch '<b><branch name></b>'
</pre>
<sup>Follows default git merge message</sup>
### Revert Commit
<pre>
Revert "<b><reverted commit subject line></b>"
</pre>
<sup>Follows default git revert message</sup>
### Inital Commit
```
chore: init
```
### Types
* API relevant changes
* `feat` Commits, that adds or remove a new feature
* `fix` Commits, that fixes a bug
* `refactor` Commits, that rewrite/restructure your code, however does not change any API behaviour
* `perf` Commits are special `refactor` commits, that improve performance
* `style` Commits, that do not affect the meaning (white-space, formatting, missing semi-colons, etc)
* `test` Commits, that add missing tests or correcting existing tests
* `docs` Commits, that affect documentation only
* `build` Commits, that affect build components like build tool, ci pipeline, dependencies, project version, ...
* `ops` Commits, that affect operational components like infrastructure, deployment, backup, recovery, ...
* `chore` Miscellaneous commits e.g. modifying `.gitignore`
### Scopes
The `scope` provides additional contextual information.
* Is an **optional** part of the format
* Allowed Scopes depends on the specific project
* Don't use issue identifiers as scopes
### Breaking Changes Indicator
Breaking changes should be indicated by an `!` before the `:` in the subject line e.g. `feat(api)!: remove status endpoint`
* Is an **optional** part of the format
### Description
The `description` contains a concise description of the change.
* Is a **mandatory** part of the format
* Use the imperative, present tense: "change" not "changed" nor "changes"
* Think of `This commit will...` or `This commit should...`
* Don't capitalize the first letter
* No dot (`.`) at the end
### Body
The `body` should include the motivation for the change and contrast this with previous behavior.
* Is an **optional** part of the format
* Use the imperative, present tense: "change" not "changed" nor "changes"
* This is the place to mention issue identifiers and their relations
### Footer
The `footer` should contain any information about **Breaking Changes** and is also the place to **reference Issues** that this commit refers to.
* Is an **optional** part of the format
* **optionally** reference an issue by its id.
* **Breaking Changes** should start with the word `BREAKING CHANGES:` followed by space or two newlines. The rest of the commit message is then used for this.
### Examples
* ```
feat: add email notifications on new direct messages
```
* ```
feat(shopping cart): add the amazing button
```
* ```
feat!: remove ticket list endpoint
refers to JIRA-1337
BREAKING CHANGES: ticket enpoints no longer supports list all entites.
```
* ```
fix(shopping-cart): prevent order an empty shopping cart
```
* ```
fix(api): fix wrong calculation of request body checksum
```
* ```
fix: add missing parameter to service call
The error occurred because of <reasons>.
```
* ```
perf: decrease memory footprint for determine uniqe visitors by using HyperLogLog
```
* ```
build: update dependencies
```
* ```
build(release): bump version to 1.0.0
```
* ```
refactor: implement fibonacci number calculation as recursion
```
* ```
style: remove empty line
```
---
## Git Hook Scripts to ensure commit message header format
<details>
<summary>Click to expand</summary>
### commit-msg Hook (local)
* Create a commit-msg hook using [git-conventional-commits cli](https://github.com/qoomon/git-conventional-commits?tab=readme-ov-file#automatically-validate-commit-message-convention-before-commit)
### pre-receive Hook (server side)
* create following file in your repository folder `.git/hooks/pre-receive`
```shell
#!/usr/bin/env bash
# Pre-receive hook that will block commits with messges that do not follow regex rule
commit_msg_type_regex='feat|fix|refactor|style|test|docs|build'
commit_msg_scope_regex='.{1,20}'
commit_msg_description_regex='.{1,100}'
commit_msg_regex="^(${commit_msg_type_regex})(\(${commit_msg_scope_regex}\))?: (${commit_msg_description_regex})\$"
merge_msg_regex="^Merge branch '.+'\$"
zero_commit="0000000000000000000000000000000000000000"
# Do not traverse over commits that are already in the repository
excludeExisting="--not --all"
error=""
while read oldrev newrev refname; do
# branch or tag get deleted
if [ "$newrev" = "$zero_commit" ]; then
continue
fi
# Check for new branch or tag
if [ "$oldrev" = "$zero_commit" ]; then
rev_span=`git rev-list $newrev $excludeExisting`
else
rev_span=`git rev-list $oldrev..$newrev $excludeExisting`
fi
for commit in $rev_span; do
commit_msg_header=$(git show -s --format=%s $commit)
if ! [[ "$commit_msg_header" =~ (${commit_msg_regex})|(${merge_msg_regex}) ]]; then
echo "$commit" >&2
echo "ERROR: Invalid commit message format" >&2
echo "$commit_msg_header" >&2
error="true"
fi
done
done
if [ -n "$error" ]; then
exit 1
fi
```
* ⚠ make `.git/hooks/pre-receive` executable (unix: `chmod +x '.git/hooks/pre-receive'`)
</details>
-----
## References
* https://www.conventionalcommits.org/
* https://github.com/angular/angular/blob/master/CONTRIBUTING.md
* http://karma-runner.github.io/1.0/dev/git-commit-msg.html
<br>
* https://github.com/github/platform-samples/tree/master/pre-receive-hooks
* https://github.community/t5/GitHub-Enterprise-Best-Practices/Using-pre-receive-hooks-in-GitHub-Enterprise/ba-p/13863
-
ANSI Escape Codes
# ANSI Escape Sequences
Standard escape codes are prefixed with `Escape`:
- Ctrl-Key: `^[`
- Octal: `\033`
- Unicode: `\u001b`
- Hexadecimal: `\x1B`
- Decimal: `27`
Followed by the command, somtimes delimited by opening square bracket (`[`), known as a Control Sequence Introducer (CSI), optionally followed by arguments and the command itself.
Arguments are delimeted by semi colon (`;`).
For example:
```sh
\x1b[1;31m # Set style to bold, red foreground.
```
## Sequences
- `ESC` - sequence starting with `ESC` (`\x1B`)
- `CSI` - Control Sequence Introducer: sequence starting with `ESC [` or CSI (`\x9B`)
- `DCS` - Device Control String: sequence starting with `ESC P` or DCS (`\x90`)
- `OSC` - Operating System Command: sequence starting with `ESC ]` or OSC (`\x9D`)
Any whitespaces between sequences and arguments should be ignored. They are present for improved readability.
## General ASCII Codes
| ----- | ------- | ----- | ---- | -------- | -------- | ------------------------------ |
| `BEL` | 7 | 007 | 0x07 | `\a` | `^G` | Terminal bell |
| `BS` | 8 | 010 | 0x08 | `\b` | `^H` | Backspace |
| `HT` | 9 | 011 | 0x09 | `\t` | `^I` | Horizontal TAB |
| `LF` | 10 | 012 | 0x0A | `\n` | `^J` | Linefeed (newline) |
| `VT` | 11 | 013 | 0x0B | `\v` | `^K` | Vertical TAB |
| `FF` | 12 | 014 | 0x0C | `\f` | `^L` | Formfeed (also: New page `NP`) |
| `CR` | 13 | 015 | 0x0D | `\r` | `^M` | Carriage return |
| `ESC` | 27 | 033 | 0x1B | `\e`[*](#escape) | `^[` | Escape character |
| `DEL` | 127 | 177 | 0x7F | `<none>` | `<none>` | Delete character |
<div id="escape"></div>
> **Note:** Some control escape sequences, like `\e` for `ESC`, are not guaranteed to work in all languages and compilers. It is recommended to use the decimal, octal or hex representation as escape code.
> **Note:** The **Ctrl-Key** representation is simply associating the non-printable characters from ASCII code 1 with the printable (letter) characters from ASCII code 65 ("A"). ASCII code 1 would be `^A` (Ctrl-A), while ASCII code 7 (BEL) would be `^G` (Ctrl-G). This is a common representation (and input method) and historically comes from one of the VT series of terminals.
## Cursor Controls
| :------------------------------------------------- | :------------------------------------------------------- |
| `ESC[H` | moves cursor to home position (0, 0) |
| `ESC[{line};{column}H` <br> `ESC[{line};{column}f` | moves cursor to line #, column # |
| `ESC[#A` | moves cursor up # lines |
| `ESC[#B` | moves cursor down # lines |
| `ESC[#C` | moves cursor right # columns |
| `ESC[#D` | moves cursor left # columns |
| `ESC[#E` | moves cursor to beginning of next line, # lines down |
| `ESC[#F` | moves cursor to beginning of previous line, # lines up |
| `ESC[#G` | moves cursor to column # |
| `ESC[6n` | request cursor position (reports as `ESC[#;#R`) |
| `ESC M` | moves cursor one line up, scrolling if needed |
| `ESC 7` | save cursor position (DEC) |
| `ESC 8` | restores the cursor to the last saved position (DEC) |
| `ESC[s` | save cursor position (SCO) |
| `ESC[u` | restores the cursor to the last saved position (SCO) |
> **Note:** Some sequences, like saving and restoring cursors, are private sequences and are not standardized. While some terminal emulators (i.e. xterm and derived) support both SCO and DEC sequences, they are likely to have different functionality. It is therefore recommended to use DEC sequences.
## Erase Functions
| :---------------- | :---------------------------------------- |
| `ESC[J` | erase in display (same as `ESC[0J`) |
| `ESC[0J` | erase from cursor until end of screen |
| `ESC[1J` | erase from cursor to beginning of screen |
| `ESC[2J` | erase entire screen |
| `ESC[3J` | erase saved lines |
| `ESC[K` | erase in line (same as `ESC[0K`) |
| `ESC[0K` | erase from cursor to end of line |
| `ESC[1K` | erase start of line to the cursor |
| `ESC[2K` | erase the entire line |
> Note: Erasing the line won't move the cursor, meaning that the cursor will stay at the last position it was at before the line was erased. You can use `\r` after erasing the line, to return the cursor to the start of the current line.
## Colors / Graphics Mode
| :---------------- | :------------- | :--------------------------------------------------------- |
| `ESC[1;34;{...}m` | | Set graphics modes for cell, separated by semicolon (`;`). |
| `ESC[0m` | | reset all modes (styles and colors) |
| `ESC[1m` | `ESC[22m` | set bold mode. |
| `ESC[2m` | `ESC[22m` | set dim/faint mode. |
| `ESC[3m` | `ESC[23m` | set italic mode. |
| `ESC[4m` | `ESC[24m` | set underline mode. |
| `ESC[5m` | `ESC[25m` | set blinking mode |
| `ESC[7m` | `ESC[27m` | set inverse/reverse mode |
| `ESC[8m` | `ESC[28m` | set hidden/invisible mode |
| `ESC[9m` | `ESC[29m` | set strikethrough mode. |
> **Note:** Some terminals may not support some of the graphic mode sequences listed above.
> **Note:** Both dim and bold modes are reset with the `ESC[22m` sequence. The `ESC[21m` sequence is a non-specified sequence for double underline mode and only work in some terminals and is reset with `ESC[24m`.
### Color codes
Most terminals support 8 and 16 colors, as well as 256 (8-bit) colors. These colors are set by the user, but have commonly defined meanings.
#### 8-16 Colors
| :--------- | :-------------------- | :-------------------- |
| Black | `30` | `40` |
| Red | `31` | `41` |
| Green | `32` | `42` |
| Yellow | `33` | `43` |
| Blue | `34` | `44` |
| Magenta | `35` | `45` |
| Cyan | `36` | `46` |
| White | `37` | `47` |
| Default | `39` | `49` |
| Reset | `0` | `0` |
> **Note:** the _Reset_ color is the reset code that resets _all_ colors and text effects, Use _Default_ color to reset colors only.
Most terminals, apart from the basic set of 8 colors, also support the "bright" or "bold" colors. These have their own set of codes, mirroring the normal colors, but with an additional `;1` in their codes:
```sh
# Set style to bold, red foreground.
\x1b[1;31mHello
# Set style to dimmed white foreground with red background.
\x1b[2;37;41mWorld
```
Terminals that support the [aixterm specification](https://sites.ualberta.ca/dept/chemeng/AIX-43/share/man/info/C/a_doc_lib/cmds/aixcmds1/aixterm.htm) provides bright versions of the ISO colors, without the need to use the bold modifier:
| :------------- | :-------------------- | :-------------------- |
| Bright Black | `90` | `100` |
| Bright Red | `91` | `101` |
| Bright Green | `92` | `102` |
| Bright Yellow | `93` | `103` |
| Bright Blue | `94` | `104` |
| Bright Magenta | `95` | `105` |
| Bright Cyan | `96` | `106` |
| Bright White | `97` | `107` |
#### 256 Colors
The following escape codes tells the terminal to use the given color ID:
| :---------------- | :-------------------- |
| `ESC[38;5;{ID}m` | Set foreground color. |
| `ESC[48;5;{ID}m` | Set background color. |
Where `{ID}` should be replaced with the color index from 0 to 255 of the following color table:
![256 Color table](https://user-images.githubusercontent.com/995050/47952855-ecb12480-df75-11e8-89d4-ac26c50e80b9.png)
The table starts with the original 16 colors (0-15).
The proceeding 216 colors (16-231) or formed by a 3bpc RGB value offset by 16, packed into a single value.
The final 24 colors (232-255) are grayscale starting from a shade slighly lighter than black, ranging up to shade slightly darker than white.
Some emulators interpret these steps as linear increments (`256 / 24`) on all three channels, although some emulators may explicitly define these values.
#### RGB Colors
More modern terminals supports [Truecolor](https://en.wikipedia.org/wiki/Color_depth#True_color_.2824-bit.29) (24-bit RGB), which allows you to set foreground and background colors using RGB.
These escape sequences are usually not well documented.
| :---------------------- | :--------------------------- |
| `ESC[38;2;{r};{g};{b}m` | Set foreground color as RGB. |
| `ESC[48;2;{r};{g};{b}m` | Set background color as RGB. |
> Note that `;38` and `;48` corresponds to the 16 color sequence and is interpreted by the terminal to set the foreground and background color respectively. Where as `;2` and `;5` sets the color format.
## Screen Modes
### Set Mode
| :---------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ESC[={value}h` | Changes the screen width or type to the mode specified by value. |
| `ESC[=0h` | 40 x 25 monochrome (text) |
| `ESC[=1h` | 40 x 25 color (text) |
| `ESC[=2h` | 80 x 25 monochrome (text) |
| `ESC[=3h` | 80 x 25 color (text) |
| `ESC[=4h` | 320 x 200 4-color (graphics) |
| `ESC[=5h` | 320 x 200 monochrome (graphics) |
| `ESC[=6h` | 640 x 200 monochrome (graphics) |
| `ESC[=7h` | Enables line wrapping |
| `ESC[=13h` | 320 x 200 color (graphics) |
| `ESC[=14h` | 640 x 200 color (16-color graphics) |
| `ESC[=15h` | 640 x 350 monochrome (2-color graphics) |
| `ESC[=16h` | 640 x 350 color (16-color graphics) |
| `ESC[=17h` | 640 x 480 monochrome (2-color graphics) |
| `ESC[=18h` | 640 x 480 color (16-color graphics) |
| `ESC[=19h` | 320 x 200 color (256-color graphics) |
| `ESC[={value}l` | Resets the mode by using the same values that Set Mode uses, except for 7, which disables line wrapping. The last character in this escape sequence is a lowercase L. |
### Common Private Modes
These are some examples of private modes, which are not defined by the specification, but are implemented in most terminals.
| :---------------- | :------------------------------ |
| `ESC[?25l` | make cursor invisible |
| `ESC[?25h` | make cursor visible |
| `ESC[?47l` | restore screen |
| `ESC[?47h` | save screen |
| `ESC[?1049h` | enables the alternative buffer |
| `ESC[?1049l` | disables the alternative buffer |
Refer to the [XTerm Control Sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html) for a more in-depth list of private modes defined by XTerm.
> Note: While these modes may be supported by the most terminals, some may not work in multiplexers like tmux.
### Keyboard Strings
```sh
ESC[{code};{string};{...}p
```
Redefines a keyboard key to a specified string.
The parameters for this escape sequence are defined as follows:
- `code` is one or more of the values listed in the following table. These values represent keyboard keys and key combinations. When using these values in a command, you must type the semicolons shown in this table in addition to the semicolons required by the escape sequence. The codes in parentheses are not available on some keyboards. `ANSI.SYS` will not interpret the codes in parentheses for those keyboards unless you specify the `/X` switch in the `DEVICE` command for `ANSI.SYS`.
- `string` is either the ASCII code for a single character or a string contained in quotation marks. For example, both 65 and "A" can be used to represent an uppercase A.
> **IMPORTANT:** Some of the values in the following table are not valid for all computers. Check your computer's documentation for values that are different.
#### List of keyboard strings
| ------------------------ | -------- | ---------- | --------- | --------- |
| F1 | 0;59 | 0;84 | 0;94 | 0;104 |
| F2 | 0;60 | 0;85 | 0;95 | 0;105 |
| F3 | 0;61 | 0;86 | 0;96 | 0;106 |
| F4 | 0;62 | 0;87 | 0;97 | 0;107 |
| F5 | 0;63 | 0;88 | 0;98 | 0;108 |
| F6 | 0;64 | 0;89 | 0;99 | 0;109 |
| F7 | 0;65 | 0;90 | 0;100 | 0;110 |
| F8 | 0;66 | 0;91 | 0;101 | 0;111 |
| F9 | 0;67 | 0;92 | 0;102 | 0;112 |
| F10 | 0;68 | 0;93 | 0;103 | 0;113 |
| F11 | 0;133 | 0;135 | 0;137 | 0;139 |
| F12 | 0;134 | 0;136 | 0;138 | 0;140 |
| HOME (num keypad) | 0;71 | 55 | 0;119 | \-- |
| UP ARROW (num keypad) | 0;72 | 56 | (0;141) | \-- |
| PAGE UP (num keypad) | 0;73 | 57 | 0;132 | \-- |
| LEFT ARROW (num keypad) | 0;75 | 52 | 0;115 | \-- |
| RIGHT ARROW (num keypad) | 0;77 | 54 | 0;116 | \-- |
| END (num keypad) | 0;79 | 49 | 0;117 | \-- |
| DOWN ARROW (num keypad) | 0;80 | 50 | (0;145) | \-- |
| PAGE DOWN (num keypad) | 0;81 | 51 | 0;118 | \-- |
| INSERT (num keypad) | 0;82 | 48 | (0;146) | \-- |
| DELETE (num keypad) | 0;83 | 46 | (0;147) | \-- |
| HOME | (224;71) | (224;71) | (224;119) | (224;151) |
| UP ARROW | (224;72) | (224;72) | (224;141) | (224;152) |
| PAGE UP | (224;73) | (224;73) | (224;132) | (224;153) |
| LEFT ARROW | (224;75) | (224;75) | (224;115) | (224;155) |
| RIGHT ARROW | (224;77) | (224;77) | (224;116) | (224;157) |
| END | (224;79) | (224;79) | (224;117) | (224;159) |
| DOWN ARROW | (224;80) | (224;80) | (224;145) | (224;154) |
| PAGE DOWN | (224;81) | (224;81) | (224;118) | (224;161) |
| INSERT | (224;82) | (224;82) | (224;146) | (224;162) |
| DELETE | (224;83) | (224;83) | (224;147) | (224;163) |
| PRINT SCREEN | \-- | \-- | 0;114 | \-- |
| PAUSE/BREAK | \-- | \-- | 0;0 | \-- |
| BACKSPACE | 8 | 8 | 127 | (0) |
| ENTER | 13 | \-- | 10 | (0 |
| TAB | 9 | 0;15 | (0;148) | (0;165) |
| NULL | 0;3 | \-- | \-- | \-- |
| A | 97 | 65 | 1 | 0;30 |
| B | 98 | 66 | 2 | 0;48 |
| C | 99 | 66 | 3 | 0;46 |
| D | 100 | 68 | 4 | 0;32 |
| E | 101 | 69 | 5 | 0;18 |
| F | 102 | 70 | 6 | 0;33 |
| G | 103 | 71 | 7 | 0;34 |
| H | 104 | 72 | 8 | 0;35 |
| I | 105 | 73 | 9 | 0;23 |
| J | 106 | 74 | 10 | 0;36 |
| K | 107 | 75 | 11 | 0;37 |
| L | 108 | 76 | 12 | 0;38 |
| M | 109 | 77 | 13 | 0;50 |
| N | 110 | 78 | 14 | 0;49 |
| O | 111 | 79 | 15 | 0;24 |
| P | 112 | 80 | 16 | 0;25 |
| Q | 113 | 81 | 17 | 0;16 |
| R | 114 | 82 | 18 | 0;19 |
| S | 115 | 83 | 19 | 0;31 |
| T | 116 | 84 | 20 | 0;20 |
| U | 117 | 85 | 21 | 0;22 |
| V | 118 | 86 | 22 | 0;47 |
| W | 119 | 87 | 23 | 0;17 |
| X | 120 | 88 | 24 | 0;45 |
| Y | 121 | 89 | 25 | 0;21 |
| Z | 122 | 90 | 26 | 0;44 |
| 1 | 49 | 33 | \-- | 0;120 |
| 2 | 50 | 64 | 0 | 0;121 |
| 3 | 51 | 35 | \-- | 0;122 |
| 4 | 52 | 36 | \-- | 0;123 |
| 5 | 53 | 37 | \-- | 0;124 |
| 6 | 54 | 94 | 30 | 0;125 |
| 7 | 55 | 38 | \-- | 0;126 |
| 8 | 56 | 42 | \-- | 0;126 |
| 9 | 57 | 40 | \-- | 0;127 |
| 0 | 48 | 41 | \-- | 0;129 |
| \- | 45 | 95 | 31 | 0;130 |
| \= | 61 | 43 | \--- | 0;131 |
| \[ | 91 | 123 | 27 | 0;26 |
| \] | 93 | 125 | 29 | 0;27 |
| | 92 | 124 | 28 | 0;43 |
| ; | 59 | 58 | \-- | 0;39 |
| ' | 39 | 34 | \-- | 0;40 |
| , | 44 | 60 | \-- | 0;51 |
| . | 46 | 62 | \-- | 0;52 |
| / | 47 | 63 | \-- | 0;53 |
| \` | 96 | 126 | \-- | (0;41) |
| ENTER (keypad) | 13 | \-- | 10 | (0;166) |
| / (keypad) | 47 | 47 | (0;142) | (0;74) |
| \* (keypad) | 42 | (0;144) | (0;78) | \-- |
| \- (keypad) | 45 | 45 | (0;149) | (0;164) |
| \+ (keypad) | 43 | 43 | (0;150) | (0;55) |
| 5 (keypad) | (0;76) | 53 | (0;143) | \-- |
## Resources
- [Wikipedia: ANSI escape code](https://en.wikipedia.org/wiki/ANSI_escape_code)
- [Build your own Command Line with ANSI escape codes](http://www.lihaoyi.com/post/BuildyourownCommandLinewithANSIescapecodes.html)
- [ascii-table: ANSI Escape sequences](http://ascii-table.com/ansi-escape-sequences.php)
- [bluesock: ansi codes](https://bluesock.org/~willkg/dev/ansi.html)
- [bash-hackers: Terminal Codes (ANSI/VT100) introduction](http://wiki.bash-hackers.org/scripting/terminalcodes)
- [XTerm Control Sequences](https://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
- [VT100 – Various terminal manuals](https://vt100.net/)
- [xterm.js – Supported Terminal Sequences](https://xtermjs.org/docs/api/vtfeatures/)
-
Merge objects in Typescript.
type Primitive = null | undefined | string | number | boolean | symbol | bigint;
function isObject(item: unknown): item is Record<string, unknown> {
return typeof item === 'object' && item !== null && !Array.isArray(item);
}
function deepMerge<T extends Record<string, unknown>, U extends Record<string, unknown>>(target: T, source: U): T & U {
const output = { ...target } as T & U;
Object.keys(source).forEach((key) => {
const sourceValue = source[key];
if (isObject(sourceValue)) {
const targetValue = (target as Record<string, unknown>)[key];
if (isObject(targetValue)) {
(output as Record<string, unknown>)[key] = deepMerge(targetValue, sourceValue);
} else {
(output as Record<string, unknown>)[key] = sourceValue;
}
} else {
(output as Record<string, unknown>)[key] = sourceValue;
}
});
return output;
}
-
Git
|--------------------------------------------------|-----------------------------------------------------------------------------------------------|
| `git blame -L 28,43 path/to/file` | Shows who modified each line in a specific range of a file. |
| `git blame -L :'class LocalFile' filepath` | Blames a block of code in a file matching a specific pattern. |
| `git log -L28,43:filepath` | Lists commits that last touched a specified region of a file. |
| `git blame -w` | Ignores whitespace changes in `git blame`. |
| `git blame -C` | Follows code movement across files in `git blame`. Can be used up to three times. |
| `git diff --word-diff` | Shows word-level differences in `git diff` output. |
| `git config --global rerere.enabled true` | Enables reuse of recorded resolution of conflicts. |
| `git config --global rerere.autoUpdate true` | Automatically updates the index with the result of rerere resolution. |
| `git config --global branch.sort -committerdate` | Sorts branches by last commit date in descending order. |
| `git push --force-with-lease` | Safely force-pushes, ensuring no updates were pushed to the branch since your last fetch. |
| `git maintenance start` | Starts Git maintenance tasks to optimize repository performance. |
-
Usefull bash commands
fd -HI node_modules -X rm -rf
-
Typescript tips and tricks. This is update whenever I come across something cool
type CreateAPIMethod = <
TInput extends Record<string, string>,
TOutput
>(opts: {
url: string;
method: "GET" | "POST"
}) = (input: TInput) = Promise<TOutput>;
declare const createAPIMethod: CreateAPIMethod;
const getUser = createAPIMethod<
{ id: string },
{ name: string }
>({
method: "GET", url: "/user",
})
getUser({ id: 123 });
type IconSize =
| "small"
| "medium"
| "large"
| (string & {});
type RemovePrefix<T, Prefix extends string> = {
[K in keyof T as K extends `${Prefix}_${infer _}` ? never : K]: T[K]
};
function throttle<T extends (...args: any[]) => any>(fn: T, wait: number): (...funcArgs: Parameters<T>) => void {
let isThrottling: boolean = false;
let lastArgs: Parameters<T> | null = null;
let lastThis: ThisParameterType<T> | null = null;
const call = () => {
if (lastArgs === null || lastThis === null) {
isThrottling = false;
return;
}
fn.apply(lastThis, lastArgs);
lastArgs = null;
lastThis = null;
setTimeout(call, wait);
};
return function(this: ThisParameterType<T>, ...args: Parameters<T>): void {
if (isThrottling) {
lastArgs = args;
lastThis = this;
return;
}
fn.apply(this, args);
isThrottling = true;
setTimeout(() => {
isThrottling = false;
call();
}, wait);
};
}
-
The CSS property to prevent scrollbar pushing the layout
.scrollbar {
scrollbar-gutter: stable;
}
-
A simple HTTP service to wrap fetch services with abstraction to handle errors
export type FetchStrategy = (
url: string,
options?: RequestInit
) => Promise<Response>;
export default class HttpService {
private fetchStrategy: FetchStrategy;
constructor(fetchStrategy: FetchStrategy = fetch) {
this.fetchStrategy = fetchStrategy;
}
async fetchHandler<T = unknown>(
url: string,
options?: RequestInit
): Promise<T> {
try {
const response = await this.fetchStrategy(url, options);
if (!response.ok) {
throw new HttpError(
`HTTP Error: ${response.status} - ${response.statusText}`,
response.status
);
}
const data = (await response.json()) as T;
return data;
} catch (error) {
throw new RequestError(`Request failed: ${error.message}`);
}
}
async get<T = unknown>(url: string, options?: RequestInit): Promise<T> {
return this.fetchHandler<T>(url, options);
}
async post<T = unknown>(
url: string,
body: Record<string, unknown> | BodyInit | null = null,
options?: Omit<RequestInit, "method" | "body">
): Promise<T> {
return this.fetchHandler<T>(url, {
method: "POST",
body: body ? JSON.stringify(body) : null,
...options
});
}
}
class HttpError extends Error {
constructor(message: string, public statusCode: number) {
super(message);
this.name = "HttpError";
}
}
class RequestError extends Error {
constructor(message: string) {
super(message);
this.name = "RequestError";
}
}
export const FetchService: HttpService = new HttpService(
globalThis.fetch.bind(globalThis)
);
const authFetch: FetchStrategy = async (url, options) => {
return fetch(`${baseUrl}${url}`, {
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
...headers
},
...options
});
};
export const AuthService = new HttpService(authFetch);
-
A simple way for error handling in Typescript
enum ResultKind {
OK = "Ok",
ERR = "Err",
}
export type Result<T, E> = Ok<T> | Err<E>;
interface ResultBase<A, E> {
kind: ResultKind;
map<B>(fn: (_: A) => B): Result<B, E>;
bind<B>(fn: (_: A) => Result<B, E>): Result<B, E>;
match<B>(obj: { ok: (_: A) => B; err: (_: E) => B }): B;
}
export type Ok<A> = Readonly<ResultBase<A, never> & { kind: ResultKind.OK; value: A }>;
export function ok<A>(a: A): Ok<A> {
return {
kind: ResultKind.OK,
value: a,
map(fn) {
return ok(fn(this.value));
},
bind(fn) {
return fn(this.value);
},
match({ ok }) {
return ok(this.value);
},
};
}
export type Err<E> = Readonly<ResultBase<never, E> & { kind: ResultKind.ERR; error: E }>;
export function err<E>(e: E): Err<E> {
return {
kind: ResultKind.ERR,
error: e,
map() {
return this;
},
bind() {
return this;
},
match({ err }) {
return err(this.error);
},
};
}
-
ZOD
import { zodResolver } from "@hookform/resolvers/zod";
import { type UseFormProps, useForm } from "react-hook-form";
import { type z } from "zod";
export default function useZodForm<TSchema extends z.ZodType>(
props: Omit<UseFormProps<TSchema["_input"]>, "resolver"> & {
schema: TSchema;
},
) {
const form = useForm<TSchema["_input"]>({
...props,
resolver: zodResolver(props.schema, undefined, {
raw: true,
}),
});
return form;
}
export function safeLoader<
LoaderInputs extends any[],
OutputValidation extends ZodTypeAny,
>({
outputValidation,
loader,
}: {
outputValidation: OutputValidation;
loader: (...argsList: LoaderInputs) => any;
}) {
return async function (
...args: LoaderInputs
): Promise<z.infer<OutputValidation>> {
const outputs = await loader.apply(null, args);
const parsedOutput = outputValidation.parse(outputs);
return parsedOutput;
};
}