)/gi, "/gi, "")
);
```
## Summary
When MDX doesn't support certain HTML elements:
1. **Don't use rehype-raw** - it conflicts with MDX's JSX processing
2. **Transform tags before compilation** - convert HTML tags to JSX component names using string replacement
3. **Create custom React components** - implement the desired behavior with your preferred UI approach
4. **Register in component mapping** - make components available to MDX
This pattern maintains compatibility with remote or external MDX content while providing full control over how elements render.
### [Golter](https://sametcc.me/project/golter)
---
title: "Golter"
publishedAt: "2026-01-17"
summary: "A terminal-based file converter with a modern TUI, built with Go. Supports
batch conversion of images, videos, audio, and documents."
tags: [Go, TUI, File Conversion, ffmpeg, Image Processing, Video Processing, Audio Processing, Document Conversion]
language: "en"
type: "project"
status: "published"
---
# Golter

Terminal-based file converter built with Go. It provides a modern, user-friendly
Terminal User Interface (TUI) for batch converting images, videos, audio, and
documents between various formats.
[Homepage](https://golter.vercel.app) |
[GitHub Repository](https://github.com/sametn99/golter)
## Table of Contents
- [Features](#features)
- [Supported Formats](#supported-formats)
- [Images](#images)
- [Videos](#videos)
- [Audio](#audio)
- [Documents](#documents)
- [Installation](#installation)
- [Prerequisites](#prerequisites)
- [Quick Install](#quick-install)
- [Build from Source](#build-from-source)
- [Platform-Specific Setup](#platform-specific-setup)
- [Usage](#usage)
- [Keyboard Controls](#keyboard-controls)
- [Notes](#notes)
- [License](#license)
## Features
- **Modern TUI Interface:** Beautiful terminal interface with smooth animations
and visual feedback.
- **Batch Conversion:** Select multiple files and convert them all at once with
concurrent processing.
- **Image Conversion:** Native Go implementation for high-performance image
processing with quality control.
- **Video Conversion:** Leverages `ffmpeg` for robust video format support with
optimized encoding presets.
- **Audio Conversion:** Convert between various audio formats using `ffmpeg`
with bitrate control.
- **Document Conversion:** Support for PDF, Markdown, HTML, EPUB, and doc data
conversions (JSON/YAML/XML/TOML/CSV/Excel).
- **Keyboard Navigation:** Full keyboard support with Vim-like keybindings
(`j`/`k`, `h`/`l`).
- **Cross-Platform:** Works on Linux, macOS, and Windows.
- **Compression Options:** Choose from High, Balanced, or Compact quality
levels.
- **Real-time Progress:** Visual progress indicators during conversion.
- **Smart File Selection:** Only files of the same type can be selected together
for consistent conversions.
### [Payload Docker Config: PostgreSQL + BunJS](https://sametcc.me/gist/payload-docker-config-pgsql-bunjs)
---
title: "Payload Docker Config: PostgreSQL + BunJS"
publishedAt: "2025-12-25"
summary: "Docker configuration files to run PayloadCMS using the Bun runtime and
PostgreSQL database. "
tags: [PayloadCMS, Docker, Bun, PostgreSQL, Docker Compose]
language: "en"
type: "gist"
status: "published"
---
# Payload Docker Config: PostgreSQL + BunJS
This gist provides Docker configuration files to run
[PayloadCMS](https://payloadcms.com/) using the [Bun](https://bun.sh/) runtime
and a PostgreSQL database.
## Files
- `Dockerfile`: Defines the Docker image for the PayloadCMS application using
Bun.
- `docker-compose.yml`: Sets up the Docker services for PayloadCMS and
PostgreSQL.
## Dockerfile
```yaml
# To use this Dockerfile, you have to set `output: 'standalone'` in your next.config.js file.
FROM oven/bun:alpine AS base
WORKDIR /app
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Install dependencies with Bun, respecting existing lockfiles when present
COPY package.json bun.lockb* yarn.lock* package-lock.json* pnpm-lock.yaml* ./
RUN \
if [ -f bun.lockb ] || [ -f yarn.lock ] || [ -f package-lock.json ] || [ -f pnpm-lock.yaml ]; then \
bun install --frozen-lockfile; \
else \
bun install; \
fi
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# ENV NEXT_TELEMETRY_DISABLED 1
RUN bun run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
ENV PORT 3000
RUN addgroup -S bunuser && adduser -S bun -G bunuser
COPY --from=builder /app/public ./public
# Prepare prerender cache with correct permissions
RUN mkdir -p .next && chown bun:bunuser .next
COPY --from=builder --chown=bun:bunuser /app/.next/standalone ./
COPY --from=builder --chown=bun:bunuser /app/.next/static ./.next/static
USER bun
EXPOSE 3000
# server.js is created by next build from the standalone output
CMD HOSTNAME="0.0.0.0" bun --bun server.js
```
## docker-compose.yml
```yaml
version: "3"
services:
payload:
image: oven/bun:1.1.14-alpine
ports:
- "3000:3000"
volumes:
- .:/home/bun/app
- node_modules:/home/bun/app/node_modules
working_dir: /home/bun/app/
command: sh -c "bun install --frozen-lockfile && bun run dev"
depends_on:
- postgres
env_file:
- .env
environment:
DATABASE_URL: postgres://payload:payload@postgres:5432/payload
postgres:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_DB: payload
POSTGRES_USER: payload
POSTGRES_PASSWORD: payload
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
node_modules:
postgres_data:
```
### [Migrate from prettier-eslint to Biome](https://sametcc.me/gist/migrate-from-prettier-eslint-to-biome)
---
title: "Migrate from prettier-eslint to Biome"
publishedAt: "2025-12-24"
summary:
"A fully automated Bash script to migrate from ESLint + Prettier to Biome. It
initializes Biome, migrates ESLint and Prettier configurations, removes old
config files, and updates package.json scripts. Designed for Bun environments
with full Next.js ESLint compatibility. "
tags: [Biome, ESLint, Prettier, JavaScript, Bun]
language: "en"
type: "gist"
status: "published"
---
# Migrate from prettier-eslint to Biome
A fully automated Bash script to migrate from ESLint + Prettier to Biome. It
initializes Biome, migrates ESLint and Prettier configurations, removes old
config files, and updates package.json scripts. Designed for Bun environments
with full Next.js ESLint compatibility.
## Files
- `migrate-to-biome.sh`: A Bash script that automates the migration process from
ESLint and Prettier to Biome.
```bash
#!/usr/bin/env bash
#
# migrate-to-biome.sh
# ---------------------------------------------------------------------------------------------------
# 🔧 Purpose:
# Fully automate the migration from ESLint + Prettier → Biome using Bun.
#
# 🧠 What it does:
# 1. Initializes Biome (if missing)
# 2. Migrates ESLint & Prettier configurations safely (handles Next.js)
# 3. Removes all ESLint/Prettier config files (including eslint.config.mjs)
# 4. Detects and removes old lint/format scripts
# 5. Removes all ESLint & Prettier dependencies
# 6. Installs Biome as devDependency
# 7. Adds Biome lint & format scripts with --write flag
#
# 🧩 Requirements:
# - Bun installed and accessible (`bunx`, `bun add`, `bun remove`)
# - jq installed (for JSON editing)
#
# ---------------------------------------------------------------------------------------------------
# © 2025 Samet • MIT License
# ---------------------------------------------------------------------------------------------------
set -euo pipefail
IFS=$'\n\t'
# ----------------------------- #
# 🌈 Helper functions
# ----------------------------- #
log() { echo -e "\033[36m[INFO]\033[0m $*"; }
warn() { echo -e "\033[33m[WARN]\033[0m $*"; }
error() { echo -e "\033[31m[ERROR]\033[0m $*" >&2; }
success() { echo -e "\033[32m[SUCCESS]\033[0m $*"; }
require() {
if ! command -v "$1" &>/dev/null; then
error "Missing required command: $1"
exit 1
fi
}
# ----------------------------- #
# 🧭 Pre-flight checks
# ----------------------------- #
require bunx
require jq
if [[ ! -f package.json ]]; then
error "package.json not found in current directory."
exit 1
fi
# ----------------------------- #
# ⚙️ Run Biome migrations
# ----------------------------- #
log "Running Biome migration commands..."
# 1. Initialize Biome if not found
if [[ ! -f biome.json && ! -f biome.config.json ]]; then
log "No Biome config found. Initializing Biome..."
bunx @biomejs/biome init || true
success "Biome initialized."
fi
# 2. Handle Next.js ESLint configs safely
TEMP_ESLINT_DIR=".tmp_eslint_migrate"
mkdir -p "$TEMP_ESLINT_DIR"
RESTORE_ESLINT=false
if [[ -f "eslint.config.mjs" ]]; then
log "Temporarily patching Next.js ESLint config to avoid @rushstack crashes..."
cp eslint.config.mjs "$TEMP_ESLINT_DIR/eslint.config.mjs"
echo "export default {};" > eslint.config.mjs
RESTORE_ESLINT=true
elif [[ -f "eslint.config.js" ]]; then
log "Temporarily patching eslint.config.js..."
cp eslint.config.js "$TEMP_ESLINT_DIR/eslint.config.js"
echo "module.exports = {};" > eslint.config.js
RESTORE_ESLINT=true
fi
# 3. Run migration commands
bunx @biomejs/biome migrate eslint --write || warn "ESLint migration encountered non-critical issues."
bunx @biomejs/biome migrate prettier --write || warn "Prettier migration encountered non-critical issues."
success "Biome migration steps completed."
# 4. Restore ESLint config temporarily, then remove permanently
if [[ "$RESTORE_ESLINT" == true ]]; then
log "Restoring original eslint.config.* for cleanup..."
mv "$TEMP_ESLINT_DIR"/* . || true
rm -rf "$TEMP_ESLINT_DIR"
fi
# ----------------------------- #
# 🧹 Clean up old config files
# ----------------------------- #
log "Cleaning up old ESLint/Prettier config files..."
FILES_TO_DELETE=(
"eslint.config.js" "eslint.config.mjs"
".eslintrc.js" ".eslintrc.cjs" ".eslintrc.json" ".eslintrc.yaml" ".eslintrc.yml"
".eslintignore"
".prettierrc" ".prettierrc.js" ".prettierrc.json" ".prettierrc.yaml" ".prettierrc.yml"
"prettier.config.js" "prettier.config.cjs" "prettier.config.mjs"
".prettierignore"
)
for file in "${FILES_TO_DELETE[@]}"; do
if [[ -f "$file" ]]; then
rm -f "$file"
echo "🗑️ Deleted $file"
fi
done
success "Old config files cleaned (including eslint.config.*)."
# ----------------------------- #
# 📦 Remove ESLint & Prettier dependencies
# ----------------------------- #
log "Scanning for ESLint/Prettier dependencies to remove..."
REMOVE_PACKAGES=$(jq -r '
(.devDependencies // {} + .dependencies // {})
| keys
| map(select(test("eslint|prettier"; "i")))
| join(" ")
' package.json)
if [[ -n "$REMOVE_PACKAGES" ]]; then
log "Removing old linting packages: $REMOVE_PACKAGES"
bun remove $REMOVE_PACKAGES || warn "Some packages could not be removed."
else
log "No ESLint/Prettier dependencies found."
fi
# ----------------------------- #
# 📥 Install Biome
# ----------------------------- #
log "Installing Biome as devDependency..."
bun add -d @biomejs/biome
success "Biome installed successfully."
# ----------------------------- #
# 🧩 Update package.json scripts
# ----------------------------- #
log "Scanning package.json for ESLint / Prettier scripts..."
mapfile -t MATCHES < <(jq -r '.scripts | to_entries[] | select(.value | test("eslint|prettier"; "i")) | "\(.key): \(.value)"' package.json)
if [[ ${#MATCHES[@]} -eq 0 ]]; then
log "No ESLint/Prettier scripts found."
else
echo -e "\n🧹 Possible ESLint/Prettier scripts found:\n"
for i in "${!MATCHES[@]}"; do
printf " [%d] %s\n" $((i + 1)) "${MATCHES[$i]}"
done
echo -e "\nEnter numbers (comma-separated) of scripts to delete, or press Enter to skip:"
read -r -p "> " SELECTION
if [[ -n "$SELECTION" ]]; then
for index in $(echo "$SELECTION" | tr ',' ' '); do
if [[ "$index" =~ ^[0-9]+$ ]] && (( index >= 1 && index <= ${#MATCHES[@]} )); then
NAME=$(echo "${MATCHES[$((index-1))]}" | cut -d: -f1 | xargs)
jq "del(.scripts.\"$NAME\")" package.json > package.tmp.json && mv package.tmp.json package.json
echo "🗑️ Removed script: $NAME"
fi
done
else
warn "Skipped script removal."
fi
fi
# 5. Add Biome scripts with --write flag
log "Adding Biome lint & format scripts..."
jq '.scripts.lint = "biome lint --write" | .scripts.format = "biome format --write"' package.json > package.tmp.json && mv package.tmp.json package.json
success "Updated package.json with biome scripts (with --write)."
# ----------------------------- #
# 🎯 Final Notes
# ----------------------------- #
echo -e "\n🎉 \033[1mMigration complete!\033[0m"
echo "✅ ESLint & Prettier fully removed (including eslint.config.* files)."
echo "✅ Biome installed and configured with auto-fix scripts."
echo "➡️ Review biome.json for final rule adjustments."
echo "➡️ You can now run:"
echo " bun run lint"
echo " bun run format"
```
### [Insta Public Archiver](https://sametcc.me/gist/insta-public-archiver)
---
title: "Insta Public Archiver"
publishedAt: "2025-12-23"
summary: "A Python script to download all media from public Instagram profiles without login."
tags: [Python, Instagram, Web Scraping, Media Download, Automation]
language: "en"
type: "gist"
status: "published"
---
# Insta Public Archiver
A Python script designed to download all media (photos and videos) from public Instagram profiles without requiring a login. It leverages Instagram's public API endpoints to fetch and save media content efficiently.
## Files
````python
import instaloader
import time
import random
import sys
import os
def download_posts_anonymous(username):
"""
Downloads posts from a specified profile anonymously.
It creates a dedicated sub-folder for EACH post to ensure
an organized file structure.
Directory Structure:
Username / YYYY-MM-DD_Shortcode / Files (jpg, mp4, txt)
"""
# Initialize Instaloader with specific configurations
L = instaloader.Instaloader(
download_pictures=True,
download_videos=True,
download_video_thumbnails=False,
download_geotags=False,
download_comments=False,
compress_json=False,
save_metadata=False # Disable metadata JSONs for a cleaner output
)
try:
print(f"--- Searching for profile: '{username}' ---")
# Create profile object (Anonymous access - No login required)
profile = instaloader.Profile.from_username(L.context, username)
print(f"Profile Found: {profile.username}")
print(f"Total Media Count: {profile.mediacount}")
print("Download starting... (Organized by folders)")
print("-" * 40)
counter = 0
# Iterate over all posts
for post in profile.get_posts():
try:
# Define folder naming convention: e.g., 2023-10-27_Cy8x...
folder_name = f"{post.date:%Y-%m-%d}_{post.shortcode}"
# Ensure profile directory exists (Username/)
profile_dir = profile.username
os.makedirs(profile_dir, exist_ok=True)
# For logging: Username / YYYY-MM-DD_Shortcode
target_directory = f"{profile_dir}/{folder_name}"
print(f"[{counter + 1}] Processing: {folder_name}")
# Download the post into the profile directory — Instaloader
# will create the per-post folder (YYYY-MM-DD_Shortcode) inside it.
L.download_post(post, target=profile_dir)
print(f" -> Saved to: {target_directory}")
counter += 1
# --- CRITICAL: Rate Limit Protection ---
# Since we are not logged in, we must add a random delay
# to mimic human behavior and avoid IP bans.
# sleep_time = random.randint(12, 25)
# print(f" -> Saved to: {target_directory}")
# print(f" -> Sleeping... ({sleep_time}s)")
# time.sleep(sleep_time)
except Exception as e_inner:
print(f" -> Error with this post, skipping: {e_inner}")
# Wait briefly before attempting the next post
time.sleep(5)
print("-" * 40)
print(f"Process Completed! Total posts: {counter}")
print(f"Check the '{username}' folder for sub-folders.")
except instaloader.ConnectionException as e:
print(f"\nConnection Error: IP restricted. Try again later.")
print(f"Error Details: {e}")
except instaloader.ProfileNotExistsException:
print(f"\nError: Profile '{username}' not found.")
except Exception as e:
print(f"\nUnexpected Error: {e}")
if __name__ == "__main__":
target_username = input("Enter Instagram Username: ")
download_posts_anonymous(target_username)
```
````
### [Tracing Edgar Allan Poe's Ghost in the Fog of Silent Hill](https://sametcc.me/blog/tracing-edgar-allan-poes-ghost-in-the-fog-of-silent-hill)
---
title: "Tracing Edgar Allan Poe's Ghost in the Fog of Silent Hill"
publishedAt: "2025-12-17"
summary:
"An analysis of the deep, foundational connections that bind Edgar Allan Poe's
literary universe to the nightmarish town of Silent Hill."
tags: ["Silent Hill", "Edgar Allan Poe", "Horror", "Game Design", "Literature"]
language: "en"
type: "post"
status: "published"
---
> The following text was generated using NotebookLM by drawing on Edgar Allan
> Poe’s literary works and critical essays written about his fiction and
> thought. Its aim is to explore and shed light on the possible intellectual and
> aesthetic intersections between Poe’s recurring themes, such as the uncanny,
> guilt, the subconscious, and psychological disintegration, and the language of
> psychological horror found within the Silent Hill universe. Rather than
> asserting a claim of direct influence, this work seeks to offer an analytical
> and interpretive reading that traces how these two dark narrative worlds
> converge at similar emotional and conceptual depths.
# Tracing Edgar Allan Poe's Ghost in the Fog of Silent Hill

Over a century separates the gaslit interiors of Edgar Allan Poe’s fiction from
the fog-drenched streets of the digital town of Silent Hill. Yet the distance
(historical, cultural, technological) can feel strangely thin once you step into
both worlds. Poe’s narrators speak as if they are confessing into a candle’s
last flame, trembling with certainty and doubt in the same breath. Silent Hill’s
protagonists walk as if the town itself is listening, waiting for a thought to
slip and become architecture. Despite the chasm of time and medium, the series
feels like a modern inheritor of the psychological horror tradition Poe helped
crystallize: terror that blooms from within, not from some neatly externalized
monster.
This is not an argument for direct influence, nor a claim that Team Silent
“adapted” Poe. It is closer to a mapping of shared techniques and shared
obsessions: how dread is paced, how guilt becomes a sound in the walls, how a
setting can behave like an unstable mind, and how a story can be built so that
the audience is never sure whether they are witnessing a haunting, or
participating in one. In short, it is an interpretive attempt to show how Silent
Hill translates foundational principles of 19th-century Gothic terror into an
interactive grammar.
If Poe’s great innovation was to treat consciousness itself as a haunted house,
Silent Hill’s great innovation was to let you walk its halls.
## 1. The Architects of Psychological Terror
To understand the lineage connecting Edgar Allan Poe and the Silent Hill series,
it helps to see them as parallel architects of psychological terror, each
defining what “horror” can do in their medium. Poe built with ink and cadence;
Team Silent built with camera angles, fog shaders, sound design, and the uneasy
rhythms of player control. Their shared genius lies in prioritizing the internal
landscape of fear over external threats. Even when monsters appear, the true
engine of dread is perception: what the mind believes, what it refuses to admit,
what it cannot stop replaying.
Poe, a central figure in American Romanticism and Gothic fiction, is celebrated
for tales of mystery and the macabre, but his most unsettling stories rarely
depend on the supernatural. He shifts the locus of fear inward. In “The
Tell-Tale Heart,” terror is a tempo: the narrator’s insistence on sanity becomes
its own indictment, and the story accelerates until sound itself feels
weaponized. In “The Black Cat,” confession turns into self-justification, and
self-justification turns into self-exposure. In “William Wilson,” doubling
becomes moral vertigo: the self is pursued by a self, not as metaphor but as
felt pressure.
Poe’s technique matters as much as his themes. He often writes as if he is
building an effect first and then selecting every detail to serve it, a
philosophy he would later articulate explicitly in “The Philosophy of
Composition.” His prose uses repetition, rhythmic emphasis, sudden pivots, and
heightened attention to sensory details to mimic the agitation, fixation, and
mania of his narrators. The terror is not only in what happens; it is in how the
mind insists on describing what happens.
Generations later, Silent Hill emerged as a landmark in psychological horror for
video games by refusing to treat fear as an “encounter” you win. It
deprioritized combat in favor of atmosphere, narrative, and investigation, and
it leaned into a premise of psychological depth: the town behaves like a
projection surface, manifesting a personalized nightmare that feels less like a
plot twist and more like a law of nature. In interviews, the developers describe
a fascination with realism filtered through dream logic, fragments of memory
reconstructed into a playable place.
Here is the evolutionary leap. Where Poe uses rhetoric to convey a character’s
inner turmoil, Silent Hill uses game design. The series translates agitated
prose into fog, soundscapes, camera control, and spaces that seem to respond to
guilt. Monsters are not merely “symbolic.” They function as interactive
pressure: a force that makes the player move, hesitate, look away, and most
importantly, continue.
This shared methodology, projecting the psyche outward, finds its most potent
expression in environment and sensation: spaces where the very fabric of reality
becomes a mirror held too close to the face.
## 2. The Uncanny and the Haunted Space: From the House of Usher to the Otherworld
Setting in horror is never merely a backdrop; it is an instrument. Both Poe and
the creators of Silent Hill weaponize place through the uncanny: the unsettling
sensation produced when the familiar turns strange without fully becoming
“other.” The uncanny does not announce itself with a roar. It arrives as a
subtle mismatch: an ordinary hallway that feels slightly too long, a room
arranged like a memory rather than like a room, a street corner that should
reveal a landmark but instead reveals only fog.
In “The Fall of the House of Usher,” Poe transforms a family mansion into a
living tomb. The house is more than setting; it is an externalized nervous
system. Its “insufferable gloom,” its fissures, its stagnant tarn; these are not
decoration, but an atmosphere that appears to think. Criticism has often noted
Poe’s use of reflection and doubling in the story: the house mirrored in water,
the twins as living reflections, the narrator’s own shifting perception. The
mansion becomes a kind of psychological diagram. You can feel that the building
is not simply decaying; it is participating in the family’s collapse.
Poe’s genius is in how he makes the reader hold two realities at once: the house
is physically present, and the house is also a mind. The story never forces you
to choose between those interpretations. The ambiguity is the point.
Silent Hill takes this concept and makes it navigable. The series’ iconic
“Otherworld,” a nightmarish parallel layer that consumes the ordinary town,
transforms it into corridors of rust, wet metal, stained tile, and industrial
decay. It is uncanny space made playable. The crucial transformation is
experiential: the player does not observe the metaphor; the player survives
inside it.
Even Silent Hill’s most famous aesthetic choice, its fog, is a perfect example
of how technical constraint becomes psychological tool. The fog began as a
practical solution to limited draw distance, but it fits the series’ dream logic
with eerie precision. It turns the town into a boundary condition: you can move
forward, but you cannot know what forward contains. That uncertainty is not
merely visual. It is moral. It suggests that your future is obscured for the
same reason your past is: because the mind does not want to see too far.
The opening of Silent Hill 2 is exemplary in its patience. Before the game
becomes overtly hostile, it invites you into the town with an almost mundane
stillness: trees, roads, distant shapes, the sensation of entering a place that
should be familiar but isn’t. It is less a “walk into danger” than a quiet
ritual of disorientation. The player is being tuned, like an instrument, to
accept that this world will not behave like a stable world.
Thus, both Usher’s mansion and Silent Hill’s Otherworld serve as architectural
mirrors for the soul: mirrors shattered not by a villain’s hand, but by grief,
fixation, and the slow violence of denial.
Where Poe can make the reader feel that a building is thinking, Silent Hill can
make the player feel that thinking is a building.
## 3. A Legacy of Loss: The "Death of a Beautiful Woman"
The trope of mourning a lost loved one is a persistent and powerful engine in
horror, because grief is already a kind of haunting: a presence that is absent,
a voice that only speaks inside your head, a replay that cannot be turned off.
Both Poe and Silent Hill 2 seize upon this engine not to manufacture melodrama,
but to explore the way love and guilt can become indistinguishable once memory
starts rewriting itself.
Poe repeatedly returns to the image of the dead beloved as an aesthetic and
psychological obsession. In “Ligeia,” the narrator admits that his memory is
“feeble through much suffering,” immediately destabilizing the account. The
beloved returns not as a straightforward ghost, but as a gravitational force
that bends reality: remembrance becomes a kind of necromancy. In poems like “The
Raven” and “Annabel Lee,” grief is staged as a rhythm: refrains that do not
merely express mourning, but enact it.
Poe is even explicit about the mechanism. In “The Philosophy of Composition,” he
claims that the most melancholy topic becomes most poetical when allied with
beauty: “the death, then, of a beautiful woman.” Whether one agrees with this
aesthetic claim or not, it reveals something useful for reading Poe: he
understood how loss can become a self-perpetuating structure, a loop of longing
and self-torment that the mind returns to because it cannot resolve it.
This framework becomes the backbone of Silent Hill 2. James Sunderland is drawn
to the town by a letter from his wife Mary, who died years earlier. The premise
is simple enough to be mythic: an impossible message from the dead. But the
story’s power comes from how relentlessly it refuses to let the message remain
comforting. Silent Hill turns the longing for reunion into a mechanism of
exposure. James’s journey is not a quest for external truth so much as a forced
encounter with what he has repressed: resentment, exhaustion, tenderness, shame,
and the unbearable weight of choices made in the long shadow of illness.
The game’s most devastating move is how it literalizes the way grief “splits” a
person. Mary is memory, idealization, accusation, and longing. Maria, her
doppelgänger, is not merely a twist; she is a symptom: a projection that feels
like wish fulfillment but behaves like a trap. In Poe’s work, doubling often
signals moral fracture (“William Wilson”) or the return of what the self wants
to disown. Silent Hill gives this doubling a body you can walk beside, protect,
lose, and meet again.
In both worlds, trauma is not merely remembered; it is productive. It generates
settings, figures, and compulsions: monsters made of whatever the mind refuses
to say aloud.
## 4. Monsters from the Id: Guilt, Trauma, and the Subjective Beast
In psychological horror, the most terrifying monsters are not alien creatures or
supernatural demons, but reflections of our own hidden natures: guilt, trauma,
and desire given flesh and teeth. This principle is a powerful throughline
connecting Poe’s tormented narrators to the literal monsters of Silent Hill,
which feel less like enemies than like accusations.
Poe’s characters are rarely threatened by external forces; they are the source
of their own horror. In “The Tell-Tale Heart,” paranoia is a metronome. In “The
Black Cat,” cruelty is rationalized until rationalization collapses into
confession. In “The Pit and the Pendulum,” the body becomes a clock that
measures fear, slice by slice. Poe’s genius was to make the reader inhabit the
narrator’s internal logic long enough to feel its seductive coherence, then to
watch it rot from the inside.
Silent Hill takes this literary concept and literalizes it in creature design,
environment, and mechanics. The monsters stalking the town are not random
wildlife; they are a personalized bestiary shaped by inner conflict. Commentary
around the series often notes parallels with Francis Bacon’s distorted figures:
bodies that look like they are being reshaped by invisible pressure. Regardless
of the exact chain of influence, the resemblance is useful as interpretation:
Silent Hill’s creatures often appear mid-transformation, as if the psyche is
still deciding what it wants to show.
Silent Hill 2 in particular is a masterclass in symbolic design:
- **Pyramid Head:** The game's iconic executioner, Pyramid Head is the brutal
embodiment of James's guilt and his subconscious desire for punishment. Based
on the town's fictional history of executioners, he is an unrelenting judge,
his violent acts mirroring James's own repressed aggression and sexual
frustration.
- **Bubble Head Nurse:** A grotesque reflection of James's hospitalization
trauma, these convulsing, faceless figures symbolize his corrosive mixture of
sexual desire and resentment. Their design captures the conflict of caring for
a sick spouse while being consumed by forbidden urges.
- **Mannequin:** A creature born of James's fractured gaze, the Mannequin
literalizes his sexual frustration into a twitching, headless form: all legs
and no identity, a perfect symbol of objectification. It is a hostile
sculpture representing his inability to see women as anything but fragmented
parts.
What makes these designs especially potent is not the symbolism alone, but how
the player must respond to them. You are not asked to “understand” guilt; you
are asked to move under its pressure, to manage distance, to decide when to
fight and when to flee, to feel your own body tense when the town decides it is
time.
This principle of a subjective reality, where one's inner state dictates the
physical world, is presented through a narrative lens that is just as unstable
and personal.
## 5. Through a Glass, Darkly: Unreliable Narration and Perceptual Terror
The unreliable narrator is one of the most potent weapons in the psychological
horror arsenal. By forcing the audience to question the reality of events, it
creates an unease no single monster can replicate. It traps you in perceptual
vertigo: the narrative itself becomes the threat.
Poe was a master of this technique. His narrators insist on their sanity, on
their logic, on the reasonableness of what they have done, then reveal, through
the very effort of insisting, that something is irreparably wrong. In “The
Tell-Tale Heart,” the narrator’s obsession with precision reads like competence
until it becomes compulsion. In “The Black Cat,” the narrator’s moral framing
shifts so often that the reader begins to feel the story sliding under their
feet. In “Ligeia,” the admission of “feeble” memory functions like a crack in
the foundation: everything after it is haunted by the possibility that the tale
is a symptom.
Silent Hill translates this device into interactive form by placing the player
inside an unreliable subjectivity. The innovation is not merely that the
protagonist may be mistaken. It is that the player’s own perceptions become
suspect because they are operational: you are making choices based on what you
see and hear. If those inputs are compromised, then your agency becomes part of
the horror.
The series makes this explicit through contrast. In Silent Hill 2, Laura
experiences the town differently, unbothered by monsters, suggesting that the
environment is not a universal “curse” but a personalized construction. In Poe,
the reader stands outside the narrator and weighs credibility. In Silent Hill,
you are inside the system that produces the credibility problem.
This is what turns familiar horror beats into something more intimate. In a
film, you can watch a character deny the truth. In a game, you can be made to
enact that denial as forward progress.
These interconnected themes are not mere echoes; they are evidence of a living
tradition being reinterpreted through new tools such as camera, sound,
interactivity, and the unique intimacy of control.
## 6. Sound as Conscience: Industrial Dread, Silence, and the Radio
If Poe’s prose can make sound feel like moral pressure (heartbeats, floorboards,
whispered repetitions), Silent Hill’s sound design can make moral pressure feel
like the air itself. Developers have described resisting “cinematic” music and
instead building a sonic world that feels as if it could exist in that town:
distant industrial groans, abrupt mechanical rhythms, and stretches of
near-silence where the player becomes hyperaware of footsteps and breath.
Akira Yamaoka’s approach is especially important here. In interviews, he
describes wanting to differentiate Silent Hill from other games and to use
atmosphere and noise rather than predictable background music. The result is a
sonic landscape that behaves like anxiety: it flares, recedes, and returns at
the exact moments your imagination begins to fill in what you cannot see.
The radio static mechanic is a brilliant example of horror translated into
design logic. It serves as a warning system, but it also damages the player’s
nerves by producing constant anticipatory tension. You are not surprised by the
monster because the game has told you it is near; you are exhausted by the
waiting, by the inability to locate what the warning refers to. This is Poe’s
method in a different register: dread as sustained attention, sustained
attention as torment.
And then there is silence, the rarest, most violent sound cue in Silent Hill.
Silence is the moment when you realize you have been relying on noise to orient
yourself, and now orientation is gone.
## 7. Mechanics as Rhetoric: How Games Make Guilt Playable
Poe’s stories often feel like arguments delivered by unstable minds. The
narrators persuade, rationalize, rehearse. Silent Hill adapts this rhetorical
structure into mechanics: systems that make you perform the logic you might
otherwise only read about.
Consider how many design decisions in Silent Hill privilege vulnerability over
mastery:
- Limited visibility (fog, darkness, cramped interiors) makes knowledge scarce.
- Awkward combat and constrained resources make control imperfect.
- Maps and navigation turn “being lost” into a repeated activity rather than a
single scene.
These are not simply gameplay features; they are rhetorical devices. They
instruct the player’s body to feel what the story is about. A mind trapped in
guilt does not move cleanly. It circles, returns, re-enters the same spaces with
a different interpretation.
Poe can write a sentence that loops on itself. Silent Hill can make you walk a
corridor that feels like the sentence.
## Conclusion: A Tradition Reborn in Pixels
From the crumbling manor of the Ushers to the rust-stained corridors of
Brookhaven Hospital, a clear and chilling lineage runs from Poe to Silent Hill,
not as a straight line of influence, but as a shared obsession with inner life
as the true site of terror. Poe shows how a mind can narrate itself into
damnation. Silent Hill shows how a mind can build a town.
Both works treat horror as an effect produced by attention: what you cannot stop
looking at, what you cannot stop listening to, what you cannot stop returning
to. Both turn the uncanny into a method by corrupting the familiar until it
becomes unbearable. Both understand that loss is not a single event but a
machine that keeps producing images. And both insist that the most frightening
monsters are the ones that resemble us, not in appearance, but in origin.
Silent Hill’s primary innovation is not merely that it “updates” Gothic themes.
It invents a grammar for interactive psychological horror. Fog becomes
uncertainty you must navigate. Sound becomes conscience. Combat becomes a clumsy
argument between desire and fear. The Otherworld becomes the inside of a
thought, rendered with rust and fluorescent light.
In that sense, Poe’s ghost does not simply linger in Silent Hill’s streets. It
is reconstituted as a design philosophy: a commitment to dread as intimacy, and
to horror as a mirror that does not flatter. And when the player presses
forward, despite the fog, despite the radio static, and despite the feeling that
the town is watching, the tradition is reborn once again: not as a story you
read about the darkness within, but as a path you are made to walk through it.
### [Deploying Calibre-Web on Coolify with Docker Compose](https://sametcc.me/gist/running-calibre-on-coolify)
---
title: "Deploying Calibre-Web on Coolify with Docker Compose"
summary:
A comprehensive guide to deploying Calibre-Web on Coolify with Docker Compose
and persistent storage
publishedAt: "2025-10-15"
tags:
[coolify, docker, docker-compose, linuxserver, calibre-web, guide, tutorial]
language: "en"
type: "gist"
status: "published"
---
# Deploying Calibre-Web on Coolify with Docker Compose
This guide walks you through setting up and running **Calibre-Web** on
**Coolify** using **Docker Compose**. It focuses on building a stable,
persistent deployment with proper permissions and fully working upload
functionality.
---
## Step 1 — Preparing the Docker Compose File
The first step is to prepare a reliable Docker Compose configuration. This
defines the container, environment variables, and how data will be stored
persistently.
```yaml
version: "3"
services:
calibre-web:
image: linuxserver/calibre-web:latest
container_name: calibre-web
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Istanbul
volumes:
- calibre_library_data:/books
- calibre_config_data:/config
restart: unless-stopped
volumes:
calibre_library_data:
calibre_config_data:
```
**Key points:**
- `PUID` and `PGID` ensure the container has proper file ownership.
- `TZ` sets your time zone.
- `/books` and `/config` are stored on named volumes, keeping data safe through
container updates or restarts.
- `restart: unless-stopped` ensures the service restarts automatically if the
server reboots.
---
## Step 2 — Creating the Docker Compose Resource and Preparing for Deployment
1. Go to **Projects** in Coolify and click **New Resource**.
2. Select **“Docker Compose Empty”** as the resource type. This option expects a
Docker Compose file to be provided manually.
3. Paste the Docker Compose content from Step 1 into the editor.
4. Click **Save**. A new **Docker Compose service** is now created for your
project, representing the Calibre-Web container.
5. In the **Services** section, locate the **“Calibre Web”** service and open
its **Settings** page. Register your domain name here.
6. Return to the service list. At this point, the service is ready to deploy.
Click **“Deploy”** to start the container.
---
## Step 3 — First Login
Once the container is running, open Calibre-Web in your browser using the domain
or IP you configured.
Default login credentials:
- **Username:** `admin`
- **Password:** `admin123`
After logging in, immediately change the password in **Admin Settings** for
security purposes.
---
## Step 4 — Initializing the Database
Calibre-Web requires a valid database in the `/books` directory. If the
directory is empty, you’ll see:
> "New db location is invalid."
To create a minimal database:
1. Open the **Terminal** tab for the Calibre-Web service in Coolify.
2. Navigate to the `/books` directory:
```bash
cd /books
```
3. Download a minimal `metadata.db` file to initialize the database:
```bash
curl -LJO https://github.com/janeczku/calibre-web/raw/refs/heads/master/library/metadata.db
```
4. In the Calibre-Web interface, set the database path to:
```text
/books
```
The application now has the basic database structure it needs to run.
---
## Step 5 — Enabling File Uploads
Uploads are disabled by default. To enable:
1. Open **Admin Settings** → **Edit Basic Configuration** → **Feature
Configuration**.
2. Check **Allow Upload** and save.
3. Go to **Manage Users**, edit the `admin` account, and enable **Allow
upload**.
4. Set an upload directory, e.g., `/books` or `/books/uploads`.
At this stage, attempting to upload a book may result in a `readonly database`
or `permission denied` error. This occurs because the container still lacks
proper write access to the mounted volumes.
→ Continue to **Step 6** to fix permissions.
---
## Step 6 — Adjusting Permissions
To fix permission issues:
1. Open the **Terminal** tab for the Calibre-Web service in Coolify.
2. Navigate to the `/books` and `/config` directories and update ownership to
match the container user:
```bash
chown -R 1000:1000 /books
chown -R 1000:1000 /config
```
3. Return to the Coolify service page and **restart the container** to apply the
changes.
After this, Calibre-Web will have full write access. Uploads, database updates,
and configuration changes should now work without errors.
---
## Final Notes
Following these steps gives you a Calibre-Web instance that:
- Uses persistent storage so your data survives container restarts.
- Has correct file permissions to prevent “readonly” errors.
- Supports uploading new books through the web interface.
From here, you can enhance your setup by adding HTTPS, scheduling backups,
creating additional users, and customizing the interface to match your library
preferences.
For any help, feel free to reach out — my contact links are on the home page.
Enjoy your new Calibre-Web deployment on Coolify!
### [GitHub Profile Viewer](https://sametcc.me/project/github-profile-viewer)
---
title: GitHub Profile Viewer
publishedAt: "2025-10-15"
summary: A dynamic web platform that transforms how you explore GitHub profiles, built
with Blazor and .NET. Dive deep into developers' open-source contributions
with a sleek interface and robust performance.
tags: [Blazor, .NET, GitHub API, Open Source, Developer Tools]
language: "en"
type: "project"
status: "published"
---
# GitHub Profile Viewer
**[Live Demo](https://gpv.sametcc.me)** |
**[Blazor Version Repository](https://github.com/sametcn99/GPVBlazor)** |
**[Original Next.js Version](https://github.com/sametcn99/github-profile-viewer)**
---
## Overview
GitHub Profile Viewer is a web platform designed to help you explore and analyze
GitHub profiles in depth. Originally built with Next.js and Radix UI, the
project was later reimagined with Blazor and .NET for improved performance and a
more robust developer experience. The application leverages the GitHub REST API
to provide real-time insights into any public GitHub profile.
## Key Features
- **Immersive Profile Navigation**: Explore repositories, descriptions, language
usage, and more with a user-friendly interface.
- **Network Mapping**: Visualize a developer’s professional network and discover
key collaborators.
- **Contribution Analysis**: View detailed contribution statistics and starred
repositories to assess activity and influence.
- **Gist Explorer**: Browse public Gists to understand unique coding patterns
and micro-projects.
- **Universal Access**: Instantly analyze any public GitHub profile without
setup or registration.
- **Recruiter Tools**: Evaluate technical skills, influence, and community
engagement in one place.
- **GitHub Login & Access Token**: Users can log in with their GitHub account.
Upon authentication, the app automatically adds the user's access token,
enabling access to private data (with permission) and higher API rate limits.
## Technology Stack
### Blazor Version
- **Blazor**
- **.NET**
- **GitHub REST API**
### Original Version
- **Next.js**
- **Radix UI**
- **GitHub REST API**
## Why Blazor?
The transition from Next.js to Blazor and .NET was driven by the need for better
performance and a more scalable architecture. The Blazor version offers:
- Improved rendering speed and responsiveness
- Real-time data updates
- A familiar environment for C# and .NET developers
## Use Cases
- **Developers**: Discover your own or others’ open-source impact and coding
style.
- **Recruiters**: Quickly assess candidates’ skills, activity, and community
involvement.
- **Open Source Enthusiasts**: Explore the stories behind contributions and
networks.
### [Mermaid Live Editor & Viewer](https://sametcc.me/project/mermaid-viewer)
---
title: Mermaid Live Editor & Viewer
publishedAt: "2025-04-23"
summary: A powerful web application built with Next.js, React, and TypeScript that
allows users to create, edit, and share Mermaid diagrams in real-time with
live preview and instant sharing capabilities.
tags: [Next.js, React, TypeScript, Mermaid, Monaco Editor, MUI, Diagram Editor, Live Preview]
language: "en"
type: "project"
status: "published"
---
# Mermaid Live Editor & Viewer
**Create, edit, and share beautiful Mermaid diagrams in real-time!**
_A powerful, modern web application that transforms your ideas into stunning
diagrams with live preview and instant sharing capabilities._
[Homepage](https://mermaid.sametcc.me/home) •
[Report Bug](https://github.com/sametcn99/mermaid-viewer/issues) •
[Request Feature](https://github.com/sametcn99/mermaid-viewer/issues) •
[GitHub Repository](https://sametcc.me/repo/mermaid-viewer)
---
## Features
### AI Integration
- **Smart Assistant:** Chat with Google Gemini to create, modify, and fix
diagrams using natural language.
- **Privacy-First:** Your Gemini API key is stored locally in your browser and
never sent to our servers.
### Powerful Editor
- **Live Preview:** Real-time rendering with debounced updates for smooth
performance.
- **Advanced Code Editor:** Monaco Editor providing syntax highlighting,
intelligent code completion, and real-time error validation.
- **Template Library:** Access 70+ ready-made templates across 18 categories,
with smart search by name or tags.
- **Responsive Split View:** Adjustable panels for code and preview that adapt
to any screen size.
- **Keyboard Shortcuts:** comprehensive shortcuts for power users.
### Data & Sync
- **Cloud Sync:** Sign in with GitHub or Google to sync your diagrams and
settings across all your devices.
- **Auto-Save:** Never lose work; changes are automatically saved to browser
storage.
- **Import/Export:**
- **Import:** Load single `.mmd` files or project `.zip` archives (supporting
optional metadata).
- **Export:** Download current diagram as `.mmd` or bulk export your entire
collection as a backup `.zip`.
- **Personal Collections:** Save your favorite snippets and templates locally
for quick reuse.
- **Persistent Settings:** Your preferences (theme, colors, fonts) stay with
you.
### User Experience
- **Mobile First:** Optimized experience with touch-friendly 44px+ targets,
speed dial menus, and bottom sheets.
- **Interactive Navigation:**
- **Desktop:** Precise mouse wheel zoom.
- **Mobile:** Native pinch-to-zoom, double-tap to reset, and momentum panning.
- **Presentation Mode:** A distraction-free, full-viewport viewer accessible via
the app bar. It preserves state in the URL for safe sharing and hides the
toolbar for focus.
### Sharing & Embedding
- **Instant Links:** Generate shareable URLs containing the full diagram state.
- **SVG Export:** Copy diagrams as high-quality SVGs with one click.
- **Iframe Embeds:** Get ready-to-use HTML code to embed your live diagrams into
blogs or documentation.
---
### [VitePress Mermaid Renderer](https://sametcc.me/project/vitepress-mermaid-renderer)
---
title: VitePress Mermaid Renderer
publishedAt: "2025-03-15"
summary: A VitePress plugin that transforms static Mermaid diagrams into interactive,
dynamic visualizations with zoom, pan, fullscreen, and more.
tags: [VitePress, Mermaid, Plugin, Documentation, Diagrams, Interactive, JavaScript, TypeScript, vitepress-plugin-mermaid]
language: "en"
type: "project"
status: "published"
---
# VitePress Mermaid Renderer
Transform your static Mermaid diagrams into interactive, dynamic visualizations
in VitePress. This powerful plugin brings life to your documentation by enabling
interactive features like zooming, panning, and fullscreen viewing.
## Key Features
- **Smooth Zooming**: Capable of zooming in and out for better readability.
- **Intuitive Navigation**: Easy panning allows for exploring complex diagrams.
- **Code Copy**: Extract the Mermaid source code with a single click.
- **View Reset**: Instantly restore the diagram to its default view.
- **Fullscreen Mode**: View diagrams in a distraction-free fullscreen mode.
- **Theme Integration**: Automatically adapts to Light and Dark modes.
- **Download Options**: Export diagrams as SVG, PNG, or JPG.
## How It Works
Your Mermaid diagrams spring to life automatically. The plugin detects Mermaid
code blocks (marked with `mermaid` language) and transforms them into
interactive diagrams equipped with a powerful toolbar.
## Links
- [NPM Package](https://www.npmjs.com/package/vitepress-mermaid-renderer)
- [GitHub Repository](https://github.com/sametcn99/vitepress-mermaid-renderer)
- [Live Examples](https://vitepress-mermaid-renderer.vercel.app/)
### [Env Protector](https://sametcc.me/project/env-protector)
---
title: Env Protector
publishedAt: "2024-09-14"
summary: A Visual Studio Code extension that enhances the security of environment files
by masking sensitive data, providing confirmation prompts before opening, and
allowing users to manage environment variables without exposing their
contents.
tags: [Visual Studio Code, VSCode Extension, Environment Variables, Security, Productivity]
language: "en"
type: "project"
status: "published"
---
# Env Protector

[GitHub](https://github.com/sametcn99/env-protector) |
[Releases](https://github.com/sametcn99/env-protector/releases) |
[VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=sametcn99.env-protector)
| [Open VSX](https://open-vsx.org/extension/sametcn99/env-protector)
## Why this extension?
In the modern development era, live streaming, screen sharing, and recording are
part of daily routines. However, most existing VS Code extensions that manage
environment variables have a critical flaw: they process `.env` files only
_after_ the file has been opened. This results in a brief millisecond window
where sensitive keys, API tokens, and passwords might be visible on screen
before being hidden or masked. For streamers and security-conscious developers,
this split-second exposure is unacceptable.
**Env Protector** solves this problem essentially by intercepting the file open
request. Instead of reacting to an opened file, it proactively asks you _how_
you want to view the file before it is rendered. This "security-first"
architecture ensures that sensitive data never hits the screen unless you
explicitly authorize it, providing a robust layer of privacy for your
development environment.
## Features
- **File Visibility Control**: The extension allows you to toggle the visibility
of environment files (e.g., .env) within the VS Code sidebar. It automates the
management of `files.exclude` in your workspace settings, hiding sensitive
files from the file explorer view to prevent accidental clicks during screen
shares.

- **Proactive Interception Prompt**: Unlike other tools, this extension
intercepts the request to open an enviroment file. It presents a confirmation
dialog asking how you wish to proceed, ensuring you are always aware before
sensitive context is loaded into the editor buffer.

- **Secure Masked View**: When you choose to view a file securely, the extension
renders a virtual document where all values are replaced with asterisks
(masking). This allows you to verify the presence of keys and structure
without exposing the actual secrets (values).

- **Safe Variable Management**: Manage your environment variables without ever
opening the file itself:
- **Add Variable**: Insert new key-value pairs via the Command Palette.
- **Edit Variable**: Modify existing values safely through input boxes.
- **Remove Variable**: Delete keys without exposing the rest of the file
content.



### [Using JSON Schema in VS Code](https://sametcc.me/gist/using-json-schema-in-vscode)
---
title: "Using JSON Schema in VS Code"
publishedAt: "2024-08-25"
summary:
"Step-by-step guide to implementing JSON Schema validation in VS Code for
automatic error detection and IntelliSense support."
tags: [VS Code, JSON Schema, Validation, IntelliSense, JSON]
language: "en"
type: "gist"
status: "published"
---
# **Using JSON Schema in VS Code - Example**
**VS Code can automatically validate JSON files against a schema and show errors
if the data is incorrect.**
Follow these steps to use **JSON Schema validation in VS Code.**
---
## **1. Create a JSON Schema File (orderSchema.json)**
First, **create a schema** and save it as `orderSchema.json`.
This schema will validate order data.
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "Order Schema",
"description": "A JSON schema for an order",
"type": "object",
"properties": {
"orderId": {
"type": "string",
"pattern": "^[A-Z0-9]{8}$",
"description": "Order ID (8 characters, uppercase letters and numbers)"
},
"customer": {
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 3 },
"email": { "type": "string", "format": "email" },
"phone": { "type": "string", "pattern": "^\\+?[0-9]{10,15}$" }
},
"required": ["name", "email"]
},
"orderDate": { "type": "string", "format": "date-time" },
"items": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"properties": {
"productId": { "type": "integer" },
"productName": { "type": "string", "minLength": 3 },
"price": { "type": "number", "minimum": 0 },
"quantity": { "type": "integer", "minimum": 1 }
},
"required": ["productId", "productName", "price", "quantity"]
}
},
"paymentMethod": {
"type": "string",
"enum": ["Credit Card", "PayPal", "Bank Transfer"]
}
},
"required": ["orderId", "customer", "orderDate", "items", "paymentMethod"]
}
```
---
## **2. Create a JSON File (order.json)**
Now, **create a JSON file** and save it as `order.json`.
By adding `$schema`, **VS Code will automatically validate the data against the
schema.**
```json
{
"$schema": "./orderSchema.json",
"orderId": "A1B2C3D4",
"customer": {
"name": "Ahmet Yılmaz",
"email": "ahmet.yilmaz@example.com",
"phone": "+905551234567"
},
"orderDate": "2025-02-01T14:30:00Z",
"items": [
{
"productId": 101,
"productName": "Wireless Headphones",
"price": 499.99,
"quantity": 2
}
],
"paymentMethod": "Credit Card"
}
```
**Since `"$schema": "./orderSchema.json"` is added:**
**VS Code automatically validates the data against the schema.**
**If there are errors, VS Code will highlight them immediately.**
---
## **3. Alternative: Define Schema in VS Code Settings**
If you don’t want to add `$schema` in your JSON files,
**you can configure VS Code to apply the schema automatically** in
`settings.json`.
**Steps:**
1. **Open `settings.json` in VS Code**
- Press `Ctrl + Shift + P`
- Search for **"Preferences: Open Settings (JSON)"** and select it
2. **Add the following setting:**
```json
{
"json.schemas": [
{
"fileMatch": ["order.json"],
"url": "./orderSchema.json"
}
]
}
```
**What does this do?**
- When you open `order.json`, VS Code **automatically validates it using
`orderSchema.json`.**
- If the JSON is **invalid, VS Code will display errors immediately.**
---
## **4. Invalid JSON Example & VS Code Warnings**
The following JSON **is invalid** because:
- `"orderId"` has an incorrect format
- `"paymentMethod"` is `"Bitcoin"`, which is not allowed
```json
{
"$schema": "./orderSchema.json",
"orderId": "1234",
"customer": {
"name": "Ahmet Yılmaz",
"email": "ahmet.yilmaz@example.com"
},
"orderDate": "2025-02-01T14:30:00Z",
"items": [
{
"productId": 101,
"productName": "Headphones",
"price": 499.99,
"quantity": 2
}
],
"paymentMethod": "Bitcoin"
}
```
**VS Code will show these errors:**
1. **`"orderId"` does not match the required format (should be 8 characters
long).**
2. **`"paymentMethod"` cannot be `"Bitcoin"` (allowed values: `"Credit Card"`,
`"PayPal"`, `"Bank Transfer"`).**
---
## **SUMMARY**
- **Create a JSON schema (`orderSchema.json`).**
- **Prepare a JSON file (`order.json`).**
- **Use `$schema` or configure `settings.json` in VS Code to enable
validation.**
- **VS Code will automatically highlight errors and guide you to fix them.**
**Now, you can validate JSON files easily in VS Code!**
### [TypeScript Types and Utility Types](https://sametcc.me/gist/typescript-types)
---
title: "TypeScript Types and Utility Types"
publishedAt: "2024-08-25"
summary: "Complete guide to TypeScript's built-in utility types, advanced type
transformations, and custom type definitions for robust type safety."
tags: [TypeScript, Types, Utility Types, Type Safety]
language: "en"
type: "gist"
status: "published"
---
# **Built-in Utility Types**
TypeScript provides several built-in utility types to facilitate common type
transformations. These utilities help manipulate types without needing to write
complex type logic from scratch, improving code readability and maintainability.
| Utility Type | Description | Example Usage |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Partial` | Constructs a type with all properties of `T` set to optional. Useful for update operations or default values. | `interface User { id: number; name: string; } type UpdateUserDto = Partial; // { id?: number; name?: string; }` |
| `Required` | Constructs a type consisting of all properties of `T` set to required. Useful when you need to ensure all properties are present. | `interface Props { a?: number; b?: string; } type RequiredProps = Required; // { a: number; b: string; }` |
| `Readonly` | Constructs a type with all properties of `T` set to `readonly`. Useful for representing immutable data structures. | `interface Config { apiKey: string; } const config: Readonly = { apiKey: "..." }; // config.apiKey = "new"; // Error` |
| `Record` | Constructs an object type whose property keys are `K` and whose property values are `T`. Useful for dictionaries or maps. | `type PageInfo = { title: string; }; type Pages = Record<'home' \| 'about', PageInfo>; // { home: PageInfo; about: PageInfo; }` |
| `Pick` | Constructs a type by picking the set of properties `K` (string literal or union of string literals) from `T`. Useful for creating smaller types from larger ones. | `interface User { id: number; name: string; email: string; } type UserPreview = Pick; // { id: number; name: string; }` |
| `Omit` | Constructs a type by picking all properties from `T` and then removing `K`. Useful for excluding sensitive or unnecessary fields. | `interface User { id: number; name: string; password?: string; } type PublicUser = Omit; // { id: number; name: string; }` |
| `Exclude` | Constructs a type by excluding from `T` all union members that are assignable to `U`. Useful for filtering union types. | `type Status = 'success' \| 'error' \| 'loading'; type NonLoadingStatus = Exclude; // 'success' \| 'error'` |
| `Extract` | Constructs a type by extracting from `T` all union members that are assignable to `U`. Useful for selecting specific members from a union. | `type Shape = { kind: 'circle'; radius: number; } \| { kind: 'square'; size: number; }; type Circle = Extract;` |
| `NonNullable` | Constructs a type by excluding `null` and `undefined` from `T`. Useful when you know a value cannot be nullish. | `type MaybeString = string \| null \| undefined; type DefiniteString = NonNullable; // string` |
| `ReturnType` | Constructs a type consisting of the return type of function `T`. Useful for typing variables based on function results. | `declare function f(): { a: number; b: string }; type FuncReturn = ReturnType; // { a: number; b: string }` |
| `InstanceType` | Constructs a type consisting of the instance type of a constructor function type `T`. Useful for working with class instances. | `class C { x = 0; } type CInstance = InstanceType; // C` |
| `Parameters` | Constructs a tuple type from the types used in the parameters of a function type `T`. Useful for manipulating function arguments. | `declare function greet(name: string, age: number): void; type GreetParams = Parameters; // [name: string, age: number]` |
| `ConstructorParameters` | Constructs a tuple or array type from the types of a constructor function's parameters. Useful for factory functions. | `class Person { constructor(name: string, age: number) {} } type PersonArgs = ConstructorParameters; // [name: string, age: number]` |
| `ThisParameterType` | Extracts the type of the `this` parameter for a function type, or `unknown` if the function type has no `this` parameter. | `function fn(this: Date, x: number) {} type ThisType = ThisParameterType; // Date` |
| `OmitThisParameter` | Removes the `this` parameter from a function type `T`. Useful for callbacks or detaching methods. | `function fn(this: Date, x: number): string { return ''; } const fnNoThis: OmitThisParameter = (x) => ''; // (x: number) => string` |
| `ThisType` | This utility does not return a transformed type. Instead, it serves as a marker for a contextual `this` type. Use with `noImplicitThis`. | `interface HelperThis { log: (msg: string) => void; } function f(this: HelperThis) {} // Advanced use, often in library design.` |
| `Awaited` | Recursively unwraps the `Awaited` type of a `Promise`. Useful for getting the resolved value type of nested promises (TS 4.5+). | `type NestedPromise = Promise>; type ResolvedValue = Awaited; // string` |
---
### Custom Utility Types (Commonly used custom combinations)
While TypeScript's built-in utilities cover many cases, sometimes you need more
specialized type transformations. Here are some commonly implemented custom
utility types.
#### `DeepPartial`
Recursively makes all properties in an object type optional, including nested
objects and arrays. This is useful for scenarios like applying partial updates
to deeply nested configuration objects.
```typescript
// T extends object checks if T is an object type (excluding null).
// If true, it maps over the keys [P in keyof T] and applies DeepPartial recursively to each property T[P].
// The '?' makes the property optional.
// If T is not an object (e.g., primitive, array), it returns T as is.
type DeepPartial = T extends object
? {
[P in keyof T]?: DeepPartial;
}
: T;
// Example usage:
interface NestedUser {
id: number;
name: string;
address: {
street: string;
city: string;
zip: number;
};
preferences: {
theme: {
dark: boolean;
fontSize: number;
};
notifications: string[];
};
}
// With DeepPartial, even nested properties can be omitted or partially provided.
const deepPartialUser: DeepPartial = {
id: 1, // Provide id
address: {
// Partially provide address
city: "New York",
},
preferences: {
// Partially provide preferences
theme: {
// Partially provide theme
fontSize: 14,
},
// notifications can be omitted entirely
},
};
```
#### `DeepReadonly`
Recursively makes all properties in an object type `readonly`, including nested
objects and arrays. This ensures deep immutability, preventing accidental
modifications anywhere in the structure.
```typescript
// Handles arrays: If T is an array (infer R captures the element type), return ReadonlyArray>.
// Handles functions: If T is a function, return it as is (functions are typically not made readonly).
// Handles objects: If T is an object, map over keys [P in keyof T] and apply DeepReadonly recursively. Add 'readonly' modifier.
// Handles primitives: If T is none of the above, return T.
type DeepReadonly = T extends (infer R)[]
? ReadonlyArray>
: T extends Function
? T
: T extends object
? {
readonly [P in keyof T]: DeepReadonly;
}
: T;
// Example:
interface Config {
apiKey: string;
settings: {
timeout: number;
retries: number;
advanced: {
logging: boolean;
};
features: string[];
};
}
const config: DeepReadonly = {
apiKey: "abc123",
settings: {
timeout: 3000,
retries: 3,
advanced: {
logging: true,
},
features: ["featureA", "featureB"],
},
};
// All attempts to modify will cause TypeScript errors:
// config.apiKey = "xyz"; // Error
// config.settings.timeout = 5000; // Error
// config.settings.advanced.logging = false; // Error
// config.settings.features.push("featureC"); // Error (ReadonlyArray has no push method)
```
#### `Mutable`
Removes the `readonly` modifier from all properties in a type `T`. This is the
inverse of `Readonly` and can be useful when you need to create a mutable
copy of a readonly object.
```typescript
// Uses a mapped type with '-readonly' modifier.
// This special syntax removes the readonly flag from each property P in T.
type Mutable = {
-readonly [P in keyof T]: T[P];
};
// Example:
interface ReadonlyUser {
readonly id: number;
readonly name: string;
readonly roles: readonly string[];
}
const readonlyUser: ReadonlyUser = { id: 1, name: "John", roles: ["admin"] };
// Create a mutable version
const mutableUser: Mutable = { ...readonlyUser };
// Now modifications are allowed:
mutableUser.id = 2;
mutableUser.name = "Jane";
// Note: Deep immutability is not removed by Mutable alone.
// mutableUser.roles.push("editor"); // Error if roles was ReadonlyArray
// To make roles mutable too, you'd need a DeepMutable type.
```
#### `Nullable`
Constructs a type that allows `T` or `null`. Useful for representing values that
might be absent or explicitly set to null.
```typescript
// Simple union type definition.
type Nullable = T | null;
// Example:
interface User {
id: number;
profileImageUrl: Nullable; // Profile image might not exist
}
function getUserProfile(userId: number): Nullable {
// Simulating data fetching
if (userId === 1) {
return { id: 1, profileImageUrl: "http://example.com/img.jpg" };
} else if (userId === 2) {
return { id: 2, profileImageUrl: null }; // User exists, but no image
}
return null; // User not found
}
const user1 = getUserProfile(1);
const user2 = getUserProfile(2);
const user3 = getUserProfile(3);
// Need null checks
if (user1) {
console.log(user1.profileImageUrl?.toUpperCase()); // Optional chaining needed for profileImageUrl
}
if (user2) {
console.log(user2.profileImageUrl); // null
}
if (user3 === null) {
console.log("User 3 not found");
}
```
#### `OptionalKeys`
Extracts the keys of `T` whose properties are optional (can be `undefined`).
```typescript
// Complex conditional mapped type:
// 1. `[K in keyof T]-?`: Iterate over all keys K of T, removing the optional modifier ('-?') temporarily.
// 2. `{} extends Pick`: This is a trick. `Pick` creates a type `{ K: T[K] }`.
// If the original property K in T was optional (e.g., `K?: type`), then `T[K]` includes `undefined`.
// `{}` (the empty object type) is assignable to `{ K: type | undefined }` only if the property K is optional (because `{}` has no properties, satisfying the optional requirement).
// If K was required (`K: type`), then `{}` is NOT assignable to `{ K: type }`.
// 3. `? K : never`: If the condition is true (K is optional), keep the key `K`. Otherwise, discard it (`never`).
// 4. `[keyof T]`: Finally, look up the resulting type using `keyof T` to get a union of the keys that were kept (the optional ones).
type OptionalKeys = {
[K in keyof T]-?: {} extends Pick ? K : never;
}[keyof T];
// Example:
interface UserConfig {
id: number; // Required
theme: string; // Required
notifications?: boolean; // Optional
language?: string; // Optional
}
// Results in: "notifications" | "language"
type ConfigOptionalKeys = OptionalKeys;
// Usage example: Setting default values for optional keys
function applyDefaults(config: UserConfig): Required {
const defaults: Pick> = {
notifications: true,
language: "en",
};
// Spread defaults first, then the provided config to override
return { ...defaults, ...config } as Required; // Asserting Required for simplicity here
}
const userConf: UserConfig = { id: 1, theme: "dark" };
const fullConfig = applyDefaults(userConf);
// fullConfig = { id: 1, theme: 'dark', notifications: true, language: 'en' }
```
#### `RequiredKeys`
Extracts the keys of `T` whose properties are required (must be present and
cannot be `undefined`).
```typescript
// Similar logic to OptionalKeys, but the condition is reversed.
// 1. `[K in keyof T]-?`: Iterate over all keys K of T, removing the optional modifier.
// 2. `{} extends Pick`: Check if K was optional.
// 3. `? never : K`: If the condition is true (K is optional), discard the key (`never`). Otherwise (K is required), keep the key `K`.
// 4. `[keyof T]`: Look up the resulting type to get a union of the required keys.
type RequiredKeys = {
[K in keyof T]-?: {} extends Pick ? never : K;
}[keyof T];
// Example:
interface UserConfig {
id: number; // Required
theme: string; // Required
notifications?: boolean; // Optional
language?: string; // Optional
}
// Results in: "id" | "theme"
type ConfigRequiredKeys = RequiredKeys;
// Usage example: Validating required fields
function validateConfig(config: Partial): boolean {
const requiredKeys: ConfigRequiredKeys[] = ["id", "theme"];
return requiredKeys.every(
(key) => config[key] !== undefined && config[key] !== null,
);
}
console.log(validateConfig({ id: 1, theme: "light" })); // true
console.log(validateConfig({ id: 1 })); // false (missing theme)
console.log(validateConfig({ theme: "dark" })); // false (missing id)
console.log(validateConfig({ id: 1, theme: "dark", notifications: false })); // true
```
#### `UnionToIntersection`
Converts a union type `U` into an intersection type. This is often used in
advanced scenarios involving function overloads or combining multiple type
definitions.
```typescript
// This uses conditional type inference and function type contravariance.
// 1. `U extends any ? (k: U) => void : never`: This distributes the union U. For each member type X in U, it creates a function type `(k: X) => void`.
// Example: If U = A | B, this becomes `((k: A) => void) | ((k: B) => void)`.
// 2. `extends (k: infer I) => void`: This attempts to infer a single type `I` for the parameter `k` such that the distributed function union is assignable to `(k: I) => void`.
// Due to contravariance of function parameters, `I` must be assignable *from* every member of the original union U. The only type that satisfies this is the intersection of all members of U.
// Example: `((k: A) => void) | ((k: B) => void)` is assignable to `(k: I) => void` only if `I` is `A & B`.
// 3. `? I : never`: If the inference succeeds, return the inferred intersection type `I`. Otherwise, return `never`.
type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (
k: infer I,
) => void
? I
: never;
// Example 1: Simple object union
type UnionObjects = { a: string } | { b: number };
// Results in: { a: string } & { b: number }
type IntersectionObjects = UnionToIntersection;
const obj: IntersectionObjects = { a: "hello", b: 123 };
// Example 2: Function overload union
type Overloads = ((a: string) => number) | ((a: number) => string);
// Results in an intersection of function signatures, representing an overloaded function
type CombinedOverload = UnionToIntersection;
// const combinedFunc: CombinedOverload = ...; // Can be called with string or number
// Example 3: Union of primitives (less common, results in 'never' as primitives can't intersect meaningfully)
type Primitives = string | number;
type IntersectionPrimitives = UnionToIntersection; // Type is 'never'
```
#### `DeepNonNullable`
Recursively removes `null` and `undefined` from all properties in a type `T`,
including nested objects.
```typescript
// Similar structure to DeepPartial/DeepReadonly.
// T extends object checks if T is an object.
// If true, maps over keys [P in keyof T] and applies DeepNonNullable recursively.
// If false (primitive or array), applies the built-in NonNullable to remove null/undefined from the value itself.
type DeepNonNullable = T extends object
? { [P in keyof T]: DeepNonNullable }
: NonNullable; // Use built-in NonNullable for non-object types
// Example:
interface UserProfile {
id: number | null;
name: string | undefined;
contact: {
email: string | null;
phone?: string | null; // Optional and potentially null
} | null;
}
// All nullable/undefinable properties must be provided with non-nullish values.
// Optional properties must also be provided if they exist in the original type.
const profile: DeepNonNullable = {
id: 1, // Must be number
name: "John", // Must be string
contact: {
// contact object cannot be null
email: "john@example.com", // Must be string
phone: "123-456-7890", // Must be string (since phone exists in the mapped type)
},
};
// This would be invalid:
// const invalidProfile: DeepNonNullable = {
// id: null, // Error: Type 'null' is not assignable to type 'number'.
// name: 'Jane',
// contact: null // Error: Type 'null' is not assignable to type '{ email: string; phone: string; }'.
// };
```
---
### Summary Table
This table categorizes the utility types based on their primary function:
| Category | Utility Types |
| -------------------------------- | ------------------------------------------------------------------------------ |
| **Property Modifiers** | `Partial`, `Required`, `Readonly`, `Mutable`, `DeepPartial`, `DeepReadonly` |
| **Property Selection** | `Pick`, `Omit` |
| **Union/Intersection** | `Exclude`, `Extract`, `UnionToIntersection` |
| **Nullability** | `NonNullable`, `Nullable`, `DeepNonNullable` |
| **Function/Class Introspection** | `ReturnType`, `Parameters`, `InstanceType`, `ConstructorParameters`, `Awaited` |
| **`this` Parameter** | `ThisParameterType`, `OmitThisParameter`, `ThisType` |
| **Key Manipulation** | `Record`, `OptionalKeys`, `RequiredKeys` |
---
Understanding and utilizing these built-in and custom utility types can
significantly streamline your TypeScript development, leading to more robust,
readable, and maintainable code. Feel free to experiment with them in your
projects!
### [Using Glob Patterns in TypeScript Projects](https://sametcc.me/gist/typescript-glob-patterns)
---
title: "Using Glob Patterns in TypeScript Projects"
publishedAt: "2024-08-25"
summary:
"Comprehensive guide to glob patterns in TypeScript with detailed character
explanations, examples, and practical applications for file matching."
tags: [TypeScript, Glob, File Matching, Patterns, Developer Tools]
language: "en"
type: "gist"
status: "published"
---
# Using Glob Patterns in TypeScript Projects: A Comprehensive Guide with Detailed Character Explanations and Examples
## Introduction to Glob Patterns in TypeScript
Glob patterns are a powerful mechanism for matching file and directory names in
a file system. In TypeScript projects, they play a crucial role in tasks such as
file selection, automation, and maintaining a modular project structure. To
harness the full power of glob patterns, it's essential to understand each
character and its role in defining matching criteria. Let's dive into a detailed
exploration of glob pattern characters:
### `*` (Asterisk)
- **Usage:** Represents any sequence of characters.
- **Example:** The pattern `*.ts` matches all files with a ".ts" extension in a
directory.
### `?` (Question Mark)
- **Usage:** Matches any single character.
- **Example:** The pattern `file?.txt` matches files like "file1.txt" or
"fileA.txt."
### `[ ]` (Square Brackets)
- **Usage:** Specifies a range of characters to match.
- **Example:** The pattern `[abc]file.txt` matches "afile.txt," "bfile.txt," or
"cfile.txt."
### `[^ ]` (Caret within Square Brackets)
- **Usage:** Negates the character set, matches any character not in the
specified set.
- **Example:** The pattern `[^0-9]` matches any character that is not a digit (0
to 9).
### `{ }` (Curly Braces)
- **Usage:** Allows for multiple options and matches one of them.
- **Example:** The pattern `{*.jpg,*.png}` matches files with either ".jpg" or
".png" extensions.
### `**` (Double Asterisk)
- **Usage:** Enables recursive matching across directories.
- **Example:** The pattern `src/**/*.ts` matches all ".ts" files in the "src"
directory and its subdirectories.
### `\` (Backslash)
- **Usage:** Escapes special characters, treating them as literals.
- **Example:** The pattern `file\?name.txt` matches "file?name.txt."
### `!` (Exclamation Mark)
- **Usage:** Negates the pattern, excluding files that match the specified
criteria.
- **Example:** The pattern `!(*-bak)` matches all files except those ending with
"-bak."
### `+` (Plus Sign)
- **Usage:** Requires one or more occurrences of the preceding character or
group.
- **Example:** The pattern `**/*.+(js|ts)` matches files with either a ".js" or
".ts" extension.
## Getting Started: Installing the Necessary Packages
Before we delve into practical examples, install the required packages. The
commonly used packages are `glob` and `globby`. Run the following command in
your terminal:
```bash
npm install glob
# or
npm install globby
```
Now, armed with a detailed understanding of glob pattern characters, let's
explore practical examples in TypeScript.
### Example 1: Selecting TypeScript Files Synchronously
In this example, we use the `glob` package to synchronously select all
TypeScript files within a specific directory:
```typescript
import * as glob from "glob";
const tsFiles = glob.sync("src/**/*.ts");
glob("src/**/*.ts", (err, tsFiles) => {
console.log("Selected TypeScript files synchronously:", tsFiles);
});
```
Here, the glob pattern `src/**/*.ts` matches all ".ts" files under the `src`
directory. The `glob.sync` method performs the file matching synchronously, and
the result is printed to the console.
### Example 2: Asynchronous File Matching
For scenarios where asynchronous file matching is preferred, the `glob` package
provides an asynchronous method:
```typescript
import * as glob from "glob";
glob("src/**/*.ts", (err, tsFiles) => {
if (err) {
console.error("Error during asynchronous file matching:", err);
return;
}
console.log("Selected TypeScript files asynchronously:", tsFiles);
});
```
In this example, the asynchronous `glob` method accomplishes the same result as
Example 1. Asynchronous methods are beneficial for non-blocking operations in
TypeScript projects.
### Example 3: Using `tsconfig.json` for Glob Patterns
In TypeScript projects, you can incorporate glob patterns directly into the
`tsconfig.json` file for compilation:
```json
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"outDir": "./dist",
"rootDir": "./src",
"include": ["src/**/*.ts"]
}
}
```
Here, the `"include"` field specifies a glob pattern that includes all
TypeScript files under the `src` directory during compilation. This approach
contributes to maintaining a clean and modular codebase.
### Conclusion: Harnessing the Power of Glob Patterns in TypeScript
In summary, glob patterns offer a versatile solution for file selection and
organization within TypeScript projects. Whether used synchronously or
asynchronously, these patterns enhance the developer's ability to manage project
structure effectively. By mastering glob patterns, TypeScript developers can
streamline workflows, automate tasks, and maintain a scalable and organized
codebase. It's crucial to carefully choose and utilize these patterns to avoid
unintended inclusions or exclusions of files. Incorporating glob patterns into
TypeScript projects empowers developers to create more maintainable and scalable
codebases, contributing to a smoother development experience. Understanding each
character in glob patterns ensures precise and intentional file selections
without skipping any aspect.
### Useful Tool
[Glob Tester - https://toools.cloud/miscellaneous/glob-tester](https://toools.cloud/miscellaneous/glob-tester)
## Glob Pattern Examples in TypeScript
```typescript
import { promisify } from "util";
import glob from "glob";
const globPromise = promisify(glob);
// Examples of glob patterns and their usage
async function globExamples() {
// Match all TypeScript files in src directory and subdirectories
const tsFiles = await globPromise("src/**/*.ts");
// Match all test files
const testFiles = await globPromise("**/*.test.{ts,tsx}");
// Match files with specific extensions
const sourceFiles = await globPromise("src/**/*.{ts,tsx,js,jsx}");
// Exclude node_modules and dist directories
const projectFiles = await globPromise("**/*.ts", {
ignore: ["**/node_modules/**", "**/dist/**"],
});
// Match files in specific directories
const componentFiles = await globPromise("src/components/**/*.tsx");
// Match files with specific naming pattern
const hookFiles = await globPromise("src/**/*.hook.{ts,tsx}");
// Match configuration files
const configFiles = await globPromise("{tsconfig,package}.json");
// Match markdown files excluding README
const docs = await globPromise("docs/**/*.md", {
ignore: ["**/README.md"],
});
// Match specific file types in multiple directories
const utilityFiles = await globPromise("{src,lib}/utils/**/*.ts");
// Match files with numbers in name
const versionedFiles = await globPromise("src/**/v[0-9]*.ts");
return {
tsFiles,
testFiles,
sourceFiles,
projectFiles,
componentFiles,
hookFiles,
configFiles,
docs,
utilityFiles,
versionedFiles,
};
}
export default globExamples;
```
### [Product Requirements Document](https://sametcc.me/gist/product-requirement-doc-guide)
---
title: "Product Requirements Document"
publishedAt: "2024-08-25"
summary:
"Complete guide to writing effective Product Requirements Documents (PRDs)
with templates, best practices, and real-world examples for software
development."
tags: [Product Requirements, Software Development, Documentation, Project Management]
language: "en"
type: "gist"
status: "published"
---
# Product Requirements Document
## Table of Contents
1. [Introduction to PRD in Software Development](#introduction-to-prd-in-software-development)
2. [Key Components of a PRD](#key-components-of-a-prd)
3. [Best Practices for Writing an Effective PRD](#best-practices-for-writing-an-effective-prd)
4. [PRD vs. Other Documentation Types](#prd-vs-other-documentation-types)
5. [Common PRD Mistakes & How to Avoid Them](#common-prd-mistakes--how-to-avoid-them)
6. [PRD Templates & Real-World Examples](#prd-templates--real-world-examples)
7. [Advanced PRD Techniques](#advanced-prd-techniques)
8. [Resources & Further Reading](#resources--further-reading)
## Introduction to PRD in Software Development
### What is a Product Requirements Document (PRD)?
A Product Requirements Document (PRD) is a comprehensive document that outlines
the requirements, specifications, features, and objectives of a software
product. It serves as a central reference point for all stakeholders involved in
the product development lifecycle.
### Evolution of PRDs
```mermaid
timeline
title Evolution of Product Requirements Documentation
1960s : Initial Documentation
Basic specifications
Hardware-focused
1970s : Traditional Waterfall PRDs
Lengthy documents
Sequential development
Formal sign-offs
1980s : Structured Analysis
Data flow diagrams
Entity-relationship models
Formal methodologies
1990s : Structured PRDs
Use case driven
UML integration
Object-oriented approach
2000s : Agile Adaptation
User stories
Iterative approach
Lightweight docs
2010s : Modern PRD
Living documents
Integration with tools
Cloud collaboration
2020s : AI-Enhanced PRDs
Automated generation
Real-time collaboration
Smart analytics
Dynamic updates
```
### Why PRDs Are Essential
PRDs play a crucial role in software development by:
1. **Alignment**
- Ensuring all stakeholders share the same vision
- Establishing clear goals and objectives
- Defining success criteria
2. **Communication**
- Facilitating cross-team collaboration
- Reducing misunderstandings
- Providing a single source of truth
3. **Decision Making**
- Guiding prioritization
- Supporting resource allocation
- Enabling informed trade-offs
### PRDs in the Software Development Lifecycle
```mermaid
graph LR
A[Market Research] --> B[PRD Creation]
B --> C[Design Phase]
C --> D[Development]
D --> E[Testing]
E --> F[Deployment]
B -.-> G[Feedback Loop]
G -.-> B
```
### PRD Stakeholders
```mermaid
mindmap
root((PRD))
Product Managers
Vision & Strategy
Feature Definition
Prioritization
Engineers
Technical Feasibility
Implementation Details
Architecture
Designers
UX/UI Design
User Flows
Prototypes
QA Teams
Test Planning
Acceptance Criteria
Quality Standards
Business Teams
ROI Analysis
Market Fit
Resource Planning
```
## Key Components of a PRD
### 1. Product Overview
#### Vision Statement
A clear, concise statement that defines:
- The product's purpose
- Target market
- Key differentiators
- Long-term goals
Example:
> "To create a collaborative project management platform that empowers remote
> teams to work efficiently by combining real-time communication, task
> management, and document collaboration in a single, intuitive interface."
#### Market Opportunity
```mermaid
pie title Target Market Distribution
"Enterprise" : 45
"SMB" : 30
"Startups" : 15
"Individual Users" : 10
```
### 2. Business Case & Justification
#### Competitive Analysis Matrix
| Feature | Our Product | Competitor A | Competitor B | Competitor C | Competitor D |
| ----------------------- | ----------- | ------------ | ------------ | ------------ | ------------ |
| Real-time Collaboration | ✅ | ✅ | ❌ | ✅ | ⚠️ |
| Custom Workflows | ✅ | ❌ | ✅ | ❌ | ✅ |
| API Integration | ✅ | ✅ | ✅ | ❌ | ✅ |
| Mobile Support | ✅ | ✅ | ❌ | ✅ | ✅ |
| Enterprise SSO | ✅ | ✅ | ❌ | ❌ | ✅ |
| Offline Mode | ✅ | ❌ | ❌ | ✅ | ❌ |
| Custom Reports | ✅ | ⚠️ | ✅ | ❌ | ✅ |
| AI-Powered Features | ✅ | ❌ | ⚠️ | ❌ | ✅ |
| White-labeling | ✅ | ✅ | ❌ | ❌ | ⚠️ |
| Data Export Options | ✅ | ✅ | ✅ | ⚠️ | ✅ |
Legend:
- ✅ Full Support
- ⚠️ Partial Support
- ❌ No Support
#### Detailed Feature Comparison
##### Real-time Collaboration
- Our Product: Full concurrent editing, presence awareness, commenting
- Competitor A: Basic real-time viewing, no concurrent editing
- Competitor B: No real-time features
- Competitor C: Limited to chat and comments
- Competitor D: Basic collaborative features
##### Custom Workflows
- Our Product:
- Visual workflow builder
- Conditional logic
- Automation rules
- Custom triggers
- API webhooks
- Competitor A: No workflow customization
- Competitor B: Basic workflow templates
- Competitor C: Fixed workflows only
- Competitor D: Limited workflow customization
#### ROI Projections
```mermaid
graph TD
A[Initial Investment] --> B[Development Costs]
A --> C[Marketing Costs]
B --> D[Expected Revenue]
C --> D
D --> E[ROI Calculation]
E --> F[Break-even Analysis]
```
### 3. User Personas & Use Cases
#### Primary Persona Example
```mermaid
mindmap
root((Sarah Chen))
Product Manager
5 years experience
Tech industry
Pain Points
Tool fragmentation
Communication gaps
Time zone challenges
Goals
Streamline workflows
Improve collaboration
Track progress effectively
Technical Proficiency
Advanced
Early adopter
```
#### Extended Persona Examples
##### Technical Lead Persona
```mermaid
mindmap
root((James Wilson))
Lead Developer
10+ years experience
Full-stack expertise
Team of 8 developers
Pain Points
Technical debt
Documentation gaps
Integration challenges
Resource constraints
Goals
Code quality
Team productivity
System scalability
Innovation enablement
Technical Proficiency
Expert level
Architecture design
Cloud platforms
CI/CD pipelines
Daily Tools
IDE
Git
JIRA
CI/CD tools
KPIs
Deploy frequency
Code coverage
Bug resolution time
Team velocity
```
##### Product Owner Persona
```mermaid
mindmap
root((Emily Chen))
Product Owner
7 years experience
Agile certified
Cross-functional teams
Pain Points
Stakeholder alignment
Feature prioritization
Resource allocation
Market timing
Goals
Product success
User satisfaction
Revenue growth
Market share
Skills
Product strategy
User research
Data analysis
Roadmapping
Tools Used
Analytics
Project management
Prototyping
CRM
Success Metrics
User adoption
Feature usage
Customer feedback
Revenue impact
```
#### User Journey Map
```mermaid
journey
title Project Creation Journey
section Login
Access platform: 5: Sarah
Authenticate: 5: Sarah
section Project Setup
Create project: 4: Sarah
Configure settings: 3: Sarah
Invite team: 5: Sarah
section Workflow
Define tasks: 4: Sarah
Assign roles: 3: Sarah
Set milestones: 4: Sarah
```
### 4. Product Features & Functional Requirements
#### Feature Hierarchy (MoSCoW Method)
```mermaid
graph TD
A[Product Features] --> B[Must Have]
A --> C[Should Have]
A --> D[Could Have]
A --> E[Won't Have]
B --> B1[User Authentication]
B --> B2[Project Creation]
B --> B3[Task Management]
C --> C1[Custom Workflows]
C --> C2[Analytics Dashboard]
D --> D1[AI Suggestions]
D --> D2[Advanced Integrations]
E --> E1[Blockchain Features]
E --> E2[VR Collaboration]
```
#### User Story Template
```markdown
As a [type of user] I want to [perform an action] So that [achieve a
goal/benefit]
Acceptance Criteria:
1. Given [precondition] When [action] Then [expected result]
2. Given [another precondition] When [action] Then [expected result]
```
#### User Stories with Acceptance Criteria
Example 1: User Authentication
```markdown
As a new user I want to create an account So that I can access the platform's
features
Acceptance Criteria:
1. Given I am on the registration page When I enter valid email and password
Then my account should be created And I should receive a confirmation email
2. Given I enter an existing email When I try to register Then I should see an
error message And the form should not be submitted
3. Given I enter an invalid password When I try to register Then I should see
password requirements And the form should show validation errors
Technical Requirements:
- Password must be at least 8 characters
- Password must contain upper, lower, number, special char
- Email verification must expire in 24 hours
- Maximum 3 failed login attempts before temporary lockout
Non-functional Requirements:
- Registration process should complete in < 3 seconds
- Support for 100,000 concurrent registrations
- 99.99% uptime for auth services
```
Example 2: Project Creation
```markdown
As a project manager I want to create a new project workspace So that I can
organize team activities and track progress
Acceptance Criteria:
1. Given I am logged in When I click "New Project" Then I should see the project
creation form And be able to set project details
2. Given I am creating a project When I submit without required fields Then I
should see validation errors And the form should not be submitted
3. Given I create a project When it is successfully created Then team members
should receive invitations And default project structure should be set up
Technical Requirements:
- Project IDs must be unique
- Support for nested project hierarchies
- Real-time project status updates
- Automated role-based access control
Non-functional Requirements:
- Project creation should take < 2 seconds
- Support for 10,000 projects per workspace
- Project data must be backed up every 6 hours
```
### 5. UX/UI and Design Considerations
#### Wireframe Example
```mermaid
graph TD
subgraph Dashboard Layout
A[Header Navigation] --> B[Quick Actions]
A --> C[Search Bar]
D[Main Content Area] --> E[Project Cards]
D --> F[Activity Feed]
G[Sidebar] --> H[Project List]
G --> I[Team Members]
end
```
#### Comprehensive Wireframe Examples
```mermaid
graph TD
subgraph Dashboard Layout
A[Global Navigation] --> B[Quick Actions Bar]
A --> C[Search & Filters]
A --> D[User Settings]
E[Main Content Area] --> F[Project Cards Grid]
E --> G[Activity Timeline]
E --> H[Performance Metrics]
I[Left Sidebar] --> J[Project Navigator]
I --> K[Team Directory]
I --> L[Favorites]
M[Right Sidebar] --> N[Notifications]
M --> O[Quick Notes]
M --> P[Team Chat]
Q[Footer] --> R[Status Bar]
Q --> S[Help & Support]
end
```
#### Accessibility Guidelines
- WCAG 2.1 Compliance
- Keyboard Navigation
- Screen Reader Support
- Color Contrast Requirements
#### Design System Components
```mermaid
graph TD
subgraph Atomic Design Structure
A[Atoms] --> AA[Buttons]
A --> AB[Input Fields]
A --> AC[Icons]
A --> AD[Typography]
B[Molecules] --> BA[Form Groups]
B --> BB[Search Bars]
B --> BC[Card Headers]
C[Organisms] --> CA[Navigation Bars]
C --> CB[Data Tables]
C --> CC[Modal Windows]
D[Templates] --> DA[Dashboard Layout]
D --> DB[Project Views]
D --> DC[Settings Pages]
E[Pages] --> EA[Home Dashboard]
E --> EB[Project Details]
E --> EC[User Profile]
end
```
### 6. Technical Requirements & Constraints
#### Architecture Overview
```mermaid
graph TD
A[Frontend SPA] --> B[API Gateway]
B --> C[Authentication Service]
B --> D[Project Service]
B --> E[Collaboration Service]
D --> F[(Primary Database)]
E --> G[(Cache)]
```
### Technical Architecture Deep Dive
```mermaid
graph TD
subgraph Frontend Layer
A[React SPA] --> AA[Redux Store]
A --> AB[Router]
A --> AC[Service Workers]
end
subgraph API Gateway
B[Kong Gateway] --> BA[Rate Limiting]
B --> BB[Authentication]
B --> BC[Load Balancing]
end
subgraph Microservices
C[User Service] --> CA[(User DB)]
D[Project Service] --> DA[(Project DB)]
E[Analytics Service] --> EA[(Analytics DB)]
F[Notification Service] --> FA[Message Queue]
end
subgraph Infrastructure
G[Kubernetes] --> GA[Auto Scaling]
G --> GB[Service Mesh]
G --> GC[Monitoring]
end
A --> B
B --> C
B --> D
B --> E
B --> F
```
#### API Dependencies
```mermaid
graph LR
A[Our Platform] --> B[Authentication API]
A --> C[Storage API]
A --> D[Analytics API]
A --> E[Notification API]
```
### 7. Assumptions & Dependencies
#### Risk Assessment Matrix
| Risk | Probability | Impact | Mitigation Strategy |
| ----------------------- | ----------- | ------ | ------------------------------------------- |
| API Service Downtime | Medium | High | Implement retry logic & fallback mechanisms |
| Data Migration Issues | Low | High | Thorough testing & rollback plan |
| User Adoption | Medium | High | Beta testing & user feedback loops |
| Performance Scalability | Low | Medium | Load testing & optimization |
### 8. Success Metrics & KPIs
#### Key Performance Indicators
```mermaid
graph TD
A[Success Metrics] --> B[User Engagement]
A --> C[Performance]
A --> D[Business Impact]
B --> B1[DAU/MAU]
B --> B2[Time on Platform]
C --> C1[Response Time]
C --> C2[Availability]
D --> D1[Revenue Growth]
D --> D2[Customer Retention]
```
### 9. Roadmap & Development Timeline
#### Development Phases
```mermaid
gantt
title Product Development Timeline
dateFormat YYYY-MM-DD
section Phase 1
Project Setup :2024-01-01, 30d
Core Features :2024-02-01, 45d
section Phase 2
Enhanced Features :2024-03-15, 40d
Integration :2024-04-25, 30d
section Phase 3
Beta Testing :2024-05-25, 30d
Launch :2024-06-25, 15d
```
### Security Requirements & Compliance
#### Authentication & Authorization Matrix
| Role | View Projects | Create Projects | Manage Users | Access Analytics | Configure System |
| --------------- | ------------- | --------------- | ------------ | ---------------- | ---------------- |
| System Admin | ✅ | ✅ | ✅ | ✅ | ✅ |
| Project Manager | ✅ | ✅ | ⚠️ | ✅ | ❌ |
| Team Lead | ✅ | ✅ | ❌ | ⚠️ | ❌ |
| Developer | ✅ | ❌ | ❌ | ❌ | ❌ |
| Viewer | ⚠️ | ❌ | ❌ | ❌ | ❌ |
Legend:
- ✅ Full Access
- ⚠️ Limited Access
- ❌ No Access
#### Security Requirements Checklist
```mermaid
mindmap
root((Security Requirements))
Authentication
Multi-factor auth
SSO integration
Password policies
Session management
Data Protection
Encryption at rest
Encryption in transit
Backup policies
Data retention
Access Control
Role-based access
IP restrictions
Device management
Audit logging
Compliance
GDPR
HIPAA
SOC2
ISO27001
Monitoring
Security alerts
Intrusion detection
Activity logging
Performance metrics
```
### Performance Requirements
#### System Performance Metrics
| Metric | Target Value | Acceptable Range | Critical Threshold |
| ------------------- | ------------ | ---------------- | ------------------ |
| Page Load Time | < 2s | 2-3s | > 3s |
| API Response Time | < 200ms | 200-500ms | > 500ms |
| Database Query Time | < 100ms | 100-300ms | > 300ms |
| Concurrent Users | 10,000 | 5,000-10,000 | < 5,000 |
| System Uptime | 99.99% | 99.9-99.99% | < 99.9% |
| Error Rate | < 0.1% | 0.1-0.5% | > 0.5% |
#### Load Testing Scenarios
```mermaid
graph TD
A[Load Testing] --> B[Normal Load]
A --> C[Peak Load]
A --> D[Stress Test]
A --> E[Endurance Test]
B --> B1[1,000 concurrent users]
B --> B2[Standard operations]
C --> C1[10,000 concurrent users]
C --> C2[Heavy operations]
D --> D1[20,000 concurrent users]
D --> D2[System breaking point]
E --> E1[5,000 users]
E --> E2[24-hour duration]
```
### Integration Requirements
#### API Integration Specifications
```mermaid
sequenceDiagram
participant Client
participant API Gateway
participant Auth Service
participant Business Logic
participant Database
Client->>API Gateway: Request with JWT
API Gateway->>Auth Service: Validate Token
Auth Service-->>API Gateway: Token Valid
API Gateway->>Business Logic: Process Request
Business Logic->>Database: Query Data
Database-->>Business Logic: Return Results
Business Logic-->>API Gateway: Processed Response
API Gateway-->>Client: Final Response
```
#### Third-party Integration Matrix
| Integration Type | Provider Options | Implementation Complexity | Timeline | Dependencies |
| ---------------- | ------------------------------------------------- | ------------------------- | -------- | ----------------- |
| Authentication | - OAuth2
- SAML
- OpenID | Medium | 2 weeks | Identity Provider |
| Payment | - Stripe
- PayPal
- Square | High | 3 weeks | Payment Gateway |
| Storage | - AWS S3
- Google Cloud
- Azure | Low | 1 week | Cloud Provider |
| Analytics | - Google Analytics
- Mixpanel
- Amplitude | Medium | 2 weeks | Tracking Setup |
### Quality Assurance Requirements
#### Test Coverage Matrix
```mermaid
pie title Test Coverage Distribution
"Unit Tests" : 40
"Integration Tests" : 30
"E2E Tests" : 20
"Performance Tests" : 10
```
#### Testing Scenarios Template
```markdown
Test Case ID: TC-001 Category: Authentication Priority: High
Scenario: User Login with Valid Credentials
Preconditions:
- User account exists
- User is not logged in
- System is accessible
Test Steps:
1. Navigate to login page
2. Enter valid username
3. Enter valid password
4. Click login button
Expected Results:
- User successfully logs in
- Redirected to dashboard
- Session is created
- Activity is logged
Actual Results: [To be filled during testing]
Pass/Fail Criteria:
- All expected results must be met
- Response time < 2 seconds
- No security warnings
```
### Deployment & DevOps Requirements
#### Infrastructure Architecture
```mermaid
graph TD
subgraph Production Environment
A[Load Balancer] --> B1[Web Server 1]
A --> B2[Web Server 2]
B1 --> C[Application Server Cluster]
B2 --> C
C --> D1[(Primary DB)]
C --> D2[(Replica DB)]
end
subgraph Monitoring
E[Prometheus] --> F[Grafana]
G[Log Aggregator] --> H[ELK Stack]
end
subgraph CI/CD Pipeline
I[Git] --> J[Jenkins]
J --> K[Build]
K --> L[Test]
L --> M[Deploy]
end
```
### Mobile Requirements
#### Platform Support Matrix
| Feature | iOS (Native) | Android (Native) | Progressive Web App |
| ------------------ | ------------ | ---------------- | ------------------- |
| Offline Mode | ✅ | ✅ | ⚠️ |
| Push Notifications | ✅ | ✅ | ⚠️ |
| File Upload | ✅ | ✅ | ✅ |
| Biometric Auth | ✅ | ✅ | ❌ |
| Camera Access | ✅ | ✅ | ⚠️ |
| Background Sync | ✅ | ✅ | ⚠️ |
| Deep Linking | ✅ | ✅ | ✅ |
| Local Storage | ✅ | ✅ | ✅ |
#### Mobile-Specific User Stories
```markdown
Story: Offline Project Access
As a field engineer I want to access project details offline So that I can view
critical information without internet connection
Acceptance Criteria:
1. User can mark projects for offline access
2. System automatically syncs when online
3. Changes made offline are queued for sync
4. Conflicts are handled gracefully
5. Storage limits are enforced
```
#### Mobile UI Components
```mermaid
graph TD
subgraph Mobile UI Architecture
A[Native Shell] --> B[Core Components]
B --> B1[Navigation Bar]
B --> B2[Tab Bar]
B --> B3[Action Sheets]
C[Custom Components] --> C1[Project Cards]
C --> C2[Data Visualizations]
C --> C3[Custom Forms]
D[Shared Logic] --> D1[State Management]
D --> D2[Network Layer]
D --> D3[Cache Management]
end
```
### Analytics & Reporting Requirements
#### Data Collection Points
```mermaid
mindmap
root((Analytics Data))
User Behavior
Page Views
Feature Usage
Time on Task
Navigation Paths
Performance Metrics
Load Times
Error Rates
API Latency
Resource Usage
Business Metrics
Conversion Rates
User Growth
Retention
Revenue
Technical Data
Browser Stats
Device Info
Network Status
App Version
```
#### Custom Report Templates
1. Executive Dashboard
```json
{
"reportType": "executive",
"metrics": [
{
"name": "Monthly Active Users",
"type": "line_chart",
"timeFrame": "last_12_months",
"comparison": "previous_period"
},
{
"name": "Revenue Growth",
"type": "bar_chart",
"breakdown": ["region", "product"],
"timeFrame": "current_quarter"
},
{
"name": "User Satisfaction",
"type": "gauge",
"source": "nps_surveys",
"target": 85
}
]
}
```
2. Technical Performance Report
```json
{
"reportType": "technical",
"sections": [
{
"name": "System Health",
"metrics": ["uptime", "error_rate", "response_time"],
"alerts": {
"critical": "threshold > 95%",
"warning": "threshold > 85%"
}
},
{
"name": "Resource Usage",
"metrics": ["cpu", "memory", "storage", "bandwidth"],
"visualization": "time_series"
}
]
}
```
### Internationalization Requirements
#### Language Support Matrix
| Language | UI Elements | Documentation | Help Content | Marketing | Priority |
| -------- | ----------- | ------------- | ------------ | --------- | -------- |
| English | ✅ | ✅ | ✅ | ✅ | P0 |
| Spanish | ✅ | ✅ | ✅ | ✅ | P0 |
| French | ✅ | ✅ | ⚠️ | ✅ | P1 |
| German | ✅ | ✅ | ⚠️ | ✅ | P1 |
| Japanese | ✅ | ⚠️ | ❌ | ⚠️ | P2 |
| Chinese | ✅ | ⚠️ | ❌ | ⚠️ | P2 |
#### Localization Implementation Details
```mermaid
graph TD
A[Localization System] --> B[Translation Management]
A --> C[Content Delivery]
A --> D[Format Handling]
B --> B1[Translation Memory]
B --> B2[Terminology Base]
B --> B3[Review Process]
C --> C1[CDN Distribution]
C --> C2[Dynamic Loading]
C --> C3[Fallback Chain]
D --> D1[Number Formats]
D --> D2[Date/Time]
D --> D3[Currency]
```
#### Cultural Adaptation Guidelines
```markdown
### Regional Considerations
1. Date/Time Formats
- US: MM/DD/YYYY, 12-hour clock
- EU: DD/MM/YYYY, 24-hour clock
- JP: YYYY 年 MM 月 DD 日, 24-hour clock
2. Number Formats
- US/UK: 1,234.56
- EU: 1.234,56
- IN: 1,23,456.78
3. Currency Display
- Pre/Post symbol placement
- Space handling
- Decimal precision
4. Cultural Elements
- Color meanings
- Icon interpretations
- Text direction (LTR/RTL)
- Personal name formats
```
### Compliance & Regulatory Requirements
#### Regulatory Compliance Matrix
| Requirement | Region | Impact Areas | Implementation Status |
| ----------- | ------- | -------------------------------------------------------- | --------------------- |
| GDPR | EU | - User Data
- Consent Management
- Data Export | Required |
| CCPA | US (CA) | - Privacy Policy
- Data Deletion
- Opt-out | Required |
| HIPAA | US | - Health Data
- Access Controls
- Audit Logs | Optional |
| SOC 2 | Global | - Security
- Availability
- Processing Integrity | Required |
#### Data Protection Implementation
```mermaid
graph TD
subgraph Data Protection
A[Data Collection] --> B[Processing]
B --> C[Storage]
C --> D[Deletion]
E[User Consent] --> A
F[Access Controls] --> B
G[Encryption] --> C
H[Audit Logs] --> D
end
subgraph Compliance Features
I[Cookie Management]
J[Privacy Center]
K[Data Export]
L[Consent Records]
end
```
## Best Practices for Writing an Effective PRD
### 1. Structural Guidelines
- Keep it concise but comprehensive
- Use clear, unambiguous language
- Include visual aids (diagrams, wireframes)
- Maintain consistent formatting
- Version control and change tracking
### 2. Collaboration Workflow
```mermaid
graph LR
A[Draft PRD] --> B[Internal Review]
B --> C[Stakeholder Input]
C --> D[Technical Review]
D --> E[Final Approval]
E --> F[Implementation]
F -.-> G[Continuous Updates]
G -.-> A
```
### 3. AI-Assisted PRD Creation
- Automated requirement analysis
- Natural language processing for clarity
- Consistency checking
- Template generation
- Real-time collaboration features
## PRD vs. Other Documentation Types
### Documentation Comparison
| Document Type | Purpose | Audience | When to Use | Level of Detail |
| ------------- | --------------------------- | ------------------------- | ------------------ | --------------- |
| PRD | Define product requirements | PMs, Engineers, Designers | Before development | High |
| BRD | Business objectives | Executives, Stakeholders | Initial strategy | Medium |
| MRD | Market analysis | Product & Marketing | Research phase | Medium |
| SRS | Technical specs | Developers, QA | Design phase | Very High |
| Epics/Stories | Agile planning | Agile Teams | Sprint planning | Low |
### Document Flow
```mermaid
graph TD
A[Market Requirements Document] --> B[Business Requirements Document]
B --> C[Product Requirements Document]
C --> D[Software Requirements Specification]
D --> E[Technical Documentation]
C --> F[User Stories & Epics]
```
## Common PRD Mistakes & How to Avoid Them
### Anti-Patterns
1. **Overcomplication**
- Too much detail
- Unclear priorities
- Excessive jargon
2. **Poor Alignment**
- Mismatched business goals
- Unclear stakeholder needs
- Inconsistent vision
3. **Inadequate Validation**
- Missing stakeholder buy-in
- Insufficient technical review
- Lack of user feedback
4. **Maintenance Issues**
- Outdated information
- Inconsistent updates
- Version control problems
### Prevention Strategies
```mermaid
graph TD
A[Common Mistakes] --> B[Prevention Strategies]
B --> C[Regular Reviews]
B --> D[Clear Templates]
B --> E[Stakeholder Involvement]
B --> F[Version Control]
C --> G[Quality PRD]
D --> G
E --> G
F --> G
```
## PRD Templates & Real-World Examples
### 1. Minimalist PRD Template
```markdown
# [Product Name] PRD
## Overview
[Brief product description]
## Objectives
- Primary goal
- Secondary goals
## Features
1. Core Features
- Feature 1
- Feature 2
2. Enhanced Features
- Feature 3
- Feature 4
## Success Metrics
- Metric 1
- Metric 2
## Timeline
[Basic timeline]
```
### 2. Enterprise PRD Template
[Extended template with full sections and examples]
### 3. AI-Assisted Template Generation
Example of AI-generated PRD structure based on project type and requirements.
## Advanced PRD Techniques
### 1. Living Document Approach
- Real-time updates
- Collaborative editing
- Version history
- Change tracking
- Automated notifications
### 2. Integration with Development Tools
- JIRA integration
- GitHub/GitLab linking
- CI/CD pipeline integration
- Automated testing links
### 3. Feedback Loops
```mermaid
graph TD
A[PRD Creation] --> B[Stakeholder Review]
B --> C[Development Input]
C --> D[User Testing]
D --> E[Feedback Analysis]
E --> F[PRD Updates]
F --> A
```
## Resources & Further Reading
1. Industry Standards
- IEEE 830-1998
- ISO/IEC/IEEE 29148:2018
- Agile PRD Frameworks
2. Tools & Templates
- Professional PRD templates
- Recommended software tools
- Collaboration platforms
3. Further Learning
- Books and publications
- Online courses
- Professional certifications
4. Community Resources
- Product management forums
- Professional networks
- Industry blogs
### Implementation & Migration Requirements
#### Migration Strategy Matrix
| Component | Current State | Target State | Migration Approach | Timeline | Risk Level |
| -------------- | ------------- | -------------------- | ---------------------------------- | -------- | ---------- |
| Database | MySQL 5.7 | PostgreSQL 14 | Dual-write with validation | 3 months | High |
| Authentication | Custom OAuth | OAuth2 + OIDC | Parallel systems with feature flag | 2 months | Medium |
| API | REST v1 | REST v2 + GraphQL | API versioning with deprecation | 4 months | Medium |
| Frontend | AngularJS | React | Module-by-module with router split | 6 months | High |
| Storage | Local FS | Cloud Object Storage | Gradual migration with CDN | 1 month | Low |
#### Phase-wise Implementation Plan
```mermaid
gantt
title Implementation Phases
dateFormat YYYY-MM-DD
section Foundation
Security & Auth :2024-01-01, 60d
Core API :2024-02-01, 90d
Base UI :2024-03-01, 45d
section Features
User Management :2024-04-15, 30d
Project Module :2024-05-15, 45d
Analytics :2024-06-01, 30d
section Integration
Third-party APIs :2024-07-01, 45d
Mobile Apps :2024-08-15, 60d
section Launch
Beta Testing :2024-10-15, 30d
GA Release :2024-11-15, 15d
```
### Support & Maintenance Requirements
#### SLA Definitions
| Service Level | Response Time | Resolution Time | Availability | Support Hours |
| ------------- | ------------- | --------------- | ------------ | ------------- |
| Platinum | 15 mins | 2 hours | 99.99% | 24/7/365 |
| Gold | 30 mins | 4 hours | 99.9% | 24/5 |
| Silver | 1 hour | 8 hours | 99.5% | 8/5 |
| Bronze | 4 hours | 24 hours | 99% | 8/5 |
#### Incident Response Framework
```mermaid
stateDiagram-v2
[*] --> Detection
Detection --> Triage
Triage --> Assessment
Assessment --> Minor
Assessment --> Major
Assessment --> Critical
Minor --> Resolution
Major --> Escalation
Critical --> Emergency
Escalation --> Resolution
Emergency --> Resolution
Resolution --> Review
Review --> Documentation
Documentation --> [*]
```
#### Maintenance Windows
```mermaid
graph TD
subgraph Maintenance Schedule
A[Regular Maintenance] --> B[Security Updates]
A --> C[Performance Tuning]
A --> D[Backup Verification]
E[Emergency Maintenance] --> F[Critical Patches]
E --> G[Security Incidents]
E --> H[System Recovery]
end
subgraph Communication
I[Advance Notice]
J[Status Updates]
K[Completion Report]
end
```
### Documentation Requirements
#### Documentation Structure
```mermaid
mindmap
root((Documentation))
Technical
Architecture
API Reference
Database Schema
Deployment Guide
User
Getting Started
User Manual
FAQ
Troubleshooting
Development
Coding Standards
Contributing Guide
Testing Guide
Release Notes
Operations
Runbooks
Monitoring Guide
Backup Procedures
Disaster Recovery
```
#### Version Control Strategy
| Component | Tool | Branching Strategy | Review Process | Release Cycle |
| --------- | ---- | -------------------- | -------------- | ------------- |
| Code | Git | GitFlow | PR + 2 reviews | 2 weeks |
| Docs | Git | Feature branches | PR + 1 review | As needed |
| Config | Git | Environment branches | PR + 2 reviews | On demand |
| Assets | LFS | Main only | Manual review | Monthly |
### Training & Knowledge Transfer
#### Training Matrix
```mermaid
graph TD
subgraph Internal Training
A[Developer Onboarding] --> A1[Architecture]
A --> A2[Code Standards]
A --> A3[Tools & Processes]
B[Support Training] --> B1[Troubleshooting]
B --> B2[Customer Service]
B --> B3[SLA Management]
end
subgraph Customer Training
C[End User Training] --> C1[Basic Features]
C --> C2[Advanced Usage]
C --> C3[Best Practices]
D[Admin Training] --> D1[System Config]
D --> D2[User Management]
D --> D3[Reporting]
end
```
#### Knowledge Base Requirements
| Content Type | Format | Update Frequency | Review Process | Access Level |
| ------------ | -------------- | ---------------- | -------------- | ------------ |
| API Docs | OpenAPI + MD | Per Release | Tech Review | Public |
| User Guides | MD + Video | Monthly | Content Review | Public |
| Tutorials | Interactive | Quarterly | UX Review | Public |
| Runbooks | MD + Playbooks | As Needed | Ops Review | Internal |
| Architecture | Diagrams + MD | Quarterly | Arch Review | Internal |
### Future Considerations & Scalability
#### Scalability Planning
```mermaid
graph TD
subgraph Current State
A[10K Users]
B[5TB Data]
C[100 TPS]
end
subgraph 6 Months
D[50K Users]
E[20TB Data]
F[500 TPS]
end
subgraph 1 Year
G[200K Users]
H[100TB Data]
I[2000 TPS]
end
A --> D --> G
B --> E --> H
C --> F --> I
```
#### Technology Roadmap
```mermaid
timeline
title Technology Evolution Plan
2024 Q1 : Core Platform
Microservices Architecture
Container Orchestration
2024 Q2 : Advanced Features
AI/ML Integration
Real-time Analytics
2024 Q3 : Scale Out
Global CDN
Multi-region Deploy
2024 Q4 : Next-gen Features
Edge Computing
Blockchain Integration
2025 Q1 : Innovation
AR/VR Support
IoT Integration
```
### Appendix
#### Glossary of Terms
| Term | Definition | Context | Related Terms |
| ---- | ------------------------ | --------------------- | --------------------- |
| SLA | Service Level Agreement | Support & Maintenance | Uptime, Response Time |
| OIDC | OpenID Connect | Authentication | OAuth2, SSO |
| CDN | Content Delivery Network | Infrastructure | Edge, Cache |
| TPS | Transactions Per Second | Performance | Throughput, Latency |
| SSO | Single Sign-On | Security | Authentication, SAML |
#### Reference Architecture
```mermaid
graph TD
subgraph Frontend
A[Web App] --> B[API Gateway]
C[Mobile App] --> B
end
subgraph Backend
B --> D[Load Balancer]
D --> E[Service Mesh]
E --> F[Microservices]
F --> G[Cache]
F --> H[Database]
F --> I[Storage]
end
subgraph Infrastructure
J[Monitoring]
K[Logging]
L[Security]
M[Backup]
end
```
### Conclusion
This comprehensive PRD guide provides a structured approach to documenting
product requirements. Remember that a PRD is a living document that should be
regularly updated to reflect changes in requirements, market conditions, and
technological advancements.
Key takeaways:
1. Start with clear objectives and scope
2. Include detailed technical specifications
3. Consider all stakeholder perspectives
4. Plan for scalability and future growth
5. Maintain clear documentation and knowledge transfer
6. Regular reviews and updates are essential
7. Focus on measurable outcomes and success criteria
### [React Component Lifecycle](https://sametcc.me/gist/react-component-lifecycle)
---
title: "React Component Lifecycle"
publishedAt: "2024-08-25"
summary:
"Deep dive into React component lifecycle methods, hooks, and modern patterns
for managing component state and side effects in React applications."
tags: [React, JavaScript, Hooks, Components, State Management]
language: "en"
type: "gist"
status: "published"
---
# React Component Lifecycle
## Table of Contents
- [Introduction](#introduction)
- [Lifecycle Overview](#lifecycle-overview)
- [Mounting Phase](#mounting-phase)
- [Updating Phase](#updating-phase)
- [Unmounting Phase](#unmounting-phase)
- [Error Handling](#error-handling)
- [Hooks vs Class Components](#hooks-vs-class-components)
- [Modern React with Hooks: A Deep Dive](#modern-react-with-hooks-a-deep-dive)
- [Best Practices](#best-practices)
- [Common Pitfalls](#common-pitfalls)
- [Real-World Examples](#real-world-examples)
- [Resources & Further Reading](#resources--further-reading)
## Introduction
React component lifecycle represents the series of events that happen from the
time a component is created and mounted on the DOM to when it is unmounted and
destroyed. Understanding these lifecycle phases is crucial for building
efficient and bug-free React applications.
### Why Understanding Lifecycle is Important
```mermaid
mindmap
root((React Lifecycle))
Performance Optimization
Efficient Updates
Memory Management
Resource Cleanup
Bug Prevention
Memory Leaks
Infinite Loops
Race Conditions
Feature Implementation
Data Fetching
State Management
Side Effects
Development Process
Debugging
Testing
Maintenance
```
## Lifecycle Overview
### Class Component Lifecycle
```mermaid
graph TD
A[Component Creation] --> B[constructor]
B --> C[getDerivedStateFromProps]
C --> D[render]
D --> E[componentDidMount]
F[Props/State Update] --> G[getDerivedStateFromProps]
G --> H[shouldComponentUpdate]
H --> I[render]
I --> J[getSnapshotBeforeUpdate]
J --> K[componentDidUpdate]
L[Component Unmount] --> M[componentWillUnmount]
```
### Functional Component with Hooks
```mermaid
graph TD
A[Component Render] --> B[useState]
B --> C[useEffect Setup]
C --> D[Component Mount]
D --> E[Effect Cleanup]
F[State/Props Change] --> G[Re-render]
G --> H[Effect Cleanup]
H --> I[Effect Setup]
J[Component Unmount] --> K[Final Effect Cleanup]
```
## Mounting Phase
### Constructor (Class Components)
```javascript
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
data: null,
loading: true,
};
}
}
```
### Initial Render
```mermaid
sequenceDiagram
participant Parent
participant Component
participant DOM
Parent->>Component: Create
Component->>Component: Initialize State
Component->>DOM: First Render
Component->>Component: componentDidMount
Component->>Parent: Mount Complete
```
## Updating Phase
### Update Triggers
1. Props Change
2. State Update
3. Parent Re-render
4. Context Change
### Update Lifecycle Methods
```mermaid
stateDiagram-v2
[*] --> getDerivedStateFromProps
getDerivedStateFromProps --> shouldComponentUpdate
shouldComponentUpdate --> render: true
shouldComponentUpdate --> [*]: false
render --> getSnapshotBeforeUpdate
getSnapshotBeforeUpdate --> componentDidUpdate
componentDidUpdate --> [*]
```
## Unmounting Phase
### Cleanup Operations
```javascript
// Class Component
componentWillUnmount() {
// Clean up subscriptions
this.subscription.unsubscribe();
// Clear intervals/timeouts
clearInterval(this.intervalId);
// Remove event listeners
window.removeEventListener('resize', this.handleResize);
}
// Functional Component
useEffect(() => {
const subscription = dataSource.subscribe();
return () => {
subscription.unsubscribe();
};
}, []);
```
## Error Handling
### Error Boundaries
```javascript
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return Something went wrong.
;
}
return this.props.children;
}
}
```
## Hooks vs Class Components
### Lifecycle Method Equivalents
| Class Component | Hooks Equivalent |
| ------------------------ | ------------------------------------------- |
| constructor | useState |
| componentDidMount | `useEffect(() => {}, [])` |
| componentDidUpdate | `useEffect(() => {}, [deps])` |
| componentWillUnmount | `useEffect(() => { return () => {}; }, [])` |
| shouldComponentUpdate | `React.memo` |
| getDerivedStateFromProps | `useState` + `useEffect` |
### Common Patterns
```mermaid
graph TD
subgraph Class Component
A[Constructor] --> B[CDM]
B[ComponentDidMount] --> C[CDU]
C[ComponentDidUpdate] --> D[CWU]
D[ComponentWillUnmount]
end
subgraph Hooks
E[useState] --> F[useEffect Setup]
F --> G[useEffect Cleanup]
G --> H[useEffect Dependencies]
end
```
## Modern React with Hooks: A Deep Dive
### Understanding Hooks Lifecycle
```mermaid
graph TD
A[Component Initial Render] --> B[useState Initialization]
B --> C[useEffect Dependencies Check]
C --> D[Effect Cleanup from Previous Render]
D --> E[Effect Setup]
F[Re-render Trigger] --> G[useState Update]
G --> H[useEffect Dependencies Compare]
H --> I[Skip Effect: Dependencies Same]
H --> J[Run Effect: Dependencies Changed]
K[Component Unmount] --> L[Run All Cleanup Functions]
```
### Key Hooks and Their Lifecycle Behaviors
#### 1. useState
```javascript
function Counter() {
// Initialize state - runs only once
const [count, setCount] = useState(0);
// Batched updates
const incrementTwice = () => {
setCount((prev) => prev + 1); // Update function form
setCount((prev) => prev + 1); // Guaranteed to use latest state
};
// State updates with object merging
const [state, setState] = useState({ count: 0, name: "John" });
const updatePartially = () => {
setState((prev) => ({
...prev, // Preserve other fields
count: prev.count + 1,
}));
};
}
```
#### 2. useEffect Variations
```javascript
function CompleteEffectGuide({ id }) {
// 1. Run on every render
useEffect(() => {
console.log("I run on every render");
});
// 2. Run only once (component mount)
useEffect(() => {
console.log("I run only once on mount");
// Cleanup on unmount
return () => {
console.log("I run only once on unmount");
};
}, []);
// 3. Run on specific dependencies
useEffect(() => {
console.log("I run when id changes");
// Cleanup before next effect run
return () => {
console.log("I clean up before next effect or unmount");
};
}, [id]);
// 4. Multiple effects organization
useEffect(() => {
// Data fetching
}, [id]);
useEffect(() => {
// Event listeners
}, []);
useEffect(() => {
// WebSocket connection
}, []);
}
```
#### 3. useLayoutEffect
```javascript
function LayoutEffectExample() {
const [width, setWidth] = useState(0);
// Runs synchronously after DOM mutations
useLayoutEffect(() => {
// DOM measurements
const measuredWidth = divRef.current.getBoundingClientRect().width;
setWidth(measuredWidth);
}, []);
return Measured Width: {width}
;
}
```
### Advanced Hooks Patterns
#### 1. Custom Hooks with Lifecycle Management
```javascript
function useDataFetching(url) {
const [state, setState] = useState({
data: null,
loading: true,
error: null,
});
useEffect(() => {
let mounted = true;
const fetchData = async () => {
try {
setState((prev) => ({ ...prev, loading: true }));
const response = await fetch(url);
const data = await response.json();
if (mounted) {
setState({
data,
loading: false,
error: null,
});
}
} catch (error) {
if (mounted) {
setState({
data: null,
loading: false,
error: error.message,
});
}
}
};
fetchData();
return () => {
mounted = false;
};
}, [url]);
return state;
}
```
#### 2. Conditional Effects Pattern
```javascript
function ConditionalEffectExample({ isEnabled }) {
useEffect(() => {
if (!isEnabled) return;
const subscription = subscribe();
return () => {
subscription.unsubscribe();
};
}, [isEnabled]);
}
```
#### 3. Dependencies Management
```javascript
function DependenciesExample({ onDataChange, data }) {
// 1. Function dependencies
useEffect(() => {
onDataChange(data);
}, [onDataChange, data]); // Include both function and data
// 2. Stable references with useCallback
const handleData = useCallback(() => {
processData(data);
}, [data]);
// 3. Object dependencies with useMemo
const config = useMemo(
() => ({
id: data.id,
type: data.type,
}),
[data.id, data.type],
);
}
```
### Common Hooks Scenarios
#### 1. Subscription Management
```javascript
function SubscriptionComponent() {
useEffect(() => {
const subscription = source.subscribe({
next: (data) => console.log(data),
error: (error) => console.error(error),
});
return () => {
subscription.unsubscribe();
};
}, []);
}
```
#### 2. Event Listener Management
```javascript
function EventListenerExample() {
useEffect(() => {
const handleResize = debounce(() => {
// Handle resize
}, 250);
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
handleResize.cancel(); // Clean up debounce
};
}, []);
}
```
#### 3. Async Operations
```javascript
function AsyncOperationsExample({ id }) {
const [data, setData] = useState(null);
useEffect(() => {
let isCancelled = false;
async function loadData() {
try {
const response = await fetch(`/api/data/${id}`);
const newData = await response.json();
if (!isCancelled) {
setData(newData);
}
} catch (error) {
if (!isCancelled) {
console.error("Failed to load data:", error);
}
}
}
loadData();
return () => {
isCancelled = true;
};
}, [id]);
}
```
### Performance Optimization with Hooks
#### 1. useMemo for Expensive Computations
```javascript
function ExpensiveComponent({ data }) {
// Memoize expensive computation
const processedData = useMemo(() => {
return data.map((item) => expensiveOperation(item));
}, [data]);
}
```
#### 2. useCallback for Stable References
```javascript
function CallbackExample({ onItemClick }) {
const handleClick = useCallback(
(item) => {
onItemClick(item.id);
},
[onItemClick],
);
}
```
#### 3. Custom Hook for Debouncing
```javascript
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
```
### Testing Hooks Lifecycle
```javascript
// 1. Testing Hook Initialization
test("initializes with correct state", () => {
const { result } = renderHook(() => useState(0));
expect(result.current[0]).toBe(0);
});
// 2. Testing Effect Cleanup
test("cleans up effect", () => {
const cleanup = jest.fn();
const { unmount } = renderHook(() => {
useEffect(() => cleanup, []);
});
unmount();
expect(cleanup).toHaveBeenCalled();
});
// 3. Testing Custom Hooks
test("custom hook behavior", async () => {
const { result, waitForNextUpdate } = renderHook(() =>
useDataFetching("api/data"),
);
expect(result.current.loading).toBe(true);
await waitForNextUpdate();
expect(result.current.loading).toBe(false);
});
```
### Common Pitfalls and Solutions
#### 1. Infinite Loops
```javascript
// Bad
function InfiniteLoopComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Will cause infinite loop
});
}
// Good
function FixedComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount((prev) => prev + 1); // Use functional update
}, 1000);
return () => clearInterval(timer);
}, []); // Empty dependency array
}
```
#### 2. Stale Closures
```javascript
// Bad
function StaleClosureExample() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // Stale closure
}, 1000);
return () => clearInterval(timer);
}, []); // Missing dependency
// Good
useEffect(() => {
const timer = setInterval(() => {
setCount((prev) => prev + 1); // Using functional update
}, 1000);
return () => clearInterval(timer);
}, []); // No need for dependencies
}
```
#### 3. Race Conditions
```javascript
function RaceConditionExample({ id }) {
const [data, setData] = useState(null);
useEffect(() => {
let isCurrent = true;
async function fetchData() {
const response = await fetch(`/api/data/${id}`);
const newData = await response.json();
if (isCurrent) {
setData(newData); // Only update if still current
}
}
fetchData();
return () => {
isCurrent = false; // Prevent updates if component unmounted
};
}, [id]);
}
```
## Best Practices
### Performance Optimization
1. **Proper Dependencies**
```javascript
// Good
useEffect(() => {
fetchData(userId);
}, [userId]);
// Bad
useEffect(() => {
fetchData(userId);
}, []); // Missing dependency
```
2. **Cleanup Operations**
```javascript
useEffect(() => {
const subscription = subscribe();
return () => {
subscription.unsubscribe();
};
}, []);
```
3. **Conditional Updates**
```javascript
shouldComponentUpdate(nextProps, nextState) {
return this.props.value !== nextProps.value;
}
```
### Common Anti-patterns to Avoid
```mermaid
graph TD
A[Anti-patterns] --> B[setState in componentDidMount]
A --> C[Missing Dependency Array]
A --> D[Direct DOM Manipulation]
A --> E[setState in componentWillUnmount]
A --> F[Async setState without Cleanup]
```
## Real-World Examples
### Data Fetching Component
```javascript
function DataFetchingComponent({ id }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true;
async function fetchData() {
try {
setLoading(true);
const response = await api.getData(id);
if (isMounted) {
setData(response);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err);
setData(null);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
}
fetchData();
return () => {
isMounted = false;
};
}, [id]);
if (loading) return ;
if (error) return ;
return ;
}
```
## Resources & Further Reading
### Official Documentation
- [React Documentation](https://react.dev/reference/react/Component)
- [Hooks API Reference](https://react.dev/reference/react/hooks)
### Additional Resources
- React DevTools for debugging
- Performance profiling tools
- Testing lifecycle methods
### Common Issues & Solutions
| Issue | Solution |
| ------------------ | ------------------------------------------------ |
| Memory Leaks | Proper cleanup in useEffect/componentWillUnmount |
| Infinite Loops | Correct dependency arrays |
| Race Conditions | Cancellation tokens or mounted flags |
| Performance Issues | Proper memoization and lifecycle optimizations |
### Appendix: Lifecycle Method Cheat Sheet
```mermaid
graph TD
subgraph Mounting
A[constructor] --> B[getDerivedStateFromProps]
B --> C[render]
C --> D[componentDidMount]
end
subgraph Updating
E[getDerivedStateFromProps] --> F[shouldComponentUpdate]
F --> G[render]
G --> H[getSnapshotBeforeUpdate]
H --> I[componentDidUpdate]
end
subgraph Unmounting
J[componentWillUnmount]
end
subgraph Error Handling
K[getDerivedStateFromError]
L[componentDidCatch]
end
```
### [A Comprehensive Guide to Creating and Using Tasks in Visual Studio Code](https://sametcc.me/gist/using-tasks-in-vscode)
---
title: "A Comprehensive Guide to Creating and Using Tasks in Visual Studio Code"
publishedAt: "2024-08-25"
summary: "Complete guide to VS Code tasks configuration including custom tasks,
automation, debugging, and integration with build systems."
tags: [VS Code, Tasks, Automation, Build Tools, Debugging]
language: "en"
type: "gist"
status: "published"
---
# A Comprehensive Guide to Creating and Using Tasks in Visual Studio Code
## Table of Contents
1. [Introduction to Tasks](#introduction-to-tasks-in-vs-code)
2. [Why Use Tasks?](#why-use-tasks)
3. [Understanding tasks.json](#understanding-tasksjson)
4. [Task Configuration in Detail](#task-configuration-in-detail)
5. [Practical Examples](#practical-examples)
6. [Advanced Task Features](#advanced-task-features)
7. [Problem Matchers](#problem-matchers)
8. [Variables and Inputs](#variables-and-inputs)
9. [Task Groups and Organization](#task-groups-and-organization)
10. [Best Practices and Tips](#best-practices-and-tips)
## Introduction to Tasks in VS Code
Visual Studio Code's task system is a powerful automation feature that
transforms your editor into a complete development environment. Tasks allow you
to:
- Execute build scripts
- Run test suites
- Deploy applications
- Perform code analysis
- And automate any command-line operation
Think of tasks as your personal development assistant that can execute commands
with a single keystroke, ensuring consistency and saving valuable development
time.
## Why Use Tasks?
### Automation Benefits
1. **Development Workflow Automation**
- Compile code automatically
- Run tests on file save
- Generate documentation
- Package applications for distribution
2. **Error Prevention**
- Consistent execution of commands
- Standardized build processes
- Automated validation steps
3. **Time Savings**
- Reduce manual command typing
- Quick access to common operations
- Parallel task execution
4. **Team Collaboration**
- Share common development tasks
- Standardize project workflows
- Onboard new team members easily
### Real-world Scenarios
- Frontend developer running webpack in watch mode
- Java developer compiling and running JUnit tests
- Python developer running linting and type checking
- Full-stack developer managing multiple services
## Understanding tasks.json
### Basic Structure Deep Dive
Here's a comprehensive example of a tasks.json file structure:
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "My Task",
"type": "shell",
"command": "echo",
"args": ["Hello World"],
"group": "build",
"presentation": {
"reveal": "always",
"panel": "shared"
},
"problemMatcher": ["$eslint-compact"]
}
]
}
```
### Location and Creation
1. Workspace-level tasks:
```plaintext
your-project/
├── .vscode/
│ └── tasks.json
├── src/
└── ...
```
2. User-level tasks:
- Windows: %APPDATA%\Code\User\tasks.json
- macOS: $HOME/Library/Application Support/Code/User/tasks.json
- Linux: $HOME/.config/Code/User/tasks.json
## Task Configuration in Detail
### 1. Basic Properties
#### Label (Required)
```json
{
"label": "Build TypeScript",
"type": "shell",
"command": "tsc"
}
```
#### Type (Required)
Supported types with examples:
- Shell Type
```json
{
"label": "List Files",
"type": "shell",
"command": "ls",
"windows": {
"command": "dir"
}
}
```
- Process Type
```json
{
"label": "Run Node Script",
"type": "process",
"command": "node",
"args": ["app.js"]
}
```
- npm Type
```json
{
"label": "Install Dependencies",
"type": "npm",
"script": "install"
}
```
### 2. Command and Arguments
#### Simple Command
```json
{
"label": "Echo Text",
"type": "shell",
"command": "echo",
"args": ["Hello", "World"]
}
```
#### Complex Command with Arguments
```json
{
"label": "Compile TypeScript",
"type": "shell",
"command": "tsc",
"args": ["--project", "tsconfig.json", "--watch", "--pretty"]
}
```
### 3. Advanced Configuration
#### Working Directory
```json
{
"label": "Build Project",
"type": "shell",
"command": "make",
"options": {
"cwd": "${workspaceFolder}/build"
}
}
```
#### Environment Variables
```json
{
"label": "Deploy to Staging",
"type": "shell",
"command": "deploy.sh",
"options": {
"env": {
"NODE_ENV": "staging",
"API_KEY": "secret-key",
"DEBUG": "true"
}
}
}
```
## Practical Examples
### 1. Full-Stack Development Setup
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Start Full Stack",
"dependsOn": ["Start Frontend", "Start Backend"],
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Start Frontend",
"type": "npm",
"script": "start",
"path": "frontend/",
"isBackground": true,
"presentation": {
"panel": "dedicated",
"group": "dev-servers"
},
"problemMatcher": {
"owner": "custom",
"pattern": {
"regexp": "^\\[.*\\] (.*):(\\d+):(\\d+): (.*)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
},
"background": {
"activeOnStart": true,
"beginsPattern": "Starting development server",
"endsPattern": "Compiled successfully"
}
}
},
{
"label": "Start Backend",
"type": "shell",
"command": "python",
"args": ["manage.py", "runserver"],
"options": {
"cwd": "${workspaceFolder}/backend"
},
"isBackground": true,
"presentation": {
"panel": "dedicated",
"group": "dev-servers"
}
}
]
}
```
### 2. Multi-Environment Docker Deployment
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Docker: Build & Deploy",
"type": "shell",
"command": "docker-compose",
"args": [
"-f",
"docker-compose.${input:environment}.yml",
"up",
"-d",
"--build"
],
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
],
"inputs": [
{
"id": "environment",
"type": "pickString",
"description": "Select deployment environment",
"options": ["dev", "staging", "prod"],
"default": "dev"
}
]
}
```
### 3. Advanced Build Pipeline
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Full Build Pipeline",
"dependsOn": ["Clean", "Lint", "Test", "Build", "Package"],
"dependsOrder": "sequence",
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": true,
"clear": false
}
},
{
"label": "Clean",
"type": "shell",
"command": "rm -rf dist/*",
"windows": {
"command": "if exist dist rd /s /q dist"
}
},
{
"label": "Lint",
"type": "npm",
"script": "lint",
"problemMatcher": "$eslint-stylish"
},
{
"label": "Test",
"type": "npm",
"script": "test",
"group": "test",
"problemMatcher": "$jest"
},
{
"label": "Build",
"type": "npm",
"script": "build",
"problemMatcher": "$tsc"
},
{
"label": "Package",
"type": "shell",
"command": "zip -r dist/app.zip dist/*",
"windows": {
"command": "powershell Compress-Archive -Path dist/* -DestinationPath dist/app.zip -Force"
}
}
]
}
```
## Advanced Task Features
- **inputs**: The `inputs` section in `tasks.json` allows you to prompt the user
for input when a task is run. This input can then be used as variables in the
task's command and args. You can define different types of inputs (text input,
pick list, etc.).
- **isBackground**: Setting `isBackground: true` in a task definition marks the
task as a background task. Background tasks typically run continuously or for
a long time (e.g., a watcher task that monitors file changes). Background
tasks have special behavior in VS Code – they don't block other operations,
and you can keep working in the editor while they are running. You can use the
"Terminate Task" command to stop a background task.
- **Composite Tasks**: You can create tasks that are composed of other tasks
using the `dependsOn` property. This allows you to chain multiple tasks
together into a single workflow. A composite task doesn't have its own command
or args – it only specifies the tasks it depends on.
## Problem Matchers
Problem matchers are essential for making tasks useful for build and linting
processes. They allow VS Code to understand the output of your tools and display
errors and warnings in the Problems panel and in the editor itself.
VS Code provides many predefined problem matchers for common tools like GCC,
TypeScript compiler (`tsc`), ESLint, JSHint, and more. These are identified by
names like `$gcc`, `$tsc`, `$eslint-compact`, etc.
When you specify a `problemMatcher` in your task, VS Code scans the output of
the task for patterns that match the problem matcher's definition. When a match
is found, VS Code extracts information like file path, line number, column
number, severity (error/warning/info), and message, and then displays this as a
problem in the Problems panel. Clicking on a problem in the Problems panel will
often take you directly to the offending line in your code.
### Predefined Problem Matchers
To use a predefined problem matcher, simply put its name (e.g., `$gcc`, `$tsc`)
in the `problemMatcher` array of your task definition.
### Custom Problem Matchers (Brief Overview)
For tools where there isn't a predefined problem matcher, or if you need more
control, you can define custom problem matchers. Custom problem matchers are
more complex and involve defining regular expressions to parse the output of
your tool. They are typically defined as objects within the `problemMatcher`
array, instead of just strings.
A custom problem matcher generally consists of:
- **name**: A name for your problem matcher.
- **owner**: Typically "external" for tasks.
- **fileLocation**: How file paths are represented in the output ("absolute",
"relative" to workspace folder).
- **pattern**: An object or array of objects that define the regular expression
patterns to match against the task output. Each pattern can have groups to
capture different parts of the error/warning information (file path, line
number, column number, message, etc.).
- **severity, code, loop, message**: Optional properties to customize the
problem reporting.
Creating custom problem matchers can be advanced and requires understanding
regular expressions and the output format of your tool. Refer to the VS Code
documentation for detailed information on custom problem matchers.
## Problem Matchers in Detail
### Custom Problem Matcher Examples
#### 1. Python unittest Problem Matcher
```json
{
"problemMatcher": {
"owner": "python",
"fileLocation": ["relative", "${workspaceFolder}"],
"pattern": {
"regexp": "^\\s*File \"(.*?)\", line (\\d+).*$",
"file": 1,
"line": 2,
"message": 0
}
}
}
```
#### 2. Custom Compiler Output Matcher
```json
{
"problemMatcher": {
"owner": "custom-compiler",
"pattern": [
{
"regexp": "^\\s*(?:ERROR|WARNING)\\s+in\\s+(.*?):(\\d+):\\s*$",
"file": 1,
"line": 2
},
{
"regexp": "^\\s*(.*)$",
"message": 1
}
]
}
}
```
#### 3. Multi-line Error Pattern
```json
{
"problemMatcher": {
"owner": "multiline-error",
"pattern": [
{
"regexp": "^Error in file: (.*)$",
"file": 1
},
{
"regexp": "^On line: (\\d+)$",
"line": 1
},
{
"regexp": "^Message: (.*)$",
"message": 1,
"loop": true
}
]
}
}
```
## Variables and Inputs
VS Code provides a rich set of predefined variables that you can use in your
task configurations. These variables are replaced with their actual values when
the task is executed. Some commonly used variables:
- `${workspaceFolder}`: The path to the workspace folder opened in VS Code.
- `${workspaceFolderBasename}`: The name of the workspace folder.
- `${file}`: The full path to the currently opened file in the editor.
- `${fileWorkspaceFolder}`: The workspace folder path of the currently opened
file.
- `${relativeFile}`: The path to the currently opened file relative to the
workspace folder.
- `${relativeFileDirname}`: The directory name of the currently opened file
relative to the workspace folder.
- `${fileBasename}`: The filename of the currently opened file.
- `${fileBasenameNoExtension}`: The filename of the currently opened file
without its extension.
- `${fileDirname}`: The directory of the currently opened file.
- `${cwd}`: The current working directory of VS Code when the task is started
(usually the workspace folder).
- `${lineNumber}`: The current line number in the active file.
- `${selectedText}`: The currently selected text in the active file.
- `${execPath}`: The path to the VS Code executable.
- `${defaultBuildTask}`, `${defaultTestTask}`: Labels of the default build/test
tasks, if set.
You can see a full list of predefined variables in the VS Code documentation.
## Variables and Inputs Deep Dive
### 1. Predefined Variables with Examples
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Build Current File",
"type": "shell",
"command": "gcc",
"args": ["-g", "${file}", "-o", "${fileBasenameNoExtension}"],
"options": {
"cwd": "${fileDirname}"
}
},
{
"label": "Process Workspace Files",
"type": "shell",
"command": "python",
"args": [
"${workspaceFolder}/scripts/process.py",
"--input",
"${workspaceFolder}/data",
"--output",
"${workspaceFolder}/output/${command:CurrentDateTime}"
]
}
]
}
```
### 2. Custom Input Variables
#### Input Types Example
```json
{
"version": "2.0.0",
"inputs": [
{
"id": "buildType",
"type": "pickString",
"description": "Select build configuration",
"options": ["debug", "release", "profile"],
"default": "debug"
},
{
"id": "serverPort",
"type": "promptString",
"description": "Enter server port",
"default": "3000"
},
{
"id": "deployTarget",
"type": "command",
"command": "extension.getDeploymentTargets",
"args": { "type": "production" }
}
],
"tasks": [
{
"label": "Deploy Application",
"type": "shell",
"command": "./deploy.sh",
"args": [
"--type",
"${input:buildType}",
"--port",
"${input:serverPort}",
"--target",
"${input:deployTarget}"
]
}
]
}
```
## Real-World Task Configurations
### 1. Full-Stack Development Environment
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Dev Environment",
"dependsOn": [
"Frontend Dev Server",
"Backend API",
"Database",
"Watch TypeScript",
"Watch Tests"
],
"group": {
"kind": "build",
"isDefault": true
},
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "dedicated",
"showReuseMessage": false
}
},
{
"label": "Frontend Dev Server",
"type": "npm",
"script": "start",
"path": "client/",
"isBackground": true,
"problemMatcher": {
"owner": "webpack",
"pattern": {
"regexp": "ERROR in (.*)",
"file": 1
},
"background": {
"activeOnStart": true,
"beginsPattern": "Starting development server",
"endsPattern": "Compiled successfully"
}
}
},
{
"label": "Backend API",
"type": "shell",
"command": "poetry",
"args": ["run", "uvicorn", "api.main:app", "--reload"],
"options": {
"cwd": "${workspaceFolder}/server",
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/devdb",
"DEBUG": "1"
}
},
"isBackground": true,
"problemMatcher": {
"pattern": {
"regexp": "^.*Error in.*$",
"message": 0
},
"background": {
"activeOnStart": true,
"beginsPattern": "^INFO:.*Application startup complete.*$"
}
}
},
{
"label": "Database",
"type": "shell",
"command": "docker-compose",
"args": ["-f", "docker-compose.dev.yml", "up", "database"],
"isBackground": true
},
{
"label": "Watch TypeScript",
"type": "typescript",
"tsconfig": "client/tsconfig.json",
"option": "watch",
"problemMatcher": ["$tsc-watch"],
"isBackground": true
},
{
"label": "Watch Tests",
"type": "npm",
"script": "test:watch",
"path": "client/",
"isBackground": true,
"problemMatcher": "$jest-watch"
}
]
}
```
### 2. CI/CD Pipeline Tasks
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "CI Pipeline",
"dependsOn": [
"Install Dependencies",
"Type Check",
"Lint",
"Unit Tests",
"Integration Tests",
"Build",
"Docker Build"
],
"dependsOrder": "sequence",
"group": "test",
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "Install Dependencies",
"type": "shell",
"command": "npm ci && cd server && poetry install",
"problemMatcher": []
},
{
"label": "Type Check",
"type": "npm",
"script": "type-check",
"problemMatcher": "$tsc"
},
{
"label": "Lint",
"type": "shell",
"command": "npm run lint && cd server && poetry run flake8",
"problemMatcher": ["$eslint-stylish", "$flake8"]
},
{
"label": "Unit Tests",
"type": "shell",
"command": "npm test -- --coverage && cd server && poetry run pytest tests/unit",
"group": "test",
"presentation": {
"reveal": "always",
"panel": "dedicated"
},
"problemMatcher": ["$jest", "$pytest"]
},
{
"label": "Integration Tests",
"dependsOn": ["Start Test DB"],
"type": "shell",
"command": "cd server && poetry run pytest tests/integration",
"problemMatcher": ["$pytest"]
},
{
"label": "Start Test DB",
"type": "shell",
"command": "docker-compose -f docker-compose.test.yml up -d db",
"isBackground": true
},
{
"label": "Build",
"type": "shell",
"command": "npm run build && cd server && poetry run python setup.py bdist_wheel",
"problemMatcher": []
},
{
"label": "Docker Build",
"type": "shell",
"command": "docker-compose -f docker-compose.prod.yml build",
"problemMatcher": []
}
]
}
```
## Debugging Tasks
### 1. Task with Integrated Debugging
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Debug Python Tests",
"type": "shell",
"command": "python",
"args": ["-m", "pytest", "--pdb", "tests/"],
"options": {
"env": {
"PYTHONBREAKPOINT": "0"
}
},
"presentation": {
"reveal": "always",
"panel": "dedicated",
"focus": true
}
}
]
}
```
### 2. Pre-launch Task Configuration
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Build Debug",
"type": "shell",
"command": "gcc",
"args": ["-g", "${file}", "-o", "${fileBasenameNoExtension}"],
"group": {
"kind": "build",
"isDefault": true
}
}
],
"configurations": [
{
"name": "Debug C++ Program",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/${fileBasenameNoExtension}",
"preLaunchTask": "Build Debug"
}
]
}
```
## Task Groups and Organization
Task groups help organize your tasks and provide convenient ways to run related
tasks together. The predefined groups "build", "test", "clean", and "rebuild"
are special as VS Code provides dedicated commands for running tasks in these
groups ("Run Build Task", "Run Test Task", etc.).
To associate a task with a group, use the `group` property in the task
definition. You can specify just the group name as a string (e.g.,
`"group": "test"`), or you can use an object for more control:
```json
"group": {
"kind": "build", // or "test", "clean", "rebuild" or a custom name
"isDefault": true // Optional: make this the default task for this group
}
```
Setting `isDefault: true` for a task in a group makes it the task that will be
run when you use the "Run Build Task" (Ctrl+Shift+B or Cmd+Shift+B), "Run Test
Task", or "Run Clean Task" command. You can have one default task per group.
## Best Practices and Tips
- Keep tasks project-specific in `tasks.json`
- Use descriptive labels
- Leverage problem matchers
- Group related tasks
- Use variables for flexibility
- Document your tasks
- Start simple, iterate
### 1. Task Organization
- Group related tasks using task dependencies
- Use meaningful labels that describe the task's purpose
- Keep task configurations in version control
- Document complex task configurations
### 2. Performance Optimization
- Use `isBackground` for long-running tasks
- Implement proper problem matchers
- Configure appropriate presentation options
- Use task groups effectively
### 3. Maintainability
- Use variables instead of hardcoded paths
- Implement cross-platform compatibility
- Document environment requirements
- Use task inputs for flexibility
### 4. Common Pitfalls to Avoid
- Not handling cross-platform differences
- Incorrect working directory configuration
- Missing error handling
- Incomplete problem matcher patterns
## Conclusion
VS Code tasks are a remarkably versatile tool for automating development
workflows. By mastering `tasks.json` and understanding the different task
properties and features, you can significantly enhance your productivity and
streamline your development process within VS Code. Experiment with different
task types, problem matchers, and configurations to find the task setup that
best suits your projects and workflows.
VS Code's task system is a powerful tool that can significantly enhance your
development workflow. By understanding and implementing these advanced concepts
and examples, you can create sophisticated automation solutions that improve
your productivity and code quality. Remember to start simple and gradually add
complexity as needed, always keeping maintainability and team collaboration in
mind.
The examples provided in this guide serve as a foundation for building your own
task configurations. Feel free to modify and combine them to match your specific
development needs. As you become more comfortable with tasks, you'll discover
new ways to automate and streamline your development process.
## Azure Tasks
### 1. Deploy to Azure App Service
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Deploy to Azure App Service",
"type": "shell",
"command": "az webapp deploy",
"args": [
"--resource-group",
"myResourceGroup",
"--name",
"myAppService",
"--src-path",
"${workspaceFolder}/dist"
],
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}
```
### 2. Run Azure CLI Command
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Run Azure CLI Command",
"type": "shell",
"command": "az vm list",
"args": ["--resource-group", "myResourceGroup", "--output", "table"],
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}
```
### 3. Deploy Azure Functions
```json
{
"version": "2.0.0",
"tasks": [
{
"label": "Deploy Azure Functions",
"type": "shell",
"command": "func azure functionapp publish myfunctionapp",
"args": [],
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
}
]
}
```
These examples provide a starting point for automating Azure-related tasks
within VS Code, helping to streamline your development and deployment workflows.
### [Git Hooks: A Comprehensive Guide](https://sametcc.me/gist/git-hooks-guide)
---
title: "Git Hooks: A Comprehensive Guide"
publishedAt: "2024-08-25"
summary: "Complete guide to Git hooks implementation, best practices, and advanced automation techniques for client-side and server-side workflows."
tags: [Git, Git Hooks, Automation, DevOps, Version Control]
language: "en"
type: "gist"
status: "published"
---
# Git Hooks: A Comprehensive Guide
## Table of Contents
1. [What are Git Hooks?](#what-are-git-hooks)
2. [The History and Evolution of Git Hooks](#the-history-and-evolution-of-git-hooks)
3. [Types of Git Hooks](#types-of-git-hooks)
4. [How Git Hooks Work](#how-git-hooks-work)
5. [Hook Environment and Variables](#hook-environment-and-variables)
6. [Client-Side Hooks](#client-side-hooks)
7. [Server-Side Hooks](#server-side-hooks)
8. [Setting Up Git Hooks](#setting-up-git-hooks)
9. [Common Use Cases](#common-use-cases)
10. [Industry-Specific Use Cases](#industry-specific-use-cases)
11. [Best Practices](#best-practices)
12. [Performance Optimization](#performance-optimization)
13. [Examples](#examples)
14. [Advanced Examples](#advanced-examples)
15. [Troubleshooting](#troubleshooting)
16. [Advanced Topics](#advanced-topics)
17. [Testing Git Hooks](#testing-git-hooks)
18. [Summary](#summary)
19. [Security and Compliance](#security-and-compliance)
20. [Integration with CI/CD Systems](#integration-with-cicd-systems)
21. [Resources and Further Reading](#resources-and-further-reading)
22. [Conclusion](#conclusion)
## What are Git Hooks?
Git hooks are scripts that Git executes before or after events such as commit, push, and receive. They are a built-in feature of Git that allows you to trigger custom scripts at specific points in the Git workflow. Git hooks enable you to automate tasks, enforce coding standards, validate commits, and integrate with external systems.
### Key Characteristics
- **Event-driven**: Triggered by specific Git operations
- **Customizable**: Written in any scripting language (shell, Python, Ruby, etc.)
- **Local and remote**: Can be implemented on both client and server sides
- **Powerful**: Can modify Git behavior or prevent operations from completing
## The History and Evolution of Git Hooks
Git hooks have been a core feature of Git since its early development by Linus Torvalds in 2005. The concept was inspired by similar mechanisms in other version control systems like CVS and Subversion, but Git's implementation provided more flexibility and power.
### Evolution Timeline
- **2005**: Initial Git release included basic hook support
- **2006**: Enhanced hook capabilities with more event types
- **2008**: Introduction of server-side hooks for repository management
- **2010**: Improved hook documentation and standardization
- **2015**: Enhanced security features and better integration options
- **2020**: Modern hook management tools and frameworks emerged
- **2025**: AI-powered hooks and advanced automation become standard
### Design Philosophy
Git hooks were designed with several key principles:
1. **Flexibility**: Support for any scripting language
2. **Non-intrusive**: Optional and easily disabled
3. **Distributed**: Work in both local and remote contexts
4. **Secure**: Controlled execution environment
5. **Extensible**: Easy to customize and enhance
### Impact on Development Workflows
Git hooks have revolutionized software development by:
- **Automating Quality Gates**: Ensuring code quality before integration
- **Enabling DevOps**: Bridging development and operations
- **Supporting Compliance**: Enforcing regulatory requirements
- **Facilitating Collaboration**: Maintaining team standards
- **Reducing Human Error**: Automating repetitive tasks
## Types of Git Hooks
Git hooks are categorized into two main types:
### 1. Client-Side Hooks
Executed on the developer's local machine and affect the local Git workflow.
### 2. Server-Side Hooks
Executed on the Git server (remote repository) and affect operations involving the remote repository.
## How Git Hooks Work
Git hooks are stored in the `.git/hooks/` directory of every Git repository. When you initialize a new repository with `git init`, Git populates this directory with sample hook scripts that have a `.sample` extension.
### Hook Execution Flow
1. A Git operation is initiated (e.g., `git commit`)
2. Git checks for the corresponding hook script
3. If the hook exists and is executable, Git runs it
4. The hook can either allow the operation to continue or abort it
5. The Git operation completes (or is aborted based on hook result)
### Return Codes
- **0**: Success - Git operation continues
- **Non-zero**: Failure - Git operation is aborted
## Hook Environment and Variables
Git hooks run in a specific environment with access to various Git-related information through environment variables and command-line arguments.
### Environment Variables Available to Hooks
#### Standard Git Environment Variables
- **`GIT_DIR`**: Path to the `.git` directory
- **`GIT_WORK_TREE`**: Path to the working directory
- **`GIT_INDEX_FILE`**: Path to the index file
- **`GIT_OBJECT_DIRECTORY`**: Path to the objects directory
- **`GIT_AUTHOR_NAME`**: Author name for commits
- **`GIT_AUTHOR_EMAIL`**: Author email for commits
- **`GIT_AUTHOR_DATE`**: Author date for commits
- **`GIT_COMMITTER_NAME`**: Committer name
- **`GIT_COMMITTER_EMAIL`**: Committer email
- **`GIT_COMMITTER_DATE`**: Committer date
#### Hook-Specific Variables
Different hooks receive different sets of environment variables:
**For pre-receive and post-receive hooks:**
- **`GIT_PUSH_OPTION_*`**: Push options passed with `--push-option`
- **`GIT_QUARANTINE_PATH`**: Temporary object storage path
**For post-update hook:**
- **`GIT_DIR`**: Always set to the repository path
### Command Line Arguments
#### pre-commit Hook
- **Arguments**: None
- **stdin**: Not used
- **Purpose**: Validate staged changes
#### prepare-commit-msg Hook
- **Arguments**:
1. Path to commit message file
2. Source of commit message (`message`, `template`, `merge`, `squash`, `commit`)
3. Commit SHA (for amend/commit)
- **Example**: `prepare-commit-msg .git/COMMIT_EDITMSG message`
#### commit-msg Hook
- **Arguments**: Path to commit message file
- **Example**: `commit-msg .git/COMMIT_EDITMSG`
#### post-commit Hook
- **Arguments**: None
- **stdin**: Not used
#### pre-push Hook
- **Arguments**:
1. Remote name
2. Remote URL
- **stdin**: List of refs being pushed
- **Format**: ` `
#### pre-receive Hook
- **Arguments**: None
- **stdin**: List of refs being updated
- **Format**: ` `
#### update Hook
- **Arguments**:
1. Reference name
2. Old SHA
3. New SHA
- **Example**: `update refs/heads/main abc123 def456`
#### post-receive Hook
- **Arguments**: None
- **stdin**: List of updated refs (same format as pre-receive)
#### post-update Hook
- **Arguments**: List of updated reference names
- **Example**: `post-update refs/heads/main refs/heads/develop`
### Accessing Git Information in Hooks
#### Getting Repository Information
```bash
#!/bin/bash
# Get current branch
current_branch=$(git rev-parse --abbrev-ref HEAD)
# Get repository root
repo_root=$(git rev-parse --show-toplevel)
# Get commit hash
commit_hash=$(git rev-parse HEAD)
# Get author information
author_name=$(git config user.name)
author_email=$(git config user.email)
```
#### Reading Commit Information
```bash
#!/bin/bash
# In commit-msg hook
commit_message=$(cat "$1")
# In post-commit hook
commit_hash=$(git rev-parse HEAD)
commit_message=$(git log -1 --pretty=%B)
author=$(git log -1 --pretty=%an)
files_changed=$(git diff-tree --no-commit-id --name-only -r HEAD)
```
#### Processing Push Information
```bash
#!/bin/bash
# In pre-receive or post-receive hook
while read oldrev newrev refname; do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "$oldrev" = "0000000000000000000000000000000000000000" ]; then
# New branch
echo "New branch: $branch"
elif [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
# Deleted branch
echo "Deleted branch: $branch"
else
# Updated branch
echo "Updated branch: $branch from $oldrev to $newrev"
# Get list of new commits
new_commits=$(git rev-list $oldrev..$newrev)
echo "New commits: $new_commits"
fi
done
```
### Hook Context and Timing
#### Understanding Hook Execution Context
1. **Working Directory**: Hooks run in the repository's working directory
2. **User Context**: Hooks run as the user who triggered the Git operation
3. **Environment**: Inherits the user's environment variables
4. **Permissions**: Subject to file system permissions
5. **Network Access**: Can make network requests (use with caution)
#### Timing Considerations
- **Pre-hooks**: Must complete before Git operation proceeds
- **Post-hooks**: Run after Git operation is complete
- **Concurrent Access**: Multiple hooks might run simultaneously
- **Lock Files**: Git may hold locks during hook execution
- **Performance Impact**: Slow hooks delay Git operations
## Client-Side Hooks
Client-side hooks run on the developer's local machine and are useful for enforcing local development practices.
### Pre-Commit Hooks
#### `pre-commit`
- **When**: Before a commit is created
- **Purpose**: Validate code quality, run tests, check formatting
- **Can abort**: Yes (non-zero exit code prevents commit)
**Example Use Cases:**
- Code linting and formatting
- Running unit tests
- Checking for debugging statements
- Validating commit message format
#### `prepare-commit-msg`
- **When**: After the default commit message is created but before the editor is opened
- **Purpose**: Modify or add to the default commit message
- **Can abort**: Yes
**Example Use Cases:**
- Adding branch name to commit message
- Including ticket numbers
- Adding commit templates
#### `commit-msg`
- **When**: After the user enters a commit message
- **Purpose**: Validate commit message format and content
- **Can abort**: Yes
**Example Use Cases:**
- Enforcing commit message conventions
- Checking for required keywords
- Validating ticket number format
### Post-Commit Hooks
#### `post-commit`
- **When**: After a commit is created
- **Purpose**: Perform actions after successful commit
- **Can abort**: No (commit already completed)
**Example Use Cases:**
- Sending notifications
- Triggering CI/CD pipelines
- Updating documentation
- Creating backups
### Push-Related Hooks
#### `pre-push`
- **When**: Before pushing to a remote repository
- **Purpose**: Validate changes before they reach the remote
- **Can abort**: Yes
**Example Use Cases:**
- Running comprehensive test suites
- Checking for large files
- Validating branch protection rules
- Security scanning
### Rebase and Merge Hooks
#### `pre-rebase`
- **When**: Before a rebase operation
- **Purpose**: Prevent problematic rebases
- **Can abort**: Yes
#### `post-rewrite`
- **When**: After commands that rewrite commits (rebase, amend)
- **Purpose**: Update references or perform cleanup
- **Can abort**: No
## Server-Side Hooks
Server-side hooks run on the Git server and are useful for enforcing repository-wide policies.
### `pre-receive`
- **When**: Before any references are updated during a push
- **Purpose**: Validate entire push operation
- **Can abort**: Yes (rejects entire push)
**Example Use Cases:**
- Enforcing branch protection
- Validating all commits in push
- Checking permissions
- Running security scans
### `update`
- **When**: Once for each branch being updated during a push
- **Purpose**: Validate individual branch updates
- **Can abort**: Yes (can reject specific branches)
**Example Use Cases:**
- Branch-specific validation rules
- Checking fast-forward requirements
- Validating branch naming conventions
### `post-receive`
- **When**: After all references are updated during a push
- **Purpose**: Perform actions after successful push
- **Can abort**: No (push already completed)
**Example Use Cases:**
- Triggering CI/CD pipelines
- Sending notifications
- Updating issue trackers
- Deploying applications
### `post-update`
- **When**: After all references are updated (similar to post-receive)
- **Purpose**: Perform cleanup or notification tasks
- **Can abort**: No
## Setting Up Git Hooks
### 1. Navigate to Hooks Directory
```bash
cd /path/to/your/repo/.git/hooks/
```
### 2. Create Hook Script
Create a new file with the hook name (without `.sample` extension):
```bash
# Create pre-commit hook
touch pre-commit
chmod +x pre-commit
```
### 3. Write Hook Script
Edit the hook file with your preferred editor:
```bash
#!/bin/bash
# Your hook logic here
echo "Running pre-commit hook..."
```
### 4. Make Executable
Ensure the hook script is executable:
```bash
chmod +x pre-commit
```
### 5. Test the Hook
Trigger the Git operation to test your hook:
```bash
git commit -m "Test commit"
```
## Common Use Cases
### 1. Code Quality Enforcement
- **Linting**: Run ESLint, Pylint, or other linters
- **Formatting**: Enforce code formatting with Prettier, Black
- **Style**: Check coding style compliance
### 2. Testing Automation
- **Unit Tests**: Run test suites before commits
- **Integration Tests**: Execute before pushes
- **Performance Tests**: Validate performance metrics
### 3. Security Validation
- **Secret Scanning**: Check for exposed secrets or keys
- **Vulnerability Scanning**: Run security analysis tools
- **Dependency Checking**: Validate third-party libraries
### 4. Process Integration
- **Issue Tracking**: Update JIRA, GitHub Issues
- **CI/CD**: Trigger build and deployment pipelines
- **Notifications**: Send Slack, email notifications
### 5. Documentation
- **Auto-generation**: Update API docs, README files
- **Change Logs**: Maintain CHANGELOG.md files
- **Version Bumping**: Update version numbers
## Industry-Specific Use Cases
Git hooks can be tailored to meet the specific requirements of different industries and domains. Here are detailed use cases for various sectors:
### Financial Services and FinTech
Financial institutions have strict regulatory requirements and security standards that Git hooks can help enforce.
#### Compliance and Regulatory Requirements
```bash
#!/bin/bash
# SOX Compliance Hook - Ensures all changes are traceable
# Check for required fields in commit message
if ! grep -q "JIRA-[0-9]\+" "$1"; then
echo "❌ SOX Compliance: Commit must reference a JIRA ticket"
exit 1
fi
# Verify code review approval
if ! git log -1 --pretty=%B | grep -q "Reviewed-by:"; then
echo "❌ SOX Compliance: All changes must be peer reviewed"
exit 1
fi
# Log for audit trail
echo "$(date): Commit $(git rev-parse HEAD) approved for SOX compliance" >> /var/log/git-audit.log
```
#### PCI DSS Compliance
```bash
#!/bin/bash
# Check for potential credit card data exposure
# Scan for credit card patterns
if git diff --cached | grep -E "[0-9]{4}[[:space:]-]?[0-9]{4}[[:space:]-]?[0-9]{4}[[:space:]-]?[0-9]{4}"; then
echo "❌ PCI DSS Violation: Potential credit card number detected"
echo "Please remove sensitive data before committing"
exit 1
fi
# Check for PCI-related keywords
if git diff --cached | grep -iE "(credit|card|cvv|cvn|expiry|cardholder)"; then
echo "⚠️ Warning: Payment-related keywords detected. Please verify no sensitive data is included."
fi
```
### Healthcare and Life Sciences
Healthcare organizations must comply with HIPAA, FDA regulations, and other medical standards.
#### HIPAA Compliance Hook
```bash
#!/bin/bash
# HIPAA Compliance validation
# Check for PHI (Protected Health Information) patterns
phi_patterns=(
"[0-9]{3}-[0-9]{2}-[0-9]{4}" # SSN
"DOB:.*[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}" # Date of Birth
"patient.*id.*[0-9]+" # Patient ID patterns
)
for pattern in "${phi_patterns[@]}"; do
if git diff --cached | grep -iE "$pattern"; then
echo "❌ HIPAA Violation: Potential PHI detected - $pattern"
echo "Please remove protected health information before committing"
exit 1
fi
done
# Require encryption for certain file types
if git diff --cached --name-only | grep -E "\.(csv|xlsx|json)$"; then
echo "⚠️ Warning: Data files detected. Ensure they are properly encrypted and anonymized."
fi
```
#### FDA 21 CFR Part 11 Compliance
```python
#!/usr/bin/env python3
# FDA 21 CFR Part 11 Electronic Records compliance
import hashlib
import json
from datetime import datetime
import subprocess
def create_audit_record(commit_hash, author, timestamp):
"""Create an immutable audit record for FDA compliance."""
record = {
"commit": commit_hash,
"author": author,
"timestamp": timestamp,
"validation_status": "pending",
"digital_signature": None
}
# Create digital signature (simplified example)
record_str = json.dumps(record, sort_keys=True)
signature = hashlib.sha256(record_str.encode()).hexdigest()
record["digital_signature"] = signature
# Store in compliance database
with open("/var/log/fda-compliance.jsonl", "a") as f:
f.write(json.dumps(record) + "\n")
return signature
def validate_commit():
"""Validate commit meets FDA requirements."""
# Get commit information
commit_hash = subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip()
author = subprocess.check_output(["git", "log", "-1", "--pretty=%an"]).decode().strip()
timestamp = datetime.now().isoformat()
# Create audit record
signature = create_audit_record(commit_hash, author, timestamp)
print(f"✅ FDA Compliance: Audit record created with signature {signature[:16]}...")
return True
if __name__ == "__main__":
if not validate_commit():
exit(1)
```
### Aerospace and Defense
Aerospace and defense organizations require stringent security and traceability measures.
#### ITAR (International Traffic in Arms Regulations) Compliance
```bash
#!/bin/bash
# ITAR Compliance check for aerospace/defense projects
# Check for ITAR-controlled technology keywords
itar_keywords=(
"encryption"
"cryptographic"
"military"
"defense"
"classified"
"restricted"
"proprietary"
)
for keyword in "${itar_keywords[@]}"; do
if git diff --cached | grep -i "$keyword"; then
echo "🔒 ITAR Alert: Keyword '$keyword' detected"
echo "Please verify this content is authorized for export"
# Require additional approval for ITAR-sensitive content
read -p "Do you have ITAR approval for this content? (yes/no): " approval
if [[ "$approval" != "yes" ]]; then
echo "❌ ITAR Compliance: Commit rejected without proper authorization"
exit 1
fi
fi
done
# Log for export control audit
echo "$(date): ITAR review completed for commit $(git rev-parse --short HEAD)" >> /var/log/itar-audit.log
```
### Automotive Industry
Automotive software development requires compliance with functional safety standards.
#### ISO 26262 Functional Safety Compliance
```bash
#!/bin/bash
# ISO 26262 Automotive Safety Integrity Level (ASIL) compliance
# Check ASIL level declaration in code
if ! git diff --cached | grep -q "ASIL_[A-D]"; then
echo "❌ ISO 26262: Safety-critical code must declare ASIL level"
echo "Add ASIL_A, ASIL_B, ASIL_C, or ASIL_D declaration"
exit 1
fi
# Require safety review for ASIL C/D code
if git diff --cached | grep -q "ASIL_[CD]"; then
if ! git log -1 --pretty=%B | grep -q "Safety-Review:"; then
echo "❌ ISO 26262: ASIL C/D code requires safety review approval"
echo "Add 'Safety-Review: ' to commit message"
exit 1
fi
fi
# Run safety-critical code analysis
echo "Running MISRA C analysis for automotive safety..."
if ! misra-check $(git diff --cached --name-only | grep '\.c$'); then
echo "❌ ISO 26262: MISRA C violations detected"
exit 1
fi
```
### Gaming and Entertainment
Gaming companies focus on performance, content validation, and anti-cheat measures.
#### Game Content Validation
```python
#!/usr/bin/env python3
# Game content validation hook
import re
import subprocess
def check_asset_sizes():
"""Validate game asset file sizes."""
large_files = []
# Get list of changed files
result = subprocess.run(
["git", "diff", "--cached", "--name-only"],
capture_output=True, text=True
)
for file in result.stdout.strip().split('\n'):
if file.endswith(('.png', '.jpg', '.mp3', '.wav', '.fbx', '.obj')):
try:
size = os.path.getsize(file)
if size > 50 * 1024 * 1024: # 50MB limit
large_files.append((file, size))
except FileNotFoundError:
continue
if large_files:
print("❌ Large asset files detected:")
for file, size in large_files:
print(f" {file}: {size / (1024*1024):.1f} MB")
print("Please optimize assets or use Git LFS")
return False
return True
def validate_content_rating():
"""Check for content that might affect game rating."""
offensive_patterns = [
r'\b(violence|blood|gore)\b',
r'\b(profanity|curse|swear)\b',
r'\b(sexual|nudity|adult)\b'
]
result = subprocess.run(
["git", "diff", "--cached"],
capture_output=True, text=True
)
for pattern in offensive_patterns:
if re.search(pattern, result.stdout, re.IGNORECASE):
print(f"⚠️ Content Warning: Pattern '{pattern}' detected")
print("Please review for content rating implications")
return True
return True
if __name__ == "__main__":
success = True
if not check_asset_sizes():
success = False
validate_content_rating()
if not success:
exit(1)
```
### Energy and Utilities (Relevant to MEKANET)
Energy sector software must comply with grid reliability standards and safety regulations.
#### NERC CIP (Critical Infrastructure Protection) Compliance
```bash
#!/bin/bash
# NERC CIP compliance for energy sector critical infrastructure
# Check for cyber security controls
if git diff --cached --name-only | grep -E "(scada|hmi|control|plc)" > /dev/null; then
echo "🔒 NERC CIP: Critical infrastructure code detected"
# Require cyber security review
if ! git log -1 --pretty=%B | grep -q "CyberSec-Review:"; then
echo "❌ NERC CIP: Critical infrastructure changes require cyber security review"
echo "Add 'CyberSec-Review: ' to commit message"
exit 1
fi
# Check for hardcoded credentials
if git diff --cached | grep -iE "(password|secret|key|token)" | grep -v "//"; then
echo "❌ NERC CIP: No hardcoded credentials allowed in critical infrastructure code"
exit 1
fi
fi
# Log for compliance audit
echo "$(date): NERC CIP review completed for commit $(git rev-parse --short HEAD)" >> /var/log/nerc-audit.log
```
#### IEC 61850 Smart Grid Compliance
```python
#!/usr/bin/env python3
# IEC 61850 smart grid protocol compliance validation
import xml.etree.ElementTree as ET
import subprocess
import re
def validate_iec61850_config():
"""Validate IEC 61850 configuration files."""
# Get changed .scd or .icd files (IEC 61850 configuration)
result = subprocess.run(
["git", "diff", "--cached", "--name-only"],
capture_output=True, text=True
)
config_files = [f for f in result.stdout.strip().split('\n')
if f.endswith(('.scd', '.icd', '.cid'))]
for config_file in config_files:
try:
tree = ET.parse(config_file)
root = tree.getroot()
# Check for required IEC 61850 elements
if root.tag != "SCL":
print(f"❌ IEC 61850: Invalid root element in {config_file}")
return False
# Validate IED (Intelligent Electronic Device) definitions
ieds = root.findall(".//IED")
if not ieds:
print(f"⚠️ Warning: No IED definitions found in {config_file}")
for ied in ieds:
if not ied.get("name"):
print(f"❌ IEC 61850: IED missing name attribute in {config_file}")
return False
print(f"✅ IEC 61850: {config_file} validation passed")
except ET.ParseError as e:
print(f"❌ IEC 61850: XML parsing error in {config_file}: {e}")
return False
except FileNotFoundError:
continue
return True
def check_modbus_mapping():
"""Validate Modbus register mappings for energy systems."""
result = subprocess.run(
["git", "diff", "--cached"],
capture_output=True, text=True
)
# Check for Modbus register conflicts
register_pattern = r'modbus_register\s*=\s*(\d+)'
registers = re.findall(register_pattern, result.stdout, re.IGNORECASE)
if len(registers) != len(set(registers)):
duplicates = [r for r in set(registers) if registers.count(r) > 1]
print(f"❌ Modbus: Duplicate register assignments detected: {duplicates}")
return False
return True
if __name__ == "__main__":
success = True
if not validate_iec61850_config():
success = False
if not check_modbus_mapping():
success = False
if not success:
exit(1)
print("✅ Energy sector compliance validation passed")
```
### E-commerce and Retail
E-commerce platforms require robust performance and security validations.
#### Performance and Scalability Validation
```bash
#!/bin/bash
# E-commerce performance validation
# Check for database query performance
if git diff --cached | grep -E "(SELECT|UPDATE|DELETE|INSERT)" > /dev/null; then
echo "🔍 Database query changes detected. Running performance analysis..."
# Check for missing indexes
if git diff --cached | grep "SELECT" | grep -v "WHERE.*INDEX"; then
echo "⚠️ Warning: SQL queries without explicit index usage detected"
echo "Please verify query performance"
fi
# Check for N+1 query patterns
if git diff --cached | grep -E "for.*in.*:.*query|forEach.*query"; then
echo "❌ Potential N+1 query pattern detected"
echo "This could cause performance issues with large datasets"
exit 1
fi
fi
# Validate caching strategies
if git diff --cached --name-only | grep -E "(controller|service)" > /dev/null; then
if ! git diff --cached | grep -E "(cache|redis|memcached)"; then
echo "⚠️ Warning: Controller/Service changes without caching consideration"
fi
fi
```
### Legal and Compliance
Legal tech requires document integrity and audit trails.
#### Document Integrity and Version Control
```python
#!/usr/bin/env python3
# Legal document integrity validation
import hashlib
import json
from datetime import datetime
def create_document_fingerprint(file_path):
"""Create cryptographic fingerprint for legal documents."""
try:
with open(file_path, 'rb') as f:
content = f.read()
fingerprint = hashlib.sha256(content).hexdigest()
return fingerprint
except FileNotFoundError:
return None
def validate_legal_documents():
"""Validate legal document changes."""
# Get changed document files
result = subprocess.run(
["git", "diff", "--cached", "--name-only"],
capture_output=True, text=True
)
legal_extensions = ['.docx', '.pdf', '.md', '.txt']
legal_files = [f for f in result.stdout.strip().split('\n')
if any(f.endswith(ext) for ext in legal_extensions)]
for file_path in legal_files:
if 'legal' in file_path.lower() or 'contract' in file_path.lower():
fingerprint = create_document_fingerprint(file_path)
# Create audit record
audit_record = {
"file": file_path,
"timestamp": datetime.now().isoformat(),
"fingerprint": fingerprint,
"author": subprocess.check_output(["git", "config", "user.name"]).decode().strip(),
"commit": subprocess.check_output(["git", "rev-parse", "HEAD"]).decode().strip()
}
# Store audit record
with open("/var/log/legal-audit.jsonl", "a") as audit_file:
audit_file.write(json.dumps(audit_record) + "\n")
print(f"✅ Legal audit record created for {file_path}")
return True
if __name__ == "__main__":
validate_legal_documents()
```
## Best Practices
### 1. Keep Hooks Fast
- Minimize execution time to avoid slowing down development
- Use parallel execution when possible
- Consider async operations for non-critical tasks
### 2. Provide Clear Feedback
- Output clear, actionable error messages
- Use colors and formatting for better readability
- Include instructions for fixing issues
### 3. Make Hooks Configurable
- Allow developers to skip hooks when necessary
- Provide configuration options
- Support different environments (dev, staging, prod)
### 4. Version Control Hooks
- Store hooks in the repository (not just `.git/hooks/`)
- Use hook management tools like `pre-commit`
- Document hook requirements and setup
### 5. Error Handling
- Implement proper error handling
- Gracefully handle edge cases
- Provide fallback mechanisms
### 6. Testing Hooks
- Test hooks thoroughly before deployment
- Include unit tests for hook logic
- Test with different scenarios and edge cases
## Performance Optimization
Git hooks can significantly impact development workflow speed. Proper optimization ensures that hooks enhance rather than hinder productivity.
### Performance Metrics and Monitoring
#### Measuring Hook Performance
```bash
#!/bin/bash
# Performance monitoring wrapper for hooks
HOOK_START_TIME=$(date +%s.%N)
HOOK_NAME="pre-commit"
# Your hook logic here
# ... existing hook code ...
HOOK_END_TIME=$(date +%s.%N)
EXECUTION_TIME=$(echo "$HOOK_END_TIME - $HOOK_START_TIME" | bc)
# Log performance metrics
echo "$(date): $HOOK_NAME executed in ${EXECUTION_TIME}s" >> /var/log/hook-performance.log
# Alert if hook takes too long
THRESHOLD=5.0
if (( $(echo "$EXECUTION_TIME > $THRESHOLD" | bc -l) )); then
echo "⚠️ Warning: $HOOK_NAME took ${EXECUTION_TIME}s (threshold: ${THRESHOLD}s)"
fi
```
#### Performance Benchmarking
```python
#!/usr/bin/env python3
# Hook performance benchmarking tool
import time
import statistics
import subprocess
import json
from datetime import datetime
class HookBenchmark:
def __init__(self, hook_name, iterations=10):
self.hook_name = hook_name
self.iterations = iterations
self.results = []
def run_benchmark(self):
"""Run hook multiple times and collect performance data."""
for i in range(self.iterations):
start_time = time.time()
# Run the hook
result = subprocess.run(
[f".git/hooks/{self.hook_name}"],
capture_output=True,
text=True
)
end_time = time.time()
execution_time = end_time - start_time
self.results.append({
'iteration': i + 1,
'execution_time': execution_time,
'success': result.returncode == 0,
'stdout_length': len(result.stdout),
'stderr_length': len(result.stderr)
})
return self.analyze_results()
def analyze_results(self):
"""Analyze benchmark results and provide insights."""
execution_times = [r['execution_time'] for r in self.results]
analysis = {
'hook_name': self.hook_name,
'iterations': self.iterations,
'timestamp': datetime.now().isoformat(),
'min_time': min(execution_times),
'max_time': max(execution_times),
'avg_time': statistics.mean(execution_times),
'median_time': statistics.median(execution_times),
'std_dev': statistics.stdev(execution_times) if len(execution_times) > 1 else 0,
'success_rate': sum(1 for r in self.results if r['success']) / len(self.results)
}
# Performance recommendations
recommendations = []
if analysis['avg_time'] > 5.0:
recommendations.append("Hook is slow (>5s). Consider optimization.")
if analysis['std_dev'] > 1.0:
recommendations.append("High variability in execution time. Investigate cause.")
if analysis['success_rate'] < 1.0:
recommendations.append(f"Hook fails {(1-analysis['success_rate'])*100:.1f}% of the time.")
analysis['recommendations'] = recommendations
return analysis
# Example usage
if __name__ == "__main__":
benchmark = HookBenchmark("pre-commit", iterations=5)
results = benchmark.run_benchmark()
print(json.dumps(results, indent=2))
```
### Optimization Strategies
#### 1. Parallel Execution
```bash
#!/bin/bash
# Parallel execution example for pre-commit hook
echo "Running parallel checks..."
# Run checks in parallel
(
echo "Linting JavaScript..."
npx eslint src/**/*.js
) &
eslint_pid=$!
(
echo "Running tests..."
npm test
) &
test_pid=$!
(
echo "Checking formatting..."
npx prettier --check .
) &
prettier_pid=$!
# Wait for all processes and collect results
wait $eslint_pid
eslint_result=$?
wait $test_pid
test_result=$?
wait $prettier_pid
prettier_result=$?
# Check if any failed
if [ $eslint_result -ne 0 ] || [ $test_result -ne 0 ] || [ $prettier_result -ne 0 ]; then
echo "❌ One or more checks failed"
exit 1
fi
echo "✅ All parallel checks passed"
```
#### 2. Incremental Checks
```bash
#!/bin/bash
# Only check changed files for better performance
# Get list of staged files
staged_files=$(git diff --cached --name-only --diff-filter=ACM)
# Filter by file type and run appropriate checks
js_files=$(echo "$staged_files" | grep '\.js$' || true)
py_files=$(echo "$staged_files" | grep '\.py$' || true)
css_files=$(echo "$staged_files" | grep '\.css$' || true)
# Only run linters on relevant files
if [ ! -z "$js_files" ]; then
echo "Linting JavaScript files: $js_files"
npx eslint $js_files
fi
if [ ! -z "$py_files" ]; then
echo "Linting Python files: $py_files"
flake8 $py_files
fi
if [ ! -z "$css_files" ]; then
echo "Linting CSS files: $css_files"
stylelint $css_files
fi
```
#### 3. Caching Results
```python
#!/usr/bin/env python3
# Caching hook results to avoid redundant work
import hashlib
import os
import json
import subprocess
from pathlib import Path
class HookCache:
def __init__(self, cache_dir=".git/hooks-cache"):
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(exist_ok=True)
def get_file_hash(self, file_path):
"""Calculate hash of file content."""
try:
with open(file_path, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
except FileNotFoundError:
return None
def get_cache_key(self, files, check_type):
"""Generate cache key based on files and check type."""
file_hashes = []
for file_path in files:
file_hash = self.get_file_hash(file_path)
if file_hash:
file_hashes.append(f"{file_path}:{file_hash}")
combined = f"{check_type}:{':'.join(sorted(file_hashes))}"
return hashlib.md5(combined.encode()).hexdigest()
def get_cached_result(self, cache_key):
"""Get cached result if available."""
cache_file = self.cache_dir / f"{cache_key}.json"
if cache_file.exists():
try:
with open(cache_file, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, IOError):
return None
return None
def cache_result(self, cache_key, result):
"""Cache the result for future use."""
cache_file = self.cache_dir / f"{cache_key}.json"
try:
with open(cache_file, 'w') as f:
json.dump(result, f)
except IOError:
pass # Silently fail caching
def run_with_cache(self, files, check_type, command):
"""Run command with caching."""
cache_key = self.get_cache_key(files, check_type)
# Check cache first
cached_result = self.get_cached_result(cache_key)
if cached_result:
print(f"✅ {check_type}: Using cached result")
return cached_result['success']
# Run the command
print(f"🔍 {check_type}: Running check...")
result = subprocess.run(command, shell=True, capture_output=True, text=True)
# Cache the result
cache_data = {
'success': result.returncode == 0,
'stdout': result.stdout,
'stderr': result.stderr
}
self.cache_result(cache_key, cache_data)
if not cache_data['success']:
print(cache_data['stderr'])
return cache_data['success']
# Example usage
def main():
cache = HookCache()
# Get staged files
result = subprocess.run(
["git", "diff", "--cached", "--name-only"],
capture_output=True, text=True
)
staged_files = result.stdout.strip().split('\n')
js_files = [f for f in staged_files if f.endswith('.js')]
if js_files:
success = cache.run_with_cache(
js_files,
"eslint",
f"npx eslint {' '.join(js_files)}"
)
if not success:
exit(1)
if __name__ == "__main__":
main()
```
#### 4. Conditional Execution
```bash
#!/bin/bash
# Conditional execution based on branch, time, or other factors
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
CURRENT_HOUR=$(date +%H)
# Skip expensive checks on feature branches during working hours
if [[ "$CURRENT_BRANCH" == feature/* ]] && [[ $CURRENT_HOUR -ge 9 ]] && [[ $CURRENT_HOUR -le 17 ]]; then
echo "ℹ️ Skipping expensive checks on feature branch during working hours"
echo "Run 'git commit --no-verify' to bypass, or commit outside 9-17h for full checks"
# Run only fast checks
npx eslint --max-warnings 0 $(git diff --cached --name-only | grep '\.js$')
exit $?
fi
# Run full checks for main branch or outside working hours
echo "Running full validation suite..."
npm run test:all
npm run lint:all
npm run security:scan
```
### Performance Monitoring Dashboard
```python
#!/usr/bin/env python3
# Performance monitoring dashboard for Git hooks
import json
import sqlite3
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
from pathlib import Path
class HookMonitor:
def __init__(self, db_path=".git/hooks-performance.db"):
self.db_path = db_path
self.init_database()
def init_database(self):
"""Initialize performance monitoring database."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS hook_performance (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hook_name TEXT NOT NULL,
execution_time REAL NOT NULL,
timestamp DATETIME NOT NULL,
success BOOLEAN NOT NULL,
file_count INTEGER,
repository_size INTEGER
)
''')
conn.commit()
conn.close()
def log_performance(self, hook_name, execution_time, success, file_count=0):
"""Log hook performance data."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
INSERT INTO hook_performance
(hook_name, execution_time, timestamp, success, file_count)
VALUES (?, ?, ?, ?, ?)
''', (hook_name, execution_time, datetime.now(), success, file_count))
conn.commit()
conn.close()
def generate_performance_report(self, days=30):
"""Generate performance report for the last N days."""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
since_date = datetime.now() - timedelta(days=days)
cursor.execute('''
SELECT hook_name,
AVG(execution_time) as avg_time,
MAX(execution_time) as max_time,
MIN(execution_time) as min_time,
COUNT(*) as executions,
SUM(CASE WHEN success THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as success_rate
FROM hook_performance
WHERE timestamp > ?
GROUP BY hook_name
ORDER BY avg_time DESC
''', (since_date,))
results = cursor.fetchall()
conn.close()
print(f"Hook Performance Report (Last {days} days)")
print("=" * 60)
print(f"{'Hook Name':<20} {'Avg Time':<10} {'Max Time':<10} {'Executions':<12} {'Success Rate':<12}")
print("-" * 60)
for row in results:
hook_name, avg_time, max_time, min_time, executions, success_rate = row
print(f"{hook_name:<20} {avg_time:<10.2f} {max_time:<10.2f} {executions:<12} {success_rate:<12.1f}%")
return results
def plot_performance_trends(self, hook_name=None, days=30):
"""Plot performance trends over time."""
conn = sqlite3.connect(self.db_path)
since_date = datetime.now() - timedelta(days=days)
if hook_name:
query = '''
SELECT timestamp, execution_time
FROM hook_performance
WHERE hook_name = ? AND timestamp > ?
ORDER BY timestamp
'''
params = (hook_name, since_date)
else:
query = '''
SELECT timestamp, execution_time
FROM hook_performance
WHERE timestamp > ?
ORDER BY timestamp
'''
params = (since_date,)
cursor = conn.cursor()
cursor.execute(query, params)
results = cursor.fetchall()
conn.close()
if not results:
print("No performance data available")
return
timestamps = [datetime.fromisoformat(row[0]) for row in results]
execution_times = [row[1] for row in results]
plt.figure(figsize=(12, 6))
plt.plot(timestamps, execution_times, 'b-', alpha=0.7)
plt.title(f"Hook Performance Trend - {hook_name or 'All Hooks'}")
plt.xlabel("Time")
plt.ylabel("Execution Time (seconds)")
plt.grid(True, alpha=0.3)
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
# Example hook wrapper with monitoring
def monitored_hook_wrapper(hook_name, hook_function):
"""Wrapper to monitor any hook function."""
monitor = HookMonitor()
start_time = time.time()
success = True
try:
hook_function()
except SystemExit as e:
success = e.code == 0
except Exception:
success = False
execution_time = time.time() - start_time
monitor.log_performance(hook_name, execution_time, success)
if not success:
exit(1)
# Usage example
if __name__ == "__main__":
monitor = HookMonitor()
# Generate report
monitor.generate_performance_report(days=7)
# Plot trends
monitor.plot_performance_trends("pre-commit", days=7)
```
### Resource Usage Optimization
#### Memory Management
```bash
#!/bin/bash
# Memory-efficient hook execution
# Set memory limits for hook processes
ulimit -v 1048576 # 1GB virtual memory limit
# Monitor memory usage
memory_usage() {
ps -o pid,vsz,rss,comm -p $$ | tail -1
}
echo "Hook started: $(memory_usage)"
# Your hook logic here with memory awareness
if [ -f package.json ]; then
# Use --max-old-space-size to limit Node.js memory
node --max-old-space-size=512 $(which eslint) src/
fi
echo "Hook finished: $(memory_usage)"
```
#### Disk I/O Optimization
```python
#!/usr/bin/env python3
# Optimize disk I/O in hooks
import os
import mmap
import subprocess
from pathlib import Path
def efficient_file_processing(file_paths):
"""Process files efficiently using memory mapping."""
for file_path in file_paths:
try:
with open(file_path, 'r+b') as f:
# Use memory mapping for large files
if os.path.getsize(file_path) > 1024 * 1024: # 1MB
with mmap.mmap(f.fileno(), 0) as mm:
# Process file content from memory
content = mm.read().decode('utf-8', errors='ignore')
# Your processing logic here
else:
# Read small files normally
content = f.read().decode('utf-8', errors='ignore')
# Your processing logic here
except (IOError, OSError):
continue
def batch_file_operations(file_paths, batch_size=10):
"""Process files in batches to reduce I/O overhead."""
for i in range(0, len(file_paths), batch_size):
batch = file_paths[i:i + batch_size]
# Process batch together
file_list = ' '.join(batch)
result = subprocess.run(
f"grep -l 'pattern' {file_list}",
shell=True,
capture_output=True,
text=True
)
# Process results
for line in result.stdout.strip().split('\n'):
if line:
print(f"Pattern found in: {line}")
```
## Examples
### Example 1: Pre-commit Hook for Code Linting
```bash
#!/bin/bash
# .git/hooks/pre-commit
echo "Running pre-commit checks..."
# Run ESLint on staged JavaScript files
staged_js_files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.js$')
if [ ! -z "$staged_js_files" ]; then
echo "Linting JavaScript files..."
npx eslint $staged_js_files
if [ $? -ne 0 ]; then
echo "❌ ESLint found issues. Please fix them before committing."
exit 1
fi
echo "✅ ESLint passed!"
fi
# Run Prettier formatting check
echo "Checking code formatting..."
npx prettier --check .
if [ $? -ne 0 ]; then
echo "❌ Code formatting issues found. Run 'npm run format' to fix."
exit 1
fi
echo "✅ Code formatting is correct!"
echo "✅ All pre-commit checks passed!"
exit 0
```
### Example 2: Commit Message Validation
```bash
#!/bin/bash
# .git/hooks/commit-msg
commit_regex='^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?: .{1,50}'
if ! grep -qE "$commit_regex" "$1"; then
echo "❌ Invalid commit message format!"
echo "Format: type(scope): description"
echo "Types: feat, fix, docs, style, refactor, test, chore"
echo "Example: feat(auth): add user login functionality"
exit 1
fi
echo "✅ Commit message format is valid!"
exit 0
```
### Example 3: Pre-push Testing
```bash
#!/bin/bash
# .git/hooks/pre-push
echo "Running pre-push checks..."
# Run test suite
echo "Running tests..."
npm test
if [ $? -ne 0 ]; then
echo "❌ Tests failed. Push aborted."
exit 1
fi
# Check for large files
echo "Checking for large files..."
large_files=$(find . -size +50M -not -path "./.git/*")
if [ ! -z "$large_files" ]; then
echo "❌ Large files detected:"
echo "$large_files"
echo "Please remove or add to .gitignore"
exit 1
fi
echo "✅ All pre-push checks passed!"
exit 0
```
### Example 4: Post-receive Deployment Hook
```bash
#!/bin/bash
# hooks/post-receive (on server)
echo "Post-receive hook triggered..."
# Read the push information
while read oldrev newrev refname; do
branch=$(git rev-parse --symbolic --abbrev-ref $refname)
if [ "$branch" = "main" ]; then
echo "Deploying to production..."
# Trigger deployment
cd /var/www/production
git pull origin main
npm install --production
npm run build
sudo systemctl restart myapp
echo "✅ Deployment completed!"
# Send notification
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"🚀 New deployment to production completed!"}' \
$SLACK_WEBHOOK_URL
fi
done
```
### Example 5: Python Code Quality Hook
```python
#!/usr/bin/env python3
# .git/hooks/pre-commit
import subprocess
import sys
import os
def run_command(command):
"""Run a command and return its result."""
try:
result = subprocess.run(command, shell=True, capture_output=True, text=True)
return result.returncode == 0, result.stdout, result.stderr
except Exception as e:
return False, "", str(e)
def check_python_files():
"""Check Python files for code quality."""
# Get staged Python files
success, stdout, stderr = run_command("git diff --cached --name-only --diff-filter=ACM | grep '\.py$'")
if not stdout.strip():
print("No Python files to check.")
return True
python_files = stdout.strip().split('\n')
# Run Black formatter check
print("Checking code formatting with Black...")
for file in python_files:
success, _, stderr = run_command(f"black --check {file}")
if not success:
print(f"❌ {file} is not properly formatted")
print("Run 'black .' to fix formatting issues")
return False
# Run Flake8 linting
print("Running Flake8 linting...")
success, stdout, stderr = run_command(f"flake8 {' '.join(python_files)}")
if not success:
print("❌ Flake8 found issues:")
print(stdout)
return False
# Run tests
print("Running Python tests...")
success, stdout, stderr = run_command("python -m pytest tests/ -q")
if not success:
print("❌ Tests failed:")
print(stderr)
return False
print("✅ All Python checks passed!")
return True
if __name__ == "__main__":
if not check_python_files():
sys.exit(1)
sys.exit(0)
```
## Advanced Examples
This section provides sophisticated, production-ready Git hook implementations that demonstrate advanced patterns and integrations.
### Multi-Language Code Quality Enforcement
```bash
#!/bin/bash
# Advanced multi-language pre-commit hook with parallel execution
set -e # Exit on any error
# Configuration
HOOK_CONFIG_FILE=".git/hooks/config.json"
PARALLEL_JOBS=4
TIMEOUT=300 # 5 minutes timeout
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging function
log() {
echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
}
warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
# Load configuration
load_config() {
if [[ -f "$HOOK_CONFIG_FILE" ]]; then
# Parse JSON configuration
ENABLE_LINTING=$(jq -r '.linting.enabled // true' "$HOOK_CONFIG_FILE")
ENABLE_TESTING=$(jq -r '.testing.enabled // true' "$HOOK_CONFIG_FILE")
ENABLE_SECURITY=$(jq -r '.security.enabled // true' "$HOOK_CONFIG_FILE")
ENABLE_PERFORMANCE=$(jq -r '.performance.enabled // false' "$HOOK_CONFIG_FILE")
LINT_TIMEOUT=$(jq -r '.linting.timeout // 60' "$HOOK_CONFIG_FILE")
TEST_TIMEOUT=$(jq -r '.testing.timeout // 180' "$HOOK_CONFIG_FILE")
else
# Default configuration
ENABLE_LINTING=true
ENABLE_TESTING=true
ENABLE_SECURITY=true
ENABLE_PERFORMANCE=false
LINT_TIMEOUT=60
TEST_TIMEOUT=180
fi
}
# Get staged files by language
get_staged_files() {
local extension="$1"
git diff --cached --name-only --diff-filter=ACM | grep -E "\.$extension$" | head -100 || true
}
# JavaScript/TypeScript validation
validate_javascript() {
local files=$(get_staged_files "js\|ts\|jsx\|tsx")
if [[ -z "$files" ]]; then
return 0
fi
log "Validating JavaScript/TypeScript files..."
# ESLint
if command -v eslint >/dev/null 2>&1; then
log "Running ESLint..."
timeout $LINT_TIMEOUT npx eslint $files --max-warnings 0
if [[ $? -ne 0 ]]; then
error "ESLint validation failed"
return 1
fi
fi
# TypeScript compilation check
if [[ -f "tsconfig.json" ]] && echo "$files" | grep -E "\.(ts|tsx)$" >/dev/null; then
log "Checking TypeScript compilation..."
timeout $LINT_TIMEOUT npx tsc --noEmit
if [[ $? -ne 0 ]]; then
error "TypeScript compilation check failed"
return 1
fi
fi
# Prettier formatting check
if command -v prettier >/dev/null 2>&1; then
log "Checking code formatting..."
timeout $LINT_TIMEOUT npx prettier --check $files
if [[ $? -ne 0 ]]; then
error "Code formatting check failed. Run 'npm run format' to fix."
return 1
fi
fi
success "JavaScript/TypeScript validation passed"
return 0
}
# Python validation
validate_python() {
local files=$(get_staged_files "py")
if [[ -z "$files" ]]; then
return 0
fi
log "Validating Python files..."
# Black formatting check
if command -v black >/dev/null 2>&1; then
log "Checking Python formatting with Black..."
timeout $LINT_TIMEOUT black --check $files
if [[ $? -ne 0 ]]; then
error "Black formatting check failed. Run 'black .' to fix."
return 1
fi
fi
# Flake8 linting
if command -v flake8 >/dev/null 2>&1; then
log "Running Flake8 linting..."
timeout $LINT_TIMEOUT flake8 $files
if [[ $? -ne 0 ]]; then
error "Flake8 linting failed"
return 1
fi
fi
# MyPy type checking
if command -v mypy >/dev/null 2>&1 && [[ -f "mypy.ini" || -f ".mypy.ini" || -f "pyproject.toml" ]]; then
log "Running MyPy type checking..."
timeout $LINT_TIMEOUT mypy $files
if [[ $? -ne 0 ]]; then
error "MyPy type checking failed"
return 1
fi
fi
# Import sorting check
if command -v isort >/dev/null 2>&1; then
log "Checking import sorting..."
timeout $LINT_TIMEOUT isort --check-only $files
if [[ $? -ne 0 ]]; then
error "Import sorting check failed. Run 'isort .' to fix."
return 1
fi
fi
success "Python validation passed"
return 0
}
# Go validation
validate_go() {
local files=$(get_staged_files "go")
if [[ -z "$files" ]]; then
return 0
fi
log "Validating Go files..."
# Go formatting check
if command -v gofmt >/dev/null 2>&1; then
log "Checking Go formatting..."
unformatted=$(gofmt -l $files)
if [[ -n "$unformatted" ]]; then
error "Go formatting check failed. Files need formatting:"
echo "$unformatted"
echo "Run 'gofmt -w .' to fix."
return 1
fi
fi
# Go linting
if command -v golint >/dev/null 2>&1; then
log "Running Go linting..."
timeout $LINT_TIMEOUT golint $files
if [[ $? -ne 0 ]]; then
error "Go linting failed"
return 1
fi
fi
# Go vet
if command -v go >/dev/null 2>&1; then
log "Running go vet..."
timeout $LINT_TIMEOUT go vet ./...
if [[ $? -ne 0 ]]; then
error "Go vet failed"
return 1
fi
fi
success "Go validation passed"
return 0
}
# Security scanning
security_scan() {
if [[ "$ENABLE_SECURITY" != "true" ]]; then
return 0
fi
log "Running security scans..."
# Check for secrets
if command -v truffleHog >/dev/null 2>&1; then
log "Scanning for secrets..."
timeout 60 truffleHog --regex --entropy=False .
if [[ $? -ne 0 ]]; then
error "Secret scanning failed - potential secrets detected"
return 1
fi
fi
# Check for hardcoded credentials patterns
local credential_patterns=(
"password\s*=\s*['\"][^'\"]+['\"]"
"api_key\s*=\s*['\"][^'\"]+['\"]"
"secret\s*=\s*['\"][^'\"]+['\"]"
"token\s*=\s*['\"][^'\"]+['\"]"
)
for pattern in "${credential_patterns[@]}"; do
if git diff --cached | grep -iE "$pattern" >/dev/null; then
error "Potential hardcoded credential detected: $pattern"
warning "Please use environment variables or configuration files for credentials"
return 1
fi
done
# Dependency vulnerability check
if [[ -f "package.json" ]] && command -v npm >/dev/null 2>&1; then
log "Checking npm dependencies for vulnerabilities..."
timeout 120 npm audit --audit-level=moderate
if [[ $? -ne 0 ]]; then
error "npm audit found vulnerabilities"
return 1
fi
fi
if [[ -f "requirements.txt" ]] && command -v safety >/dev/null 2>&1; then
log "Checking Python dependencies for vulnerabilities..."
timeout 120 safety check -r requirements.txt
if [[ $? -ne 0 ]]; then
error "Python dependency vulnerability check failed"
return 1
fi
fi
success "Security scans passed"
return 0
}
# Performance analysis
performance_analysis() {
if [[ "$ENABLE_PERFORMANCE" != "true" ]]; then
return 0
fi
log "Running performance analysis..."
# Check for large files
local large_files=$(git diff --cached --name-only | xargs -I {} find {} -size +10M 2>/dev/null || true)
if [[ -n "$large_files" ]]; then
error "Large files detected (>10MB):"
echo "$large_files"
warning "Consider using Git LFS for large files"
return 1
fi
# Check for performance anti-patterns in code
local perf_patterns=(
"SELECT \* FROM" # SQL wildcard select
"for.*in.*query" # N+1 query pattern
"while.*true.*without.*break" # Infinite loop risk
)
for pattern in "${perf_patterns[@]}"; do
if git diff --cached | grep -E "$pattern" >/dev/null; then
warning "Potential performance issue detected: $pattern"
fi
done
success "Performance analysis completed"
return 0
}
# Test execution
run_tests() {
if [[ "$ENABLE_TESTING" != "true" ]]; then
return 0
fi
log "Running tests..."
# Detect test framework and run appropriate tests
if [[ -f "package.json" ]]; then
if jq -e '.scripts.test' package.json >/dev/null 2>&1; then
log "Running npm tests..."
timeout $TEST_TIMEOUT npm test
if [[ $? -ne 0 ]]; then
error "npm tests failed"
return 1
fi
fi
fi
if [[ -f "pytest.ini" || -f "setup.cfg" ]] && command -v pytest >/dev/null 2>&1; then
log "Running pytest..."
timeout $TEST_TIMEOUT pytest --tb=short
if [[ $? -ne 0 ]]; then
error "pytest failed"
return 1
fi
fi
if [[ -f "go.mod" ]] && command -v go >/dev/null 2>&1; then
log "Running Go tests..."
timeout $TEST_TIMEOUT go test ./...
if [[ $? -ne 0 ]]; then
error "Go tests failed"
return 1
fi
fi
success "All tests passed"
return 0
}
# Main execution with parallel processing
main() {
log "Starting advanced pre-commit validation..."
# Load configuration
load_config
# Create temporary directory for parallel execution results
local temp_dir=$(mktemp -d)
trap "rm -rf $temp_dir" EXIT
# Run validations in parallel
local pids=()
if [[ "$ENABLE_LINTING" == "true" ]]; then
(validate_javascript && validate_python && validate_go) >$temp_dir/linting.log 2>&1 &
pids+=($!)
fi
(security_scan) >$temp_dir/security.log 2>&1 &
pids+=($!)
(performance_analysis) >$temp_dir/performance.log 2>&1 &
pids+=($!)
(run_tests) >$temp_dir/testing.log 2>&1 &
pids+=($!)
# Wait for all processes and collect results
local failed=false
for pid in "${pids[@]}"; do
if ! wait $pid; then
failed=true
fi
done
# Display all logs
for log_file in $temp_dir/*.log; do
if [[ -f "$log_file" ]]; then
cat "$log_file"
fi
done
if [[ "$failed" == "true" ]]; then
error "Pre-commit validation failed"
echo ""
echo "To skip these checks (not recommended), use:"
echo " git commit --no-verify"
echo ""
echo "To fix formatting issues automatically:"
echo " npm run format # for JavaScript/TypeScript"
echo " black . # for Python"
echo " gofmt -w . # for Go"
return 1
fi
success "All pre-commit validations passed!"
return 0
}
# Execute main function
main "$@"
```
### Intelligent Commit Message Generator
```python
#!/usr/bin/env python3
# Intelligent commit message generator using AI/ML techniques
import re
import subprocess
import sys
from pathlib import Path
from collections import Counter
import json
class CommitMessageGenerator:
def __init__(self):
self.file_patterns = {
'feat': [r'new', r'add', r'create', r'implement'],
'fix': [r'fix', r'bug', r'error', r'issue', r'correct'],
'refactor': [r'refactor', r'reorganize', r'restructure', r'cleanup'],
'docs': [r'readme', r'documentation', r'doc', r'comment'],
'style': [r'format', r'style', r'prettier', r'lint'],
'test': [r'test', r'spec', r'__test__'],
'chore': [r'config', r'build', r'package', r'dependency']
}
self.scope_mappings = {
'src/components': 'ui',
'src/services': 'api',
'src/utils': 'utils',
'src/hooks': 'hooks',
'tests/': 'test',
'docs/': 'docs',
'config/': 'config',
'scripts/': 'scripts'
}
def get_changed_files(self):
"""Get list of changed files and their modifications."""
result = subprocess.run(
['git', 'diff', '--cached', '--name-status'],
capture_output=True, text=True
)
changes = []
for line in result.stdout.strip().split('\n'):
if line:
status, filepath = line.split('\t', 1)
changes.append({
'status': status,
'filepath': filepath,
'filename': Path(filepath).name
})
return changes
def get_diff_stats(self):
"""Get statistics about the changes."""
result = subprocess.run(
['git', 'diff', '--cached', '--numstat'],
capture_output=True, text=True
)
total_added = 0
total_removed = 0
for line in result.stdout.strip().split('\n'):
if line and not line.startswith('-'):
parts = line.split('\t')
if len(parts) >= 2 and parts[0].isdigit() and parts[1].isdigit():
total_added += int(parts[0])
total_removed += int(parts[1])
return total_added, total_removed
def detect_type(self, changes):
"""Detect the type of commit based on file changes."""
type_scores = Counter()
for change in changes:
filepath = change['filepath'].lower()
filename = change['filename'].lower()
# Score based on file patterns
for commit_type, patterns in self.file_patterns.items():
for pattern in patterns:
if re.search(pattern, filepath) or re.search(pattern, filename):
type_scores[commit_type] += 1
# Score based on file extensions and locations
if filepath.endswith(('.test.js', '.spec.js', '.test.py', '.spec.py')):
type_scores['test'] += 2
elif filepath.endswith(('.md', '.rst', '.txt')):
type_scores['docs'] += 2
elif 'config' in filepath or filepath.endswith(('.json', '.yaml', '.yml')):
type_scores['chore'] += 1
elif change['status'] == 'A': # Added files
type_scores['feat'] += 1
elif change['status'] == 'D': # Deleted files
type_scores['chore'] += 1
# Return most likely type
if type_scores:
return type_scores.most_common(1)[0][0]
return 'chore'
def detect_scope(self, changes):
"""Detect the scope based on file locations."""
scope_scores = Counter()
for change in changes:
filepath = change['filepath']
for path_pattern, scope in self.scope_mappings.items():
if filepath.startswith(path_pattern):
scope_scores[scope] += 1
if scope_scores:
return scope_scores.most_common(1)[0][0]
return None
def analyze_diff_content(self):
"""Analyze the actual diff content for more context."""
result = subprocess.run(
['git', 'diff', '--cached'],
capture_output=True, text=True
)
diff_content = result.stdout
# Analyze patterns in the diff
analysis = {
'has_new_functions': bool(re.search(r'^\+.*def\s+\w+|^\+.*function\s+\w+', diff_content, re.MULTILINE)),
'has_new_classes': bool(re.search(r'^\+.*class\s+\w+', diff_content, re.MULTILINE)),
'has_imports': bool(re.search(r'^\+.*import\s+|^\+.*from\s+.*import', diff_content, re.MULTILINE)),
'has_exports': bool(re.search(r'^\+.*export\s+', diff_content, re.MULTILINE)),
'has_api_calls': bool(re.search(r'^\+.*(fetch|axios|requests|http)', diff_content, re.MULTILINE)),
'has_database': bool(re.search(r'^\+.*(SELECT|INSERT|UPDATE|DELETE|query)', diff_content, re.MULTILINE)),
'has_tests': bool(re.search(r'^\+.*(test|expect|assert|should)', diff_content, re.MULTILINE)),
'has_comments': bool(re.search(r'^\+.*(/\*|\*|//|#)', diff_content, re.MULTILINE)),
}
return analysis
def generate_description(self, commit_type, changes, analysis, added_lines, removed_lines):
"""Generate a descriptive commit message."""
descriptions = []
# Type-specific descriptions
if commit_type == 'feat':
if analysis['has_new_functions']:
descriptions.append("implement new functionality")
elif analysis['has_api_calls']:
descriptions.append("integrate API endpoints")
elif analysis['has_new_classes']:
descriptions.append("create new components")
else:
descriptions.append("add new features")
elif commit_type == 'fix':
if analysis['has_database']:
descriptions.append("resolve database query issues")
elif analysis['has_api_calls']:
descriptions.append("fix API integration problems")
else:
descriptions.append("resolve critical bugs")
elif commit_type == 'refactor':
if removed_lines > added_lines:
descriptions.append("simplify code structure")
else:
descriptions.append("improve code organization")
elif commit_type == 'test':
descriptions.append("enhance test coverage")
elif commit_type == 'docs':
descriptions.append("update documentation")
elif commit_type == 'style':
descriptions.append("format code and fix style issues")
else: # chore
descriptions.append("update configuration and dependencies")
# Add file-specific context
file_types = set()
for change in changes:
ext = Path(change['filepath']).suffix
if ext:
file_types.add(ext)
if file_types:
file_context = ', '.join(sorted(file_types))
descriptions.append(f"({file_context} files)")
return ' '.join(descriptions)
def generate_commit_message(self):
"""Generate a complete commit message."""
changes = self.get_changed_files()
if not changes:
return "chore: update repository"
commit_type = self.detect_type(changes)
scope = self.detect_scope(changes)
analysis = self.analyze_diff_content()
added_lines, removed_lines = self.get_diff_stats()
description = self.generate_description(
commit_type, changes, analysis, added_lines, removed_lines
)
# Construct the commit message
if scope:
message = f"{commit_type}({scope}): {description}"
else:
message = f"{commit_type}: {description}"
# Add body with statistics if significant changes
body = []
if added_lines + removed_lines > 50:
body.append(f"Changes: +{added_lines}/-{removed_lines} lines")
if len(changes) > 5:
body.append(f"Modified {len(changes)} files")
# Add detailed file list for large changes
if len(changes) > 10:
body.append("\nModified files:")
for change in changes[:10]: # Limit to first 10 files
status_symbol = {'A': '+', 'M': '~', 'D': '-'}.get(change['status'], '?')
body.append(f" {status_symbol} {change['filepath']}")
if len(changes) > 10:
body.append(f" ... and {len(changes) - 10} more files")
full_message = message
if body:
full_message += "\n\n" + "\n".join(body)
return full_message
def main():
"""Main function for prepare-commit-msg hook."""
if len(sys.argv) < 2:
print("Usage: prepare-commit-msg [source] [sha]")
sys.exit(1)
commit_msg_file = sys.argv[1]
source = sys.argv[2] if len(sys.argv) > 2 else None
# Only generate message for new commits (not amend, merge, etc.)
if source in ['message', 'template', 'merge', 'squash', 'commit']:
return
# Generate intelligent commit message
generator = CommitMessageGenerator()
suggested_message = generator.generate_commit_message()
# Read existing message
try:
with open(commit_msg_file, 'r') as f:
existing_message = f.read().strip()
except FileNotFoundError:
existing_message = ""
# If no existing message or it's empty, use generated message
if not existing_message or existing_message.startswith('#'):
with open(commit_msg_file, 'w') as f:
f.write(suggested_message + "\n\n")
f.write("# Generated commit message\n")
f.write("# Edit above to customize your commit message\n")
f.write("# Lines starting with '#' will be ignored\n")
if __name__ == "__main__":
main()
```
### Sophisticated Deployment Hook with Rollback
```bash
#!/bin/bash
# Advanced post-receive deployment hook with rollback capabilities
set -e
# Configuration
DEPLOY_CONFIG="/etc/git-deploy/config.json"
LOG_FILE="/var/log/git-deploy.log"
NOTIFICATION_WEBHOOK=""
SLACK_CHANNEL="#deployments"
# Load configuration
if [[ -f "$DEPLOY_CONFIG" ]]; then
NOTIFICATION_WEBHOOK=$(jq -r '.notifications.webhook // ""' "$DEPLOY_CONFIG")
SLACK_CHANNEL=$(jq -r '.notifications.slack_channel // "#deployments"' "$DEPLOY_CONFIG")
DEPLOY_STRATEGY=$(jq -r '.deploy.strategy // "blue_green"' "$DEPLOY_CONFIG")
HEALTH_CHECK_URL=$(jq -r '.deploy.health_check_url // ""' "$DEPLOY_CONFIG")
ROLLBACK_ENABLED=$(jq -r '.deploy.rollback_enabled // true' "$DEPLOY_CONFIG")
fi
# Logging functions
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
error() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1" | tee -a "$LOG_FILE" >&2
}
notify() {
local message="$1"
local status="$2" # success, warning, error
# Send to Slack if webhook is configured
if [[ -n "$NOTIFICATION_WEBHOOK" ]]; then
local color="good"
[[ "$status" == "warning" ]] && color="warning"
[[ "$status" == "error" ]] && color="danger"
curl -X POST -H 'Content-type: application/json' \
--data "{
\"channel\": \"$SLACK_CHANNEL\",
\"attachments\": [{
\"color\": \"$color\",
\"text\": \"$message\",
\"footer\": \"Git Deploy Hook\",
\"ts\": $(date +%s)
}]
}" \
"$NOTIFICATION_WEBHOOK" 2>/dev/null || true
fi
log "$message"
}
# Deployment strategies
deploy_blue_green() {
local app_dir="$1"
local backup_dir="${app_dir}_backup_$(date +%s)"
log "Starting blue-green deployment..."
# Create backup
if [[ -d "$app_dir" ]]; then
log "Creating backup at $backup_dir"
cp -r "$app_dir" "$backup_dir"
echo "$backup_dir" > "${app_dir}/.last_backup"
fi
# Deploy new version
log "Deploying new version to $app_dir"
git --git-dir="$GIT_DIR" --work-tree="$app_dir" checkout -f
# Set proper permissions
chown -R www-data:www-data "$app_dir" 2>/dev/null || true
return 0
}
deploy_rolling() {
local app_dir="$1"
log "Starting rolling deployment..."
# Gradual deployment simulation
git --git-dir="$GIT_DIR" --work-tree="$app_dir" checkout -f
# Restart services gradually
if systemctl is-active --quiet nginx; then
systemctl reload nginx
fi
return 0
}
deploy_canary() {
local app_dir="$1"
local canary_dir="${app_dir}_canary"
log "Starting canary deployment..."
# Deploy to canary environment first
git --git-dir="$GIT_DIR" --work-tree="$canary_dir" checkout -f
# Run canary tests
if run_canary_tests "$canary_dir"; then
log "Canary tests passed, promoting to production"
rsync -av --delete "$canary_dir/" "$app_dir/"
else
error "Canary tests failed, aborting deployment"
return 1
fi
return 0
}
run_canary_tests() {
local canary_dir="$1"
# Example canary tests
log "Running canary tests..."
# Check if application starts correctly
cd "$canary_dir"
if [[ -f "package.json" ]]; then
timeout 60 npm start &
local app_pid=$!
sleep 10
if kill -0 "$app_pid" 2>/dev/null; then
kill "$app_pid"
log "Canary application startup test passed"
return 0
else
log "Canary application failed to start"
return 1
fi
fi
return 0
}
# Health check function
health_check() {
local url="$1"
local max_attempts=5
local attempt=1
log "Performing health check on $url"
while [[ $attempt -le $max_attempts ]]; do
if curl -f -s --max-time 10 "$url" >/dev/null; then
log "Health check passed (attempt $attempt)"
return 0
fi
log "Health check failed (attempt $attempt/$max_attempts)"
sleep 10
((attempt++))
done
error "Health check failed after $max_attempts attempts"
return 1
}
# Rollback function
rollback_deployment() {
local app_dir="$1"
local backup_file="${app_dir}/.last_backup"
if [[ ! -f "$backup_file" ]]; then
error "No backup information found for rollback"
return 1
fi
local backup_dir=$(cat "$backup_file")
if [[ ! -d "$backup_dir" ]]; then
error "Backup directory $backup_dir not found"
return 1
fi
log "Rolling back to $backup_dir"
# Stop application
systemctl stop myapp 2>/dev/null || true
# Restore backup
rm -rf "$app_dir"
mv "$backup_dir" "$app_dir"
# Restart application
systemctl start myapp
# Verify rollback
if [[ -n "$HEALTH_CHECK_URL" ]]; then
if health_check "$HEALTH_CHECK_URL"; then
notify "🔄 Rollback completed successfully" "success"
return 0
else
notify "❌ Rollback failed - health check unsuccessful" "error"
return 1
fi
fi
notify "🔄 Rollback completed" "success"
return 0
}
# Build and test functions
run_build() {
local app_dir="$1"
cd "$app_dir"
log "Running build process..."
# Node.js build
if [[ -f "package.json" ]]; then
log "Installing Node.js dependencies..."
npm ci --production
if jq -e '.scripts.build' package.json >/dev/null; then
log "Running build script..."
npm run build
fi
fi
# Python build
if [[ -f "requirements.txt" ]]; then
log "Installing Python dependencies..."
pip install -r requirements.txt
fi
# Go build
if [[ -f "go.mod" ]]; then
log "Building Go application..."
go build -o app ./cmd/...
fi
return 0
}
run_deployment_tests() {
local app_dir="$1"
cd "$app_dir"
log "Running deployment tests..."
# Run smoke tests
if [[ -f "scripts/smoke-tests.sh" ]]; then
log "Running smoke tests..."
bash scripts/smoke-tests.sh
if [[ $? -ne 0 ]]; then
error "Smoke tests failed"
return 1
fi
fi
# Run integration tests
if [[ -f "package.json" ]] && jq -e '.scripts["test:integration"]' package.json >/dev/null; then
log "Running integration tests..."
npm run test:integration
if [[ $? -ne 0 ]]; then
error "Integration tests failed"
return 1
fi
fi
return 0
}
# Main deployment process
deploy() {
local branch="$1"
local old_commit="$2"
local new_commit="$3"
local app_dir="/var/www/production"
local deploy_start_time=$(date +%s)
notify "🚀 Starting deployment of $branch ($new_commit)" "warning"
# Create application directory if it doesn't exist
mkdir -p "$app_dir"
# Execute deployment strategy
case "$DEPLOY_STRATEGY" in
"blue_green")
deploy_blue_green "$app_dir" || return 1
;;
"rolling")
deploy_rolling "$app_dir" || return 1
;;
"canary")
deploy_canary "$app_dir" || return 1
;;
*)
deploy_blue_green "$app_dir" || return 1
;;
esac
# Run build process
if ! run_build "$app_dir"; then
error "Build process failed"
if [[ "$ROLLBACK_ENABLED" == "true" ]]; then
rollback_deployment "$app_dir"
fi
return 1
fi
# Run deployment tests
if ! run_deployment_tests "$app_dir"; then
error "Deployment tests failed"
if [[ "$ROLLBACK_ENABLED" == "true" ]]; then
rollback_deployment "$app_dir"
fi
return 1
fi
# Restart application services
log "Restarting application services..."
systemctl restart myapp
systemctl reload nginx
# Wait for application to start
sleep 5
# Perform health check
if [[ -n "$HEALTH_CHECK_URL" ]]; then
if ! health_check "$HEALTH_CHECK_URL"; then
error "Health check failed after deployment"
if [[ "$ROLLBACK_ENABLED" == "true" ]]; then
rollback_deployment "$app_dir"
fi
return 1
fi
fi
# Calculate deployment time
local deploy_end_time=$(date +%s)
local deploy_duration=$((deploy_end_time - deploy_start_time))
# Clean up old backups (keep last 5)
find "$(dirname "$app_dir")" -name "$(basename "$app_dir")_backup_*" -type d | sort | head -n -5 | xargs rm -rf 2>/dev/null || true
notify "✅ Deployment completed successfully in ${deploy_duration}s" "success"
log "Deployment completed in ${deploy_duration} seconds"
return 0
}
# Main hook execution
main() {
while read oldrev newrev refname; do
branch=$(git rev-parse --symbolic --abbrev-ref "$refname")
# Only deploy main/master branch
if [[ "$branch" == "main" || "$branch" == "master" ]]; then
log "Processing deployment for branch: $branch"
if deploy "$branch" "$oldrev" "$newrev"; then
log "Deployment successful for $branch"
else
error "Deployment failed for $branch"
notify "❌ Deployment failed for $branch" "error"
exit 1
fi
else
log "Skipping deployment for branch: $branch (not main/master)"
fi
done
}
# Execute main function
main
```
## Troubleshooting
### Common Issues and Solutions
#### 1. Hook Not Executing
**Problem**: Hook script exists but doesn't run **Solution**:
- Check file permissions: `chmod +x .git/hooks/hook-name`
- Verify shebang line: `#!/bin/bash` or `#!/usr/bin/env python3`
- Check file location: Must be in `.git/hooks/` directory
#### 2. Hook Failing Silently
**Problem**: Hook runs but doesn't provide feedback **Solution**:
- Add echo statements for debugging
- Check exit codes: `echo $?` after hook execution
- Review Git output for error messages
#### 3. Performance Issues
**Problem**: Hooks take too long to execute **Solution**:
- Profile hook execution time
- Optimize slow operations
- Consider running checks asynchronously
- Cache results when possible
#### 4. Environment Issues
**Problem**: Commands work in terminal but fail in hooks **Solution**:
- Set proper PATH in hook script
- Use full paths to executables
- Source environment files if needed
#### 5. Cross-Platform Compatibility
**Problem**: Hooks work on one OS but not another **Solution**:
- Use cross-platform scripting approaches
- Test on all target platforms
- Consider using Python/Node.js for better compatibility
### Debugging Hooks
#### Enable Debug Output
```bash
#!/bin/bash
set -x # Enable debug mode
# Your hook code here
```
#### Log Hook Execution
```bash
#!/bin/bash
echo "$(date): Pre-commit hook started" >> /tmp/git-hooks.log
# Your hook code here
echo "$(date): Pre-commit hook finished" >> /tmp/git-hooks.log
```
#### Test Hooks Manually
```bash
# Test pre-commit hook
.git/hooks/pre-commit
# Test with specific Git operation
git commit --dry-run
```
## Advanced Topics
### Hook Management Tools
#### 1. pre-commit Framework
A framework for managing multi-language pre-commit hooks:
```yaml
# .pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
```
#### 2. Husky (for Node.js projects)
```json
{
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "npm test"
}
}
}
```
### Sharing Hooks Across Teams
#### 1. Repository Hooks Directory
Create a `hooks/` directory in your repository:
```text
project/
├── .git/
├── hooks/
│ ├── pre-commit
│ ├── pre-push
│ └── install.sh
└── src/
```
#### 2. Installation Script
```bash
#!/bin/bash
# hooks/install.sh
ln -sf ../../hooks/pre-commit .git/hooks/pre-commit
ln -sf ../../hooks/pre-push .git/hooks/pre-push
chmod +x .git/hooks/*
echo "Git hooks installed successfully!"
```
## Testing Git Hooks
Testing Git hooks is crucial for ensuring they work correctly and don't disrupt the development workflow. This section covers comprehensive testing strategies.
### Unit Testing Hooks
#### Testing Framework for Bash Hooks
```bash
#!/bin/bash
# test-hooks.sh - Testing framework for Git hooks
# Test configuration
TEST_DIR="$(mktemp -d)"
HOOK_DIR="$(pwd)/.git/hooks"
ORIGINAL_DIR="$(pwd)"
# Colors for test output
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Test counters
TESTS_RUN=0
TESTS_PASSED=0
TESTS_FAILED=0
# Setup test environment
setup_test_env() {
cd "$TEST_DIR"
git init --quiet
git config user.name "Test User"
git config user.email "test@example.com"
# Copy hooks to test repository
mkdir -p .git/hooks
cp "$HOOK_DIR"/* .git/hooks/ 2>/dev/null || true
chmod +x .git/hooks/* 2>/dev/null || true
}
# Cleanup test environment
cleanup_test_env() {
cd "$ORIGINAL_DIR"
rm -rf "$TEST_DIR"
}
# Test assertion functions
assert_success() {
local command="$1"
local description="$2"
((TESTS_RUN++))
if eval "$command" >/dev/null 2>&1; then
echo -e "${GREEN}✓${NC} $description"
((TESTS_PASSED++))
return 0
else
echo -e "${RED}✗${NC} $description"
((TESTS_FAILED++))
return 1
fi
}
assert_failure() {
local command="$1"
local description="$2"
((TESTS_RUN++))
if ! eval "$command" >/dev/null 2>&1; then
echo -e "${GREEN}✓${NC} $description"
((TESTS_PASSED++))
return 0
else
echo -e "${RED}✗${NC} $description"
((TESTS_FAILED++))
return 1
fi
}
assert_contains() {
local text="$1"
local pattern="$2"
local description="$3"
((TESTS_RUN++))
if echo "$text" | grep -q "$pattern"; then
echo -e "${GREEN}✓${NC} $description"
((TESTS_PASSED++))
return 0
else
echo -e "${RED}✗${NC} $description"
echo " Expected pattern: $pattern"
echo " Actual text: $text"
((TESTS_FAILED++))
return 1
fi
}
# Test cases
test_pre_commit_hook() {
echo "Testing pre-commit hook..."
# Test 1: Hook exists and is executable
assert_success "test -x .git/hooks/pre-commit" "Pre-commit hook is executable"
# Test 2: Hook passes with valid code
cat > test.js << 'EOF'
const validCode = () => {
console.log("Hello, world!");
};
EOF
git add test.js
assert_success "git commit -m 'test: valid code'" "Pre-commit allows valid code"
# Test 3: Hook fails with invalid code
cat > test2.js << 'EOF'
const invalidCode = () => {
console.log("Missing semicolon")
}
EOF
git add test2.js
assert_failure "git commit -m 'test: invalid code'" "Pre-commit rejects invalid code"
}
test_commit_msg_hook() {
echo "Testing commit-msg hook..."
# Test 1: Hook exists and is executable
assert_success "test -x .git/hooks/commit-msg" "Commit-msg hook is executable"
# Test 2: Valid commit message format
echo "dummy file" > dummy.txt
git add dummy.txt
assert_success "git commit -m 'feat: add new feature'" "Valid commit message is accepted"
# Test 3: Invalid commit message format
echo "dummy file 2" > dummy2.txt
git add dummy2.txt
assert_failure "git commit -m 'invalid message format'" "Invalid commit message is rejected"
}
test_pre_push_hook() {
echo "Testing pre-push hook..."
# Test 1: Hook exists and is executable
assert_success "test -x .git/hooks/pre-push" "Pre-push hook is executable"
# Test 2: Setup remote repository
git remote add origin https://github.com/test/repo.git 2>/dev/null || true
# Test 3: Pre-push validation (simulate)
# Note: This would need actual remote setup for real testing
echo "Pre-push hook tests require remote repository setup"
}
# Performance testing
test_hook_performance() {
echo "Testing hook performance..."
# Create multiple files to test performance
for i in {1..10}; do
echo "console.log('file $i');" > "file$i.js"
git add "file$i.js"
done
# Measure hook execution time
start_time=$(date +%s.%N)
git commit -m "test: performance test" >/dev/null 2>&1 || true
end_time=$(date +%s.%N)
execution_time=$(echo "$end_time - $start_time" | bc)
# Assert performance threshold (5 seconds)
if (( $(echo "$execution_time < 5" | bc -l) )); then
echo -e "${GREEN}✓${NC} Hook executes within performance threshold (${execution_time}s)"
((TESTS_PASSED++))
else
echo -e "${RED}✗${NC} Hook execution too slow (${execution_time}s > 5s)"
((TESTS_FAILED++))
fi
((TESTS_RUN++))
}
# Run all tests
run_tests() {
echo "Starting Git hooks test suite..."
echo "======================================="
setup_test_env
test_pre_commit_hook
echo
test_commit_msg_hook
echo
test_pre_push_hook
echo
test_hook_performance
echo
cleanup_test_env
# Test summary
echo "======================================="
echo "Test Results:"
echo " Total tests: $TESTS_RUN"
echo -e " Passed: ${GREEN}$TESTS_PASSED${NC}"
echo -e " Failed: ${RED}$TESTS_FAILED${NC}"
if [[ $TESTS_FAILED -eq 0 ]]; then
echo -e "${GREEN}All tests passed!${NC}"
exit 0
else
echo -e "${RED}Some tests failed!${NC}"
exit 1
fi
}
# Execute tests
run_tests
```
#### Python Hook Testing Framework
```python
#!/usr/bin/env python3
# test_hooks.py - Comprehensive testing framework for Git hooks
import unittest
import subprocess
import tempfile
import os
import shutil
import time
from pathlib import Path
class GitHookTestCase(unittest.TestCase):
"""Base class for Git hook testing."""
def setUp(self):
"""Set up test environment."""
self.test_dir = tempfile.mkdtemp()
self.original_dir = os.getcwd()
# Initialize git repository
os.chdir(self.test_dir)
subprocess.run(['git', 'init'], capture_output=True)
subprocess.run(['git', 'config', 'user.name', 'Test User'], capture_output=True)
subprocess.run(['git', 'config', 'user.email', 'test@example.com'], capture_output=True)
# Copy hooks to test repository
hooks_dir = Path('.git/hooks')
hooks_dir.mkdir(exist_ok=True)
# Copy hooks from main repository
main_hooks_dir = Path(self.original_dir) / '.git' / 'hooks'
if main_hooks_dir.exists():
for hook_file in main_hooks_dir.glob('*'):
if hook_file.is_file() and not hook_file.name.endswith('.sample'):
shutil.copy2(hook_file, hooks_dir / hook_file.name)
os.chmod(hooks_dir / hook_file.name, 0o755)
def tearDown(self):
"""Clean up test environment."""
os.chdir(self.original_dir)
shutil.rmtree(self.test_dir)
def run_git_command(self, command, should_succeed=True):
"""Run a git command and return the result."""
result = subprocess.run(
command.split() if isinstance(command, str) else command,
capture_output=True,
text=True
)
if should_succeed:
self.assertEqual(result.returncode, 0,
f"Command failed: {command}\nError: {result.stderr}")
return result
def create_file(self, filename, content="test content"):
"""Create a file with given content."""
with open(filename, 'w') as f:
f.write(content)
def stage_file(self, filename):
"""Stage a file for commit."""
self.run_git_command(f"git add {filename}")
class TestPreCommitHook(GitHookTestCase):
"""Test cases for pre-commit hook."""
def test_hook_exists(self):
"""Test that pre-commit hook exists and is executable."""
hook_path = Path('.git/hooks/pre-commit')
self.assertTrue(hook_path.exists(), "Pre-commit hook should exist")
self.assertTrue(os.access(hook_path, os.X_OK), "Pre-commit hook should be executable")
def test_valid_javascript_passes(self):
"""Test that valid JavaScript code passes the hook."""
self.create_file('test.js', '''
const validFunction = () => {
console.log("Hello, world!");
return true;
};
module.exports = validFunction;
''')
self.stage_file('test.js')
result = self.run_git_command('git commit -m "feat: add valid function"')
self.assertEqual(result.returncode, 0)
def test_invalid_javascript_fails(self):
"""Test that invalid JavaScript code fails the hook."""
self.create_file('test.js', '''
const invalidFunction = () => {
console.log("Missing semicolon")
return true
}
''')
self.stage_file('test.js')
result = self.run_git_command('git commit -m "feat: add invalid function"', should_succeed=False)
self.assertNotEqual(result.returncode, 0)
def test_valid_python_passes(self):
"""Test that valid Python code passes the hook."""
self.create_file('test.py', '''
def valid_function():
"""A valid Python function."""
print("Hello, world!")
return True
if __name__ == "__main__":
valid_function()
''')
self.stage_file('test.py')
result = self.run_git_command('git commit -m "feat: add valid Python function"')
self.assertEqual(result.returncode, 0)
def test_invalid_python_fails(self):
"""Test that invalid Python code fails the hook."""
self.create_file('test.py', '''
def invalid_function():
print("Missing indentation")
return True
''')
self.stage_file('test.py')
result = self.run_git_command('git commit -m "feat: add invalid Python function"', should_succeed=False)
self.assertNotEqual(result.returncode, 0)
def test_hook_performance(self):
"""Test that hook executes within reasonable time."""
# Create multiple files
for i in range(10):
self.create_file(f'file_{i}.js', f'console.log("File {i}");')
self.stage_file(f'file_{i}.js')
start_time = time.time()
result = self.run_git_command('git commit -m "test: performance test"')
end_time = time.time()
execution_time = end_time - start_time
self.assertLess(execution_time, 10, f"Hook took too long: {execution_time}s")
class TestCommitMsgHook(GitHookTestCase):
"""Test cases for commit-msg hook."""
def test_hook_exists(self):
"""Test that commit-msg hook exists and is executable."""
hook_path = Path('.git/hooks/commit-msg')
self.assertTrue(hook_path.exists(), "Commit-msg hook should exist")
self.assertTrue(os.access(hook_path, os.X_OK), "Commit-msg hook should be executable")
def test_valid_conventional_commit(self):
"""Test that valid conventional commit messages pass."""
self.create_file('dummy.txt', 'dummy content')
self.stage_file('dummy.txt')
valid_messages = [
'feat: add new feature',
'fix: resolve critical bug',
'docs: update README',
'style: format code',
'refactor: improve performance',
'test: add unit tests',
'chore: update dependencies'
]
for message in valid_messages:
with self.subTest(message=message):
result = self.run_git_command(f'git commit -m "{message}"')
self.assertEqual(result.returncode, 0)
# Reset for next test
self.run_git_command('git reset --soft HEAD~1')
def test_invalid_commit_messages(self):
"""Test that invalid commit messages fail."""
self.create_file('dummy.txt', 'dummy content')
self.stage_file('dummy.txt')
invalid_messages = [
'invalid message',
'FIX: wrong case',
'feat add feature without colon',
'feat: ', # empty description
'unknown: invalid type'
]
for message in invalid_messages:
with self.subTest(message=message):
result = self.run_git_command(f'git commit -m "{message}"', should_succeed=False)
self.assertNotEqual(result.returncode, 0)
class TestPrePushHook(GitHookTestCase):
"""Test cases for pre-push hook."""
def test_hook_exists(self):
"""Test that pre-push hook exists and is executable."""
hook_path = Path('.git/hooks/pre-push')
self.assertTrue(hook_path.exists(), "Pre-push hook should exist")
self.assertTrue(os.access(hook_path, os.X_OK), "Pre-push hook should be executable")
def test_large_file_detection(self):
"""Test that large files are detected and rejected."""
# Create a large file (simulate with truncate)
large_file = 'large_file.bin'
subprocess.run(['truncate', '-s', '100M', large_file], capture_output=True)
self.stage_file(large_file)
self.run_git_command('git commit -m "test: add large file"')
# Setup remote (mock)
self.run_git_command('git remote add origin https://github.com/test/repo.git')
# This would test the actual pre-push hook
# Note: Requires proper remote setup for real testing
# result = self.run_git_command('git push origin main', should_succeed=False)
# self.assertNotEqual(result.returncode, 0)
class TestHookIntegration(GitHookTestCase):
"""Integration tests for multiple hooks working together."""
def test_full_workflow(self):
"""Test complete workflow from commit to push."""
# Create valid code
self.create_file('app.js', '''
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello, World!');
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
module.exports = app;
''')
# Stage and commit
self.stage_file('app.js')
result = self.run_git_command('git commit -m "feat: create express app"')
self.assertEqual(result.returncode, 0)
# Verify commit was created
result = self.run_git_command('git log --oneline')
self.assertIn('feat: create express app', result.stdout)
# Performance benchmarking
class HookPerformanceBenchmark:
"""Benchmark hook performance with various scenarios."""
def __init__(self, test_dir):
self.test_dir = test_dir
self.results = {}
def benchmark_file_count(self, file_counts=[1, 5, 10, 25, 50]):
"""Benchmark hook performance with different file counts."""
for count in file_counts:
# Create files
for i in range(count):
with open(f'file_{i}.js', 'w') as f:
f.write(f'console.log("File {i}");')
# Stage all files
subprocess.run(['git', 'add', '.'], capture_output=True)
# Measure commit time
start_time = time.time()
result = subprocess.run(
['git', 'commit', '-m', f'test: {count} files'],
capture_output=True
)
end_time = time.time()
self.results[f'{count}_files'] = {
'time': end_time - start_time,
'success': result.returncode == 0
}
# Reset for next test
subprocess.run(['git', 'reset', '--hard', 'HEAD~1'], capture_output=True)
def generate_report(self):
"""Generate performance report."""
print("Hook Performance Benchmark Results")
print("=" * 40)
for scenario, result in self.results.items():
status = "✓" if result['success'] else "✗"
print(f"{status} {scenario}: {result['time']:.2f}s")
# Test runner
def run_all_tests():
"""Run all hook tests."""
# Create test suite
suite = unittest.TestSuite()
# Add test classes
test_classes = [
TestPreCommitHook,
TestCommitMsgHook,
TestPrePushHook,
TestHookIntegration
]
for test_class in test_classes:
tests = unittest.TestLoader().loadTestsFromTestCase(test_class)
suite.addTests(tests)
# Run tests
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
return result.wasSuccessful()
if __name__ == '__main__':
success = run_all_tests()
exit(0 if success else 1)
```
### Integration Testing
#### Testing Hooks with CI/CD Systems
```yaml
# .github/workflows/test-hooks.yml
name: Test Git Hooks
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test-hooks:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16, 18, 20]
python-version: [3.8, 3.9, 3.10, 3.11]
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
npm install
pip install -r requirements-dev.txt
- name: Install hook testing tools
run: |
npm install -g eslint prettier
pip install black flake8 pytest
- name: Test hook installation
run: |
./scripts/install-hooks.sh
chmod +x .git/hooks/*
- name: Run hook unit tests
run: |
python test_hooks.py
bash test-hooks.sh
- name: Test hook performance
run: |
./scripts/benchmark-hooks.sh
- name: Test hook edge cases
run: |
./scripts/test-edge-cases.sh
```
### Load Testing and Stress Testing
```python
#!/usr/bin/env python3
# stress_test_hooks.py - Stress testing for Git hooks
import subprocess
import threading
import time
import tempfile
import os
import shutil
from concurrent.futures import ThreadPoolExecutor, as_completed
import statistics
class HookStressTester:
"""Stress testing framework for Git hooks."""
def __init__(self, repo_path, concurrent_users=10):
self.repo_path = repo_path
self.concurrent_users = concurrent_users
self.results = []
self.errors = []
def simulate_user_workflow(self, user_id, iterations=5):
"""Simulate a user's Git workflow."""
user_results = []
for i in range(iterations):
try:
# Create temporary working directory for user
user_dir = tempfile.mkdtemp(prefix=f"user_{user_id}_")
# Clone repository
subprocess.run([
'git', 'clone', self.repo_path, user_dir
], capture_output=True, check=True)
os.chdir(user_dir)
# Configure git
subprocess.run(['git', 'config', 'user.name', f'User {user_id}'], capture_output=True)
subprocess.run(['git', 'config', 'user.email', f'user{user_id}@test.com'], capture_output=True)
# Create and commit file
filename = f'user_{user_id}_file_{i}.js'
with open(filename, 'w') as f:
f.write(f'''
// File created by user {user_id}, iteration {i}
const message = "Hello from user {user_id}";
console.log(message);
function user{user_id}Function() {{
return "User {user_id} function";
}}
module.exports = {{ user{user_id}Function }};
''')
# Time the commit operation (includes hooks)
start_time = time.time()
subprocess.run(['git', 'add', filename], capture_output=True, check=True)
result = subprocess.run([
'git', 'commit', '-m', f'feat: add file by user {user_id}'
], capture_output=True)
end_time = time.time()
user_results.append({
'user_id': user_id,
'iteration': i,
'duration': end_time - start_time,
'success': result.returncode == 0,
'output': result.stdout.decode() if result.stdout else '',
'error': result.stderr.decode() if result.stderr else ''
})
# Cleanup
os.chdir('/')
shutil.rmtree(user_dir)
except Exception as e:
self.errors.append({
'user_id': user_id,
'iteration': i,
'error': str(e)
})
return user_results
def run_stress_test(self, iterations_per_user=5):
"""Run concurrent stress test."""
print(f"Starting stress test with {self.concurrent_users} concurrent users...")
print(f"Each user will perform {iterations_per_user} iterations")
with ThreadPoolExecutor(max_workers=self.concurrent_users) as executor:
# Submit all user simulations
futures = [
executor.submit(self.simulate_user_workflow, user_id, iterations_per_user)
for user_id in range(self.concurrent_users)
]
# Collect results
for future in as_completed(futures):
try:
user_results = future.result()
self.results.extend(user_results)
except Exception as e:
self.errors.append({'error': str(e)})
def analyze_results(self):
"""Analyze stress test results."""
if not self.results:
print("No results to analyze")
return
# Calculate statistics
durations = [r['duration'] for r in self.results if r['success']]
success_rate = len([r for r in self.results if r['success']]) / len(self.results)
print("\nStress Test Results")
print("=" * 50)
print(f"Total operations: {len(self.results)}")
print(f"Successful operations: {len(durations)}")
print(f"Failed operations: {len(self.results) - len(durations)}")
print(f"Success rate: {success_rate:.2%}")
if durations:
print(f"\nPerformance Statistics:")
print(f" Average duration: {statistics.mean(durations):.2f}s")
print(f" Median duration: {statistics.median(durations):.2f}s")
print(f" Min duration: {min(durations):.2f}s")
print(f" Max duration: {max(durations):.2f}s")
print(f" Standard deviation: {statistics.stdev(durations):.2f}s")
# Error analysis
if self.errors:
print(f"\nErrors encountered: {len(self.errors)}")
for error in self.errors[:5]: # Show first 5 errors
print(f" - {error}")
# Performance thresholds
if durations:
slow_operations = len([d for d in durations if d > 10])
if slow_operations > 0:
print(f"\n⚠️ Warning: {slow_operations} operations took longer than 10 seconds")
if statistics.mean(durations) > 5:
print("⚠️ Warning: Average hook execution time exceeds 5 seconds")
if success_rate < 0.95:
print("❌ Warning: Success rate below 95%")
else:
print("✅ Success rate is acceptable")
def main():
"""Main function for stress testing."""
import argparse
parser = argparse.ArgumentParser(description='Stress test Git hooks')
parser.add_argument('--repo', required=True, help='Path to Git repository')
parser.add_argument('--users', type=int, default=10, help='Number of concurrent users')
parser.add_argument('--iterations', type=int, default=5, help='Iterations per user')
args = parser.parse_args()
tester = HookStressTester(args.repo, args.users)
tester.run_stress_test(args.iterations)
tester.analyze_results()
if __name__ == '__main__':
main()
```
### Security Considerations
1. **Code Review**: Review hook scripts like any other code
2. **Access Control**: Limit who can modify server-side hooks
3. **Input Validation**: Validate all inputs to prevent injection attacks
4. **Secrets Management**: Don't hardcode secrets in hook scripts
5. **Audit Logging**: Log hook executions for security monitoring
## Summary
Git hooks are a powerful feature that can significantly improve your development workflow by automating repetitive tasks, enforcing quality standards, and integrating with external systems. When implemented correctly, they provide:
- **Consistency**: Enforce coding standards across the team
- **Quality**: Catch issues before they reach the repository
- **Automation**: Reduce manual work and human error
- **Integration**: Connect Git with other development tools
Start with simple hooks and gradually add complexity as your team becomes comfortable with the concept. Remember to keep hooks fast, provide clear feedback, and make them easy to maintain and update.
By following the practices and examples in this guide, you can leverage Git hooks to create a more efficient and reliable development process for your team.
## Security and Compliance
Security and compliance are critical aspects of Git hooks implementation, especially in enterprise environments and regulated industries.
### Security Best Practices
#### Secure Hook Development
```bash
#!/bin/bash
# Secure hook template with security best practices
set -euo pipefail # Exit on error, undefined vars, pipe failures
IFS=$'\n\t' # Secure Internal Field Separator
# Security configuration
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/var/log/git-hooks/security.log"
readonly MAX_EXECUTION_TIME=300 # 5 minutes
readonly ALLOWED_USERS_FILE="/etc/git-hooks/allowed-users"
# Logging function with timestamp and user info
log_security_event() {
local level="$1"
local message="$2"
local timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local user="${USER:-unknown}"
local pid="$$"
echo "${timestamp} [${level}] PID:${pid} USER:${user} ${message}" >> "$LOG_FILE"
}
# Input validation function
validate_input() {
local input="$1"
local max_length="${2:-1000}"
# Check length
if [[ ${#input} -gt $max_length ]]; then
log_security_event "ERROR" "Input exceeds maximum length: ${#input} > ${max_length}"
return 1
fi
# Check for malicious patterns
local malicious_patterns=(
'\$\(' # Command substitution
'`' # Backticks
'\|\|' # OR operator
'&&' # AND operator
';' # Command separator
'\.\./\.\.' # Directory traversal
'\x00' # Null bytes
)
for pattern in "${malicious_patterns[@]}"; do
if [[ "$input" =~ $pattern ]]; then
log_security_event "ERROR" "Malicious pattern detected: $pattern"
return 1
fi
done
return 0
}
# User authorization check
check_user_authorization() {
local user="${USER:-$(whoami)}"
if [[ ! -f "$ALLOWED_USERS_FILE" ]]; then
log_security_event "WARNING" "Allowed users file not found: $ALLOWED_USERS_FILE"
return 0 # Default to allow if file doesn't exist
fi
if grep -q "^${user}$" "$ALLOWED_USERS_FILE"; then
log_security_event "INFO" "User authorized: $user"
return 0
else
log_security_event "ERROR" "Unauthorized user: $user"
return 1
fi
}
# Secure file operations
secure_file_check() {
local file_path="$1"
# Validate file path
if ! validate_input "$file_path" 500; then
return 1
fi
# Check for directory traversal
if [[ "$file_path" =~ \.\./\.\. ]]; then
log_security_event "ERROR" "Directory traversal attempt: $file_path"
return 1
fi
# Ensure file is within repository
local repo_root=$(git rev-parse --show-toplevel)
local real_path=$(realpath "$file_path" 2>/dev/null || echo "$file_path")
if [[ ! "$real_path" =~ ^"$repo_root" ]]; then
log_security_event "ERROR" "File outside repository: $real_path"
return 1
fi
return 0
}
# Timeout wrapper for commands
timeout_command() {
local timeout_duration="$1"
shift
timeout "$timeout_duration" "$@"
local exit_code=$?
if [[ $exit_code -eq 124 ]]; then
log_security_event "ERROR" "Command timed out after ${timeout_duration}s: $*"
fi
return $exit_code
}
# Main security wrapper
secure_hook_wrapper() {
local hook_name="$1"
shift
# Check authorization
if ! check_user_authorization; then
echo "❌ Access denied: User not authorized to execute hooks" >&2
exit 1
fi
# Log hook execution start
log_security_event "INFO" "Hook execution started: $hook_name"
# Execute with timeout
if timeout_command "$MAX_EXECUTION_TIME" "$@"; then
log_security_event "INFO" "Hook execution completed successfully: $hook_name"
exit 0
else
local exit_code=$?
log_security_event "ERROR" "Hook execution failed: $hook_name (exit code: $exit_code)"
exit $exit_code
fi
}
# Example usage in actual hook
main() {
# Validate commit message file parameter
if [[ $# -lt 1 ]] || ! validate_input "$1"; then
log_security_event "ERROR" "Invalid parameters for commit-msg hook"
exit 1
fi
local commit_msg_file="$1"
# Secure file check
if ! secure_file_check "$commit_msg_file"; then
exit 1
fi
# Your hook logic here
# ...
log_security_event "INFO" "Commit message validation completed"
}
# Execute with security wrapper
secure_hook_wrapper "commit-msg" main "$@"
```
#### Cryptographic Verification
```python
#!/usr/bin/env python3
# Cryptographic verification for Git hooks
import hashlib
import hmac
import secrets
import json
import time
from pathlib import Path
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
class SecureHookValidator:
"""Cryptographic validation for Git hooks."""
def __init__(self, secret_key_file="/etc/git-hooks/secret.key"):
self.secret_key_file = Path(secret_key_file)
self.load_or_generate_key()
def load_or_generate_key(self):
"""Load existing key or generate new one."""
if self.secret_key_file.exists():
with open(self.secret_key_file, 'rb') as f:
self.key = f.read()
else:
# Generate new key
self.key = Fernet.generate_key()
self.secret_key_file.parent.mkdir(parents=True, exist_ok=True)
with open(self.secret_key_file, 'wb') as f:
f.write(self.key)
# Secure the key file
self.secret_key_file.chmod(0o600)
self.cipher = Fernet(self.key)
def create_signature(self, data):
"""Create HMAC signature for data."""
return hmac.new(
self.key,
data.encode() if isinstance(data, str) else data,
hashlib.sha256
).hexdigest()
def verify_signature(self, data, signature):
"""Verify HMAC signature."""
expected_signature = self.create_signature(data)
return hmac.compare_digest(expected_signature, signature)
def encrypt_sensitive_data(self, data):
"""Encrypt sensitive data."""
if isinstance(data, str):
data = data.encode()
return self.cipher.encrypt(data)
def decrypt_sensitive_data(self, encrypted_data):
"""Decrypt sensitive data."""
return self.cipher.decrypt(encrypted_data)
def create_secure_token(self, user_id, expiry_hours=24):
"""Create secure, time-limited token."""
expiry_time = int(time.time()) + (expiry_hours * 3600)
token_data = {
'user_id': user_id,
'expiry': expiry_time,
'nonce': secrets.token_hex(16)
}
token_json = json.dumps(token_data, sort_keys=True)
signature = self.create_signature(token_json)
return base64.b64encode(f"{token_json}:{signature}".encode()).decode()
def verify_token(self, token):
"""Verify secure token."""
try:
decoded = base64.b64decode(token.encode()).decode()
token_json, signature = decoded.rsplit(':', 1)
# Verify signature
if not self.verify_signature(token_json, signature):
return False, "Invalid signature"
# Parse token data
token_data = json.loads(token_json)
# Check expiry
if time.time() > token_data['expiry']:
return False, "Token expired"
return True, token_data
except Exception as e:
return False, f"Token validation error: {e}"
def validate_commit_integrity(self, commit_hash):
"""Validate commit integrity using Git's internal mechanisms."""
import subprocess
try:
# Verify commit object integrity
result = subprocess.run(
['git', 'fsck', '--strict', commit_hash],
capture_output=True,
text=True,
check=True
)
return True, "Commit integrity verified"
except subprocess.CalledProcessError as e:
return False, f"Commit integrity check failed: {e.stderr}"
# Usage example in hook
def secure_pre_receive_hook():
"""Secure pre-receive hook with cryptographic validation."""
validator = SecureHookValidator()
# Read push information
import sys
for line in sys.stdin:
old_rev, new_rev, ref_name = line.strip().split()
# Validate commit integrity
valid, message = validator.validate_commit_integrity(new_rev)
if not valid:
print(f"❌ Security error: {message}")
sys.exit(1)
# Additional security checks
# ... your security logic here
print(f"✅ Security validation passed for {ref_name}")
if __name__ == "__main__":
secure_pre_receive_hook()
```
### Compliance Frameworks
#### SOX (Sarbanes-Oxley) Compliance
```python
#!/usr/bin/env python3
# SOX compliance implementation for Git hooks
import json
import sqlite3
import hashlib
from datetime import datetime, timezone
from pathlib import Path
import subprocess
class SOXComplianceManager:
"""Manage SOX compliance for Git operations."""
def __init__(self, audit_db_path="/var/audit/sox_compliance.db"):
self.audit_db_path = Path(audit_db_path)
self.init_audit_database()
def init_audit_database(self):
"""Initialize audit database with required tables."""
self.audit_db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(self.audit_db_path)
cursor = conn.cursor()
# Create audit trail table
cursor.execute('''
CREATE TABLE IF NOT EXISTS sox_audit_trail (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp DATETIME NOT NULL,
user_id TEXT NOT NULL,
action TEXT NOT NULL,
repository TEXT NOT NULL,
commit_hash TEXT,
branch TEXT,
files_changed TEXT,
approval_status TEXT,
reviewer_id TEXT,
risk_level TEXT,
compliance_notes TEXT,
digital_signature TEXT NOT NULL,
UNIQUE(commit_hash, action)
)
''')
# Create change approval table
cursor.execute('''
CREATE TABLE IF NOT EXISTS sox_change_approvals (
id INTEGER PRIMARY KEY AUTOINCREMENT,
change_request_id TEXT UNIQUE NOT NULL,
requester_id TEXT NOT NULL,
reviewer_id TEXT,
approval_timestamp DATETIME,
approval_status TEXT CHECK(approval_status IN ('pending', 'approved', 'rejected')),
business_justification TEXT,
technical_impact TEXT,
risk_assessment TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
''')
conn.commit()
conn.close()
def create_digital_signature(self, data):
"""Create digital signature for audit records."""
return hashlib.sha256(json.dumps(data, sort_keys=True).encode()).hexdigest()
def log_sox_event(self, action, commit_hash=None, **kwargs):
"""Log SOX compliance event."""
conn = sqlite3.connect(self.audit_db_path)
cursor = conn.cursor()
# Get user and repository info
user_id = subprocess.check_output(['git', 'config', 'user.email']).decode().strip()
repository = subprocess.check_output(['git', 'remote', 'get-url', 'origin']).decode().strip()
branch = subprocess.check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD']).decode().strip()
# Prepare audit record
audit_data = {
'timestamp': datetime.now(timezone.utc).isoformat(),
'user_id': user_id,
'action': action,
'repository': repository,
'commit_hash': commit_hash,
'branch': branch,
**kwargs
}
# Create digital signature
digital_signature = self.create_digital_signature(audit_data)
audit_data['digital_signature'] = digital_signature
# Insert audit record
cursor.execute('''
INSERT OR REPLACE INTO sox_audit_trail
(timestamp, user_id, action, repository, commit_hash, branch,
files_changed, approval_status, reviewer_id, risk_level,
compliance_notes, digital_signature)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (
audit_data['timestamp'],
audit_data['user_id'],
audit_data['action'],
audit_data['repository'],
audit_data.get('commit_hash'),
audit_data['branch'],
audit_data.get('files_changed'),
audit_data.get('approval_status'),
audit_data.get('reviewer_id'),
audit_data.get('risk_level'),
audit_data.get('compliance_notes'),
audit_data['digital_signature']
))
conn.commit()
conn.close()
return audit_data
def validate_change_approval(self, commit_message):
"""Validate that change has proper approval."""
# Extract change request ID from commit message
import re
change_request_pattern = r'CR-\d{6}'
match = re.search(change_request_pattern, commit_message)
if not match:
return False, "No change request ID found in commit message"
change_request_id = match.group()
# Check approval status
conn = sqlite3.connect(self.audit_db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT approval_status, reviewer_id, approval_timestamp
FROM sox_change_approvals
WHERE change_request_id = ?
''', (change_request_id,))
result = cursor.fetchone()
conn.close()
if not result:
return False, f"Change request {change_request_id} not found"
approval_status, reviewer_id, approval_timestamp = result
if approval_status != 'approved':
return False, f"Change request {change_request_id} not approved (status: {approval_status})"
return True, {
'change_request_id': change_request_id,
'reviewer_id': reviewer_id,
'approval_timestamp': approval_timestamp
}
def assess_change_risk(self, changed_files):
"""Assess risk level of changes."""
high_risk_patterns = [
r'.*config.*',
r'.*security.*',
r'.*auth.*',
r'.*database.*',
r'.*production.*'
]
medium_risk_patterns = [
r'.*api.*',
r'.*service.*',
r'.*controller.*'
]
risk_level = 'low'
for file_path in changed_files:
for pattern in high_risk_patterns:
if re.match(pattern, file_path, re.IGNORECASE):
risk_level = 'high'
break
if risk_level == 'high':
break
for pattern in medium_risk_patterns:
if re.match(pattern, file_path, re.IGNORECASE):
risk_level = 'medium'
return risk_level
def validate_sox_compliance(self, commit_hash):
"""Validate SOX compliance for a commit."""
# Get commit information
commit_message = subprocess.check_output([
'git', 'log', '-1', '--pretty=%B', commit_hash
]).decode().strip()
changed_files = subprocess.check_output([
'git', 'diff-tree', '--no-commit-id', '--name-only', '-r', commit_hash
]).decode().strip().split('\n')
# Validate change approval
approval_valid, approval_info = self.validate_change_approval(commit_message)
if not approval_valid:
self.log_sox_event(
'sox_validation_failed',
commit_hash=commit_hash,
compliance_notes=f"Approval validation failed: {approval_info}",
risk_level='high'
)
return False, approval_info
# Assess risk level
risk_level = self.assess_change_risk(changed_files)
# Log compliance validation
self.log_sox_event(
'sox_validation_passed',
commit_hash=commit_hash,
files_changed=json.dumps(changed_files),
approval_status='approved',
reviewer_id=approval_info['reviewer_id'],
risk_level=risk_level,
compliance_notes='SOX compliance validation successful'
)
return True, "SOX compliance validation passed"
# Hook implementation
def sox_compliant_pre_receive():
"""SOX compliant pre-receive hook."""
sox_manager = SOXComplianceManager()
import sys
for line in sys.stdin:
old_rev, new_rev, ref_name = line.strip().split()
# Only validate non-zero commits (not deletions)
if new_rev != '0' * 40:
valid, message = sox_manager.validate_sox_compliance(new_rev)
if not valid:
print(f"❌ SOX Compliance Error: {message}")
print("All changes must have approved change requests (format: CR-XXXXXX)")
sys.exit(1)
else:
print(f"✅ SOX Compliance: {message}")
if __name__ == "__main__":
sox_compliant_pre_receive()
```
#### GDPR Compliance for Development
```bash
#!/bin/bash
# GDPR compliance hook for protecting personal data
set -euo pipefail
# GDPR configuration
readonly GDPR_CONFIG="/etc/git-hooks/gdpr-config.json"
readonly PII_PATTERNS_FILE="/etc/git-hooks/pii-patterns.txt"
readonly GDPR_LOG="/var/log/git-hooks/gdpr.log"
# PII detection patterns
declare -a DEFAULT_PII_PATTERNS=(
'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' # Email addresses
'\b\d{3}-\d{2}-\d{4}\b' # SSN (US format)
'\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b' # Credit card numbers
'\b\d{1,2}/\d{1,2}/\d{4}\b' # Dates (potential DOB)
'\b(?:phone|tel|mobile)[\s:=]+\+?[\d\s\-\(\)]+\b' # Phone numbers
'\b(?:address|addr)[\s:=]+[^\n]+\b' # Addresses
'\b(?:first_name|last_name|full_name)[\s:=]+[^\n]+\b' # Names in code
)
log_gdpr_event() {
local level="$1"
local message="$2"
echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] [$level] $message" >> "$GDPR_LOG"
}
load_pii_patterns() {
local patterns=()
if [[ -f "$PII_PATTERNS_FILE" ]]; then
while IFS= read -r pattern; do
[[ -n "$pattern" && ! "$pattern" =~ ^# ]] && patterns+=("$pattern")
done < "$PII_PATTERNS_FILE"
fi
# Add default patterns if file doesn't exist or is empty
if [[ ${#patterns[@]} -eq 0 ]]; then
patterns=("${DEFAULT_PII_PATTERNS[@]}")
fi
printf '%s\n' "${patterns[@]}"
}
scan_for_pii() {
local file_path="$1"
local findings=()
log_gdpr_event "INFO" "Scanning file for PII: $file_path"
while IFS= read -r pattern; do
if grep -P "$pattern" "$file_path" >/dev/null 2>&1; then
local matches=$(grep -nP "$pattern" "$file_path" | head -5)
findings+=("Pattern '$pattern' found in $file_path:")
findings+=("$matches")
fi
done < <(load_pii_patterns)
if [[ ${#findings[@]} -gt 0 ]]; then
log_gdpr_event "WARNING" "PII detected in $file_path"
printf '%s\n' "${findings[@]}"
return 1
fi
return 0
}
check_data_retention_metadata() {
local file_path="$1"
# Check for GDPR metadata in file comments
local has_retention_policy=false
local has_lawful_basis=false
if grep -q "GDPR-RETENTION:" "$file_path" 2>/dev/null; then
has_retention_policy=true
fi
if grep -q "GDPR-LAWFUL-BASIS:" "$file_path" 2>/dev/null; then
has_lawful_basis=true
fi
# For files that handle personal data, require GDPR metadata
if grep -qE "(personal|user|customer|client).*data|data.*(personal|user|customer|client)" "$file_path" 2>/dev/null; then
if [[ "$has_retention_policy" != true ]]; then
echo "❌ GDPR Compliance: File handles personal data but lacks retention policy metadata"
echo "Add comment: // GDPR-RETENTION: "
return 1
fi
if [[ "$has_lawful_basis" != true ]]; then
echo "❌ GDPR Compliance: File handles personal data but lacks lawful basis metadata"
echo "Add comment: // GDPR-LAWFUL-BASIS: "
return 1
fi
fi
return 0
}
validate_data_encryption() {
local file_path="$1"
# Check for unencrypted personal data storage
if grep -qE "(password|secret|token|key).*=.*['\"][^'\"]+['\"]" "$file_path" 2>/dev/null; then
if ! grep -q "encrypt\|hash\|bcrypt\|scrypt" "$file_path" 2>/dev/null; then
echo "⚠️ GDPR Warning: Potential unencrypted sensitive data in $file_path"
echo "Ensure all personal data is properly encrypted"
return 1
fi
fi
return 0
}
check_consent_management() {
local file_path="$1"
# Check for consent management in data collection code
if grep -qE "(collect|store|process).*data" "$file_path" 2>/dev/null; then
if ! grep -qE "(consent|permission|agree|opt.?in)" "$file_path" 2>/dev/null; then
echo "⚠️ GDPR Warning: Data collection without consent management in $file_path"
echo "Ensure proper consent mechanisms are implemented"
return 1
fi
fi
return 0
}
validate_gdpr_compliance() {
echo "🔍 Running GDPR compliance checks..."
local files_changed=$(git diff --cached --name-only --diff-filter=ACM)
local gdpr_violations=()
for file in $files_changed; do
if [[ -f "$file" ]]; then
echo "Checking GDPR compliance for: $file"
# PII detection
if ! scan_for_pii "$file"; then
gdpr_violations+=("PII detected in $file")
fi
# Data retention metadata check
if ! check_data_retention_metadata "$file"; then
gdpr_violations+=("Missing GDPR metadata in $file")
fi
# Encryption validation
if ! validate_data_encryption "$file"; then
gdpr_violations+=("Encryption concerns in $file")
fi
# Consent management check
if ! check_consent_management "$file"; then
gdpr_violations+=("Consent management concerns in $file")
fi
fi
done
if [[ ${#gdpr_violations[@]} -gt 0 ]]; then
echo ""
echo "❌ GDPR Compliance Issues Found:"
for violation in "${gdpr_violations[@]}"; do
echo " - $violation"
done
echo ""
echo "GDPR Compliance Guide:"
echo "1. Remove or anonymize any personal data"
echo "2. Add GDPR metadata comments for data handling code"
echo "3. Ensure proper encryption for sensitive data"
echo "4. Implement consent mechanisms for data collection"
echo ""
echo "To bypass (not recommended): git commit --no-verify"
log_gdpr_event "ERROR" "GDPR compliance check failed with ${#gdpr_violations[@]} violations"
return 1
fi
echo "✅ GDPR compliance check passed"
log_gdpr_event "INFO" "GDPR compliance check passed for commit"
return 0
}
# Generate GDPR compliance report
generate_gdpr_report() {
local report_file="/tmp/gdpr-compliance-report-$(date +%Y%m%d).txt"
cat > "$report_file" << EOF
GDPR Compliance Report
Generated: $(date)
Repository: $(git remote get-url origin 2>/dev/null || echo "local")
Branch: $(git rev-parse --abbrev-ref HEAD)
Files Scanned:
$(git diff --cached --name-only --diff-filter=ACM | sed 's/^/ - /')
Compliance Status: PASSED
No personal data or GDPR violations detected in staged changes.
Recommendations:
1. Regularly audit code for personal data handling
2. Implement data minimization principles
3. Ensure proper consent mechanisms
4. Regular security audits and penetration testing
5. Staff training on GDPR compliance
Report saved to: $report_file
EOF
echo "📋 GDPR compliance report generated: $report_file"
}
main() {
log_gdpr_event "INFO" "Starting GDPR compliance check"
if validate_gdpr_compliance; then
generate_gdpr_report
log_gdpr_event "INFO" "GDPR compliance check completed successfully"
exit 0
else
log_gdpr_event "ERROR" "GDPR compliance check failed"
exit 1
fi
}
main "$@"
```
## Integration with CI/CD Systems
Git hooks integrate seamlessly with Continuous Integration and Continuous Deployment systems, creating a comprehensive automation pipeline.
### Jenkins Integration
```groovy
// Jenkinsfile with Git hooks integration
pipeline {
agent any
environment {
HOOK_VALIDATION_ENABLED = 'true'
NOTIFICATION_WEBHOOK = credentials('slack-webhook')
}
stages {
stage('Hook Validation') {
steps {
script {
// Validate that all required hooks are present
sh '''
echo "Validating Git hooks..."
required_hooks=("pre-commit" "commit-msg" "pre-push")
for hook in "${required_hooks[@]}"; do
if [[ ! -x ".git/hooks/$hook" ]]; then
echo "❌ Required hook missing: $hook"
exit 1
fi
done
echo "✅ All required hooks are present"
'''
}
}
}
stage('Pre-Commit Validation') {
steps {
script {
// Run pre-commit checks
sh '''
echo "Running pre-commit validation..."
# Install dependencies for hooks
npm ci
pip install -r requirements-dev.txt
# Run pre-commit on all files
.git/hooks/pre-commit || {
echo "❌ Pre-commit validation failed"
exit 1
}
echo "✅ Pre-commit validation passed"
'''
}
}
}
stage('Deploy') {
when {
branch 'main'
}
steps {
script {
sh '''
echo "Deploying to production..."
# Trigger post-receive hook equivalent
if [[ -x "scripts/deploy.sh" ]]; then
scripts/deploy.sh
fi
'''
}
}
}
}
post {
success {
script {
// Notify success
sh '''
curl -X POST -H 'Content-type: application/json' \
--data '{"text":"✅ Pipeline completed successfully"}' \
"${NOTIFICATION_WEBHOOK}"
'''
}
}
}
}
```
## Resources and Further Reading
### Official Documentation
- [Git Hooks Documentation](https://git-scm.com/docs/githooks)
- [Pro Git Book - Git Hooks Chapter](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks)
### Recommended Tools
- [pre-commit framework](https://pre-commit.com/)
- [Husky (Node.js)](https://github.com/typicode/husky)
- [lefthook](https://github.com/evilmartians/lefthook)
### Industry Standards and Compliance
- [NIST Cybersecurity Framework](https://www.nist.gov/cyberframework)
- [PCI DSS Requirements](https://www.pcisecuritystandards.org/)
- [SOX Compliance Guidelines](https://www.sarbanes-oxley.com/)
### Security Resources
- [OWASP Secure Coding Practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/)
- [CIS Controls](https://www.cisecurity.org/controls)
### Performance and Optimization
- [Git Performance Best Practices](https://git-scm.com/book/en/v2/Git-Internals-Git-Objects)
- [Large File Storage (LFS)](https://git-lfs.github.io/)
### Advanced Resources
- [Git Internals](https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Porcelain)
- [Custom Git Commands](https://git-scm.com/docs/git-config#Documentation/git-config.txt-aliasalias)
- [Git Workflow Strategies](https://www.atlassian.com/git/tutorials/comparing-workflows)
## Conclusion
Git hooks are powerful tools that can significantly improve your development workflow by automating quality checks, enforcing standards, and integrating with various tools and systems. This comprehensive guide has covered:
- **Basic and advanced hook implementations** across multiple programming languages
- **Industry-specific use cases** for energy, financial, healthcare, and aerospace sectors
- **Security and compliance** frameworks including SOX, GDPR, and PCI DSS
- **Performance optimization** techniques and monitoring
- **Testing frameworks** for validating hook functionality
- **Cross-platform considerations** for diverse development environments
- **Integration patterns** with modern CI/CD systems
By implementing the practices and examples in this guide, you can create a robust, secure, and efficient development process that scales with your organization's needs while maintaining compliance with industry standards and regulations.
Remember to start with simple hooks and gradually build complexity as your team becomes comfortable with the automation. Regular reviews and updates of your hook implementations will ensure they continue to serve your evolving development practices effectively.
### [Script Gists](https://sametcc.me/gist/scripts-gist)
---
title: "Script Gists"
publishedAt: "2024-08-25"
summary: "Collection of useful development scripts including Prettier setup, Git
utilities, and automation tools for modern development workflows."
tags: [Scripts, Prettier, Git, Automation, Developer Tools]
language: "en"
type: "gist"
status: "published"
---
# Script Gists
## Prettier Setup Script
This script sets up Prettier in a project by installing it and creating
configuration files.
```bash
bun add --dev --exact prettier && \
node -e 'require("fs").writeFileSync(".prettierrc","{}\n")' && \
node -e 'require("fs").writeFileSync(".prettierignore","# Ignore artifacts:\nbuild\ncoverage\n")' && \
npx exec prettier . --write
```
## Git Tag and Push Script
This script automates the process of tagging a new version in a Git repository
and pushing it to the remote. It ensures that you are on the main branch, pulls
the latest changes, creates a new tag, and pushes both the tag and the main
branch to the remote repository.
```bash
#!/bin/bash
# Check if version tag is provided
if [ -z "$1" ]; then
echo "Please provide a version tag (e.g., v1.0.0)"
exit 1
fi
VERSION=$1
# Ensure we're on main branch
git checkout main
# Pull latest changes
git pull origin main
# Create and push tag
git tag -a $VERSION -m "Release $VERSION"
git push origin $VERSION
# Push all changes
git push origin main
echo "Successfully tagged and pushed version $VERSION"
```
## Making the Script Executable
To make the script executable, you can run the following command in your
terminal:
```bash
chmod +x git-tag-push.sh
```
## Combine and Validate Locale Files
This advanced TypeScript utility manages multilingual application translation
files, specifically focusing on synchronizing English and Turkish localization
files. It provides real-time validation and automatic combining of locale JSON
files, detecting missing translation keys, type mismatches, and format
inconsistencies between languages. The script includes a file watcher that
automatically processes changes, making it ideal for development workflows. With
detailed error reporting and proper file management, this utility ensures
translation consistency and completeness in multilingual applications.
```typescript
import { EventEmitter } from "events";
import path from "path";
import fs from "fs";
interface KeyData {
keys: Set;
keysByFile: Map;
}
interface TranslationError {
message: string;
details?: unknown;
}
type TranslationValue =
| string
| number
| boolean
| null
| TranslationObject
| TranslationArray;
type TranslationObject = { [key: string]: TranslationValue };
type TranslationArray = TranslationValue[];
class LocaleError extends Error {
public details?: unknown;
constructor(message: string, details?: unknown) {
super(message);
this.name = "LocaleError";
this.details = details;
}
}
class TranslationManager extends EventEmitter {
private enPath: string;
private trPath: string;
private combinedPath: string;
private watchers: fs.FSWatcher[] = [];
private debounceTimeout: NodeJS.Timeout | null = null;
private isProcessing = false;
constructor(basePath: string) {
super();
this.enPath = path.join(basePath, "en");
this.trPath = path.join(basePath, "tr");
this.combinedPath = path.join(basePath, "combined");
}
private validateDirectory(dirPath: string, dirName: string): void {
if (!fs.existsSync(dirPath)) {
throw new LocaleError(`${dirName} directory does not exist: ${dirPath}`);
}
const files = fs.readdirSync(dirPath);
const jsonFiles = files.filter((file) => file.endsWith(".json"));
if (jsonFiles.length === 0) {
throw new LocaleError(
`No JSON files found in ${dirName} directory: ${dirPath}`,
);
}
}
private safeParseJson(filePath: string): TranslationObject {
try {
let content = fs.readFileSync(filePath, "utf8");
// Remove UTF-8 BOM if present
if (content.charCodeAt(0) === 0xfeff) {
content = content.slice(1);
}
if (!content.trim()) {
throw new LocaleError(`Empty translation file: ${filePath}`);
}
const parsed = JSON.parse(content);
if (
typeof parsed !== "object" ||
parsed === null ||
Array.isArray(parsed)
) {
throw new LocaleError(
`Invalid translation file format. Expected an object: ${filePath}`,
);
}
return parsed as TranslationObject;
} catch (error) {
if (error instanceof SyntaxError) {
throw new LocaleError(`Invalid JSON format in file: ${filePath}`, {
originalError: error.message,
});
}
throw error;
}
}
private getNestedKeys(obj: TranslationObject, prefix = ""): string[] {
const keys: string[] = [];
for (const key in obj) {
const fullKey = prefix ? `${prefix}.${key}` : key;
keys.push(fullKey);
if (
obj[key] &&
typeof obj[key] === "object" &&
!Array.isArray(obj[key])
) {
keys.push(
...this.getNestedKeys(obj[key] as TranslationObject, fullKey),
);
}
}
return keys;
}
private getValueByPath(
obj: TranslationObject,
path: string,
): TranslationValue | undefined {
return path.split(".").reduce((acc, part) => {
if (acc && typeof acc === "object" && !Array.isArray(acc)) {
return (acc as TranslationObject)[part];
}
return undefined;
}, obj);
}
private getAllKeys(dirPath: string): KeyData {
const keys: Set = new Set();
const keysByFile: Map = new Map();
const files = fs.readdirSync(dirPath);
files.forEach((file: string) => {
if (file.endsWith(".json")) {
const filePath = path.join(dirPath, file);
const content = this.safeParseJson(filePath);
const fileKeys = this.getNestedKeys(content);
if (fileKeys.length === 0) {
throw new LocaleError(
`No translation keys found in file: ${filePath}`,
);
}
fileKeys.forEach((key) => {
if (!key.match(/^[a-zA-Z0-9_.-]+$/)) {
throw new LocaleError(
`Invalid key format found: "${key}" in file: ${filePath}. Keys should only contain letters, numbers, underscores, dots, and hyphens.`,
);
}
keys.add(key);
});
keysByFile.set(file, fileKeys);
}
});
return { keys, keysByFile };
}
private compareLanguageKeys(): void {
const enData = this.getAllKeys(this.enPath);
const trData = this.getAllKeys(this.trPath);
const enKeys = Array.from(enData.keys);
const trKeys = Array.from(trData.keys);
const missingInTr = enKeys.filter((key) => !trKeys.includes(key));
const missingInEn = trKeys.filter((key) => !enKeys.includes(key));
const typeMismatches: {
key: string;
enType: string;
trType: string;
enFile: string;
trFile: string;
}[] = [];
enKeys.forEach((key) => {
if (trKeys.includes(key)) {
for (const [enFile, enFileKeys] of enData.keysByFile.entries()) {
if (enFileKeys.includes(key)) {
const enContent = this.safeParseJson(
path.join(this.enPath, enFile),
);
const trContent = this.safeParseJson(
path.join(this.trPath, enFile),
);
const enValue = this.getValueByPath(enContent, key);
const trValue = this.getValueByPath(trContent, key);
if (enValue !== undefined && trValue !== undefined) {
const enType = Array.isArray(enValue) ? "array" : typeof enValue;
const trType = Array.isArray(trValue) ? "array" : typeof trValue;
if (enType !== trType) {
typeMismatches.push({
key,
enType,
trType,
enFile,
trFile: enFile,
});
}
}
}
}
}
});
if (
missingInTr.length > 0 ||
missingInEn.length > 0 ||
typeMismatches.length > 0
) {
const details: {
missingInTr?: string[];
missingInEn?: string[];
typeMismatches?: Array<{
key: string;
enType: string;
trType: string;
enFile: string;
trFile: string;
}>;
} = {};
let errorMessage = "Translation issues found:\n";
if (missingInTr.length > 0) {
errorMessage += "\nKeys missing in Turkish translations:\n";
details.missingInTr = [];
missingInTr.forEach((key) => {
for (const [file, keys] of enData.keysByFile.entries()) {
if (keys.includes(key)) {
const detail = `"${key}" (en/${file})`;
errorMessage += `- ${detail}\n`;
details.missingInTr!.push(detail);
}
}
});
}
if (missingInEn.length > 0) {
errorMessage += "\nKeys missing in English translations:\n";
details.missingInEn = [];
missingInEn.forEach((key) => {
for (const [file, keys] of trData.keysByFile.entries()) {
if (keys.includes(key)) {
const detail = `"${key}" (tr/${file})`;
errorMessage += `- ${detail}\n`;
details.missingInEn!.push(detail);
}
}
});
}
if (typeMismatches.length > 0) {
errorMessage += "\nType mismatches between translations:\n";
details.typeMismatches = typeMismatches;
typeMismatches.forEach(({ key, enType, trType, enFile, trFile }) => {
const detail = `"${key}" has different types: ${enType} (en/${enFile}) vs ${trType} (tr/${trFile})`;
errorMessage += `- ${detail}\n`;
});
}
throw new LocaleError(errorMessage, details);
}
}
private combineJsonFiles(dirPath: string): Record {
const combined: Record = {};
const files = fs.readdirSync(dirPath);
files.forEach((file: string) => {
if (file.endsWith(".json")) {
const filePath = path.join(dirPath, file);
const content = this.safeParseJson(filePath);
const namespace = file.replace(".json", "");
combined[namespace] = content;
}
});
return combined;
}
private ensureCombinedDirExists(): void {
if (!fs.existsSync(this.combinedPath)) {
try {
fs.mkdirSync(this.combinedPath, { recursive: true });
} catch (error) {
throw new LocaleError(
`Failed to create combined directory: ${this.combinedPath}`,
{
originalError:
error instanceof Error ? error.message : "Unknown error",
},
);
}
}
}
private safeWriteFile(filePath: string, content: string): void {
try {
fs.writeFileSync(filePath, content, { encoding: "utf8" });
} catch (error) {
throw new LocaleError(`Failed to write file: ${filePath}`, {
originalError: error instanceof Error ? error.message : "Unknown error",
});
}
}
private async processDictionaries(): Promise {
if (this.isProcessing) {
return;
}
this.isProcessing = true;
try {
// Validate directories
this.validateDirectory(this.enPath, "English");
this.validateDirectory(this.trPath, "Turkish");
// Ensure combined directory exists
this.ensureCombinedDirExists();
// Compare keys between languages
this.compareLanguageKeys();
// Combine translations
const enCombined = this.combineJsonFiles(this.enPath);
const trCombined = this.combineJsonFiles(this.trPath);
// Write combined files
this.safeWriteFile(
path.join(this.combinedPath, "en.json"),
JSON.stringify(enCombined, null, 2),
);
this.safeWriteFile(
path.join(this.combinedPath, "tr.json"),
JSON.stringify(trCombined, null, 2),
);
this.emit("success", "Translations processed successfully");
} catch (error) {
if (error instanceof LocaleError) {
this.emit("error", { message: error.message, details: error.details });
} else if (error instanceof Error) {
this.emit("error", { message: error.message });
} else {
this.emit("error", { message: "An unknown error occurred" });
}
} finally {
this.isProcessing = false;
}
}
public startWatching(): void {
if (this.watchers.length > 0) {
return;
}
try {
const watchOptions = { persistent: true, encoding: "utf8" as const };
// Watch English translations directory
const enWatcher = fs.watch(
this.enPath,
watchOptions,
this.handleFileChange.bind(this),
);
this.watchers.push(enWatcher);
// Watch Turkish translations directory
const trWatcher = fs.watch(
this.trPath,
watchOptions,
this.handleFileChange.bind(this),
);
this.watchers.push(trWatcher);
this.emit("info", "Started watching translation files");
this.processDictionaries(); // Initial processing
} catch (error) {
this.emit("error", {
message: "Failed to start file watchers",
details: error instanceof Error ? error.message : "Unknown error",
});
}
}
private handleFileChange(eventType: string, filename: string | null): void {
if (!filename || !filename.endsWith(".json")) {
return;
}
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout);
}
// Debounce file changes to prevent multiple rapid processing
this.debounceTimeout = setTimeout(() => {
this.processDictionaries();
}, 300);
}
public stopWatching(): void {
if (this.watchers.length > 0) {
this.watchers.forEach((watcher) => watcher.close());
this.watchers = [];
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = null;
}
this.emit("info", "Stopped watching translation files");
}
}
}
// Create and start the translation manager
const manager = new TranslationManager(__dirname);
manager
.on("success", (message) => {
console.log("✅", message);
})
.on("error", (error: TranslationError) => {
console.error(`${new Date().toISOString()}❌ Error:`, error.message);
if (error.details) {
console.error("Details:", JSON.stringify(error.details, null, 2));
}
})
.on("info", (message) => {
console.log("ℹ️", message);
});
manager.startWatching();
```
### [Running Scripts in Linux](https://sametcc.me/gist/running-scripts-in-linux)
---
title: "Running Scripts in Linux"
publishedAt: "2024-08-25"
summary:
"Essential guide to running scripts in Linux including permissions, execution
methods, and best practices for shell scripting."
tags: [Linux, Shell Scripting, Bash, Permissions, Automation]
language: "en"
type: "gist"
status: "published"
---
# Running Scripts in Linux
Scripts in Linux need proper permissions and execution settings to run
correctly. Here's how to run scripts in Linux:
- Navigate to the scripts directory.
- Add execute permissions to the script using: `chmod +x script_name.sh`.
- Run the script using: `./script_name.sh`.
## Additional Tips
1. Using `chmod`
- `chmod +x` adds execution permission
- `chmod 755` sets read, write, execute for owner and read, execute for
others
2. Alternative Run Methods
- Using bash directly: `bash script_name.sh`
- Using sh: `sh script_name.sh`
3. Best Practices
- Always verify script contents before executing
- Use appropriate shebang line (`#!/bin/bash`)
- Test scripts in a safe environment first
### [PowerShell Scripting](https://sametcc.me/gist/powershell-scripting)
---
title: "PowerShell Scripting"
publishedAt: "2024-08-25"
summary:
"Complete PowerShell scripting guide covering automation, DevOps practices,
Azure integration, and advanced techniques for cross-platform environments."
tags: [PowerShell, Scripting, Automation, DevOps, Azure]
language: "en"
type: "gist"
status: "published"
---
# PowerShell Scripting
## Introduction
PowerShell is a powerful task automation and configuration management framework
from Microsoft, consisting of a command-line shell and associated scripting
language. Initially built on .NET Framework, and now cross-platform with
PowerShell Core (built on .NET Core), it provides robust capabilities for system
administrators and developers to automate tasks across Windows, macOS, and Linux
environments.
This guide covers PowerShell fundamentals, advanced techniques, best practices,
and real-world applications to help you leverage its full potential in your
DevOps workflows.
## Table of Contents
1. [PowerShell Basics](#powershell-basics)
2. [Script Structure and Syntax](#script-structure-and-syntax)
3. [Variables and Data Types](#variables-and-data-types)
4. [Flow Control](#flow-control)
5. [Functions and Modules](#functions-and-modules)
6. [Error Handling](#error-handling)
7. [Working with Files and Folders](#working-with-files-and-folders)
8. [Network Operations](#network-operations)
9. [Working with APIs](#working-with-apis)
10. [PowerShell and Azure](#powershell-and-azure)
11. [PowerShell in DevOps](#powershell-in-devops)
12. [Security Best Practices](#security-best-practices)
13. [Performance Optimization](#performance-optimization)
14. [Common Use Cases](#common-use-cases)
15. [Resources](#resources)
## PowerShell Basics
### PowerShell Versions
PowerShell has evolved significantly over time:
- **Windows PowerShell 1.0-5.1**: Built on .NET Framework, Windows-only
- **PowerShell Core 6.x+**: Cross-platform, built on .NET Core
- **PowerShell 7+**: Modern, cross-platform version (current recommendation)
Check your PowerShell version with:
```powershell
$PSVersionTable
```
Example output:
```txt
Name Value
---- -----
PSVersion 7.3.0
PSEdition Core
GitCommitId 7.3.0
OS Microsoft Windows 10.0.19045
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
```
To install the latest PowerShell version:
```powershell
# On Windows using winget
winget install Microsoft.PowerShell
# On Windows using chocolatey
choco install powershell-core
# On macOS using Homebrew
brew install --cask powershell
# On Ubuntu Linux
sudo apt-get update
sudo apt-get install -y wget apt-transport-https software-properties-common
wget -q "https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/packages-microsoft-prod.deb"
sudo dpkg -i packages-microsoft-prod.deb
sudo apt-get update
sudo apt-get install -y powershell
```
### Command Types
PowerShell has several command types:
1. **Cmdlets**: Native PowerShell commands following verb-noun format (e.g.,
`Get-Process`)
2. **Functions**: Custom reusable code blocks
3. **Scripts**: Collections of commands saved as .ps1 files
4. **Aliases**: Shortcuts for commands (e.g., `dir` is an alias for
`Get-ChildItem`)
Discovering commands:
```powershell
# List all commands
Get-Command
# Find commands with specific noun
Get-Command -Noun Process
# Find commands with specific verb
Get-Command -Verb Get
# Get all aliases
Get-Alias
# Find help on how to use a command
Get-Help Get-Process -Detailed
Get-Help Get-Process -Examples
Get-Help Get-Process -Online # Opens browser documentation
```
### Basic Command Structure
PowerShell cmdlets follow a verb-noun naming convention:
```powershell
Verb-Noun -Parameter Value
```
Common verbs include `Get`, `Set`, `New`, `Remove`, `Start`, `Stop`, etc.
Examples of common cmdlets:
```powershell
# List running processes
Get-Process
# List specific processes
Get-Process -Name chrome, firefox
# Get process by ID
Get-Process -Id 1234
# Get services
Get-Service
# Start/stop a service
Start-Service -Name Spooler
Stop-Service -Name Spooler
# Get event logs
Get-EventLog -LogName System -Newest 10
# Get system information
Get-ComputerInfo
# List environment variables
Get-ChildItem Env:
$env:USERNAME # Access specific environment variable
```
### Execution Policy
PowerShell's execution policy determines which scripts can run:
```powershell
# View the current execution policy
Get-ExecutionPolicy
# View execution policy for all scopes
Get-ExecutionPolicy -List
# Set execution policy (run as administrator)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
# Set execution policy for current user only
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Bypass execution policy for a single script execution
PowerShell -ExecutionPolicy Bypass -File "C:\Scripts\MyScript.ps1"
```
Common policies:
- **Restricted**: No scripts can run (default)
- **AllSigned**: Only signed scripts can run
- **RemoteSigned**: Local scripts can run; downloaded scripts need signing
- **Unrestricted**: All scripts can run (not recommended for production)
- **Bypass**: No restrictions; nothing is blocked and no warnings (use with
caution)
### PowerShell Profiles
PowerShell profiles allow you to customize your PowerShell environment by
loading settings, functions, and aliases whenever you start PowerShell:
```powershell
# Check if you have a profile
Test-Path $PROFILE
# Create a profile if it doesn't exist
if (!(Test-Path $PROFILE)) {
New-Item -Type File -Path $PROFILE -Force
}
# Edit your profile
notepad $PROFILE
# or
code $PROFILE # With VS Code
# Example profile content
# Add this to your profile file:
function prompt {
$currentDir = $executionContext.SessionState.Path.CurrentLocation.Path
"PS [$env:COMPUTERNAME] $currentDir> "
}
# Create custom aliases
Set-Alias -Name np -Value notepad
```
Profile locations:
- Current user, current host: `$PROFILE`
- Current user, all hosts: `$PROFILE.CurrentUserAllHosts`
- All users, current host: `$PROFILE.AllUsersCurrentHost`
- All users, all hosts: `$PROFILE.AllUsersAllHosts`
## Script Structure and Syntax
### Basic Script Structure
PowerShell scripts use the `.ps1` extension. Here's a more comprehensive example
of a well-structured script:
```powershell
<#
.SYNOPSIS
Brief description of what the script does.
.DESCRIPTION
Detailed description of the script's functionality.
.PARAMETER ComputerName
The name of the computer to query.
.PARAMETER OutputPath
The path where the report will be saved.
.EXAMPLE
.\Get-SystemReport.ps1 -ComputerName "Server01" -OutputPath "C:\Reports"
.NOTES
Author: Your Name
Date: April 13, 2025
Version: 1.0
#>
#Requires -Version 7.0
#Requires -Modules ActiveDirectory, SqlServer
#Requires -RunAsAdministrator
param (
[Parameter(Mandatory=$true, Position=0)]
[string]$ComputerName,
[Parameter(Mandatory=$false)]
[string]$OutputPath = ".\Reports",
[switch]$IncludeServices
)
# Script initialization
$ErrorActionPreference = "Stop"
$VerbosePreference = "Continue"
# Import required modules
Import-Module ActiveDirectory -ErrorAction Stop
# Define functions
function Get-ComputerInfo {
[CmdletBinding()]
param (
[string]$Name
)
Write-Verbose "Querying system information for $Name"
return Get-CimInstance -ComputerName $Name -ClassName Win32_ComputerSystem
}
function Write-Report {
[CmdletBinding()]
param (
[object]$Data,
[string]$Path
)
if (!(Test-Path -Path $Path)) {
New-Item -Path $Path -ItemType Directory -Force | Out-Null
}
$reportPath = Join-Path -Path $Path -ChildPath "$($Data.Name)_Report.json"
$Data | ConvertTo-Json -Depth 5 | Out-File -FilePath $reportPath
return $reportPath
}
# Main script execution
try {
Write-Verbose "Script started at $(Get-Date)"
# Verify computer is reachable
if (!(Test-Connection -ComputerName $ComputerName -Count 1 -Quiet)) {
throw "Computer $ComputerName is not reachable."
}
# Get computer information
$systemInfo = Get-ComputerInfo -Name $ComputerName
# Add services if requested
if ($IncludeServices) {
Write-Verbose "Including services information"
$services = Get-Service -ComputerName $ComputerName
$systemInfo | Add-Member -MemberType NoteProperty -Name Services -Value $services
}
# Generate report
$reportFile = Write-Report -Data $systemInfo -Path $OutputPath
Write-Output "Report generated successfully at $reportFile"
}
catch {
Write-Error "An error occurred: $_"
exit 1
}
finally {
Write-Verbose "Script completed at $(Get-Date)"
}
```
### Advanced Script Header Comments
PowerShell supports special comment-based help that VS Code and PowerShell ISE
can recognize:
```powershell
<#
.SYNOPSIS
Short description of the script's purpose.
.DESCRIPTION
Detailed explanation of what the script does and how it works.
.PARAMETER ParameterName
Description of a parameter.
.EXAMPLE
PS> .\MyScript.ps1 -Parameter1 "Value"
Example description of what happens.
.EXAMPLE
PS> .\MyScript.ps1 -Parameter1 "Value" -Switch
Another example with different parameters.
.INPUTS
Description of input objects if your script accepts pipeline input.
.OUTPUTS
Description of objects that the script returns.
.NOTES
Additional information about the script.
.LINK
https://related-documentation-url.com
#>
```
### Parameter Declarations with Validation
PowerShell allows for sophisticated parameter validation:
```powershell
param (
[Parameter(Mandatory=$true,
Position=0,
HelpMessage="Enter the server name:")]
[ValidateNotNullOrEmpty()]
[string]$ServerName,
[Parameter(Mandatory=$false)]
[ValidateSet("Development", "Testing", "Production")]
[string]$Environment = "Development",
[Parameter(Mandatory=$false)]
[ValidateRange(1, 100)]
[int]$MaxItems = 25,
[Parameter(Mandatory=$false)]
[ValidatePattern("[a-zA-Z][a-zA-Z0-9]{5,10}")]
[string]$UserName,
[Parameter(Mandatory=$false)]
[ValidateScript({Test-Path $_ -PathType Container})]
[string]$OutputFolder = ".\Output",
[switch]$Force
)
```
### Here-Strings for Multi-line Text
PowerShell provides "here-strings" for multi-line text content:
```powershell
# Basic here-string with variable substitution
$name = "John"
$message = @"
Hello, $name!
This is a multi-line message
that preserves all whitespace and line breaks.
Today is $(Get-Date -Format "yyyy-MM-dd").
"@
# Single-quoted here-string without variable substitution
$sql = @'
SELECT *
FROM Customers
WHERE Region = 'North'
AND Status = 'Active';
'@
```
### Pipeline Techniques and Examples
The pipeline is one of PowerShell's most powerful features:
```powershell
# Basic pipeline example
Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 | Format-Table Name, CPU, WorkingSet
# Pipeline with calculated properties
Get-ChildItem -Path C:\Windows -Filter *.exe -Recurse -ErrorAction SilentlyContinue |
Select-Object -Property Name,
@{Name="SizeKB"; Expression={[math]::Round($_.Length/1KB, 2)}},
LastWriteTime |
Sort-Object -Property SizeKB -Descending |
Select-Object -First 10
# Pipeline with filtering and grouping
Get-Service |
Where-Object {$_.Status -eq "Running"} |
Group-Object -Property StartType |
Select-Object Name, Count
# Processing each item in the pipeline
Get-ChildItem -Path C:\Logs -Filter *.log |
ForEach-Object {
$content = Get-Content -Path $_.FullName
$errorCount = ($content | Select-String -Pattern "ERROR" -SimpleMatch).Count
$warningCount = ($content | Select-String -Pattern "WARNING" -SimpleMatch).Count
[PSCustomObject]@{
LogFile = $_.Name
ErrorCount = $errorCount
WarningCount = $warningCount
TotalLines = $content.Count
}
} |
Sort-Object -Property ErrorCount -Descending
```
### Using the Splatting Technique
Splatting is a technique for passing parameters to a command using a hashtable:
```powershell
# Traditional approach - long command line
Send-MailMessage -From "sender@example.com" -To "recipient@example.com" -Subject "Report" -Body "See attached report." -SmtpServer "smtp.example.com" -Port 587 -UseSsl -Credential $credential -Attachments "C:\Reports\Report.pdf"
# Using splatting - cleaner and more maintainable
$emailParams = @{
From = "sender@example.com"
To = "recipient@example.com"
Subject = "Report"
Body = "See attached report."
SmtpServer = "smtp.example.com"
Port = 587
UseSsl = $true
Credential = $credential
Attachments = "C:\Reports\Report.pdf"
}
Send-MailMessage @emailParams # Note the @ instead of $
```
### Script Flow Control with Break, Continue, and Return
```powershell
# Break and Continue example
foreach ($server in $servers) {
if ($server.Status -eq "Maintenance") {
Write-Warning "Server $($server.Name) is in maintenance mode. Skipping..."
continue # Skip to the next iteration
}
if ($server.Status -eq "Offline") {
Write-Error "Server $($server.Name) is offline. Stopping script."
break # Exit the loop completely
}
# Process server
Write-Output "Processing server $($server.Name)"
}
# Return example in a function
function Test-ServerConnection {
param (
[string]$ServerName
)
if (!(Test-Connection -ComputerName $ServerName -Count 1 -Quiet)) {
return $false # Exit the function with a false value
}
# Continue with other tests
$portTest = Test-NetConnection -ComputerName $ServerName -Port 3389 -WarningAction SilentlyContinue
return $portTest.TcpTestSucceeded # Return the result of the port test
}
```
### Using Requires Statements
Require statements help ensure script prerequisites are met:
```powershell
#Requires -Version 7.0 # Minimum PowerShell version
#Requires -Modules ActiveDirectory, Az # Required modules
#Requires -RunAsAdministrator # Must run as admin
#Requires -PSEdition Core # Must be PowerShell Core
```
## Variables and Data Types
### Variable Declaration
Variables in PowerShell start with `$`:
```powershell
$name = "PowerShell"
$age = 15
$isAwesome = $true
```
### Common Data Types
PowerShell variables can hold different data types:
```powershell
$string = "Hello" # String
$int = 42 # Integer
$double = 3.14 # Double
$bool = $true # Boolean
$array = 1, 2, 3, "four" # Array
$hash = @{Key1 = "Value1"; Key2 = 2} # Hashtable
$null = $null # Null value
```
### Arrays
```powershell
# Array creation
$array = @(1, 2, 3, 4, 5)
$array = 1..5 # Range operator
# Accessing elements
$firstElement = $array[0]
$lastElement = $array[-1]
# Adding elements
$array += 6
# Filtering arrays
$filtered = $array | Where-Object { $_ -gt 3 }
```
### Hashtables (Dictionaries)
```powershell
# Creating a hashtable
$user = @{
Name = "John Doe"
Age = 30
Role = "Developer"
}
# Accessing elements
$userName = $user["Name"]
$userAge = $user.Age
# Adding or updating elements
$user["Department"] = "IT"
$user.Location = "New York"
# Removing an element
$user.Remove("Age")
```
## Flow Control
### Conditional Statements
```powershell
# If-ElseIf-Else
if ($condition1) {
# Code block
}
elseif ($condition2) {
# Code block
}
else {
# Code block
}
# Switch statement
$value = "apple"
switch ($value) {
"apple" { "It's an apple" }
"orange" { "It's an orange" }
default { "Unknown fruit" }
}
# Switch with wildcards
switch -Wildcard ($filename) {
"*.txt" { "Text file" }
"*.jpg" { "Image file" }
default { "Other file type" }
}
```
### Loops
```powershell
# For loop
for ($i = 0; $i -lt 10; $i++) {
# Code block
}
# ForEach loop
foreach ($item in $collection) {
# Code block
}
# ForEach-Object in pipeline
$collection | ForEach-Object {
# Process $_ (current item)
}
# While loop
while ($condition) {
# Code block
}
# Do-While loop (executes at least once)
do {
# Code block
} while ($condition)
# Do-Until loop
do {
# Code block
} until ($condition)
```
## Functions and Modules
### Basic Functions
```powershell
function Get-FullName {
param (
[string]$FirstName,
[string]$LastName
)
return "$FirstName $LastName"
}
$fullName = Get-FullName -FirstName "John" -LastName "Doe"
```
### Advanced Functions
```powershell
function Get-SystemInfo {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0)]
[string]$ComputerName,
[Parameter(Mandatory=$false)]
[switch]$IncludeServices,
[ValidateSet("Basic", "Detailed", "Full")]
[string]$Level = "Basic"
)
begin {
# Initialization code
}
process {
# Main processing
$systemInfo = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_OperatingSystem
if ($IncludeServices) {
$services = Get-Service -ComputerName $ComputerName
}
# Return based on level
switch ($Level) {
"Basic" { return $systemInfo | Select-Object Caption, Version }
"Detailed" { return $systemInfo }
"Full" { return @{ OS = $systemInfo; Services = $services } }
}
}
end {
# Cleanup code
}
}
```
### Modules
Modules are collections of related functions, cmdlets, variables, etc.:
```powershell
# Module structure (MyModule.psm1)
function Get-Something {
# Function code
}
function Set-Something {
# Function code
}
# Export only specific functions
Export-ModuleMember -Function Get-Something, Set-Something
```
Using modules:
```powershell
# Import a module
Import-Module -Name MyModule
# List available modules
Get-Module -ListAvailable
# Find commands in a module
Get-Command -Module MyModule
```
## Error Handling
### Try-Catch-Finally
```powershell
try {
# Code that might cause an error
$result = 10 / 0
}
catch [System.DivideByZeroException] {
# Handle specific exception
Write-Error "Division by zero error"
}
catch {
# Handle any other exception
Write-Error "An error occurred: $_"
}
finally {
# Code that always runs
Write-Output "Cleanup operations"
}
```
### Error Preference Variables
```powershell
# Set behavior for non-terminating errors
$ErrorActionPreference = "Stop" # Options: Continue, SilentlyContinue, Stop, Inquire
# Use -ErrorAction parameter for individual commands
Get-Content -Path "NonExistentFile.txt" -ErrorAction SilentlyContinue
```
## Working with Files and Folders
### File Operations
```powershell
# Read file content
$content = Get-Content -Path "C:\path\to\file.txt"
# Write to a file
"Content" | Out-File -FilePath "C:\path\to\output.txt"
"Appended content" | Add-Content -FilePath "C:\path\to\output.txt"
# Test if file exists
if (Test-Path -Path "C:\path\to\file.txt") {
# File exists
}
# Copy files
Copy-Item -Path "C:\source\file.txt" -Destination "C:\destination\"
# Move files
Move-Item -Path "C:\source\file.txt" -Destination "C:\destination\"
# Delete files
Remove-Item -Path "C:\path\to\file.txt"
```
### Folder Operations
```powershell
# Create a new directory
New-Item -Path "C:\path\to\new\folder" -ItemType Directory
# List directory contents
Get-ChildItem -Path "C:\path" -Recurse
# Filter files by extension
Get-ChildItem -Path "C:\path" -Filter "*.txt"
```
### Working with CSV/JSON/XML
```powershell
# CSV
$csvData = Import-Csv -Path "data.csv"
$objects | Export-Csv -Path "output.csv" -NoTypeInformation
# JSON
$jsonData = Get-Content -Path "data.json" | ConvertFrom-Json
$objects | ConvertTo-Json | Out-File -FilePath "output.json"
# XML
[xml]$xmlData = Get-Content -Path "data.xml"
$objects | Export-Clixml -Path "output.xml"
```
## Network Operations
### Basic Network Commands
```powershell
# Test network connectivity
Test-NetConnection -ComputerName "www.example.com" -Port 443
# Get IP configuration
Get-NetIPConfiguration
# DNS resolution
Resolve-DnsName -Name "www.example.com"
# TCP port test
Test-NetConnection -ComputerName "server" -Port 80 -InformationLevel Detailed
```
### Web Requests
```powershell
# GET request
$response = Invoke-WebRequest -Uri "https://api.example.com/data"
$responseContent = $response.Content
# POST request with JSON body
$body = @{
name = "John Doe"
email = "john@example.com"
} | ConvertTo-Json
$response = Invoke-WebRequest -Uri "https://api.example.com/users" -Method Post -Body $body -ContentType "application/json"
# REST API calls
$params = @{
Uri = "https://api.example.com/users"
Method = "POST"
Headers = @{ Authorization = "Bearer $token" }
ContentType = "application/json"
Body = $body
}
$response = Invoke-RestMethod @params
```
## Working with APIs
### REST API Example
```powershell
# Function to interact with REST API
function Invoke-ApiRequest {
[CmdletBinding()]
param (
[string]$Endpoint,
[string]$Method = "GET",
[hashtable]$Headers = @{},
[object]$Body = $null
)
$baseUrl = "https://api.example.com/v1"
$uri = "$baseUrl/$Endpoint"
$params = @{
Uri = $uri
Method = $Method
Headers = $Headers
ContentType = "application/json"
}
if ($Body -and $Method -ne "GET") {
$params.Body = ($Body | ConvertTo-Json -Depth 10)
}
try {
$response = Invoke-RestMethod @params
return $response
}
catch {
Write-Error "API error: $_"
throw
}
}
# Usage
$token = "YOUR_API_TOKEN"
$headers = @{
"Authorization" = "Bearer $token"
}
# Get users
$users = Invoke-ApiRequest -Endpoint "users" -Headers $headers
# Create user
$newUser = @{
name = "Jane Smith"
email = "jane@example.com"
role = "admin"
}
$createdUser = Invoke-ApiRequest -Endpoint "users" -Method "POST" -Headers $headers -Body $newUser
```
## PowerShell and Azure
### Azure PowerShell Module
```powershell
# Install Azure PowerShell module
Install-Module -Name Az -AllowClobber -Force
# Connect to Azure
Connect-AzAccount
# Select subscription
Set-AzContext -SubscriptionId "subscription-id"
# Common Azure operations
$resourceGroup = "MyResourceGroup"
$location = "eastus"
# Create a resource group
New-AzResourceGroup -Name $resourceGroup -Location $location
# Deploy a virtual machine
New-AzVM -ResourceGroupName $resourceGroup -Name "myVM" -Location $location -Image "UbuntuLTS"
# List resources
Get-AzResource -ResourceGroupName $resourceGroup
```
### Azure Automation
```powershell
# Azure Automation runbook example
param (
[Parameter(Mandatory=$true)]
[string]$ResourceGroupName
)
# Connect to Azure with managed identity
Connect-AzAccount -Identity
# Start all stopped VMs in a resource group
$vms = Get-AzVM -ResourceGroupName $ResourceGroupName -Status
foreach ($vm in $vms) {
if ($vm.PowerState -eq "VM deallocated") {
Write-Output "Starting VM: $($vm.Name)"
Start-AzVM -ResourceGroupName $ResourceGroupName -Name $vm.Name
}
}
```
## PowerShell in DevOps
### CI/CD Integration
```powershell
# Example: Deployment script for a web application
param (
[string]$Environment = "dev",
[string]$Version
)
# Configuration for different environments
$config = @{
dev = @{
ServerPath = "\\devserver\sites\"
AppPoolName = "DevAppPool"
}
staging = @{
ServerPath = "\\stagingserver\sites\"
AppPoolName = "StagingAppPool"
}
prod = @{
ServerPath = "\\prodserver\sites\"
AppPoolName = "ProdAppPool"
}
}
# Environment-specific settings
$envConfig = $config[$Environment]
$deployPath = Join-Path -Path $envConfig.ServerPath -ChildPath "MyApp"
# Stop the application pool
Write-Output "Stopping application pool: $($envConfig.AppPoolName)"
Invoke-Command -ComputerName "webserver" -ScriptBlock {
param($appPoolName)
Import-Module WebAdministration
Stop-WebAppPool -Name $appPoolName
} -ArgumentList $envConfig.AppPoolName
# Deploy the application
Write-Output "Deploying version $Version to $Environment environment"
$sourcePath = ".\build\$Version\*"
Copy-Item -Path $sourcePath -Destination $deployPath -Recurse -Force
# Start the application pool
Write-Output "Starting application pool: $($envConfig.AppPoolName)"
Invoke-Command -ComputerName "webserver" -ScriptBlock {
param($appPoolName)
Import-Module WebAdministration
Start-WebAppPool -Name $appPoolName
} -ArgumentList $envConfig.AppPoolName
Write-Output "Deployment complete"
```
### Infrastructure as Code
```powershell
# Example: Creating a testing environment with PowerShell
function New-TestEnvironment {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$ProjectName,
[Parameter(Mandatory=$true)]
[string]$BuildNumber
)
# Create resource group
$resourceGroupName = "$ProjectName-Test-$BuildNumber"
$deploymentName = "Deployment-$BuildNumber"
$location = "eastus"
New-AzResourceGroup -Name $resourceGroupName -Location $location -Force
# Deploy ARM template
$templateFile = ".\infrastructure\template.json"
$templateParameters = @{
projectName = $ProjectName
environment = "test"
buildNumber = $BuildNumber
}
$deployment = New-AzResourceGroupDeployment -Name $deploymentName `
-ResourceGroupName $resourceGroupName `
-TemplateFile $templateFile `
-TemplateParameterObject $templateParameters
# Return environment information
return @{
ResourceGroup = $resourceGroupName
Deployment = $deployment
Endpoints = @{
WebApp = $deployment.Outputs.webAppUrl.Value
API = $deployment.Outputs.apiUrl.Value
}
}
}
# Usage
$env = New-TestEnvironment -ProjectName "MyProject" -BuildNumber "20250413.1"
```
## Security Best Practices
### Secure Credential Handling
```powershell
# Never store credentials in plain text in scripts
# Use encrypted credentials
$securePassword = ConvertTo-SecureString "PlainTextPassword" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential("username", $securePassword)
# Better: Store encrypted credentials in a file (Windows only)
$credential = Get-Credential
$credential | Export-CliXml -Path "C:\secure\credentials.xml"
# Later, retrieve the credentials
$credential = Import-CliXml -Path "C:\secure\credentials.xml"
# For automation, use managed identities or key vaults
```
### Script Signing
```powershell
# Create a self-signed certificate for testing
$cert = New-SelfSignedCertificate -Subject "CN=PowerShell Code Signing" -Type CodeSigningCert -CertStoreLocation "Cert:\CurrentUser\My"
# Sign a script
Set-AuthenticodeSignature -FilePath ".\MyScript.ps1" -Certificate $cert
# In production, use certificates from a trusted certification authority
```
### Permission Management
```powershell
# Use Just Enough Administration (JEA)
# Example: Create a JEA configuration file
# PSSC file (session configuration)
New-PSSessionConfigurationFile -Path ".\JEAConfig.pssc" `
-SessionType RestrictedRemoteServer `
-VisibleCmdlets "Get-Service", "Restart-Service" `
-VisibleFunctions "Get-SystemInfo" `
-LanguageMode NoLanguage
# Register the configuration
Register-PSSessionConfiguration -Path ".\JEAConfig.pssc" `
-Name "MaintenanceSession" `
-Force
```
## Performance Optimization
### Efficient Coding Practices
```powershell
# Bad: Slow string concatenation in a loop
$result = ""
foreach ($item in 1..10000) {
$result += $item.ToString() + ","
}
# Good: Use StringBuilder for string operations
$sb = New-Object System.Text.StringBuilder
foreach ($item in 1..10000) {
[void]$sb.Append("$item,")
}
$result = $sb.ToString()
# Bad: Filtering objects in the pipeline multiple times
Get-Process | Where-Object { $_.CPU -gt 100 } | Where-Object { $_.Name -like "S*" }
# Good: Use a single Where-Object with compound conditions
Get-Process | Where-Object { $_.CPU -gt 100 -and $_.Name -like "S*" }
# Use Jobs for parallel processing
$jobs = 1..10 | ForEach-Object {
$server = "Server$_"
Start-Job -ScriptBlock {
param($serverName)
Get-WmiObject Win32_OperatingSystem -ComputerName $serverName
} -ArgumentList $server
}
Wait-Job $jobs
$results = Receive-Job $jobs
# PowerShell 7+: Use parallel foreach
$results = 1..10 | ForEach-Object -Parallel {
$server = "Server$_"
Get-WmiObject Win32_OperatingSystem -ComputerName $server
} -ThrottleLimit 5
```
## Common Use Cases
### System Administration
```powershell
# Get system information
function Get-DetailedSystemInfo {
[CmdletBinding()]
param (
[string]$ComputerName = $env:COMPUTERNAME
)
$os = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_OperatingSystem
$cs = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_ComputerSystem
$proc = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_Processor
$disk = Get-CimInstance -ComputerName $ComputerName -ClassName Win32_LogicalDisk -Filter "DriveType=3"
[PSCustomObject]@{
ComputerName = $ComputerName
OSName = $os.Caption
OSVersion = $os.Version
Manufacturer = $cs.Manufacturer
Model = $cs.Model
Processor = $proc.Name
PhysicalMemoryGB = [math]::Round($cs.TotalPhysicalMemory / 1GB, 2)
Disks = $disk | ForEach-Object {
[PSCustomObject]@{
Drive = $_.DeviceID
SizeGB = [math]::Round($_.Size / 1GB, 2)
FreeSpaceGB = [math]::Round($_.FreeSpace / 1GB, 2)
PercentFree = [math]::Round(($_.FreeSpace / $_.Size) * 100, 2)
}
}
}
}
```
### Automation Examples
#### User Account Management
```powershell
# Bulk user creation
Import-Csv ".\users.csv" | ForEach-Object {
$securePassword = ConvertTo-SecureString $_.InitialPassword -AsPlainText -Force
$params = @{
Name = $_.Username
GivenName = $_.FirstName
Surname = $_.LastName
SamAccountName = $_.Username
UserPrincipalName = "$($_.Username)@domain.com"
AccountPassword = $securePassword
Enabled = $true
Path = "OU=$($_.Department),DC=domain,DC=com"
ChangePasswordAtLogon = $true
}
New-ADUser @params
}
```
#### System Monitoring
```powershell
# Monitor services and restart if necessary
$servicesToMonitor = @("MSSQLSERVER", "W3SVC", "BITS")
foreach ($service in $servicesToMonitor) {
$serviceStatus = Get-Service -Name $service -ErrorAction SilentlyContinue
if ($serviceStatus -and $serviceStatus.Status -ne "Running") {
Write-Output "$(Get-Date) - Service $service is not running. Attempting to start..."
try {
Start-Service -Name $service
Write-Output "$(Get-Date) - Service $service started successfully."
}
catch {
Write-Error "$(Get-Date) - Failed to start service $service. Error: $_"
Send-MailMessage -To "admin@example.com" -From "monitor@example.com" -Subject "Service Failure: $service" -Body "The service $service failed to start. Error: $_" -SmtpServer "smtp.example.com"
}
}
}
```
## Resources
### Official Documentation and Learning Resources
- [PowerShell Documentation](https://docs.microsoft.com/en-us/powershell/)
- [PowerShell GitHub Repository](https://github.com/PowerShell/PowerShell)
- [PowerShell Gallery](https://www.powershellgallery.com/)
### Community Resources
- [PowerShell.org](https://powershell.org/)
- [r/PowerShell subreddit](https://www.reddit.com/r/PowerShell/)
- [Stack Overflow PowerShell tag](https://stackoverflow.com/questions/tagged/powershell)
### Books and Courses
- "PowerShell in a Month of Lunches" by Don Jones and Jeffrey Hicks
- "PowerShell for Sysadmins" by Adam Bertram
- "Learn PowerShell in Y Minutes" - Quick reference guide
## Conclusion
PowerShell is a versatile and powerful tool for automation, system
administration, and DevOps workflows. By mastering PowerShell, you can
significantly improve your productivity, create consistent and reliable
processes, and effectively manage complex IT environments.
This guide covers the fundamentals, but PowerShell's capabilities extend far
beyond what's documented here. As you continue to work with PowerShell, you'll
discover innovative ways to solve problems and automate tasks across your
infrastructure.
### [Electron Widgets](https://sametcc.me/project/electron-widgets)
---
title: Electron Widgets
publishedAt: "2024-03-25"
summary: A desktop application built with Electron.js and Node.js that allows users to
create and manage customizable widgets on their desktops.
tags: [Electron.js, Node.js, Desktop Application, Widgets, Cross-Platform]
language: "en"
type: "project"
status: "published"
---
# Electron Widgets
A desktop application built with Electron.js and Node.js that allows users to
create and manage customizable widgets on their desktops.
[GitHub Repository](https://github.com/sametcn99/electron-widgets)
[Homepage](https://electron-widgets.vercel.app/)
## Project Overview
This project is a desktop application developed using Electron.js and Node.js.
The application allows users to create and manage widgets on their desktops.
These widgets enable users to quickly and easily access the information or tools
they need.
## Features
- **Widget Management**: This feature allows users to manage widgets on their
desktops effortlessly. They can create new widgets, edit existing ones, and
delete widgets they no longer need. This functionality provides users with the
flexibility to organize their desktops according to their preferences and
needs.
- **Customizable Widget Options**: The application offers a variety of widgets
such as clocks, weather forecasts, notes, calendars, to-do lists, and more.
Each widget comes with a range of customizable options, allowing users to
tailor them to their liking. For instance, users can choose different clock
formats, select specific locations for weather forecasts, customize the
appearance of notes, and set reminders on calendars. This level of
customization ensures that users can personalize their desktop experience
according to their unique requirements.
- **Ease of Use**: The user interface of the application is designed to be
intuitive and user-friendly. Even users with limited technical expertise can
navigate the application with ease. The interface features clear icons, simple
menu structures, and straightforward controls, making it easy for users to
understand and interact with the various functionalities offered by the
application. Whether users are creating new widgets, customizing existing
ones, or managing their desktop layout, they can do so without encountering
any unnecessary complexities.
- **Cross-Platform Compatibility**: Leveraging Electron.js technology, the
application is compatible with multiple operating systems, including Windows,
macOS, and Linux. This cross-platform compatibility ensures that users can
enjoy a consistent experience regardless of the operating system they use.
Whether they are working on a Windows PC, a macOS laptop, or a Linux desktop,
users can install and use the application without any compatibility issues.
This versatility makes the application accessible to a wide range of users
across different platforms, enhancing its usability and appeal.
- **Create Your Own Widgets and Contribute to the Project!**: Our project
encourages users to create their own widgets and contribute to the project. By
creating your own widgets, you can increase the diversity of our project and
contribute to the growth and strength of our community.
[See how](https://github.com/sametcn99/electron-widgets/wiki)
### [Resume Builder](https://sametcc.me/project/resume-builder)
---
title: Resume Builder
publishedAt: "2023-01-12"
summary: A desktop application developed with C# and Windows Forms that allows users to
create, manage, and export resumes using various templates, with features like
automatic saving, import/export, and multi-language support.
tags: [C#, Windows Forms, SQL Server, Resume, Desktop Application, Productivity, MAUI]
language: "en"
type: "project"
status: "published"
---
# Resume Builder
This project is a desktop application that allows users to create and manage
their resumes.
It is developed with C# and Windows Forms, and uses a SQL Server database to
store and retrieve data.

The application provides a user-friendly interface for entering personal
details, job history, education, skills, and other relevant information.
[Github Repository of Form Application Version](https://sametcc.me/repo/ResumeBuilder)
[Github Repository of MAUI Version](https://github.com/sametcn99/ResumeBuilderMAUI)
I also tried re creating this project as a MAUI app, but I couldn't finish it.
## Features
- Save multiple resumes for one person and edit anytime.
- Save your resume using different templates.
- Automatic saving feature.
- Import and export all created resumes.
- Print as PDF or MS Word (docx) file.
- You can add: Name, Address, Phone Number, Email, Website, Social Media Links,
Summary, Job Information, Education Information, Skills, Languages,
Certifications, and Photo to your resume.
- Edit titles in any language.
- Change font and picture sizes.
- English and Turkish UI.
## Built With
- Visual Studio 2022
- SQL Express 2022
- C# Windows Forms App .NET 7.0
- Quest PDF .NET Library
- SautinSoft Pdf Focus .NET Library
- Newtonsoft JSON.NET .NET Library
## GitHub Repository Snapshot
### [sametcn99](https://github.com/sametcn99/sametcn99)
- Language: TypeScript
- Stars: 1
- Forks: 0
- Archived: False
- Topics: handlebars, profile-readme, profile-readme-generator, readme, readme-generator
- Fork: False
- Last pushed: 2026-09-08
### [libredirect-mobile](https://github.com/sametcn99/libredirect-mobile)
LibRedirect Mobile is an Android URL routing app. It intercepts links to services such as YouTube, Reddit, or X/Twitter and redirects them to privacy-friendly frontends (Invidious, Redlib, Nitter-style alternatives, and similar) before opening them in the browser you choose.
- Language: Kotlin
- Stars: 1
- Forks: 0
- Archived: False
- Topics: kotlin, kotlin-android, kotlin-native, libredirect, privacy-frontend
- Fork: False
- Last pushed: 2026-08-24
### [vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer)
Transform your static Mermaid diagrams into interactive, dynamic visualizations in VitePress! This powerful plugin brings life to your documentation by enabling interactive features like zooming, panning, and fullscreen viewing.
- Language: TypeScript
- Stars: 67
- Forks: 6
- Archived: False
- Topics: mermaid, mermaidjs, vitepress, vitepress-mermaid-renderer, vitepress-plugin
- Fork: False
- Last pushed: 2026-09-05
### [apps](https://github.com/sametcn99/apps)
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-09-04
### [server](https://github.com/sametcn99/server)
A simple server for sending and receiving messages in real-time per WebSocket. (Includes a sleek web-ui)
- Language: Go
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-09-04
### [orhan-elektronik](https://github.com/sametcn99/orhan-elektronik)
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-09-02
### [my-stars-atlas](https://github.com/sametcn99/my-stars-atlas)
A generated catalog of starred GitHub repositories, grouped into stable categories.
- Language: TypeScript
- Stars: 5
- Forks: 1
- Archived: False
- Topics: awesome, awesome-list, awesome-list-generator, github-actions, github-api, github-pages, handlebars
- Fork: False
- Last pushed: 2026-08-15
### [computer-science-resources](https://github.com/sametcn99/computer-science-resources)
Curated collection of computer science learning resources, coding exercises, practice platforms, and online courses.
- Language: JavaScript
- Stars: 0
- Forks: 2
- Archived: False
- Topics: awesome-list, computer-science, cs101, vitepress
- Fork: False
- Last pushed: 2026-09-01
### [case-fx-tool](https://github.com/sametcn99/case-fx-tool)
- Language: Python
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-09-01
### [personal-website-blazor](https://github.com/sametcn99/personal-website-blazor)
- Language: MDX
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-08-28
### [libredirect-instances-list](https://github.com/sametcn99/libredirect-instances-list)
A client-side web app that fetches and displays LibRedirect alternative front-end instances directly in your browser.
- Language: JavaScript
- Stars: 2
- Forks: 0
- Archived: False
- Topics: alternative-frontend, alternative-frontends, libredirect
- Fork: False
- Last pushed: 2026-08-24
### [gotify-web-extension](https://github.com/sametcn99/gotify-web-extension)
A browser extension for monitoring a self-hosted Gotify server and receiving native desktop notifications.
- Language: TypeScript
- Stars: 1
- Forks: 0
- Archived: False
- Topics: browser-extension, extensionjs, gotify, gotify-client
- Fork: False
- Last pushed: 2026-08-21
### [scripts](https://github.com/sametcn99/scripts)
- Language: PowerShell
- Stars: 0
- Forks: 0
- Archived: False
- Topics: powershell, powershell-script, tui
- Fork: False
- Last pushed: 2026-08-20
### [HTWind](https://github.com/sametcn99/HTWind)
the missing html based widget manager created with .net
- Language: C#
- Stars: 8
- Forks: 0
- Archived: False
- Topics: dotnet, dotnet-10, fluentui, html-widget, htwind, htwind-widget, rainmeter, rainmeter-plugin, rainmeter-skin, widget-manager, windhawk, windhawk-mods, wpf-application, xaml, xaml-ui
- Fork: False
- Last pushed: 2026-07-14
### [linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs)
A browser tool that hides or highlights the job postings you have already viewed on LinkedIn — so you can focus on what is new.
- Language: TypeScript
- Stars: 5
- Forks: 0
- Archived: False
- Topics: browser-extension, browser-extensions, chrome-extension, extensionjs, firefox-extension, job-hunting, job-search, linkedin, linkedin-scraper, userscript, userscripts-for-browser
- Fork: False
- Last pushed: 2026-08-15
### [gh-block-spam-accounts](https://github.com/sametcn99/gh-block-spam-accounts)
A browser-only React application that helps you detect suspicious GitHub accounts in your followers/following graph, review detection reasons, and block or unblock accounts in a controlled queue.
- Language: TypeScript
- Stars: 0
- Forks: 1
- Archived: False
- Topics: github-actions, github-rest-api, octokit, octokit-rest, profile-management
- Fork: False
- Last pushed: 2026-08-15
### [electron-widgets](https://github.com/sametcn99/electron-widgets)
the missing html based widget manager
- Language: TypeScript
- Stars: 20
- Forks: 2
- Archived: False
- Topics: cross-platform, desktop-app, desktop-customization, electron, electron-forge, electron-vue, electron-widgets, electronjs, hacker-news, hacker-news-reader, hacker-news-widget, hacktoberfest, rainmeter, rss-reader, rss-widget, vue, vue3, vuejs, widget-manager
- Fork: False
- Last pushed: 2026-02-28
### [reddit-rss-api](https://github.com/sametcn99/reddit-rss-api)
This project appears to be a Deno-based server application that serves as an API for fetching Reddit posts from rss feed. It provides several endpoints to fetch posts from one or more subreddits.
- Language: TypeScript
- Stars: 3
- Forks: 1
- Archived: False
- Topics: api, deno, reddit, reddit-api, reddit-rss, rss, rss-feed
- Fork: False
- Last pushed: 2026-08-01
### [pdf-email-extractor](https://github.com/sametcn99/pdf-email-extractor)
- Language: HTML
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-08-11
### [n8n-automations](https://github.com/sametcn99/n8n-automations)
Workflow sources are generated with TypeScript and Bun. The n8n-workflow package provides the n8n workflow types; no Node.js runtime is required.
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: n8n, n8n-automation, n8n-template, n8n-workflow, n8n-workflows
- Fork: False
- Last pushed: 2026-08-07
### [code-nest-web](https://github.com/sametcn99/code-nest-web)
- Language: TypeScript
- Stars: 1
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2024-09-18
### [wibesoft-backend-case](https://github.com/sametcn99/wibesoft-backend-case)
WibeSoft Backend Case study
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: nest, nestjs, nestjs-backend
- Fork: False
- Last pushed: 2026-04-10
### [IsTakipSistemi](https://github.com/sametcn99/IsTakipSistemi)
- Language: C#
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-07-14
### [TakvimDemo](https://github.com/sametcn99/TakvimDemo)
- Language: HTML
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-07-17
### [maalesef-tr](https://github.com/sametcn99/maalesef-tr)
maalesef, iş başvurusu süreçlerini daha şeffaf, topluluk odaklı ve anlamlı hale getirmeyi amaçlayan kurgusal bir platformdur.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: nestjs, nestjs-backend, nextjs, nextjs16, proof-of-concept
- Fork: False
- Last pushed: 2026-07-18
### [markdown-file-renamer-web-app](https://github.com/sametcn99/markdown-file-renamer-web-app)
This project is a web application that allows users to upload Markdown files, store them locally, and later download the files with renamed filenames. It has been created to help develop the NextUI Blog Template project.
- Language: TypeScript
- Stars: 4
- Forks: 0
- Archived: False
- Topics: file-renamer, front-matter, frontmatter, markdown, nextjs, nextjs14
- Fork: False
- Last pushed: 2025-10-20
### [github-profile-viewer](https://github.com/sametcn99/github-profile-viewer)
moved: https://github.com/sametcn99/GPVBlazor
- Language: TypeScript
- Stars: 5
- Forks: 0
- Archived: False
- Topics: github, github-api, github-profile-viewer, github-rest-api, github-stats, nextjs, octokit-js, profile-stats, radix-ui, readme-stats, rest-api, star-history
- Fork: False
- Last pushed: 2026-03-08
### [nextjs-auth-and-crud-with-supabase](https://github.com/sametcn99/nextjs-auth-and-crud-with-supabase)
nextjs auth and crud example with supabase.
- Language: TypeScript
- Stars: 8
- Forks: 1
- Archived: False
- Topics: auth, crud, example, next, nextjs, nextjs13
- Fork: False
- Last pushed: 2024-05-10
### [mermaid-viewer](https://github.com/sametcn99/mermaid-viewer)
A powerful, modern web application that transforms your ideas into stunning diagrams with live preview and instant sharing capabilities.
- Language: TypeScript
- Stars: 8
- Forks: 2
- Archived: False
- Topics: diagram, diagram-editor, diagram-generator, docker, mermaid, mermaid-viewer, mermaidjs, monaco-editor, mui, mui-material, nestjs, nestjs-backend, nginx, pako
- Fork: False
- Last pushed: 2026-03-30
### [booking-calendar](https://github.com/sametcn99/booking-calendar)
Booking Calendar is a self-hosted PWA designed for single-admin appointment management. It runs on your own server, keeps your data under your control, and supports a complete booking flow using shareable booking links.
- Language: TypeScript
- Stars: 10
- Forks: 1
- Archived: False
- Topics: booking-platform, calendar, coolify, dokploy, handlebars-template, i18n, pwa, pwa-app, self-hosted, smtp, smtp-mail, webhook
- Fork: False
- Last pushed: 2026-04-04
### [nextui-blog-template](https://github.com/sametcn99/nextui-blog-template)
The Next UI Blog Template is a powerful foundation for crafting your very own blog website using Next.js, coupled with sleek UI components from Next UI. This template not only jumpstarts your project but also ensures a responsive and customizable blog experience.
- Language: TypeScript
- Stars: 11
- Forks: 6
- Archived: False
- Topics: blog-template, blog-theme, nextjs, nextjs14, nextui, nextui-template, nextui-theme, react, reactjs, redux-toolkit, templae
- Fork: False
- Last pushed: 2026-01-14
### [ResumeBuilder](https://github.com/sametcn99/ResumeBuilder)
moved to https://github.com/sametcn99/ResumeBuilderMAUI
- Language: C#
- Stars: 14
- Forks: 4
- Archived: False
- Topics: converter, create-pdf-file, csharp, generate-pdf, json, pdf, pdf-generation, questpdf, resume, resume-builder, resume-creator, sourceforge, visual-studio, windows-form-application
- Fork: False
- Last pushed: 2024-02-07
### [env-protector](https://github.com/sametcn99/env-protector)
Protect your secrets from being exposed
- Language: TypeScript
- Stars: 14
- Forks: 4
- Archived: False
- Topics: environment-manager, environment-variables, privacy-extension, privacy-protection, productivity, secret-management, vscode, vscode-extension, vscode-plugin
- Fork: False
- Last pushed: 2026-01-12
### [local-folder-file-explorer](https://github.com/sametcn99/local-folder-file-explorer)
A single-file, browser-based file explorer for viewing local folders. No install, no server — just open index.html in your browser.
- Language: HTML
- Stars: 0
- Forks: 0
- Archived: False
- Topics: file-explorer, file-preview
- Fork: False
- Last pushed: 2026-07-11
### [python-wayback-machine-downloader](https://github.com/sametcn99/python-wayback-machine-downloader)
Query and download archive.org as simple as possible.
- Language: Not specified
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-07-07
### [OpenLayersPostgisDotnetReactDemo](https://github.com/sametcn99/OpenLayersPostgisDotnetReactDemo)
- Language: C#
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-04-26
### [OpenLayersDotnetTest](https://github.com/sametcn99/OpenLayersDotnetTest)
- Language: C#
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-04-26
### [interview](https://github.com/sametcn99/interview)
- Language: C#
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-05-24
### [instaloader-api](https://github.com/sametcn99/instaloader-api)
A FastAPI-based wrapper around Instaloader that downloads Instagram profile content
- Language: Python
- Stars: 0
- Forks: 0
- Archived: False
- Topics: instagram-api, instaloader, instaloader-api, osint
- Fork: False
- Last pushed: 2026-06-18
### [catchapage](https://github.com/sametcn99/catchapage)
an automated page capture toolkit that crawls a curated list of URLs, renders each page in multiple device profiles, and saves both the rendered HTML and a full-page screenshot for every variation.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: browser-automation-toolkit, html-snapshotting, osint, playwright-automation, qa-automation, website-archiving
- Fork: False
- Last pushed: 2026-06-18
### [wvw.dev](https://github.com/sametcn99/wvw.dev)
World Vibe Web — The distributed app store for vibe-coded projects. Aggregates apps from multiple GitHub repos.
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-05-24
### [application-tracker](https://github.com/sametcn99/application-tracker)
self-hosted job search operating system
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: application-tracking-system, job-hunting, job-search
- Fork: False
- Last pushed: 2026-05-15
### [instances](https://github.com/sametcn99/instances)
Automated instances list for LibRedirect
- Language: Not specified
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-05-06
### [htwind-lain-aesthetic-widget-pack](https://github.com/sametcn99/htwind-lain-aesthetic-widget-pack)
A handcrafted widget pack designed for HTWind, the HTML-based desktop widget manager for Windows.
- Language: HTML
- Stars: 0
- Forks: 0
- Archived: False
- Topics: html-widget, htwind, htwind-widget, htwind-widget-pack
- Fork: False
- Last pushed: 2026-02-27
### [open-on-gpv-crx](https://github.com/sametcn99/open-on-gpv-crx)
This Chrome extension allows you to easily open a GitHub Profile on Github Profile Viewer Website.
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: chrome-extension, github-profile-stats, github-profile-viewer
- Fork: False
- Last pushed: 2024-08-04
### [tic-tac-toe](https://github.com/sametcn99/tic-tac-toe)
Tic-Tac-Toe with AI is a web-based game built using React. The game provides a simple interface for playing Tic-Tac-Toe against an AI opponent. The game logic is implemented in JavaScript, and it uses the minimax algorithm to create a challenging AI opponent.
- Language: TypeScript
- Stars: 1
- Forks: 0
- Archived: False
- Topics: minimax-algorithm, nextjs, tic-tac-toe, tic-tac-toe-javascript
- Fork: False
- Last pushed: 2024-12-13
### [rock-paper-scissors](https://github.com/sametcn99/rock-paper-scissors)
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2024-06-21
### [GPVBlazor](https://github.com/sametcn99/GPVBlazor)
This platform provides a comprehensive and user-friendly interface for exploring GitHub profiles and gaining valuable insights into developers' open-source contributions.
- Language: HTML
- Stars: 0
- Forks: 0
- Archived: False
- Topics: blazor, blazor-application, bulma, bulma-css, bulma-css-framework, chartjs, dotnet, dotnet-core, razor-pages
- Fork: False
- Last pushed: 2026-03-17
### [color-img-downloader](https://github.com/sametcn99/color-img-downloader)
Color Studio is a professional-grade color picker and image generation tool built with modern web technologies. Create, manipulate, and export beautiful colors in multiple formats with an intuitive and powerful interface.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-03-12
### [clone-all-gists](https://github.com/sametcn99/clone-all-gists)
This script downloads all public gists for a specified GitHub user and saves them to the local filesystem. The gists are organized by username and gist ID.
- Language: TypeScript
- Stars: 1
- Forks: 2
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2025-10-27
### [write-lyric-to-audio](https://github.com/sametcn99/write-lyric-to-audio)
This project is designed to fetch and display metadata for audio files, specifically focusing on .flac and .mp3 formats. It also integrates with the Genius API to retrieve lyrics for the songs based on the metadata obtained. The project utilizes Deno for runtime and leverages various npm packages for metadata parsing and lyrics fetching.
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: audio, audio-tag, deno, ffmetadata, music-metadata, node-js
- Fork: False
- Last pushed: 2025-09-13
### [MicroServiceLearn](https://github.com/sametcn99/MicroServiceLearn)
learning repo
- Language: C#
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-03-25
### [golter](https://github.com/sametcn99/golter)
TUI file converter built with Go
- Language: Go
- Stars: 0
- Forks: 0
- Archived: False
- Topics: file-compression, file-converter, go-app, go-terminal, tui-app
- Fork: False
- Last pushed: 2026-04-11
### [MarkdownFileRenamer](https://github.com/sametcn99/MarkdownFileRenamer)
Markdown File Renamer is a simple C# application for renaming and moving files using the titles from Markdown files.
- Language: C#
- Stars: 1
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2023-11-02
### [open-github-dev](https://github.com/sametcn99/open-github-dev)
This Chrome extension allows you to easily open a GitHub repository on github.dev.
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2023-12-20
### [ResumeBuilderMAUI](https://github.com/sametcn99/ResumeBuilderMAUI)
I’m currently learning .NET MAUI and MVVM architecture as I rebuild my ResumeBuilder app. This project is a work in progress, and I’m gaining knowledge as I proceed.
- Language: C#
- Stars: 0
- Forks: 1
- Archived: False
- Topics: dotnet, dotnet-maui, maui, maui-app, mvvm, mvvm-architecture
- Fork: False
- Last pushed: 2024-06-11
### [BlogAPIDotnet](https://github.com/sametcn99/BlogAPIDotnet)
learning progress repo
- Language: C#
- Stars: 1
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2024-09-27
### [node-genius-lyrics-gui](https://github.com/sametcn99/node-genius-lyrics-gui)
This project is an Electron application designed to interact with the Genius Lyrics API, providing a graphical user interface for fetching and writing song lyrics to audio file metadata tags.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: audio, audio-tag, electron-vue, electronjs, ffmetadata, music-metadata, node-js
- Fork: False
- Last pushed: 2024-09-27
### [redux-practice](https://github.com/sametcn99/redux-practice)
This project is an example application created to learn the Redux library.
- Language: TypeScript
- Stars: 1
- Forks: 0
- Archived: False
- Topics: nextjs-template, redux-starter, redux-template, redux-toolkit
- Fork: False
- Last pushed: 2024-12-18
### [SolidColorBackground](https://github.com/sametcn99/SolidColorBackground)
This application allows users to select a color using a color picker, which updates the background color of the window.
- Language: C#
- Stars: 0
- Forks: 0
- Archived: False
- Topics: c-sharp, desktop-app, winui3
- Fork: False
- Last pushed: 2025-01-24
### [coordinat-auto-login](https://github.com/sametcn99/coordinat-auto-login)
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2025-05-04
### [sql-query-safety-checker](https://github.com/sametcn99/sql-query-safety-checker)
A comprehensive TypeScript library for analyzing SQL queries and detecting potential security threats, including SQL injection patterns, dangerous operations, and data modification commands. Perfect for applications that need to validate user-provided SQL queries before execution.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: bunjs, bunup, npm-package, sql-query-analysis
- Fork: False
- Last pushed: 2025-06-19
### [svg-split](https://github.com/sametcn99/svg-split)
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2025-07-22
### [fullstack-template](https://github.com/sametcn99/fullstack-template)
A modern, production-ready fullstack template for rapid application development. This template provides a solid foundation for building scalable web applications with a React frontend and NestJS backend, all powered by modern tooling and best practices.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: monorepo, nestjs-template, reactjs-template, template-project, turborepo, turborepo-template
- Fork: False
- Last pushed: 2025-09-06
### [xls-to-gantt](https://github.com/sametcn99/xls-to-gantt)
A powerful and user-friendly tool for converting Excel files directly into interactive Gantt charts. Perfect for project managers, team leaders, and anyone who wants to visualize project timelines without the hassle of manual chart creation.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2025-12-16
### [vitepress-mermaid-renderer-web](https://github.com/sametcn99/vitepress-mermaid-renderer-web)
moved to source repo
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-01-11
### [personal-website](https://github.com/sametcn99/personal-website)
- Language: MDX
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-02-19
### [golter-web](https://github.com/sametcn99/golter-web)
golter landing page
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-02-21
### [product-planning-copilot](https://github.com/sametcn99/product-planning-copilot)
This repository is designed to be forked so teams can set up their own documentation-first planning system quickly.
- Language: HTML
- Stars: 0
- Forks: 0
- Archived: False
- Topics: agent-skills, copilot, gh-copilot, prd, project-management, project-management-system, project-management-tool
- Fork: False
- Last pushed: 2026-03-13
### [dreamtui](https://github.com/sametcn99/dreamtui)
A terminal-based generative dream engine.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: ascii-art, ascii-graphics, opentui, tui-app
- Fork: False
- Last pushed: 2026-03-16
### [sametcn99.github.io](https://github.com/sametcn99/sametcn99.github.io)
redirect to personal website
- Language: HTML
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-04-05
### [ArcDrop](https://github.com/sametcn99/ArcDrop)
- Language: C#
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: False
- Last pushed: 2026-03-12
### [vitepress-image-viewer](https://github.com/sametcn99/vitepress-image-viewer)
VitePress image viewer with zoom, drag, fullscreen overlay, captions and download button. Automatically enhances all images on the page. Built with Vue 3.
- Language: JavaScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-02-23
### [Letterboxd-to-IMDb](https://github.com/sametcn99/Letterboxd-to-IMDb)
Import your Letterboxd ratings into IMDb
- Language: Python
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2025-06-28
### [RepoHub](https://github.com/sametcn99/RepoHub)
RepoHub provides a unified interface for package discovery and installation across different operating systems.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2025-12-05
### [nestlens](https://github.com/sametcn99/nestlens)
Laravel Telescope-inspired debugging and monitoring for NestJS. Track requests, queries, exceptions, jobs, and 14 more watchers with a beautiful real-time dashboard.
- Language: TypeScript
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-01-25
### [crono-event](https://github.com/sametcn99/crono-event)
This program is a timer for events and activities that allows you to set the time and set alarms.
- Language: Not specified
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-02-07
### [letterboxd-api](https://github.com/sametcn99/letterboxd-api)
An API to expose scraped Letterboxd data
- Language: Python
- Stars: 0
- Forks: 0
- Archived: False
- Topics: None listed
- Fork: True
- Last pushed: 2026-02-02