# Samet Can Cincik — Full Agent Context > Full Markdown context for researching Samet Can Cıncık's professional profile, authored content, projects, and public GitHub work. This document is generated from the profile source, content metadata, published Markdown files, and the public GitHub repository snapshot. Prefer the individual linked pages when citing a specific claim. ## Author Profile --- title: About Samet Can Cıncık description: Software developer focused on modern, sustainable, and user-oriented software solutions. type: profile lastUpdated: 2026-08-19 --- # Samet Can Cıncık **Role:** Software Developer **Location:** Ankara, Türkiye Software developer focused on modern, sustainable, and user-oriented software solutions. Samet follows current software development practices and values problem solving, continuous learning, fast adaptation to new technologies, and practical engineering. He actively uses AI tools for research, analysis, debugging, refactoring, and exploring alternative solutions. ## Skills - C# - .NET - ASP.NET Core - Blazor - React - TypeScript - JavaScript - Next.js - Node.js - NestJS - Kotlin - Android - OpenCV - TensorFlow Lite - Google ML Kit - Docker - GitHub Actions - PostgreSQL - SQL Server - Bun - Go - WPF - Electron - AI-assisted development ## Areas Of Interest - Full-stack web development - Developer experience - AI-assisted software engineering - Open-source software - Self-hosted applications - Technical documentation - Software architecture - Automation and DevOps - Real-time interfaces - Developer tools ## Languages - Turkish - English ## Additional Public Notes - Completed military service in November 2025. ## Experience ### Full Stack Developer — Mekanet Enerji Otomasyon **Period:** 2024-10 to 2025-09 **Location:** İzmir, Türkiye - Led the redesign and development of the corporate website with Next.js, TypeScript, and Node.js. - Contributed to a web-based HMI platform for energy automation systems. - Built frontend modules with React.js and backend modules with NestJS for real-time data tracking, authorization, and alarm management. - Managed GitHub Actions CI/CD, Docker containerization, and deployment automation. ### Software Development Intern — Boru Hatları İle Petrol Taşıma Anonim Şirketi (BOTAŞ) **Period:** 2022-07 to 2022-08 **Location:** Ankara, Türkiye - Worked on an Android application focused on artificial intelligence and image processing with Kotlin. - Used OpenCV, TensorFlow Lite, and Google ML Kit for image processing and model integration. - Gained practical experience in mobile development and AI/ML model deployment. ## Education ### Computer Programming — Gazi University **Level:** Associate degree **Period:** 2019-08-17 to 2022-06-27 - Object-oriented programming and algorithm design with C# and Java. - Relational database design and T-SQL with Microsoft SQL Server. - Foundations for adapting to modern software architectures. ### Accounting and Finance — Fatma Yaşar Önen Vocational and Technical Anatolian High School **Level:** High school **Period:** 2013 to 2017 ## Certifications - **Frontend Developer (React)** — HackerRank ([certificate](https://hackerrank.com/certificates/48d222ff5e54)) - **Node.js (Intermediate)** — HackerRank ([certificate](https://hackerrank.com/certificates/9a63aba196f9)) - **JavaScript (Intermediate)** — HackerRank ([certificate](https://hackerrank.com/certificates/00d65b004f0d)) - **REST API (Intermediate)** — HackerRank ([certificate](https://hackerrank.com/certificates/602684d7fe43)) - **Software Architecture Foundations** — LinkedIn Learning ([certificate](https://www.linkedin.com/learning/certificates/7d3aeb5b229707c0e31cfdf59a66a52a816e1d8d135c91643efcff7372472423)) - **Hands-On AI: Implementing Agentic Systems** — LinkedIn Learning ([certificate](https://www.linkedin.com/learning/certificates/55ca2c12bb36682cdcf7f7f64b9d0ef84bd81deb094d88507b6780ff1a83a4ea)) - **CSS (Basic)** — HackerRank ([certificate](https://hackerrank.com/certificates/2f51117fa2cf)) - **C# (Basic)** — HackerRank ([certificate](https://hackerrank.com/certificates/7ac7d1982705)) - **Learning Design Thinking** — LinkedIn Learning ([certificate](https://www.linkedin.com/learning/certificates/af61a5d6a352975bf8bccdb9b77b08034773ccfb6da5b5991448c689461617d1)) ## Profiles And Sources - [Full CV](https://sametcc.me/cv.md) - [GitHub profile](https://sametcc.me/readme.md) - [Website](https://sametcc.me) - [GitHub](https://github.com/sametcn99) - [LinkedIn](https://linkedin.com/in/sametc0) - [Email](mailto:sametcn99@gmail.com) ## GitHub Profile README --- title: README | Samet Can Cıncık description: GitHub profile README with background, interests, and current focus areas. --- [Website](https://sametcc.me) · [Project Board](https://github.com/users/sametcn99/projects/9) · [LinkedIn](https://sametcc.me/link/linkedin) · [Mail](https://sametcc.me/link/mail) [![committers.top badge](https://user-badge.committers.top/turkey/sametcn99.svg)](https://user-badge.committers.top/turkey/sametcn99)
Table of Contents - [Highlight Repositories](#highlight-repositories) - [Latest Writing](#latest-writing) - [Tech Stack](#tech-stack) - [GitHub Snapshot](#github-snapshot) - [Releases](#releases) - [Recent GitHub Activity](#recent-github-activity) - [Collaboration](#collaboration) - [More Repositories](#more-repositories)
## Highlight Repositories | Repository | Description | Language | Stars | Updated | Links | | :---------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------- | :--------: | ----: | :----------: | :--------------------------------------------------------------------------------------------------------------------------------------- | | [**vitepress-mermaid-renderer**](https://github.com/sametcn99/vitepress-mermaid-renderer) | Transform your static Mermaid diagrams into interactive, dynamic visualizations in VitePress! This… | TypeScript | ★ 67 | Sep 5, 2026 | [Repo](https://github.com/sametcn99/vitepress-mermaid-renderer) · [Demo](https://vitepress-mermaid-renderer.vercel.app) | | [**env-protector**](https://github.com/sametcn99/env-protector) | Protect your secrets from being exposed | TypeScript | ★ 14 | Jan 12, 2026 | [Repo](https://github.com/sametcn99/env-protector) · [Demo](https://marketplace.visualstudio.com/items?itemName=sametcn99.env-protector) | | [**my-stars-atlas**](https://github.com/sametcn99/my-stars-atlas) | A generated catalog of starred GitHub repositories, grouped into stable categories. | TypeScript | ★ 5 | Aug 15, 2026 | [Repo](https://github.com/sametcn99/my-stars-atlas) · [Demo](https://sametcn99.github.io/my-stars-atlas/) | | [**booking-calendar**](https://github.com/sametcn99/booking-calendar) | Booking Calendar is a self-hosted PWA designed for single-admin appointment management. It runs on… | TypeScript | ★ 10 | Apr 4, 2026 | [Repo](https://github.com/sametcn99/booking-calendar) · [Demo](https://booking-calendar-docs.vercel.app) | | [**HTWind**](https://github.com/sametcn99/HTWind) | the missing html based widget manager created with .net | C# | ★ 8 | Jul 14, 2026 | [Repo](https://github.com/sametcn99/HTWind) · [Demo](https://htwind.vercel.app) | | [**mermaid-viewer**](https://github.com/sametcn99/mermaid-viewer) | A powerful, modern web application that transforms your ideas into stunning diagrams with live prev… | TypeScript | ★ 8 | Mar 30, 2026 | [Repo](https://github.com/sametcn99/mermaid-viewer) · [Demo](https://mermaid.sametcc.me/home) | | [**instaloader-api**](https://github.com/sametcn99/instaloader-api) | A FastAPI-based wrapper around Instaloader that downloads Instagram profile content | Python | — | Jun 18, 2026 | [Repo](https://github.com/sametcn99/instaloader-api) | | [**golter**](https://github.com/sametcn99/golter) | TUI file converter built with Go | Go | — | Apr 11, 2026 | [Repo](https://github.com/sametcn99/golter) · [Demo](https://golter.vercel.app) | | [**catchapage**](https://github.com/sametcn99/catchapage) | an automated page capture toolkit that crawls a curated list of URLs, renders each page in multiple… | TypeScript | — | Jun 18, 2026 | [Repo](https://github.com/sametcn99/catchapage) | | [**libredirect-mobile**](https://github.com/sametcn99/libredirect-mobile) | LibRedirect Mobile is an Android URL routing app. It intercepts links to services such as YouTube,… | Kotlin | ★ 1 | Aug 24, 2026 | [Repo](https://github.com/sametcn99/libredirect-mobile) | | [**computer-science-resources**](https://github.com/sametcn99/computer-science-resources) | Curated collection of computer science learning resources, coding exercises, practice platforms, an… | JavaScript | — | Sep 1, 2026 | [Repo](https://github.com/sametcn99/computer-science-resources) · [Demo](https://computer-science-resources.vercel.app) | ## Latest Writing I publish notes, guides, and project write-ups about software engineering, tooling, and things I learn while building. - [**Projeler İçin Ortak Developer ve AI Dokümantasyonu**](https://sametcc.me/blog/projeler-icin-ortak-developer-ve-ai-dokumantasyonu) _(published: Aug 16, 2026)_
AI coding agent'larının ihtiyaç duyduğu proje bağlamını insanlar ve makineler için ortak, sürdürülebilir bir dokümantasyon yapısıyla oluşturmak. - [**GitHub Account Notifications to Gotify with n8n**](https://sametcc.me/gist/github-account-notifications-to-gotify-with-n8n) _(published: Aug 7, 2026)_
A production-ready guide for delivering GitHub App events from every repository in an account to a Gotify phone notification channel. - [**Coolify Notifications to Gotify with n8n**](https://sametcc.me/gist/coolify-notifications-to-gotify-with-n8n) _(published: Aug 7, 2026)_
A production-ready guide for delivering Coolify deployment, backup, task, server, and container notifications to a Gotify phone notification channel. - [**LinkedIn Hide Viewed Jobs**](https://sametcc.me/project/linkedin-hide-viewed-jobs) _(published: Jul 22, 2026)_
A privacy-first browser tool that hides or highlights the job postings you have already viewed on LinkedIn, available as a userscript or a standalone browser extension for Chrome and Firefox. - [**My Stars Atlas**](https://sametcc.me/project/my-stars-atlas) _(published: Jul 20, 2026)_
A generated, searchable catalog of starred GitHub repositories grouped into stable categories, built with a deterministic classification engine and published as a static site on GitHub Pages.
Show 34 more posts... - [**Application Tracker**](https://sametcc.me/project/application-tracker) _(published: May 15, 2026)_
A self-hosted job search operating system that keeps applications, company notes, follow-ups, documents, and progress history together in a single private dashboard. - [**shadcn Theme Configuration for MUI**](https://sametcc.me/gist/shadcn-ui-for-mui) _(published: May 13, 2026)_
A complete MUI dark theme configuration styled to resemble shadcn's default dark theme, mapping zinc color tokens to MUI palette, typography, and component overrides. - [**How to Synchronize Obsidian Vault Using Git (PC + Android)**](https://sametcc.me/gist/how-to-synchronize-obsidian-vault-using-git) _(published: May 5, 2026)_
A comprehensive, step-by-step guide on synchronizing your Obsidian vault across PC and Android devices using Git, covering setup, workflow, conflict resolution, and best practices. - [**Docker Usage Guide**](https://sametcc.me/gist/docker-commands) _(published: Aug 25, 2024 • updated: Apr 27, 2026)_
Comprehensive guide to Docker commands for containerization, image management, and container orchestration with practical examples and best practices. - [**Hiretea**](https://sametcc.me/project/hiretea) _(published: Apr 15, 2026)_
A self-hosted platform for running technical assessments end to end, bundling Gitea as the code host and PostgreSQL as the data store with no external SaaS dependencies. - [**Booking Calendar (Self-Hosted)**](https://sametcc.me/project/booking-calendar) _(published: Apr 4, 2026)_
A self-hosted PWA designed for single-admin appointment management, running on your own server with shareable booking links, smart notifications, and integrations for webhooks, CalDAV, and iCal. - [**DreamTUI**](https://sametcc.me/project/dreamtui) _(published: Mar 17, 2026)_
A terminal-based generative dream engine that transforms written dreams into full-screen, animated ASCII art experiences using a two-stage AI pipeline. - [**HTWind**](https://sametcc.me/project/htwind) _(published: Mar 15, 2026)_
A highly customizable, HTML-based widget manager for Windows built with .NET and WPF, bringing web tools and system helpers directly to your desktop with native PowerShell execution support. - [**ArcDrop**](https://sametcc.me/project/arcdrop) _(published: Mar 10, 2026)_
A self-host-first bookmarking platform with cross-platform desktop clients and AI-assisted organization, built as a .NET 10 monorepo with MAUI, Blazor, and ASP.NET Core. - [**GitHub Repo TUI Cloner (PowerShell)**](https://sametcc.me/gist/git-clone-repo-tui) _(published: Feb 19, 2026)_
An interactive PowerShell script to fetch and clone your owned GitHub repositories using a terminal UI (TUI). - [**TypeSpec for .NET and Next.js Teams**](https://sametcc.me/gist/typespec-for-dotnet-nextjs) _(published: Feb 19, 2026)_
Practical TypeSpec workflow for contract-first API design with ASP.NET Core and Next.js, including OpenAPI generation and typed client usage. - [**Rendering HTML Details/Summary in MDX with Custom Components**](https://sametcc.me/gist/mdx-details-custom-components) _(published: Jan 30, 2026)_
A comprehensive guide on how to transform HTML details and summary elements into custom React components in Next.js MDX projects - [**Golter**](https://sametcc.me/project/golter) _(published: Jan 17, 2026)_
A terminal-based file converter with a modern TUI, built with Go. Supports batch conversion of images, videos, audio, and documents. - [**Payload Docker Config: PostgreSQL + BunJS**](https://sametcc.me/gist/payload-docker-config-pgsql-bunjs) _(published: Dec 25, 2025)_
Docker configuration files to run PayloadCMS using the Bun runtime and PostgreSQL database. - [**Migrate from prettier-eslint to Biome**](https://sametcc.me/gist/migrate-from-prettier-eslint-to-biome) _(published: Dec 24, 2025)_
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. - [**Insta Public Archiver**](https://sametcc.me/gist/insta-public-archiver) _(published: Dec 23, 2025)_
A Python script to download all media from public Instagram profiles without login. - [**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) _(published: Dec 17, 2025)_
An analysis of the deep, foundational connections that bind Edgar Allan Poe's literary universe to the nightmarish town of Silent Hill. - [**Deploying Calibre-Web on Coolify with Docker Compose**](https://sametcc.me/gist/running-calibre-on-coolify) _(published: Oct 15, 2025)_
A comprehensive guide to deploying Calibre-Web on Coolify with Docker Compose and persistent storage - [**GitHub Profile Viewer**](https://sametcc.me/project/github-profile-viewer) _(published: Oct 15, 2025)_
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. - [**Mermaid Live Editor & Viewer**](https://sametcc.me/project/mermaid-viewer) _(published: Apr 23, 2025)_
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. - [**VitePress Mermaid Renderer**](https://sametcc.me/project/vitepress-mermaid-renderer) _(published: Mar 15, 2025)_
A VitePress plugin that transforms static Mermaid diagrams into interactive, dynamic visualizations with zoom, pan, fullscreen, and more. - [**Env Protector**](https://sametcc.me/project/env-protector) _(published: Sep 14, 2024)_
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. - [**Using JSON Schema in VS Code**](https://sametcc.me/gist/using-json-schema-in-vscode) _(published: Aug 25, 2024)_
Step-by-step guide to implementing JSON Schema validation in VS Code for automatic error detection and IntelliSense support. - [**TypeScript Types and Utility Types**](https://sametcc.me/gist/typescript-types) _(published: Aug 25, 2024)_
Complete guide to TypeScript's built-in utility types, advanced type transformations, and custom type definitions for robust type safety. - [**Using Glob Patterns in TypeScript Projects**](https://sametcc.me/gist/typescript-glob-patterns) _(published: Aug 25, 2024)_
Comprehensive guide to glob patterns in TypeScript with detailed character explanations, examples, and practical applications for file matching. - [**Product Requirements Document**](https://sametcc.me/gist/product-requirement-doc-guide) _(published: Aug 25, 2024)_
Complete guide to writing effective Product Requirements Documents (PRDs) with templates, best practices, and real-world examples for software development. - [**React Component Lifecycle**](https://sametcc.me/gist/react-component-lifecycle) _(published: Aug 25, 2024)_
Deep dive into React component lifecycle methods, hooks, and modern patterns for managing component state and side effects in React applications. - [**A Comprehensive Guide to Creating and Using Tasks in Visual Studio Code**](https://sametcc.me/gist/using-tasks-in-vscode) _(published: Aug 25, 2024)_
Complete guide to VS Code tasks configuration including custom tasks, automation, debugging, and integration with build systems. - [**Git Hooks: A Comprehensive Guide**](https://sametcc.me/gist/git-hooks-guide) _(published: Aug 25, 2024)_
Complete guide to Git hooks implementation, best practices, and advanced automation techniques for client-side and server-side workflows. - [**Script Gists**](https://sametcc.me/gist/scripts-gist) _(published: Aug 25, 2024)_
Collection of useful development scripts including Prettier setup, Git utilities, and automation tools for modern development workflows. - [**Running Scripts in Linux**](https://sametcc.me/gist/running-scripts-in-linux) _(published: Aug 25, 2024)_
Essential guide to running scripts in Linux including permissions, execution methods, and best practices for shell scripting. - [**PowerShell Scripting**](https://sametcc.me/gist/powershell-scripting) _(published: Aug 25, 2024)_
Complete PowerShell scripting guide covering automation, DevOps practices, Azure integration, and advanced techniques for cross-platform environments. - [**Electron Widgets**](https://sametcc.me/project/electron-widgets) _(published: Mar 25, 2024)_
A desktop application built with Electron.js and Node.js that allows users to create and manage customizable widgets on their desktops. - [**Resume Builder**](https://sametcc.me/project/resume-builder) _(published: Jan 12, 2023)_
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.
## Tech Stack | Area | Technologies | | :-------------- | :-------------------------------------- | | **Frontend** | React, Next.js, Vue, Blazor | | **Backend** | .NET, NestJS, Node.js, Bun | | **Data** | PostgreSQL, MS SQL, SQLite, InfluxDB | | **DevOps** | GitHub Actions, Docker, Coolify | | **Desktop** | Electron, .NET MAUI, .NET Windows Forms | | **Languages** | C#, TypeScript, JavaScript, Python | | **Environment** | Fedora KDE, Ubuntu GNOME, Windows | ## GitHub Snapshot | Metric | Value | | :------------------------ | :--------------------------------------- | | **Total Stars Earned** | 193 | | **Commits (7d)** | 40+ | | **Total Repositories** | 78 | | **Active Repositories** | 68 | | **Forked Repositories** | 10 | | **Archived Repositories** | 0 | | **Total Gists** | [56](https://gist.github.com/sametcn99) | | **Top Languages** | TypeScript, C#, JavaScript, HTML, Python | | **Account Age** | 6 years | ## Releases Recent releases across my repositories: | Release | Repository | Published | Status | | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------ | :----------: | :----: | | [**v1.2.1**](https://github.com/sametcn99/vitepress-mermaid-renderer/releases/tag/v1.2.1) — v1.2.1 | [vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) | Sep 5, 2026 | Stable | | [**v0.1.0**](https://github.com/sametcn99/libredirect-mobile/releases/tag/v0.1.0) — LibRedirect Mobile v0.1.0 | [libredirect-mobile](https://github.com/sametcn99/libredirect-mobile) | Aug 24, 2026 | Stable | | [**v1.1.1**](https://github.com/sametcn99/gotify-web-extension/releases/tag/v1.1.1) — Gotify Web Extension 1.1.1 | [gotify-web-extension](https://github.com/sametcn99/gotify-web-extension) | Aug 21, 2026 | Stable | | [**v1.2.3**](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases/tag/v1.2.3) — v1.2.3 | [linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs) | Aug 15, 2026 | Stable | | [**v1.2.1**](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases/tag/v1.2.1) — v1.2.1 | [linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs) | Aug 14, 2026 | Stable |
Show 15 more releases... - [**v1.1.13**](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases/tag/v1.1.13) — v1.1.13 in [linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs) _(Aug 11, 2026)_ - [**v1.1.0**](https://github.com/sametcn99/gotify-web-extension/releases/tag/v1.1.0) — Gotify Web Extension 1.1.0 in [gotify-web-extension](https://github.com/sametcn99/gotify-web-extension) _(Aug 9, 2026)_ - [**v1.0.0**](https://github.com/sametcn99/gotify-web-extension/releases/tag/v1.0.0) — Gotify Web Extension 1.0.0 in [gotify-web-extension](https://github.com/sametcn99/gotify-web-extension) _(Aug 9, 2026)_ - [**v1.2.0**](https://github.com/sametcn99/vitepress-mermaid-renderer/releases/tag/v1.2.0) — v1.2.0 in [vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) _(Aug 8, 2026)_ - [**v1.1.32**](https://github.com/sametcn99/vitepress-mermaid-renderer/releases/tag/v1.1.32) — v1.1.32 in [vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) _(Aug 8, 2026)_ - [**v1.1.31**](https://github.com/sametcn99/vitepress-mermaid-renderer/releases/tag/v1.1.31) — v1.1.31 in [vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) _(Aug 7, 2026)_ - [**release-20260807-173241**](https://github.com/sametcn99/n8n-automations/releases/tag/release-20260807-173241) — release-20260807-173241 in [n8n-automations](https://github.com/sametcn99/n8n-automations) _(Aug 7, 2026)_ - [**release-20260807-161157**](https://github.com/sametcn99/n8n-automations/releases/tag/release-20260807-161157) — release-20260807-161157 in [n8n-automations](https://github.com/sametcn99/n8n-automations) _(Aug 7, 2026)_ - [**release-20260807-160640**](https://github.com/sametcn99/n8n-automations/releases/tag/release-20260807-160640) — release-20260807-160640 in [n8n-automations](https://github.com/sametcn99/n8n-automations) _(Aug 7, 2026)_ - [**release-20260807-155649**](https://github.com/sametcn99/n8n-automations/releases/tag/release-20260807-155649) — release-20260807-155649 in [n8n-automations](https://github.com/sametcn99/n8n-automations) _(Aug 7, 2026)_ - [**v1.1.30**](https://github.com/sametcn99/vitepress-mermaid-renderer/releases/tag/v1.1.30) — v1.1.30 in [vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) _(Aug 6, 2026)_ - [**1.2**](https://github.com/sametcn99/gh-block-spam-accounts/releases/tag/1.2) — GitHub Spam Blocker 1.2 in [gh-block-spam-accounts](https://github.com/sametcn99/gh-block-spam-accounts) _(Aug 6, 2026)_ - [**1.1**](https://github.com/sametcn99/gh-block-spam-accounts/releases/tag/1.1) — GitHub Spam Blocker 1.1 in [gh-block-spam-accounts](https://github.com/sametcn99/gh-block-spam-accounts) _(Aug 6, 2026)_ - [**v1.1.29**](https://github.com/sametcn99/vitepress-mermaid-renderer/releases/tag/v1.1.29) — v1.1.29 in [vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) _(Aug 5, 2026)_ - [**v1.1.12**](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases/tag/v1.1.12) — v1.1.12 in [linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs) _(Aug 1, 2026)_
## Recent GitHub Activity Recent public activity across pull requests, discussions, releases, and other open-source work. ### Pushes - **Pushed 1 commit to `main` in [sametcn99/vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer): [.](https://github.com/sametcn99/vitepress-mermaid-renderer/commit/97d8fffe74e5b3610bd970525cb36fe08857c56b)** _(Sep 5, 2026)_ - **Pushed 1 commit to `main` in [sametcn99/apps](https://github.com/sametcn99/apps): [fix: avoid unnecessary sitemap lastmod updates](https://github.com/sametcn99/apps/commit/fc4e5081457698503d7e745cc4a42153c071cc69)** _(Sep 4, 2026)_ - **Pushed 1 commit to `feature/improve-responsive-ui-shell` in [sametcn99/server](https://github.com/sametcn99/server): [Merge branch 'master' into feature/improve-responsive-ui-shell](https://github.com/sametcn99/server/commit/8378d1fc0da1b9948c0fbfdbd570c28f8bf4a61a)** _(Sep 4, 2026)_ - **Pushed 1 commit to `main` in [sametcn99/vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer): [bump docs version](https://github.com/sametcn99/vitepress-mermaid-renderer/commit/cd6263326353b2122c4c0fcc05fa85febac8f7a5)** _(Sep 5, 2026)_ - **Pushed 1 commit to `main` in [sametcn99/vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer): [## 1.2.1](https://github.com/sametcn99/vitepress-mermaid-renderer/commit/7eaafc643ff95d74a8cf87da0f449f34129b0b7a)** _(Sep 5, 2026)_
Show 90 more pushes... - **Pushed to `master` in [sametcn99/server](https://github.com/sametcn99/server): [c27a381...14bfc25](https://github.com/sametcn99/server/compare/c27a381a67eaaa61886be868d14d15236f9f408f...14bfc256276775c425f988d621dccfe705de18ac)** _(Sep 4, 2026)_ - **Pushed to `master` in [sametcn99/orhan-elektronik](https://github.com/sametcn99/orhan-elektronik): [6dd39c9...17730da](https://github.com/sametcn99/orhan-elektronik/compare/6dd39c9e25d3e0ebbf9487dc47e30c85f9c2f35b...17730da71549dee3c58c3dc22b7155fa1e923740)** _(Sep 2, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [42007c1...6fd2991](https://github.com/sametcn99/sametcn99/compare/42007c1f1cc575d6b3a8b3458dcc34a6d0898845...6fd299108205512e4b11f05db92673a85e224bf4)** _(Sep 2, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [7aa9c04...c3de2f6](https://github.com/sametcn99/sametcn99/compare/7aa9c04c37e26a4c949e0042f358f469598a0e17...c3de2f63dc2c4c58885e172301025d8e7f729ae1)** _(Sep 2, 2026)_ - **Pushed to `main` in [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources): [63ad70e...93887fe](https://github.com/sametcn99/computer-science-resources/compare/63ad70e3e3b6923124061343592d63c4c3e5b09e...93887feb4ed25d965c4b23f50d9eb00faa17fa56)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [bb82d4b...394873b](https://github.com/sametcn99/case-fx-tool/compare/bb82d4b21db605c8e6217d346cab1757b67d2391...394873b167aeae55fe176741b7dc854b97ea0bd3)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [28a072b...0d4bf0f](https://github.com/sametcn99/sametcn99/compare/28a072b558f492f229daa7bec7c81482d1bb8d29...0d4bf0fefae18d0de1734d99c97c6f7fd99f42a9)** _(Sep 2, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [394873b...e5780d9](https://github.com/sametcn99/case-fx-tool/compare/394873b167aeae55fe176741b7dc854b97ea0bd3...e5780d91c57108b9db984fb5179e0bedf00efae7)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [05d086d...449c0c0](https://github.com/sametcn99/case-fx-tool/compare/05d086d0688a0001b18bc7c10dd9131567697b6e...449c0c04f9c4118ae169eb295a0003eef93381c5)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [59d6ab4...a2e4186](https://github.com/sametcn99/sametcn99/compare/59d6ab471a288a4981c898e33526802e664950a1...a2e4186794c24b872ad191df3a91d9f8b9d53de7)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [722b783...287024e](https://github.com/sametcn99/case-fx-tool/compare/722b783af739de64cc0769577e6e2eef3e63091a...287024e80d412b82e4187a8828fa868d40794b18)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [1f427b9...722b783](https://github.com/sametcn99/case-fx-tool/compare/1f427b9c7956958bc03c8e02b1d1ceddee93a916...722b783af739de64cc0769577e6e2eef3e63091a)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [68999d8...349be78](https://github.com/sametcn99/case-fx-tool/compare/68999d84a8d19ecb863378bf79d44c218039671c...349be78be9f153a3f39a3c3e7eb7ed7a29a7a6e6)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources): [637a6cb...7ce0594](https://github.com/sametcn99/computer-science-resources/compare/637a6cb5366098a7fd2b76423578032eedb842d7...7ce05942403aa217992a2820991a3b5bc3b83485)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [f63511c...abcda73](https://github.com/sametcn99/sametcn99/compare/f63511ceece5a801474126e5295fd8e38e6c4041...abcda73f2abedaad57ab210cbd37bdd2afdd5006)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [f04f474...510fdb1](https://github.com/sametcn99/sametcn99/compare/f04f474f16b9b5193c00c6c6bc315774ab874c0d...510fdb10aad2a5c4358821f54e8cd88a4ecd997e)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [bfa61e6...8401b8e](https://github.com/sametcn99/case-fx-tool/compare/bfa61e67e7bab47a0a521831ffd1ab90c9f28114...8401b8edbff9e4961b7f2e2550327be6157715c3)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [47aa6cf...bb82d4b](https://github.com/sametcn99/case-fx-tool/compare/47aa6cff8fe461f5ffd4a1d110599944383b0768...bb82d4b21db605c8e6217d346cab1757b67d2391)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [449c0c0...47aa6cf](https://github.com/sametcn99/case-fx-tool/compare/449c0c04f9c4118ae169eb295a0003eef93381c5...47aa6cff8fe461f5ffd4a1d110599944383b0768)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources): [93887fe...75665d7](https://github.com/sametcn99/computer-science-resources/compare/93887feb4ed25d965c4b23f50d9eb00faa17fa56...75665d726d2c1005b30d15dc6e30887a2dc41ae2)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources): [7ce0594...63ad70e](https://github.com/sametcn99/computer-science-resources/compare/7ce05942403aa217992a2820991a3b5bc3b83485...63ad70e3e3b6923124061343592d63c4c3e5b09e)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources): [2d94cbf...637a6cb](https://github.com/sametcn99/computer-science-resources/compare/2d94cbf4e71cd6e940d09efb3d646aa6b164f75c...637a6cb5366098a7fd2b76423578032eedb842d7)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [3a3f30d...7c2112f](https://github.com/sametcn99/sametcn99/compare/3a3f30db313eae4a5387631afa3c57088e4422d7...7c2112f5dbf2a14f2cd74be1ea68b01080408471)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [d500e7b...ad6f13e](https://github.com/sametcn99/sametcn99/compare/d500e7bb22d8487c7ee4d58503556e022abf90b3...ad6f13e32a7883e1f1ce1b00746121b575a5623a)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [349be78...7e4e23c](https://github.com/sametcn99/case-fx-tool/compare/349be78be9f153a3f39a3c3e7eb7ed7a29a7a6e6...7e4e23c857cdf1038bcf80acd57ba5ac632f7d07)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources): [75665d7...04ee637](https://github.com/sametcn99/computer-science-resources/compare/75665d726d2c1005b30d15dc6e30887a2dc41ae2...04ee637f7e7d663fe4dfa43ad5a97f480b7fd991)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [e5780d9...68999d8](https://github.com/sametcn99/case-fx-tool/compare/e5780d91c57108b9db984fb5179e0bedf00efae7...68999d84a8d19ecb863378bf79d44c218039671c)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [9affb72...bfa61e6](https://github.com/sametcn99/case-fx-tool/compare/9affb7289c2f2e2991da231ccbdee7b63c19f330...bfa61e67e7bab47a0a521831ffd1ab90c9f28114)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [8401b8e...05d086d](https://github.com/sametcn99/case-fx-tool/compare/8401b8edbff9e4961b7f2e2550327be6157715c3...05d086d0688a0001b18bc7c10dd9131567697b6e)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool): [287024e...9affb72](https://github.com/sametcn99/case-fx-tool/compare/287024e80d412b82e4187a8828fa868d40794b18...9affb7289c2f2e2991da231ccbdee7b63c19f330)** _(Sep 1, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [8c3bce5...2f71670](https://github.com/sametcn99/personal-website-blazor/compare/8c3bce52b93105547fc4dce707186449617df61e...2f716703922857529e33546d99c1f5e5803d977a)** _(Aug 28, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [2f71670...ac08769](https://github.com/sametcn99/personal-website-blazor/compare/2f716703922857529e33546d99c1f5e5803d977a...ac08769251aaf82f0ae21663e6c14aca8d978275)** _(Aug 28, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [8c3bce5...2f71670](https://github.com/sametcn99/personal-website-blazor/compare/8c3bce52b93105547fc4dce707186449617df61e...2f716703922857529e33546d99c1f5e5803d977a)** _(Aug 28, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [4cf0df2...8c3bce5](https://github.com/sametcn99/personal-website-blazor/compare/4cf0df2744248e4f6b7464b9a352fbd3765b6a68...8c3bce52b93105547fc4dce707186449617df61e)** _(Aug 25, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [57566b6...4cf0df2](https://github.com/sametcn99/personal-website-blazor/compare/57566b68664ff41f123602a90b5eedfdd1eb5ae1...4cf0df2744248e4f6b7464b9a352fbd3765b6a68)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-instances-list](https://github.com/sametcn99/libredirect-instances-list): [b9f1dae...6bb8a7a](https://github.com/sametcn99/libredirect-instances-list/compare/b9f1dae04ebe2130e6322615fb4d2a5bc160a4ff...6bb8a7ad076f380ff5b583c858c117ff3df33a1c)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/apps](https://github.com/sametcn99/apps): [800ad1e...46c2fb0](https://github.com/sametcn99/apps/compare/800ad1e9f12f11dc34b34ab87955c403955a8e3d...46c2fb0177cccdcc663835f220c2225eac7c0b33)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile): [f169a09...69cf5d1](https://github.com/sametcn99/libredirect-mobile/compare/f169a0955f88d75cea41467dd1151d40aa68e8d6...69cf5d1753667b4740a176ed38f1667445e371c1)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile): [69cf5d1...3881a67](https://github.com/sametcn99/libredirect-mobile/compare/69cf5d1753667b4740a176ed38f1667445e371c1...3881a675f949b37d199ed98984dc606af2de7ad2)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile): [0196b90...f169a09](https://github.com/sametcn99/libredirect-mobile/compare/0196b903b7d4413016622890f5f6aad7b6c35190...f169a0955f88d75cea41467dd1151d40aa68e8d6)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile): [caf0975...0196b90](https://github.com/sametcn99/libredirect-mobile/compare/caf0975fd4d327a767d510efb2c97844fde9b96a...0196b903b7d4413016622890f5f6aad7b6c35190)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-instances-list](https://github.com/sametcn99/libredirect-instances-list): [0cc2035...b9f1dae](https://github.com/sametcn99/libredirect-instances-list/compare/0cc2035e36c7e2194412755fbbf95f99b4a573fc...b9f1dae04ebe2130e6322615fb4d2a5bc160a4ff)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile): [b7c1282...caf0975](https://github.com/sametcn99/libredirect-mobile/compare/b7c12826a30bc76b6cb625bc29bc53c034ee3046...caf0975fd4d327a767d510efb2c97844fde9b96a)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile): [10f548c...b7c1282](https://github.com/sametcn99/libredirect-mobile/compare/10f548ca3c5824f833b48aa7c1ed998e518236e7...b7c12826a30bc76b6cb625bc29bc53c034ee3046)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile): [9e730a5...10f548c](https://github.com/sametcn99/libredirect-mobile/compare/9e730a572b63c4b6619a0fc17dab4a2cafccb506...10f548ca3c5824f833b48aa7c1ed998e518236e7)** _(Aug 24, 2026)_ - **Pushed to `main` in [sametcn99/libredirect-instances-list](https://github.com/sametcn99/libredirect-instances-list): [fee77bf...0cc2035](https://github.com/sametcn99/libredirect-instances-list/compare/fee77bf5116d7b914b86136cef2150ef69805e28...0cc2035e36c7e2194412755fbbf95f99b4a573fc)** _(Aug 23, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [6d3e569...57566b6](https://github.com/sametcn99/personal-website-blazor/compare/6d3e569a021eb71b4030af1c3967a0d9decf2514...57566b68664ff41f123602a90b5eedfdd1eb5ae1)** _(Aug 22, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [f790a37...6d3e569](https://github.com/sametcn99/personal-website-blazor/compare/f790a374be48f8d9abe3e570c4e12324bcc521db...6d3e569a021eb71b4030af1c3967a0d9decf2514)** _(Aug 22, 2026)_ - **Pushed to `master` in [sametcn99/gotify-web-extension](https://github.com/sametcn99/gotify-web-extension): [ac7ccaf...eb33fd8](https://github.com/sametcn99/gotify-web-extension/compare/ac7ccaf6fb58b7ec42de10ca1eb6ae10ccda1f03...eb33fd8347a5aa7a476a5835e75c7001d59f4fb3)** _(Aug 21, 2026)_ - **Pushed to `master` in [sametcn99/gotify-web-extension](https://github.com/sametcn99/gotify-web-extension): [6289cc2...1b0bb58](https://github.com/sametcn99/gotify-web-extension/compare/6289cc2dcad8634c955dc45fc940ee909194b177...1b0bb58ab31909328ac340bc02a57ef4dd0625b2)** _(Aug 21, 2026)_ - **Pushed to `master` in [sametcn99/gotify-web-extension](https://github.com/sametcn99/gotify-web-extension): [1b0bb58...ac7ccaf](https://github.com/sametcn99/gotify-web-extension/compare/1b0bb58ab31909328ac340bc02a57ef4dd0625b2...ac7ccaf6fb58b7ec42de10ca1eb6ae10ccda1f03)** _(Aug 21, 2026)_ - **Pushed to `master` in [sametcn99/gotify-web-extension](https://github.com/sametcn99/gotify-web-extension): [e05ab22...6289cc2](https://github.com/sametcn99/gotify-web-extension/compare/e05ab22eae64133e135ac06155a80afc3515bc64...6289cc2dcad8634c955dc45fc940ee909194b177)** _(Aug 21, 2026)_ - **Pushed to `master` in [sametcn99/scripts](https://github.com/sametcn99/scripts): [5eb5a10...08101ba](https://github.com/sametcn99/scripts/compare/5eb5a109e1921ae1c54ff64709b5afcfdbb23f38...08101bab5448375b33ae9edefeef6bccde15e8ab)** _(Aug 20, 2026)_ - **Pushed to `master` in [sametcn99/scripts](https://github.com/sametcn99/scripts): [08101ba...de54e07](https://github.com/sametcn99/scripts/compare/08101bab5448375b33ae9edefeef6bccde15e8ab...de54e074aad19c590ca1abcf9b1f3ca4f68daff3)** _(Aug 20, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [9c734c0...f790a37](https://github.com/sametcn99/personal-website-blazor/compare/9c734c09f80404a2ebcc2261caa8e6593ce27b64...f790a374be48f8d9abe3e570c4e12324bcc521db)** _(Aug 20, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [3515009...cb9dc31](https://github.com/sametcn99/personal-website-blazor/compare/3515009faff46c11a86e3bac8dc8696cc1ff5253...cb9dc31b8bd13d131225bb4bac586271f0c1828a)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [af13256...3515009](https://github.com/sametcn99/personal-website-blazor/compare/af132569a9b9d954eff064427ce271388077d59c...3515009faff46c11a86e3bac8dc8696cc1ff5253)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [cb9dc31...9c734c0](https://github.com/sametcn99/personal-website-blazor/compare/cb9dc31b8bd13d131225bb4bac586271f0c1828a...9c734c09f80404a2ebcc2261caa8e6593ce27b64)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [3d76f80...ee3b585](https://github.com/sametcn99/personal-website-blazor/compare/3d76f807f6c1a006c97f57f776846b1a99514215...ee3b585fb56a20a8e240076be5637dda307e7101)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [ee3b585...af13256](https://github.com/sametcn99/personal-website-blazor/compare/ee3b585fb56a20a8e240076be5637dda307e7101...af132569a9b9d954eff064427ce271388077d59c)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [90baac4...0dfdb30](https://github.com/sametcn99/personal-website-blazor/compare/90baac4fd51ca44a7db651a614883baec3e53a86...0dfdb30ba34d8988df2227d55022f005651b33ae)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [c3d1418...3d76f80](https://github.com/sametcn99/personal-website-blazor/compare/c3d14183cd870646bd4d56a1d55cd963b296a6f5...3d76f807f6c1a006c97f57f776846b1a99514215)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [0dfdb30...c3d1418](https://github.com/sametcn99/personal-website-blazor/compare/0dfdb30ba34d8988df2227d55022f005651b33ae...c3d14183cd870646bd4d56a1d55cd963b296a6f5)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [62c552b...90baac4](https://github.com/sametcn99/personal-website-blazor/compare/62c552be11d16a4a600b7017d675e1af64959ee7...90baac4fd51ca44a7db651a614883baec3e53a86)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [976b69e...62c552b](https://github.com/sametcn99/personal-website-blazor/compare/976b69e10e91b19761e30b182b4edbe70ef7c39a...62c552be11d16a4a600b7017d675e1af64959ee7)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [dba1e74...25f7bb4](https://github.com/sametcn99/sametcn99/compare/dba1e74a965f504c46d61f83d31af76c6f2a4b15...25f7bb418cef90af7aff29dc3e870a0c8d8e2436)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [8c16615...976b69e](https://github.com/sametcn99/personal-website-blazor/compare/8c166152feedeb5739687847902dd23c2a1c20d3...976b69e10e91b19761e30b182b4edbe70ef7c39a)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [81dbea3...8c16615](https://github.com/sametcn99/personal-website-blazor/compare/81dbea351b311e422646adc85b7129a641833e06...8c166152feedeb5739687847902dd23c2a1c20d3)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [349358e...81dbea3](https://github.com/sametcn99/personal-website-blazor/compare/349358e5b8f6e1ab8b626991c2045eb154c75725...81dbea351b311e422646adc85b7129a641833e06)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [c53859a...b2edc19](https://github.com/sametcn99/sametcn99/compare/c53859ac29e8358d16b2ed0b13e4cbcc749cd788...b2edc19d0f303c040e5d25e11807b4c531f586dd)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/sametcn99](https://github.com/sametcn99/sametcn99): [9da7969...2173d6f](https://github.com/sametcn99/sametcn99/compare/9da7969edab6c6c08bb61edfc0d5c7f7edcfd2eb...2173d6fd794200a9a972a1260107b142fc66fe20)** _(Aug 19, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [681c690...38fa6d8](https://github.com/sametcn99/personal-website-blazor/compare/681c6908b24d0b129f58855927fc8f3cd95a25b3...38fa6d8a30427b8e1f64fe8a021f25e29f3f269b)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [38fa6d8...349358e](https://github.com/sametcn99/personal-website-blazor/compare/38fa6d8a30427b8e1f64fe8a021f25e29f3f269b...349358e5b8f6e1ab8b626991c2045eb154c75725)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [24570f4...681c690](https://github.com/sametcn99/personal-website-blazor/compare/24570f461e300904512ae3fac7d2d03d2c410912...681c6908b24d0b129f58855927fc8f3cd95a25b3)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [a7f82cb...24570f4](https://github.com/sametcn99/personal-website-blazor/compare/a7f82cbdb0bb0e47d0b3d441955886aeafd8ef96...24570f461e300904512ae3fac7d2d03d2c410912)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [8203cf4...a7f82cb](https://github.com/sametcn99/personal-website-blazor/compare/8203cf43f55dfa47ce8d580f85648c2131bce195...a7f82cbdb0bb0e47d0b3d441955886aeafd8ef96)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [7185836...8203cf4](https://github.com/sametcn99/personal-website-blazor/compare/7185836148bb59191f696fb346bc7451816fccbb...8203cf43f55dfa47ce8d580f85648c2131bce195)** _(Aug 18, 2026)_ - **Pushed to `master` in [sametcn99/scripts](https://github.com/sametcn99/scripts): [45bdd8c...5eb5a10](https://github.com/sametcn99/scripts/compare/45bdd8cb49bc2634b2ebcb08098943cf5b8207f6...5eb5a109e1921ae1c54ff64709b5afcfdbb23f38)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [7185836...5f1429f](https://github.com/sametcn99/personal-website-blazor/compare/7185836148bb59191f696fb346bc7451816fccbb...5f1429f59653b78d0993323a869bac04df791800)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [5f1429f...7185836](https://github.com/sametcn99/personal-website-blazor/compare/5f1429f59653b78d0993323a869bac04df791800...7185836148bb59191f696fb346bc7451816fccbb)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [eaa3e81...68531d2](https://github.com/sametcn99/personal-website-blazor/compare/eaa3e81829033bb934d614c583ea65a1e950e62c...68531d2e0766982850abb13a4a64785cedcd0263)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [68531d2...7185836](https://github.com/sametcn99/personal-website-blazor/compare/68531d2e0766982850abb13a4a64785cedcd0263...7185836148bb59191f696fb346bc7451816fccbb)** _(Aug 18, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [7f96bd0...b0399df](https://github.com/sametcn99/personal-website-blazor/compare/7f96bd07c28429e1f116669b185133f6d55e4da3...b0399dffbbb75b639c740b5991bbd1e6d520c9aa)** _(Aug 17, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [b0399df...eaa3e81](https://github.com/sametcn99/personal-website-blazor/compare/b0399dffbbb75b639c740b5991bbd1e6d520c9aa...eaa3e81829033bb934d614c583ea65a1e950e62c)** _(Aug 17, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [f6d00be...7f96bd0](https://github.com/sametcn99/personal-website-blazor/compare/f6d00be1f58c117f0574969ac64c18cc9ed358d4...7f96bd07c28429e1f116669b185133f6d55e4da3)** _(Aug 17, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [d12f49a...f6d00be](https://github.com/sametcn99/personal-website-blazor/compare/d12f49a0b8b1e1d33764e678c25ce1cd6fd18ccc...f6d00be1f58c117f0574969ac64c18cc9ed358d4)** _(Aug 17, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [2e4983e...d12f49a](https://github.com/sametcn99/personal-website-blazor/compare/2e4983e7f7c4fc7920c41d86140e71b898cb0112...d12f49a0b8b1e1d33764e678c25ce1cd6fd18ccc)** _(Aug 17, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [d7f03be...c1f553b](https://github.com/sametcn99/personal-website-blazor/compare/d7f03bed07f87bb1f7f07737f8ba1ef92d42eda0...c1f553b63b692f5bfaa6ae1f36f5b4c2a50822c0)** _(Aug 17, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [536f8e7...2e4983e](https://github.com/sametcn99/personal-website-blazor/compare/536f8e73dd99b2aa2feee075ea06635bbbc91018...2e4983e7f7c4fc7920c41d86140e71b898cb0112)** _(Aug 17, 2026)_ - **Pushed to `main` in [sametcn99/personal-website-blazor](https://github.com/sametcn99/personal-website-blazor): [5225224...536f8e7](https://github.com/sametcn99/personal-website-blazor/compare/5225224ccb1f24f9f2534fb216ebb1b31c75c637...536f8e73dd99b2aa2feee075ea06635bbbc91018)** _(Aug 17, 2026)_
### Pull Requests - **Merged pull request [#5: refactor extension UI and frame detection](https://github.com/sametcn99/linkedin-hide-viewed-jobs/pull/5) in [sametcn99/linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs)** _(Aug 14, 2026)_ - **Opened pull request [#5: refactor extension UI and frame detection](https://github.com/sametcn99/linkedin-hide-viewed-jobs/pull/5) in [sametcn99/linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs)** _(Aug 14, 2026)_ ### Comments - **Commented on issue [希望增加菜单栏的折叠功能,令牌Mermaid图形居中功能有bug,浏览器变化时不总是居中](https://github.com/sametcn99/vitepress-mermaid-renderer/issues/18) in [sametcn99/vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer)** _(Sep 5, 2026)_ - **Commented on issue [Improve responsive UI shell behavior](https://github.com/gotify/server/pull/1024) in [gotify/server](https://github.com/gotify/server)** _(Sep 4, 2026)_ - **Commented on issue [Improve responsive UI shell behavior](https://github.com/gotify/server/pull/1024) in [gotify/server](https://github.com/gotify/server)** _(Sep 4, 2026)_ - **Commented on issue [Not able to clear notification pop-up number](https://github.com/sametcn99/gotify-web-extension/issues/1) in [sametcn99/gotify-web-extension](https://github.com/sametcn99/gotify-web-extension)** _(Aug 21, 2026)_ - **Commented on issue [[NEED HELP] userscript stops detecting viewed jobs after navigation to /jobs/collections/\* — avoid reloading page](https://github.com/sametcn99/linkedin-hide-viewed-jobs/issues/1) in [sametcn99/linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs)** _(Aug 9, 2026)_ ### Starred Repositories - **Starred [tt-a1i/archify](https://github.com/tt-a1i/archify)** _(Sep 7, 2026)_ - **Starred [nimbalyst/nimbalyst](https://github.com/nimbalyst/nimbalyst)** _(Sep 5, 2026)_ - **Starred [yusufipk/dikte](https://github.com/yusufipk/dikte)** _(Sep 5, 2026)_ - **Starred [gohugoio/hugo](https://github.com/gohugoio/hugo)** _(Sep 3, 2026)_ - **Starred [fujibee/agmsg](https://github.com/fujibee/agmsg)** _(Sep 3, 2026)_
Show 51 more starred repositories... - **Starred [microsoft/dev-tunnels](https://github.com/microsoft/dev-tunnels)** _(Sep 1, 2026)_ - **Starred [facebookresearch/tribev2](https://github.com/facebookresearch/tribev2)** _(Sep 1, 2026)_ - **Starred [lineofflight/frankfurter](https://github.com/lineofflight/frankfurter)** _(Sep 1, 2026)_ - **Starred [superset-sh/superset](https://github.com/superset-sh/superset)** _(Aug 31, 2026)_ - **Starred [ratatui/ratatui](https://github.com/ratatui/ratatui)** _(Aug 30, 2026)_ - **Starred [vercel-labs/vgpu](https://github.com/vercel-labs/vgpu)** _(Aug 27, 2026)_ - **Starred [OrchardCMS/OrchardCore](https://github.com/OrchardCMS/OrchardCore)** _(Aug 25, 2026)_ - **Starred [nocobase/nocobase](https://github.com/nocobase/nocobase)** _(Aug 24, 2026)_ - **Starred [cordiverse/cordis](https://github.com/cordiverse/cordis)** _(Aug 24, 2026)_ - **Starred [JetBrains/kotlin](https://github.com/JetBrains/kotlin)** _(Aug 24, 2026)_ - **Starred [gotify/android](https://github.com/gotify/android)** _(Aug 24, 2026)_ - **Starred [getpaseo/paseo](https://github.com/getpaseo/paseo)** _(Aug 21, 2026)_ - **Starred [traycerai/traycer](https://github.com/traycerai/traycer)** _(Aug 21, 2026)_ - **Starred [stablyai/orca](https://github.com/stablyai/orca)** _(Aug 21, 2026)_ - **Starred [herdrdev/herdr](https://github.com/herdrdev/herdr)** _(Aug 21, 2026)_ - **Starred [router-for-me/CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI)** _(Aug 20, 2026)_ - **Starred [LiteLLM-Labs/litellm-agent-control-plane](https://github.com/LiteLLM-Labs/litellm-agent-control-plane)** _(Aug 20, 2026)_ - **Starred [JuliusBrussee/caveman](https://github.com/JuliusBrussee/caveman)** _(Aug 20, 2026)_ - **Starred [oven-sh/awesome-bun](https://github.com/oven-sh/awesome-bun)** _(Aug 20, 2026)_ - **Starred [tui-cs/Terminal.Gui](https://github.com/tui-cs/Terminal.Gui)** _(Aug 20, 2026)_ - **Starred [modelcontextprotocol/registry](https://github.com/modelcontextprotocol/registry)** _(Aug 20, 2026)_ - **Starred [Macawls/umami-mcp-server](https://github.com/Macawls/umami-mcp-server)** _(Aug 20, 2026)_ - **Starred [StuMason/coolify-mcp](https://github.com/StuMason/coolify-mcp)** _(Aug 19, 2026)_ - **Starred [ashkulz/committers.top](https://github.com/ashkulz/committers.top)** _(Aug 19, 2026)_ - **Starred [nilbuild/developer-roadmap](https://github.com/nilbuild/developer-roadmap)** _(Aug 18, 2026)_ - **Starred [onllm-dev/onWatch](https://github.com/onllm-dev/onWatch)** _(Aug 18, 2026)_ - **Starred [zioder/TaskbarQuota](https://github.com/zioder/TaskbarQuota)** _(Aug 18, 2026)_ - **Starred [zellij-org/zellij](https://github.com/zellij-org/zellij)** _(Aug 18, 2026)_ - **Starred [orhun/git-cliff](https://github.com/orhun/git-cliff)** _(Aug 18, 2026)_ - **Starred [github/github-mcp-server](https://github.com/github/github-mcp-server)** _(Aug 17, 2026)_ - **Starred [deepseek-ai/deepseek-harness](https://github.com/deepseek-ai/deepseek-harness)** _(Aug 17, 2026)_ - **Starred [fasouto/termaid](https://github.com/fasouto/termaid)** _(Aug 16, 2026)_ - **Starred [lancedb/lancedb](https://github.com/lancedb/lancedb)** _(Aug 16, 2026)_ - **Starred [WrongStack/WrongStack](https://github.com/WrongStack/WrongStack)** _(Aug 15, 2026)_ - **Starred [warp-tech/warpgate](https://github.com/warp-tech/warpgate)** _(Aug 15, 2026)_ - **Starred [Termix-SSH/Termix](https://github.com/Termix-SSH/Termix)** _(Aug 15, 2026)_ - **Starred [Kikobeats/free-email-domains](https://github.com/Kikobeats/free-email-domains)** _(Aug 15, 2026)_ - **Starred [pbakaus/impeccable](https://github.com/pbakaus/impeccable)** _(Aug 15, 2026)_ - **Starred [mem0ai/mem0](https://github.com/mem0ai/mem0)** _(Aug 15, 2026)_ - **Starred [openai/codex](https://github.com/openai/codex)** _(Aug 14, 2026)_ - **Starred [guillaumemeyer/watermarks-remover](https://github.com/guillaumemeyer/watermarks-remover)** _(Aug 13, 2026)_ - **Starred [diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute)** _(Aug 13, 2026)_ - **Starred [Zen4-bit/Proxima](https://github.com/Zen4-bit/Proxima)** _(Aug 13, 2026)_ - **Starred [salihcantekin/loadbalancing](https://github.com/salihcantekin/loadbalancing)** _(Aug 13, 2026)_ - **Starred [eugeniughelbur/obsidian-second-brain](https://github.com/eugeniughelbur/obsidian-second-brain)** _(Aug 12, 2026)_ - **Starred [earendil-works/pi](https://github.com/earendil-works/pi)** _(Aug 11, 2026)_ - **Starred [LinkedInLearning/complete-guide-to-sql-for-data-engineering-from-beginner-to-advanced-3338018](https://github.com/LinkedInLearning/complete-guide-to-sql-for-data-engineering-from-beginner-to-advanced-3338018)** _(Aug 11, 2026)_ - **Starred [can1357/oh-my-pi](https://github.com/can1357/oh-my-pi)** _(Aug 9, 2026)_ - **Starred [jixserver/free-for-dev](https://github.com/jixserver/free-for-dev)** _(Aug 9, 2026)_ - **Starred [wxt-dev/wxt](https://github.com/wxt-dev/wxt)** _(Aug 8, 2026)_ - **Starred [extension-js/extension.js](https://github.com/extension-js/extension.js)** _(Aug 8, 2026)_
### Other Activity - **Opened issue [#1754](https://github.com/traycerai/traycer/issues/1754) in [traycerai/traycer](https://github.com/traycerai/traycer)** _(Sep 6, 2026)_ - **Closed issue [#1753](https://github.com/traycerai/traycer/issues/1753) in [traycerai/traycer](https://github.com/traycerai/traycer)** _(Sep 6, 2026)_ - **Closed issue [#1753](https://github.com/traycerai/traycer/issues/1753) in [traycerai/traycer](https://github.com/traycerai/traycer)** _(Sep 6, 2026)_ - **Opened issue [#1753](https://github.com/traycerai/traycer/issues/1753) in [traycerai/traycer](https://github.com/traycerai/traycer)** _(Sep 6, 2026)_ - **Assigned issue [#18](https://github.com/sametcn99/vitepress-mermaid-renderer/issues/18) in [sametcn99/vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer)** _(Sep 5, 2026)_
Show 8 more other activity... - **Created branch `main` in [sametcn99/case-fx-tool](https://github.com/sametcn99/case-fx-tool)** _(Sep 1, 2026)_ - **Deleted tag `v0.1.0` in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile)** _(Aug 24, 2026)_ - **Public in [sametcn99/libredirect-mobile](https://github.com/sametcn99/libredirect-mobile)** _(Aug 23, 2026)_ - **Closed issue [#1](https://github.com/sametcn99/gotify-web-extension/issues/1) in [sametcn99/gotify-web-extension](https://github.com/sametcn99/gotify-web-extension)** _(Aug 21, 2026)_ - **Assigned issue [#1](https://github.com/sametcn99/gotify-web-extension/issues/1) in [sametcn99/gotify-web-extension](https://github.com/sametcn99/gotify-web-extension)** _(Aug 21, 2026)_ - **Created branch `master` in [sametcn99/scripts](https://github.com/sametcn99/scripts)** _(Aug 18, 2026)_ - **Public in [sametcn99/pdf-email-extractor](https://github.com/sametcn99/pdf-email-extractor)** _(Aug 10, 2026)_ - **Closed issue [#1](https://github.com/sametcn99/linkedin-hide-viewed-jobs/issues/1) in [sametcn99/linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs)** _(Aug 9, 2026)_
## Collaboration Open issues and pull requests that I am currently tracking or planning to work on. ### Issues | Issue | Repository | Updated | | :------------------------------------------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------- | :------------------: | | [**希望增加菜单栏的折叠功能,令牌Mermaid图形居中功能有bug,浏览器变化时不总是居中**](https://github.com/sametcn99/vitepress-mermaid-renderer/issues/18) | [sametcn99/vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) | Updated Sep 5, 2026 | | [**add/remove tags on Events, Planner entries & Appointments**](https://github.com/sametcn99/booking-calendar/issues/6) | [sametcn99/booking-calendar](https://github.com/sametcn99/booking-calendar) | Updated Feb 22, 2026 | | [**[Bug] Git diff not showing properly when wanting to open diff view**](https://github.com/sametcn99/env-protector/issues/8) | [sametcn99/env-protector](https://github.com/sametcn99/env-protector) | Updated Jan 13, 2026 | | [**[Bug] Env file not protected when starting repository with env last opened**](https://github.com/sametcn99/env-protector/issues/7) | [sametcn99/env-protector](https://github.com/sametcn99/env-protector) | Updated Jan 11, 2026 | | [**bug: Widget position resets to (0,0) on Linux after drag/drop**](https://github.com/sametcn99/electron-widgets/issues/33) | [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) | Updated Oct 23, 2025 | ### Pull Requests | Pull Request | Repository | Status | Updated | | :--------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------- | :-----: | :------------------: | | [**fix: preserve Source Control diff view for env files**](https://github.com/sametcn99/env-protector/pull/9) | [sametcn99/env-protector](https://github.com/sametcn99/env-protector) | waiting | Updated Jul 19, 2026 | | [**data: add PracHub free coding-interview study-plan generator**](https://github.com/sametcn99/computer-science-resources/pull/1) | [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources) | merged | Updated Sep 1, 2026 | | [**Add FrontendAtlas Essential 60**](https://github.com/sametcn99/computer-science-resources/pull/2) | [sametcn99/computer-science-resources](https://github.com/sametcn99/computer-science-resources) | merged | Updated Sep 1, 2026 | | [**refactor extension UI and frame detection**](https://github.com/sametcn99/linkedin-hide-viewed-jobs/pull/5) | [sametcn99/linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs) | merged | Updated Aug 14, 2026 | | [**Install Vercel Web Analytics**](https://github.com/sametcn99/gh-block-spam-accounts/pull/1) | [sametcn99/gh-block-spam-accounts](https://github.com/sametcn99/gh-block-spam-accounts) | closed | Updated Jun 16, 2026 |
Show 27 more pull requests... - [**Install Vercel Speed Insights**](https://github.com/sametcn99/gh-block-spam-accounts/pull/2) in [sametcn99/gh-block-spam-accounts](https://github.com/sametcn99/gh-block-spam-accounts) _(merged, Updated Jun 13, 2026)_ - [**[codex] fix css export path**](https://github.com/sametcn99/vitepress-mermaid-renderer/pull/11) in [sametcn99/vitepress-mermaid-renderer](https://github.com/sametcn99/vitepress-mermaid-renderer) _(merged, Updated Jun 13, 2026)_ - [**chore: update dependencies in package.json**](https://github.com/sametcn99/booking-calendar/pull/11) in [sametcn99/booking-calendar](https://github.com/sametcn99/booking-calendar) _(merged, Updated Apr 4, 2026)_ - [**Caldav**](https://github.com/sametcn99/booking-calendar/pull/10) in [sametcn99/booking-calendar](https://github.com/sametcn99/booking-calendar) _(merged, Updated Apr 4, 2026)_ - [**Dev**](https://github.com/sametcn99/booking-calendar/pull/9) in [sametcn99/booking-calendar](https://github.com/sametcn99/booking-calendar) _(merged, Updated Mar 16, 2026)_ - [**vite-plugin-monkey refactor**](https://github.com/sametcn99/linkedin-hide-viewed-jobs/pull/2) in [sametcn99/linkedin-hide-viewed-jobs](https://github.com/sametcn99/linkedin-hide-viewed-jobs) _(merged, Updated Mar 2, 2026)_ - [**refactor: enhance GitHub strategy logging and configuration handling**](https://github.com/sametcn99/mermaid-viewer/pull/6) in [sametcn99/mermaid-viewer](https://github.com/sametcn99/mermaid-viewer) _(merged, Updated Jan 9, 2026)_ - [**Fullstack dev**](https://github.com/sametcn99/mermaid-viewer/pull/5) in [sametcn99/mermaid-viewer](https://github.com/sametcn99/mermaid-viewer) _(merged, Updated Jan 9, 2026)_ - [**[WIP] Theme Compatibility Issue in Deployed Mermaid Components**](https://github.com/sametcn99/personal-website/pull/6) in [sametcn99/personal-website](https://github.com/sametcn99/personal-website) _(closed, Updated Dec 23, 2025)_ - [**Fix React Server Components CVE vulnerabilities**](https://github.com/sametcn99/xls-to-gantt/pull/2) in [sametcn99/xls-to-gantt](https://github.com/sametcn99/xls-to-gantt) _(merged, Updated Dec 16, 2025)_ - [**Merge pull request #1 from sametcn99/master**](https://github.com/sametcn99/mermaid-viewer/pull/2) in [sametcn99/mermaid-viewer](https://github.com/sametcn99/mermaid-viewer) _(merged, Updated May 8, 2025)_ - [**.**](https://github.com/sametcn99/mermaid-viewer/pull/1) in [sametcn99/mermaid-viewer](https://github.com/sametcn99/mermaid-viewer) _(merged, Updated May 8, 2025)_ - [**feat: add edit env var without opening the file**](https://github.com/sametcn99/env-protector/pull/3) in [sametcn99/env-protector](https://github.com/sametcn99/env-protector) _(merged, Updated Nov 29, 2024)_ - [**Dev**](https://github.com/sametcn99/env-protector/pull/1) in [sametcn99/env-protector](https://github.com/sametcn99/env-protector) _(merged, Updated Sep 16, 2024)_ - [**Dev**](https://github.com/sametcn99/electron-widgets/pull/23) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated May 8, 2024)_ - [**Update package.json and src/types/index.d.ts, add rss-feed-parser and…**](https://github.com/sametcn99/electron-widgets/pull/22) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated May 3, 2024)_ - [**Update next.config.js and package.json**](https://github.com/sametcn99/personal-website/pull/1) in [sametcn99/personal-website](https://github.com/sametcn99/personal-website) _(merged, Updated May 1, 2024)_ - [**Update renderer.js**](https://github.com/sametcn99/electron-widgets/pull/21) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Apr 30, 2024)_ - [**Merge pull request #19 from sametcn99/master**](https://github.com/sametcn99/electron-widgets/pull/20) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Apr 23, 2024)_ - [**2.0.2 Release**](https://github.com/sametcn99/electron-widgets/pull/19) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Apr 23, 2024)_ - [**Merge pull request #16 from sametcn99/master**](https://github.com/sametcn99/electron-widgets/pull/17) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Apr 20, 2024)_ - [**merge**](https://github.com/sametcn99/electron-widgets/pull/16) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Apr 16, 2024)_ - [**widget ui**](https://github.com/sametcn99/electron-widgets/pull/15) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Apr 14, 2024)_ - [**Update README.md**](https://github.com/sametcn99/electron-widgets/pull/13) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Mar 29, 2024)_ - [**clock update**](https://github.com/sametcn99/electron-widgets/pull/11) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Mar 26, 2024)_ - [**init**](https://github.com/sametcn99/electron-widgets/pull/9) in [sametcn99/electron-widgets](https://github.com/sametcn99/electron-widgets) _(merged, Updated Mar 26, 2024)_ - [**Create LICENSE**](https://github.com/sametcn99/ResumeBuilder/pull/1) in [sametcn99/ResumeBuilder](https://github.com/sametcn99/ResumeBuilder) _(closed, Updated Jan 12, 2023)_
## More Repositories ### Forked Repositories
Show 10 forked repositories... - [**server**](https://github.com/sametcn99/server) — Go · Updated Sep 4, 2026
A simple server for sending and receiving messages in real-time per WebSocket. (Includes a sleek web-ui) - [**python-wayback-machine-downloader**](https://github.com/sametcn99/python-wayback-machine-downloader) · Updated Jul 7, 2026
Query and download archive.org as simple as possible. - [**wvw.dev**](https://github.com/sametcn99/wvw.dev) — JavaScript · Updated May 24, 2026
World Vibe Web — The distributed app store for vibe-coded projects. Aggregates apps from multiple GitHub repos. - [**instances**](https://github.com/sametcn99/instances) · Updated May 6, 2026
Automated instances list for LibRedirect - [**vitepress-image-viewer**](https://github.com/sametcn99/vitepress-image-viewer) — JavaScript · Updated Feb 23, 2026
VitePress image viewer with zoom, drag, fullscreen overlay, captions and download button. Automatically enhances all images on the page. Built with Vue 3. - [**crono-event**](https://github.com/sametcn99/crono-event) · Updated Feb 7, 2026
This program is a timer for events and activities that allows you to set the time and set alarms. - [**letterboxd-api**](https://github.com/sametcn99/letterboxd-api) — Python · Updated Feb 2, 2026
An API to expose scraped Letterboxd data - [**nestlens**](https://github.com/sametcn99/nestlens) — TypeScript · Updated Jan 25, 2026
Laravel Telescope-inspired debugging and monitoring for NestJS. Track requests, queries, exceptions, jobs, and 14 more watchers with a beautiful real-time dashboard. - [**RepoHub**](https://github.com/sametcn99/RepoHub) — TypeScript · Updated Dec 5, 2025
RepoHub provides a unified interface for package discovery and installation across different operating systems. - [**Letterboxd-to-IMDb**](https://github.com/sametcn99/Letterboxd-to-IMDb) — Python · Updated Jun 28, 2025
Import your Letterboxd ratings into IMDb
## Published Content ### [Projeler İçin Ortak Developer ve AI Dokümantasyonu](https://sametcc.me/blog/projeler-icin-ortak-developer-ve-ai-dokumantasyonu) --- title: "Projeler İçin Ortak Developer ve AI Dokümantasyonu" publishedAt: "2026-08-16" summary: "AI coding agent'larının ihtiyaç duyduğu proje bağlamını insanlar ve makineler için ortak, sürdürülebilir bir dokümantasyon yapısıyla oluşturmak." tags: ["AI Agents", "Documentation", "Open Source", "Software Architecture", "Developer Experience"] language: "tr" type: "post" status: "published" --- # Projeler İçin Ortak Developer ve AI Dokümantasyonu AI coding agent’ları artık yalnızca kod üreten yardımcı araçlar değil. Codex, Claude Code, OpenCode ve benzeri araçlar repository içinde arama yapabiliyor, dosyaları okuyabiliyor, test çalıştırabiliyor, hata ayıklayabiliyor ve verilen görevleri büyük ölçüde bağımsız şekilde tamamlayabiliyor. Bu değişim proje dokümantasyonunun rolünü de değiştiriyor. Eskiden dokümantasyonun ana hedef kitlesi insanlardı. README dosyaları, katkı rehberleri, mimari açıklamalar ve geliştirme prosedürleri yeni developer’ların projeyi anlamasını kolaylaştırıyordu. Bugün aynı bilgiye AI agent’larının da ihtiyacı var. Buradaki temel soru şu: > İnsanlar için ayrı, AI agent’ları için ayrı dokümantasyon mu hazırlamalıyız; yoksa her ikisinin de kullanabileceği ortak bir proje bilgi yapısı mı kurmalıyız? Özellikle açık kaynak projelerde ikinci yaklaşım çok daha sürdürülebilir görünüyor. --- ## 1. Agent’ın Asıl İhtiyacı Kod Değil, Proje Bağlamı Bir coding agent’a şu görevi vermek kolaydır: ```text Payment refund endpoint'ini ekle. ``` Agent repository’yi inceleyebilir, benzer endpoint’leri bulabilir ve çalışan bir implementation üretebilir. Ancak source code her şeyi anlatmaz. Agent’ın şu soruların cevaplarına da ihtiyacı vardır: - Projenin mimarisi nasıl? - Business logic hangi katmanda bulunmalı? - Validation nasıl yapılmalı? - Error handling standardı nedir? - Test stratejisi nasıl? - Yeni dependency ne zaman eklenebilir? - Database migration prosedürü nedir? - Hangi mimari kararlar daha önce alındı? - Hangi yaklaşımlar özellikle tercih edilmedi? İnsan contributor da aynı bilgilere ihtiyaç duyar. Bu nedenle insan ve AI’ın ihtiyaç duyduğu proje bilgisi aslında büyük ölçüde aynıdır. ```mermaid flowchart TD Task["Yeni görev"] --> Human["İnsan contributor"] Task --> Agent["AI agent"] Human --> Knowledge["Proje bilgisi"] Agent --> Knowledge Knowledge --> Architecture["Mimari"] Knowledge --> Standards["Kodlama standartları"] Knowledge --> Procedures["Geliştirme prosedürleri"] Knowledge --> Decisions["Mimari kararlar"] ``` Sorun AI’ın kod yazamaması değil, **projenin kendi kurallarını ve geçmişini bilmemesi**. --- ## 2. AI İçin Ayrı Dokümantasyon Yazmak Yeni Bir Problem Yaratıyor İlk akla gelen yaklaşım, AI’a özel bilgileri `.agents` gibi klasörlerde toplamaktır. Örneğin: ```text project/ ├── docs/ │ └── database-migrations.md ├── .agents/ │ ├── rules/ │ │ └── database-migrations.md │ └── skills/ │ └── migration/ │ └── SKILL.md ├── CONTRIBUTING.md └── AGENTS.md ``` Buradaki problem aynı bilginin birkaç yerde tekrar edilmesidir. Zamanla şu durum oluşabilir: ```mermaid flowchart TD Rule["Database migration prosedürü"] Rule --> Docs["docs/database-migrations.md"] Rule --> AgentsRule[".agents/rules/database-migrations.md"] Rule --> Skill[".agents/skills/migration/SKILL.md"] Rule --> Contributing["CONTRIBUTING.md"] Docs --> Drift["Bilgi drift'i"] AgentsRule --> Drift Skill --> Drift Contributing --> Drift ``` Bir dosya güncellenir, diğeri unutulur. Sonrasında şu soru ortaya çıkar: > Hangisi gerçek kaynak? AI için ikinci bir dokümantasyon evreni oluşturmak yerine proje bilgisini tek yerde tutmak daha sağlıklıdır. --- ## 3. `docs/` Projenin Canonical Knowledge Base’i Olabilir Daha temiz yaklaşım, repository içindeki `docs/` klasörünü projenin ana bilgi kaynağı olarak kullanmaktır. Örneğin: ```text project/ ├── README.md ├── CONTRIBUTING.md ├── AGENTS.md │ ├── docs/ │ ├── index.md │ ├── getting-started/ │ │ ├── installation.md │ │ └── local-development.md │ │ │ ├── architecture/ │ │ ├── overview.md │ │ ├── frontend.md │ │ ├── backend.md │ │ └── data-flow.md │ │ │ ├── contributing/ │ │ ├── overview.md │ │ ├── coding-standards.md │ │ ├── testing.md │ │ ├── debugging.md │ │ ├── commits.md │ │ └── pull-requests.md │ │ │ ├── development/ │ │ ├── adding-feature.md │ │ ├── fixing-bugs.md │ │ ├── database-migrations.md │ │ └── dependency-upgrades.md │ │ │ ├── adr/ │ ├── api/ │ ├── security/ │ └── operations/ │ ├── .agents/ ├── src/ └── tests/ ``` Bu yapıda `docs/` yalnızca kullanıcı dokümantasyonu değildir. Aynı zamanda: - contributor rehberi, - mimari referans, - geliştirme prosedürü, - karar arşivi, - AI agent knowledge base’i olarak çalışır. --- ## 4. Açık Kaynak Projelerde Bu Model Özellikle Güçlü Open source projelerde proje bilgisinin yalnızca core maintainer’lar tarafından değil, dışarıdan katkı sağlayacak kişiler tarafından da tüketilmesi gerekir. Yeni bir contributor genellikle şu soruların cevaplarını arar: - Projeyi localde nasıl çalıştırırım? - Kod yapısını nasıl öğrenirim? - Yeni feature eklerken hangi kurallara uymalıyım? - Testleri nasıl yazmalıyım? - PR göndermeden önce hangi kontrolleri çalıştırmalıyım? - Mimari değişikliklerde nasıl bir süreç izleniyor? AI agent’ın ihtiyacı da neredeyse aynıdır. ```mermaid flowchart LR Docs["Proje Dokümantasyonu"] Docs --> Contributor["İnsan Contributor"] Docs --> Maintainer["Maintainer"] Docs --> Agent["AI Agent"] Contributor --> Contribution["Katkı"] Maintainer --> Contribution Agent --> Contribution ``` Dolayısıyla iyi bir contributor dokümantasyonu aynı zamanda iyi bir agent context’idir. Örneğin `docs/development/database-migrations.md` şöyle olabilir: ```markdown # Database Migrations When changing the database schema: 1. Do not modify migrations already deployed to production. 2. Create a new migration. 3. Verify backward compatibility. 4. Review affected indexes. 5. Run migration tests. 6. Verify rollback behavior where applicable. 7. Update related documentation. ``` Aynı metni hem insan hem agent kullanabilir. --- ## 5. VitePress ile Aynı Dokümantasyonu Birden Fazla Yerde Kullanmak Markdown tabanlı dokümantasyon sistemlerinin en büyük avantajlarından biri, aynı kaynak dosyalarının farklı tüketicilere sunulabilmesidir. VitePress gibi bir sistem kullanıldığında: ```mermaid flowchart TD Source["docs/*.md"] Source --> GitHub["GitHub"] Source --> VitePress["VitePress"] Source --> Agent["AI Agent"] GitHub --> Contributors["Contributor'lar"] VitePress --> Website["docs.example.com"] Agent --> Coding["Kodlama süreci"] ``` Aynı Markdown dosyaları: - GitHub üzerinde okunabilir, - VitePress ile dokümantasyon sitesine dönüşebilir, - agent tarafından repository içinden doğrudan okunabilir. Bu, dokümantasyonu gerçek anlamda **single source of truth** haline getirir. --- ## 6. Dokümantasyonu Aynı Zamanda MCP Sunucusu Olarak Sunmak Dokümantasyon iyi yapılandırıldığında bir başka önemli avantaj ortaya çıkar: aynı içerik yalnızca web sitesi veya repository içindeki Markdown dosyaları olarak değil, **remote bir MCP sunucusu üzerinden de yayınlanabilir**. Bu özellikle open source projeler için oldukça güçlü bir modeldir. Dokümantasyon zaten: - Markdown tabanlı, - kategorize edilmiş, - başlıklarla ayrılmış, - metadata içeren, - canonical kaynaklardan oluşan bir yapıya sahipse MCP tarafında tekrar ayrı bir knowledge base hazırlamaya gerek kalmaz. Aynı `docs/` ağacı doğrudan MCP tarafından okunabilir. ```mermaid flowchart TD Docs["Repository docs/"] Docs --> VitePress["VitePress Build"] Docs --> MCP["Documentation MCP Server"] VitePress --> Website["docs.example.com"] MCP --> Codex["Codex"] MCP --> Claude["Claude Code"] MCP --> OpenCode["OpenCode"] MCP --> Other["Diğer MCP Client'ları"] ``` Bu durumda projenin dokümantasyonu iki farklı interface üzerinden sunulur: ```text Humans → https://docs.example.com Agents → https://mcp.example.com ``` Ancak arkadaki bilgi aynıdır. Bu önemli çünkü MCP için ayrı içerik üretmek yerine mevcut dokümantasyon **machine-accessible interface** kazanmış olur. --- ## 7. Remote Documentation MCP Neden Faydalı? Local repository içerisindeki dokümantasyonu agent zaten okuyabilir. Fakat remote MCP özellikle repository henüz localde bulunmadığında veya agent’ın başka bir proje bağlamından bilgiye erişmesi gerektiğinde önem kazanır. Örneğin bir contributor kendi projesinde bir açık kaynak kütüphaneyi kullanıyor olabilir: ```text my-project/ └── package.json ``` Kütüphanenin repository’sini clone etmemiş olabilir. Ancak agent: ```text "Bu kütüphanede custom adapter nasıl yazılıyor?" ``` sorusunda projenin public MCP sunucusuna bağlanabilir. ```mermaid sequenceDiagram participant Dev as Developer participant Agent as Coding Agent participant MCP as Project Docs MCP participant Docs as Canonical Docs Dev->>Agent: Custom adapter ekle Agent->>MCP: search("custom adapter") MCP->>Docs: İlgili dokümanları ara Docs-->>MCP: Adapter guide + API reference MCP-->>Agent: Relevant context Agent-->>Dev: Projeye uygun implementation ``` Bu modelde contributor’ın: - repository’yi clone etmesi, - doküman klasörünü bulması, - hangi sayfanın doğru olduğunu anlaması gerekmeyebilir. Agent gerekli bilgiyi doğrudan projenin resmi documentation MCP’sinden alabilir. --- ## 8. Maintainer’lar İçin de Kullanışlı Remote documentation MCP yalnızca dış contributor’lara yardımcı olmaz. Maintainer’lar açısından da faydalıdır. Bir maintainer aynı anda farklı repository’lerde çalışıyor olabilir. Örneğin: ```text frontend/ backend/ sdk/ cli/ docs/ ``` Her repository’deki agent bütün diğer repository’leri local olarak göremeyebilir. Buna rağmen merkezi bir MCP sunucusu sayesinde şirket veya proje genelindeki bilgiye erişebilir. ```mermaid flowchart TD Frontend["Frontend Repo Agent"] Backend["Backend Repo Agent"] SDK["SDK Repo Agent"] MCP["Shared Documentation MCP"] Frontend --> MCP Backend --> MCP SDK --> MCP MCP --> Docs["Canonical Project Documentation"] ``` Bu özellikle monorepo kullanılmayan büyük projelerde anlamlıdır. --- ## 9. MCP Sunucusu Hangi Araçları Sağlayabilir? Basit bir documentation MCP sunucusu birkaç temel capability ile başlayabilir. Örneğin: ```text search_docs(query) get_document(path) list_sections() list_documents(category) get_related_documents(path) ``` Daha gelişmiş durumda: ```text search_architecture(query) search_adrs(query) search_contributing_guides(query) search_api_docs(query) ``` gibi domain-specific araçlar da sunulabilir. Ancak mümkün olduğunca MCP interface’i dokümantasyon yapısını birebir tekrar etmek yerine basit tutulmalıdır. Örneğin: ```text search_docs({ query: "database migration rollback", category: "development", status: "canonical" }) ``` yeterli olabilir. Sonuç: ```json { "results": [ { "title": "Database Migrations", "path": "development/database-migrations.md", "section": "Rollback", "status": "canonical" } ] } ``` Agent ardından gerekli bölümü okuyabilir. --- ## 10. Structured Documentation MCP İçin Büyük Avantaj Sağlar Dokümanlara metadata eklemek burada daha da önemli hale gelir. Örneğin: ```yaml --- title: Database Migrations description: Procedure for creating and reviewing database migrations. category: development status: canonical owner: backend-team last-reviewed: 2026-08-01 audience: - contributor - maintainer - agent --- ``` MCP server bu metadata’yı index oluştururken kullanabilir. Örneğin agent: ```text search_docs( query = "database schema changes", status = "canonical" ) ``` dediğinde deprecated veya draft dokümanlar otomatik olarak daha düşük önceliğe alınabilir. ```mermaid flowchart LR Docs["Markdown Documents"] --> Parser["Metadata Parser"] Parser --> Category["Category"] Parser --> Status["Status"] Parser --> Owner["Owner"] Parser --> Updated["Last Reviewed"] Category --> Index["Documentation Index"] Status --> Index Owner --> Index Updated --> Index Index --> MCP["MCP Search"] ``` Bu nedenle insan dostu dokümantasyon yapısı aynı zamanda iyi bir MCP data modeline dönüşür. --- ## 11. MCP İçin Semantic Search Şart mı? Hayır. Dokümantasyon düzgün yapılandırılmışsa remote MCP ilk etapta oldukça basit çalışabilir: ```text Markdown + metadata + full-text search ``` Örneğin: ```mermaid flowchart LR Query["Agent Query"] --> Search["BM25 / Full-text Search"] Search --> Docs["Relevant Docs"] Docs --> Agent["Agent"] ``` Modern agent sonuçları okuyup gerekirse yeni aramalar yapabilir. Dokümantasyon çok büyüdüğünde semantic search eklenebilir: ```mermaid flowchart TD Query["Query"] Query --> Keyword["Keyword / BM25"] Query --> Semantic["Embedding Search"] Keyword --> Merge["Hybrid Ranking"] Semantic --> Merge Merge --> Results["Relevant Documentation"] ``` Bu nedenle documentation MCP tasarlanırken semantic search zorunlu dependency olarak görülmek zorunda değildir. Önce basit full-text retrieval ile başlanıp ihtiyaç arttığında hybrid search’e geçilebilir. --- ## 12. Resmi Documentation MCP Güvenilir Kaynak Problemini de Çözebilir AI agent’larının açık kaynak projelerdeki bir diğer problemi hangi bilginin resmi olduğunu anlamaktır. Agent internette arama yaptığında: - eski blog postları, - Stack Overflow cevapları, - eski GitHub issue’ları, - üçüncü taraf tutorial’ları, - deprecated documentation bulabilir. Projenin resmi MCP endpoint’i olduğunda agent’a doğrudan: ```text Official documentation source: https://mcp.example.com ``` verilebilir. ```mermaid flowchart TD Agent["AI Agent"] Agent --> Official["Official Docs MCP"] Agent --> Internet["General Internet"] Official --> Canonical["Canonical Project Knowledge"] Internet --> Mixed["Eski / üçüncü taraf / değişken kalite"] Canonical --> Prefer["Primary Source"] ``` Böylece projenin kendi dokümantasyonu AI açısından da primary source haline gelir. Bu özellikle hızlı değişen framework, SDK ve API projelerinde önemli olabilir. --- ## 13. `README.md`, `CONTRIBUTING.md` ve `AGENTS.md` Ana Bilgi Kaynağı Değil, Router Olmalı Bütün proje bilgisini birkaç dev dosyanın içine doldurmak yerine bunları giriş noktası olarak kullanmak daha temizdir. ```mermaid flowchart TD README["README.md"] --> Docs["docs/"] CONTRIBUTING["CONTRIBUTING.md"] --> Docs AGENTS["AGENTS.md"] --> Docs README --> User["Kullanıcı"] CONTRIBUTING --> Human["Contributor"] AGENTS --> Agent["AI Agent"] ``` Rol dağılımı şöyle olabilir: ```text README.md → Projeyi tanıtır. CONTRIBUTING.md → İnsan contributor'ı yönlendirir. AGENTS.md → AI agent'ı yönlendirir. docs/ → Gerçek proje bilgisini tutar. ``` `AGENTS.md` ayrıca remote MCP’yi de gösterebilir: ```markdown # Agent Instructions Project documentation is the canonical source of truth. Local documentation: - `docs/index.md` Remote documentation MCP: - `https://mcp.example.com` When the repository documentation is available locally, prefer local files. Use the documentation MCP for cross-project or remote documentation lookup. If implementation and documentation disagree, report the inconsistency. ``` Bu yaklaşım local ve remote retrieval arasında oldukça temiz bir ayrım sağlar. --- ## 14. Dokümantasyonu WHAT, WHY ve HOW Olarak Ayırmak Dokümantasyonun hem insanlar hem agent’lar tarafından rahat tüketilebilmesi için bilgi yapısının açık olması gerekir. Bunu üç temel kategoriye ayırmak oldukça kullanışlıdır. ### WHAT — Sistem nasıl çalışıyor? ```text docs/architecture/ docs/api/ docs/domain/ ``` ### WHY — Neden böyle yapıldı? ```text docs/adr/ ``` ### HOW — Bu projede bir iş nasıl yapılmalı? ```text docs/development/ docs/contributing/ ``` ```mermaid flowchart TD Docs["docs/"] Docs --> What["WHAT"] Docs --> Why["WHY"] Docs --> How["HOW"] What --> Architecture["architecture / api / domain"] Why --> ADR["ADR"] How --> Procedures["development / contributing"] ``` Aynı yapı MCP tarafında da doğal olarak kullanılabilir. Örneğin agent: ```text "Bu projede transaction neden application layer'da yönetiliyor?" ``` diye sorduğunda retrieval: ```text architecture/ + adr/ ``` alanlarını önceleyebilir. --- ## 15. `.agents` Klasörü Yine de Gerekli Olabilir Bu yaklaşım `.agents` klasörünü tamamen gereksiz hale getirmez. Ancak rolünü ciddi şekilde daraltır. ```text docs/ → İnsanların da bilmesi gereken proje bilgisi .agents/ → Yalnızca agent runtime/tooling'e özgü şeyler ``` Örneğin: ```text .agents/ ├── skills/ │ └── repository-context-builder/ ├── scripts/ │ └── collect-diagnostics.ps1 └── automation/ ``` mantıklıdır. Ancak: ```text .agents/rules/testing.md ``` yerine: ```text docs/contributing/testing.md ``` daha doğru olur. --- ## 16. Şirketlerde Aynı Yaklaşım Nasıl Kullanılabilir? Bu model şirket seviyesinde daha da güçlü hale gelir. Her repository kendi proje bilgisini barındırabilir: ```text payment-service/ └── docs/ notification-service/ └── docs/ frontend/ └── docs/ ``` Bunun üzerinde şirket genelindeki engineering bilgileri için ortak bir katman bulunabilir: ```text Company Engineering Knowledge/ ├── architecture/ ├── engineering-standards/ ├── security/ ├── infrastructure/ ├── patterns/ ├── adr/ └── lessons-learned/ ``` Ve bu şirket knowledge base’i de remote MCP olarak sunulabilir. ```mermaid flowchart TD CompanyDocs["Company Engineering Knowledge"] ProjectDocs["Repository Documentation"] CompanyDocs --> CompanyMCP["Company MCP"] ProjectDocs --> ProjectMCP["Project Docs MCP"] CompanyMCP --> Agent["Developer's AI Agent"] ProjectMCP --> Agent Agent --> Code["Implementation"] ``` Agent iki farklı bilgi scope’una erişir: ```text Company MCP → Şirket genelindeki standartlar Project MCP → Repository-specific bilgi ``` Örneğin: ```text Company: - Logging standardı - Security policy - API conventions - Observability standardı Project: - Payment domain modeli - Repository architecture - Project-specific ADR'ler - Migration prosedürleri ``` Bu ayrım büyük organizasyonlarda oldukça değerlidir. --- ## 17. Developer Hangi AI Aracını Kullanırsa Kullansın Aynı Bilgiye Erişebilir Şirket içinde developer’lar farklı araçlar kullanabilir: ```mermaid flowchart LR MCP["Company / Project MCP"] MCP --> Codex["Developer A / Codex"] MCP --> Claude["Developer B / Claude Code"] MCP --> OpenCode["Developer C / OpenCode"] MCP --> IDE["Developer D / IDE Agent"] ``` Bilgi agent’ın kendi memory sistemine veya vendor-specific configuration’a bağlı kalmaz. Bu da şirket açısından önemli bir avantaj yaratır: > AI tooling değişebilir, şirket bilgisi aynı kalır. Bir ekip bugün Codex kullanıp daha sonra başka bir agent’a geçebilir. Dokümantasyon ve MCP katmanı yeniden yazılmak zorunda değildir. --- ## 18. Ortak Dokümantasyon Kod Kalitesini Nasıl Etkileyebilir? Ortak knowledge layer olmadan her agent kendi genel model bilgisini kullanır. Bir agent repository pattern ekleyebilir. Diğeri doğrudan DbContext kullanabilir. Bir başkası farklı error handling yaklaşımı seçebilir. Ortak dokümantasyon kullanıldığında agent’ın çözüm alanı daralır. Örneğin: ```text - Controllers must not contain business logic. - Validation uses FluentValidation. - API errors use Problem Details. - Integration tests are required for new endpoints. - PII must not be logged. - Repository abstraction is not used in this project. ``` Agent artık: > Bu problem genel olarak nasıl çözülür? yerine: > Bu projede bu problem nasıl çözülür? sorusuna göre hareket eder. --- ## 19. Senior Developer Bilgisini Organizasyon Hafızasına Dönüştürmek Şirketlerde teknik bilginin önemli bir bölümü dokümante edilmemiş durumdadır. Örneğin bir senior developer PR review sırasında şöyle bir yorum bırakabilir: ```text Burada repository abstraction oluşturma. Bu serviste EF Core DbContext doğrudan kullanılıyor. ``` Daha gelişmiş bir sistem bu feedback’i dokümantasyon adayına çevirebilir. ```mermaid flowchart TD PR["PR Review Feedback"] --> Candidate["Documentation Candidate"] Candidate --> Review["Maintainer / Tech Lead Review"] Review -->|Approve| Docs["Project Docs / Company Knowledge"] Review -->|Reject| Drop["Discard"] Docs --> Website["Documentation Website"] Docs --> MCP["Documentation MCP"] MCP --> Agents["AI Agent'lar"] Website --> Humans["Developer'lar"] ``` Buradaki önemli nokta dokümantasyon güncellendiğinde hem insan hem AI tarafının aynı anda güncellenmesidir. Ayrı bir AI knowledge base’i senkronize etmek gerekmez. --- ## 20. AI Dokümantasyon Drift’ini Tespit Etmek İçin de Kullanılabilir Agent yalnızca dokümantasyonu tüketmek zorunda değildir. Dokümantasyon ile implementation arasındaki farkları da tespit edebilir. ```mermaid flowchart TD Docs["Documentation"] --> Compare["Agent Comparison"] Code["Implementation"] --> Compare Compare --> Match{"Uyuşuyor mu?"} Match -->|Evet| Continue["Devam"] Match -->|Hayır| Report["Drift raporu oluştur"] ``` Bu sayede dokümantasyon geliştirme sürecinin aktif bir parçası haline gelir. --- ## 21. Bilgi Katmanı Tek Başına Yeterli Değil Dokümantasyon agent’a nasıl davranması gerektiğini anlatır. Ancak bunu gerçekten uyguladığını garanti etmez. Bu nedenle deterministic kontroller gerekir. ```mermaid flowchart TD Docs["Documentation / Standards"] --> Agent["AI Agent"] Agent --> Code["Generated Code"] Code --> Format["Formatter"] Format --> Lint["Linter"] Lint --> Types["Typecheck"] Types --> Tests["Tests"] Tests --> Security["Security Checks"] Security --> CI["CI"] CI --> Review["Code Review"] ``` Dokümantasyon: > Nasıl yapılmalı? sorusuna cevap verir. CI: > Gerçekten öyle yapılmış mı? sorusuna cevap verir. --- ## 22. Önerilen Açık Kaynak Repository Yapısı Sonuç olarak açık kaynak bir proje için şu yapı oldukça temiz olabilir: ```text project/ ├── README.md ├── CONTRIBUTING.md ├── AGENTS.md │ ├── docs/ │ ├── .vitepress/ │ ├── index.md │ ├── getting-started/ │ ├── architecture/ │ ├── contributing/ │ ├── development/ │ ├── adr/ │ ├── api/ │ ├── security/ │ └── operations/ │ ├── .agents/ │ ├── skills/ │ └── scripts/ │ ├── src/ └── tests/ ``` Aynı `docs/` klasörü üç farklı interface oluşturabilir: ```mermaid flowchart TD Docs["docs/"] Docs --> Git["Repository / GitHub"] Docs --> Web["VitePress Documentation Site"] Docs --> MCP["Remote Documentation MCP"] Git --> Contributor["Contributor"] Web --> Human["Human Reader"] MCP --> Agent["AI Agent"] ``` Görev dağılımı: | Kaynak | Rol | | ----------------- | ------------------------------------------- | | `README.md` | Projeyi tanıtır | | `CONTRIBUTING.md` | İnsan contributor için giriş noktası | | `AGENTS.md` | AI agent için giriş noktası | | `docs/` | Canonical proje bilgisi | | VitePress | Dokümantasyonu insanlar için yayınlar | | Documentation MCP | Aynı bilgiyi agent’lara remote olarak sunar | | `.agents/` | Sadece agent-specific tooling | | `src/` | Implementation | | `tests/` | Davranış doğrulaması | --- ## 23. Şirket Seviyesinde Nihai Mimari Şirket tarafında yapı biraz daha genişleyebilir: ```mermaid flowchart TD CompanyDocs["Company Engineering Docs"] RepoDocs["Repository Docs"] CompanyDocs --> CompanyWeb["Internal Docs Site"] CompanyDocs --> CompanyMCP["Company MCP"] RepoDocs --> ProjectWeb["Project Docs Site"] RepoDocs --> ProjectMCP["Project MCP"] CompanyMCP --> Agent["Developer AI Agent"] ProjectMCP --> Agent Agent --> Repo["Repository"] Repo --> CI["CI / Tests / Static Analysis"] CI --> PR["Pull Request"] PR --> Review["Human Review"] Review --> Candidate["Documentation Update Candidate"] Candidate --> CompanyDocs Candidate --> RepoDocs ``` Bu yapıda dokümantasyon yalnızca bilgi saklayan bir sistem değildir. Aynı zamanda: - insan arayüzü, - agent retrieval katmanı, - organizational memory, - development standardı, - AI context provider haline gelir. --- ## Sonuç AI coding agent’larının yaygınlaşmasıyla birlikte proje dokümantasyonunun hedef kitlesi değişiyor. Artık iyi bir dokümantasyon yalnızca şu soruya cevap vermemeli: > Yeni bir developer projeyi nasıl öğrenir? Aynı zamanda şu soruya da cevap vermeli: > Bir AI agent bu projede doğru karar verebilmek için ihtiyaç duyduğu bağlamı nasıl edinir? Bunun çözümü AI için ayrı bir dokümantasyon sistemi oluşturmak olmak zorunda değil. Daha sürdürülebilir yaklaşım, insanlarla agent’ların aynı canonical bilgi kaynağını kullanmasıdır. ```mermaid flowchart TD Docs["Canonical Project Documentation"] Docs --> Website["Documentation Website"] Docs --> Repo["Repository"] Docs --> MCP["Documentation MCP"] Website --> Humans["Humans"] Repo --> Contributors["Contributors"] MCP --> Agents["AI Agents"] Humans --> Shared["Aynı bilgi"] Contributors --> Shared Agents --> Shared Shared --> Consistency["Daha tutarlı geliştirme"] ``` Özellikle açık kaynak projelerde bu yaklaşım güçlüdür. Bir proje dokümantasyonunu iyi yapılandırdığında yalnızca güzel bir VitePress sitesi elde etmiş olmazsın. Aynı içerik aynı zamanda projenin resmi **AI-readable knowledge interface’i** haline gelebilir. Örneğin: ```text https://docs.example.com → Humans https://mcp.example.com → AI Agents ``` İkisi de aynı Markdown kaynaklarından beslenir. Şirketlerde ise bunun üzerine ortak engineering documentation ve company-level MCP eklenebilir. Böylece developer hangi AI aracını kullanırsa kullansın aynı şirket standartlarına, aynı mimari kararlara ve aynı proje bilgisine erişebilir. Asıl değer burada ortaya çıkar: **Dokümantasyon artık sadece okunacak bir site değil; insanlar ve AI agent’ları için ortak, version-controlled, erişilebilir ve yeniden kullanılabilir bir proje bilgi altyapısına dönüşür.** ### [GitHub Account Notifications to Gotify with n8n](https://sametcc.me/gist/github-account-notifications-to-gotify-with-n8n) --- title: GitHub Account Notifications to Gotify with n8n summary: A production-ready guide for delivering GitHub App events from every repository in an account to a Gotify phone notification channel. publishedAt: "2026-08-07" tags: [github, github-apps, gotify, n8n, webhooks, notifications, automation] language: "en" type: "gist" status: "published" --- # GitHub Account Notifications to Gotify with n8n This guide configures a GitHub App to send repository activity from a personal account or organization to the **Gotify Android app** through an importable **n8n** workflow. n8n sends events to the Gotify server, and the Gotify app receives them as real-time Android notifications on the phone. It uses the released workflow JSON from [sametcn99/n8n-automations](https://github.com/sametcn99/n8n-automations), not a locally built or hand-edited workflow. The workflow formats GitHub webhook payloads into compact phone notifications with an event title, useful context, and a direct GitHub link. It supports pushes, pull requests, issues, comments, workflow runs, releases, stars, watches, forks, ref changes, and other documented GitHub webhook events that the installed GitHub App is allowed to receive. --- ## Architecture and Scope The event path is: ```text GitHub App -> public n8n production webhook -> event formatter -> Gotify server -> Gotify Android app -> phone notification ``` In Gotify terminology, n8n is an **application** that can send messages. The phone is a separate **client** that receives those messages from the Gotify server. Creating the Gotify application token configures the sending side; it does not by itself configure a phone to receive notifications. GitHub does not offer a general webhook for all activity on a personal user account. A repository webhook covers only one repository and an organization webhook covers only one organization. To cover repositories belonging to an account, create a **GitHub App**, install it on the target account, and choose **All repositories**. New repositories are included while that installation remains scoped to all repositories. This is not a feed of every action performed by a GitHub user. GitHub only sends events supported by GitHub Apps, enabled under the App's event subscriptions, and available through the App's repository or organization permissions. The App cannot observe repositories that the account does not own or administer. --- ## Prerequisites Before starting, make sure you have: - A GitHub account or organization where you can create and install a GitHub App. - A public HTTPS URL for an n8n instance. GitHub must reach its production webhook URL from the internet. - An n8n instance where you can import and activate workflows and create Gotify credentials. n8n Cloud and Community Edition are both suitable. - A running Gotify server, a user account, and permission to create an application token. - An Android phone with the official Gotify app installed from [Google Play](https://play.google.com/store/apps/details?id=com.github.gotify), [F-Droid](https://f-droid.org/packages/com.github.gotify/), or the [latest GitHub release](https://github.com/gotify/android/releases/latest). Do not put Gotify tokens, webhook URLs, or production workflow exports in a public repository. --- ## Step 1 - Download the Released Workflow JSON Open the [latest GitHub release](https://github.com/sametcn99/n8n-automations/releases/latest) and download the `github-gotify.json` asset. The repository rebuilds this JSON from its TypeScript source during release publication, so the release asset is the supported import artifact. You can also use this download URL: ```text https://github.com/sametcn99/n8n-automations/releases/latest/download/github-gotify.json ``` Release assets intentionally contain this placeholder instead of a real secret: ```text ``` That is expected. Each installation must replace it with its own random value before importing the workflow. For repeatable production rollouts, retain the exact release URL or release tag you approved in your deployment documentation. Review workflow changes before upgrading to a newer release. --- ## Step 2 - Generate a Webhook URL Secret This workflow protects its endpoint with an unguessable path. Generate a unique 64-character lowercase hexadecimal value, which provides 256 bits of random entropy. ### Windows PowerShell ```powershell [Convert]::ToHexString([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)).ToLowerInvariant() ``` ### macOS or Linux ```bash openssl rand -hex 32 ``` ### Python 3 ```bash python3 -c "import secrets; print(secrets.token_hex(32))" ``` On Windows systems where `python3` is unavailable, use `python` instead. Replace every occurrence of `` in the downloaded `github-gotify.json` file with the generated value. The final n8n webhook path must look like this: ```text github-account-gotify-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ``` The path is effectively a password. Never send it in chat, screenshots, logs, or source control. Use a distinct value for each environment and rotate it immediately if it is exposed. --- ## Step 3 - Create a Gotify Application and n8n Credential 1. Sign in to the Gotify server. 2. Create an application, for example **GitHub Notifications**. 3. Copy the application's generated token. 4. In n8n, create a **Gotify** credential. 5. Enter the Gotify server URL and application token in that credential. Keep the token in n8n's credential store. Do not add it to the workflow JSON, the Code node, or notification content. Using a dedicated Gotify application for this integration makes token rotation and message management easier. --- ## Step 4 - Connect the Gotify Android App The official Gotify Android app maintains a connection to the Gotify server and displays a device notification whenever the server receives a new message. Install the app and connect it before testing the GitHub workflow: 1. Open the Gotify app on the Android phone. 2. Add the public HTTPS URL of the Gotify server. 3. Sign in with the Gotify user account that owns the **GitHub Notifications** application created in Step 3. 4. Allow Android notification permission when prompted. 5. Send a test message from the Gotify server's web interface and confirm that it appears immediately as an Android notification. The phone needs network access to the Gotify server. For reliable background delivery, exclude Gotify from the device's battery optimization or unrestricted battery restrictions. Some Android vendors terminate background connections aggressively; their device-specific guidance is available at [DontKillMyApp](https://dontkillmyapp.com/). The app's persistent foreground connection indicator is normal. It keeps the real-time notification channel alive while the app is running in the background. --- ## Step 5 - Import and Activate the n8n Workflow 1. In n8n, create a new workflow or open the import screen. 2. Import the edited `github-gotify.json` file from Step 2. 3. Open the **Send to Gotify** node and select the Gotify credential created in Step 3. 4. Open **GitHub Account Webhook Input** and verify its path contains the new 64-character secret rather than ``. 5. Save the workflow. 6. Activate the workflow. 7. Open **GitHub Account Webhook Input** and copy its **Production URL**. Use the production URL in GitHub. The n8n test URL only works while n8n is listening for a manual test request and is not a durable webhook endpoint. If n8n is behind a reverse proxy, configure its public webhook base URL before copying the production URL. A typical configuration is: ```text WEBHOOK_URL=https://n8n.example.com/ ``` The final URL must be public HTTPS, resolve from the internet, and pass through the proxy without rewriting its path. --- ## Step 6 - Create and Install the GitHub App Create the App from the target account: 1. Open **Settings** -> **Developer settings** -> **GitHub Apps** -> **New GitHub App**. 2. Give the App a recognizable name, such as **Account Gotify Notifications**. 3. Paste the n8n production URL into **Webhook URL**. 4. Leave GitHub's **Webhook secret** field empty. This workflow authenticates requests with the random n8n URL path instead. 5. Keep webhook delivery active. 6. Select **Only on this account** for installation access unless another account genuinely needs to install the App. 7. Grant only the permissions GitHub requires for the events you select. Apply the principle of least privilege. 8. Under **Subscribe to events**, select the events you want to receive. 9. Create the GitHub App, open its install page, install it on the target account, and select **All repositories**. Useful subscriptions include **Push**, **Pull request**, **Issues**, **Issue comment**, **Workflow run**, **Release**, **Star**, **Watch**, **Fork**, **Create**, and **Delete**. GitHub may require additional permissions before some subscriptions are available. Selecting **All repositories** is what gives the App account-wide repository coverage. Revisit this installation after repository transfers, archival, or permission changes. --- ## Step 7 - Verify the End-to-End Delivery GitHub sends a `ping` webhook when the App webhook is configured. It should produce a low-priority Gotify notification indicating that webhook delivery was verified. To confirm the integration: 1. Open the GitHub App's webhook delivery or **Recent Deliveries** page. 2. Open the initial `ping` delivery and confirm GitHub records a successful response. 3. Confirm the Gotify Android app displays an immediate device notification. 4. Create a small test event, such as pushing a commit or opening a pull request. 5. Confirm the Android notification has a concise summary, relevant GitHub URL, and no raw JSON payload. Use GitHub's **Redeliver** action after correcting a configuration issue. Do not rotate the URL secret unless you also update the n8n path and the GitHub App's Webhook URL together. --- ## Security and Operations - Keep HTTPS enabled from GitHub through the n8n endpoint. Do not terminate TLS at an untrusted proxy. - Treat the 64-character webhook path as an authentication secret. It is not included in Gotify messages. - Limit access to the n8n workflow, workflow execution history, and Gotify application. - GitHub event data can contain issue titles, pull request titles, comment excerpts, author names, commit messages, and URLs. Configure n8n execution retention according to your privacy and compliance requirements. - Review the GitHub App's permissions and selected event subscriptions regularly. Fewer event types and permissions reduce exposure and notification noise. - Rotate the random webhook path if it may have leaked: generate a new value, update the n8n Webhook node, save and activate the workflow, then immediately update the GitHub App webhook URL. - When updating the automation, download and review a new release asset, make the same secret replacement, import it into a non-production workflow first, then switch production after successful delivery tests. --- ## Troubleshooting | Symptom | Likely cause | Resolution | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | GitHub reports `404` or `Not Found` | The GitHub App URL differs from the active n8n production URL. | Copy the production URL again from the active **GitHub Account Webhook Input** node. Do not use the test URL. | | No n8n execution appears | The workflow is inactive or GitHub cannot reach the endpoint. | Confirm the workflow is active, the URL is public HTTPS, and firewall or proxy rules permit GitHub requests. | | A request reaches n8n unexpectedly | The random URL path was disclosed. | Rotate the path in n8n and update the GitHub App's Webhook URL immediately. | | Gotify receives no message | The Gotify server URL, application token, or selected credential is wrong. | Test the Gotify credential in n8n and verify the token belongs to the intended application. | | The Gotify server receives a message but the phone stays silent | The Android app is disconnected, notifications are blocked, or battery optimization stopped its background connection. | Open the Gotify Android app, verify it is connected to the right server, allow notifications, and exempt it from battery optimization. | | Only some events arrive | The App is not subscribed to those events, lacks permission, or the event is unavailable to the installation. | Review the GitHub App's event subscriptions, required permissions, and repository installation scope. | | The copied URL is internal or invalid | n8n's public webhook URL is not configured behind the proxy. | Set the correct public `WEBHOOK_URL`, restart n8n if required, reactivate the workflow, and copy the production URL again. | --- ## What You Get Once active, the workflow turns GitHub App deliveries into concise messages on the Gotify server. The Gotify Android app receives each new message in real time and raises the device notification. Each notification identifies the event and repository, summarizes the most useful event details, and includes the best available direct GitHub link. The formatter limits large values such as commit lists, comments, and release notes, keeping routine notifications practical on a phone while preserving the link back to the full GitHub context. ### [Coolify Notifications to Gotify with n8n](https://sametcc.me/gist/coolify-notifications-to-gotify-with-n8n) --- title: Coolify Notifications to Gotify with n8n summary: A production-ready guide for delivering Coolify deployment, backup, task, server, and container notifications to a Gotify phone notification channel. publishedAt: "2026-08-07" tags: [coolify, gotify, n8n, webhooks, notifications, self-hosting, automation] language: "en" type: "gist" status: "published" --- # Coolify Notifications to Gotify with n8n This guide configures Coolify to send deployment, backup, scheduled-task, server, and container events to the **Gotify Android app** through an importable **n8n** workflow. Coolify sends webhook notifications to n8n, n8n formats the payload, and Gotify delivers the result as a real-time Android notification. It uses the released workflow JSON from [sametcn99/n8n-automations](https://github.com/sametcn99/n8n-automations), not a locally built or hand-edited workflow. The workflow supports every event in Coolify's [Webhook Payloads](https://coolify.io/docs/knowledge-base/webhook-payloads) reference: application deployments and unexpected stops, database backups, scheduled tasks, Docker cleanup, server health and patch alerts, Traefik update alerts, container status changes, and test notifications. Failures and alerts use Gotify priority `8`, warnings use `6`, and successful events use `3`. --- ## Architecture and Scope The event path is: ```text Coolify -> public n8n production webhook -> event formatter -> Gotify server -> Gotify Android app -> phone notification ``` In Gotify terminology, n8n is an **application** that can send messages. The phone is a separate **client** that receives them. The Gotify application token configures the sending side; it does not by itself connect a phone. Coolify delivers webhook notifications as JSON `POST` requests. Every official payload contains `success`, `event`, and `message`; additional fields describe the associated application, database, task, server, or container. The workflow turns those fields into concise messages and does not forward raw JSON payloads to Gotify. --- ## Prerequisites Before starting, make sure you have: - A Coolify instance where you can configure notification channels and events. - A public URL for an n8n instance that Coolify can reach. HTTPS is strongly recommended. - An n8n instance where you can import and activate workflows and create Gotify credentials. n8n Cloud and Community Edition are both suitable. - A running Gotify server, a user account, and permission to create an application token. - An Android phone with the official Gotify app installed from [Google Play](https://play.google.com/store/apps/details?id=com.github.gotify), [F-Droid](https://f-droid.org/packages/com.github.gotify/), or the [latest GitHub release](https://github.com/gotify/android/releases/latest). Do not put Gotify tokens, webhook URLs, or production workflow exports in a public repository. --- ## Step 1 - Download the Released Workflow JSON Open the [latest GitHub release](https://github.com/sametcn99/n8n-automations/releases/latest) and download the `coolify-gotify.json` asset. The repository rebuilds this JSON from its TypeScript source during release publication, so the release asset is the supported import artifact. You can also use this download URL: ```text https://github.com/sametcn99/n8n-automations/releases/latest/download/coolify-gotify.json ``` Release assets intentionally contain this placeholder instead of a real secret: ```text ``` Each installation must replace it with its own random value before importing the workflow. --- ## Step 2 - Generate a Webhook URL Secret This workflow protects its endpoint with an unguessable path. Generate a unique 64-character lowercase hexadecimal value, which provides 256 bits of random entropy. ### Windows PowerShell ```powershell [Convert]::ToHexString([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32)).ToLowerInvariant() ``` ### macOS or Linux ```bash openssl rand -hex 32 ``` ### Python 3 ```bash python3 -c "import secrets; print(secrets.token_hex(32))" ``` On Windows systems where `python3` is unavailable, use `python` instead. Replace every occurrence of `` in the downloaded `coolify-gotify.json` file with the generated value. The final n8n webhook path must look like this: ```text coolify-gotify-0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef ``` The path is effectively a password. Never send it in chat, screenshots, logs, or source control. Use a distinct value for each environment and rotate it immediately if it is exposed. --- ## Step 3 - Create a Gotify Application and n8n Credential 1. Sign in to the Gotify server. 2. Create an application, for example **Coolify Notifications**. 3. Copy the application's generated token. 4. In n8n, create a **Gotify** credential. 5. Enter the Gotify server URL, without `/message`, and the **application token** in that credential. Use the application token, not a Gotify client or user token. Keep it in n8n's credential store; do not add it to the workflow JSON, the Code node, or notification content. A dedicated Gotify application makes token rotation and message management easier. --- ## Step 4 - Connect the Gotify Android App The official Gotify Android app maintains a connection to the Gotify server and displays a device notification whenever the server receives a message. Connect it before testing the Coolify workflow: 1. Open the Gotify app on the Android phone. 2. Add the public HTTPS URL of the Gotify server. 3. Sign in with the Gotify user account that owns the **Coolify Notifications** application created in Step 3. 4. Allow Android notification permission when prompted. 5. Send a test message from the Gotify server's web interface and confirm that it appears immediately as an Android notification. The phone needs network access to the Gotify server. For reliable background delivery, exclude Gotify from the device's battery optimization. Some Android vendors terminate background connections aggressively; see [DontKillMyApp](https://dontkillmyapp.com/) for device-specific guidance. --- ## Step 5 - Import and Activate the n8n Workflow 1. In n8n, create a new workflow or open the import screen. 2. Import the edited `coolify-gotify.json` file from Step 2. 3. Open the **Send to Gotify** node and select the Gotify credential created in Step 3. 4. Open **Coolify Webhook Input** and verify its path contains the new 64-character secret rather than ``. 5. Save the workflow. 6. Activate the workflow. 7. Open **Coolify Webhook Input** and copy its **Production URL**. Use the production URL in Coolify. The n8n test URL only works while n8n is listening for a manual test request and is not a durable webhook endpoint. If n8n is behind a reverse proxy, configure its public webhook base URL before copying the production URL. A typical configuration is: ```text WEBHOOK_URL=https://n8n.example.com/ ``` The final URL must be reachable from the Coolify instance and pass through the proxy without rewriting its path. --- ## Step 6 - Configure Coolify Webhook Notifications 1. In Coolify, open **Notifications** > **Webhook**. 2. Paste the n8n production URL from Step 5 into **Webhook URL**. 3. Save the notification channel and enable it. 4. Enable these notification events for the webhook channel: - **Deployment Success** and **Deployment Failure** - **Container Status Changes** - **Backup Success** and **Backup Failure** - **Scheduled Task Success** and **Scheduled Task Failure** - **Docker Cleanup Success** and **Docker Cleanup Failure** - **Server Disk Usage**, **Server Reachable**, and **Server Unreachable** - **Server Patching** and **Traefik Proxy Outdated** 5. Save the notification event selection. Coolify sends JSON `POST` requests to this endpoint. Different channels can have different event selections, so confirm the webhook channel itself has each event enabled rather than relying on another notification channel's settings. --- ## Step 7 - Verify the End-to-End Delivery First, use **Send Test Notification** in Coolify's Webhook notification settings. It should produce a Gotify message titled **Coolify Webhook Test**. Then confirm real events: 1. Trigger a small application deployment. 2. Confirm n8n records a successful execution. 3. Confirm the Gotify Android app displays an immediate notification with the application name, project, environment, deployment link, and application URL where those fields are available. 4. Review a controlled failure, backup, scheduled task, or server event as appropriate for the environment and confirm it reaches Gotify with the expected priority. Successful notifications use priority `3`. Failed deployments, failed backups, failed tasks, server availability alerts, disk alerts, patch errors, Traefik alerts, and stopped containers use priority `8`. A successful local backup with an S3 upload warning uses priority `6`. --- ## Security and Operations - Keep HTTPS enabled between Coolify and n8n when the endpoint crosses a network boundary. - Treat the 64-character webhook path as an authentication secret. It is not included in Gotify messages. - Limit access to the n8n workflow, workflow execution history, and Gotify application. - Coolify webhook payloads can contain application, database, task, server, container, error, output, and URL information. Configure n8n execution-data retention according to your privacy and compliance requirements. - Rotate the random webhook path if it may have leaked: generate a new value, update the n8n Webhook node, save and activate the workflow, then immediately update Coolify's Webhook URL. - When updating the automation, download and review a new release asset, make the same secret replacement, import it into a non-production workflow first, then switch production after successful delivery tests. --- ## Troubleshooting | Symptom | Likely cause | Resolution | | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | Coolify test request receives `404` | The configured URL differs from the active n8n production URL. | Copy the Production URL again from **Coolify Webhook Input**. Do not use the n8n test URL. | | No n8n execution appears | The workflow is inactive or Coolify cannot reach the endpoint. | Confirm the workflow is active, the URL is reachable from the Coolify instance, and proxy or firewall rules permit the request. | | A request reaches n8n unexpectedly | The random URL path was disclosed. | Rotate the path in n8n and update Coolify's Webhook URL immediately. | | Gotify receives no message | The Gotify server URL, application token, or selected credential is wrong. | Test the Gotify credential in n8n and verify the token belongs to the intended application. | | The Gotify server receives a message but the phone stays silent | The Android app is disconnected, notifications are blocked, or battery optimization stopped its background connection. | Open the app, verify it is connected to the right server, allow notifications, and exempt it from battery optimization. | | Only some Coolify events arrive | The event is not enabled for the Webhook notification channel. | Review the event selection in **Notifications** > **Webhook**, save it, and trigger the event again. | | The copied URL is internal or invalid | n8n's public webhook URL is not configured behind the proxy. | Set the correct public `WEBHOOK_URL`, restart n8n if required, reactivate the workflow, and copy the Production URL again. | --- ## What You Get Once active, the workflow turns Coolify webhook deliveries into concise Gotify messages. The Gotify Android app receives each new message in real time and raises the device notification. Each message identifies the event and affected resource, adds its relevant context, includes Coolify or application links when available, and keeps large error or task-output values short enough for a phone notification. ### [LinkedIn Hide Viewed Jobs](https://sametcc.me/project/linkedin-hide-viewed-jobs) --- title: LinkedIn Hide Viewed Jobs publishedAt: "2026-07-22" summary: A privacy-first browser tool that hides or highlights the job postings you have already viewed on LinkedIn, available as a userscript or a standalone browser extension for Chrome and Firefox. tags: [TypeScript, Browser Extension, Userscript, LinkedIn, Job Search, Chrome Extension, Firefox Extension, Privacy] language: "en" type: "project" status: "published" --- # LinkedIn Hide Viewed Jobs A privacy-first browser tool that hides or highlights the job postings you have already viewed on LinkedIn — so you can focus on what is new. Available as a userscript (Tampermonkey / Violentmonkey) or a standalone browser extension for Chrome and Firefox. No account. No tracking. No external requests. [Homepage](https://sametcn99.github.io/linkedin-hide-viewed-jobs/) | [GitHub Repository](https://github.com/sametcn99/linkedin-hide-viewed-jobs) | [Releases](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases) | [Greasy Fork](https://greasyfork.org/scripts/567795-linkedin-hide-viewed-jobs) ## What It Does LinkedIn remembers every job you open, but its interface does not make it easy to skip the ones you have already seen. This tool scans each job card, detects whether you have viewed it or applied to it, and either removes it from your feed or paints it with a subtle color overlay. It works with LinkedIn's single-page app routing, so detection stays accurate as you switch between search results, collections, and recommended jobs. ## Features - **Hide or Highlight mode**: Choose between removing viewed jobs entirely or keeping them visible with a color overlay. - **Smart detection**: Separate colors for `viewed`, `applied`, `active`, and keyword-matched cards so you can read your feed at a glance. - **Active card accent**: The currently selected job on search and listings pages gets its own color so it never gets lost. - **Custom keywords**: Add your own keywords to highlight or hide cards by company name, title, or any text on the card. Matches take priority over viewed/applied detection. - **Scroll Guard**: Slows down rapid scrolling through hidden items to reduce the risk of hitting LinkedIn's rate limits. - **Draggable badge**: Reposition the on-page control anywhere on the screen. - **Live counter**: See how many cards have been detected and how many are keyword matches. - **SPA-aware**: Automatically rescans when you navigate between search results or collections. - **Multilingual**: Works out of the box on 15+ languages including English, Turkish, Spanish, Portuguese, French, German, Italian, Dutch, Russian, Polish, Swedish, Chinese, Japanese, Korean, Arabic, and Hindi. - **Customizable colors and opacity**: Tune the overlay to your taste with native color pickers and an opacity slider. - **Import / export**: Back up and restore your settings as a JSON file. You can replace everything or merge on top of existing settings. - **Persistent window**: The extension remembers your window size and position between sessions. ## Installation ### Option 1 — Userscript (Tampermonkey / Violentmonkey) Works on Chrome, Edge, and Firefox. 1. Install a userscript manager (Tampermonkey or Violentmonkey). 2. Install the userscript from the [latest release](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases/latest/download/linkedin-hide-viewed-jobs.user.js). 3. Confirm the install prompt in your userscript manager. 4. Open any LinkedIn Jobs page — the badge will appear in the bottom-left corner. ### Option 2 — Browser Extension (Chrome, Recommended) 1. Download `linkedin-hide-viewed-jobs-chrome.zip` from the [latest release](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases). 2. Unzip the file to a permanent folder on your computer. 3. Open `chrome://extensions` in Chrome. 4. Enable Developer mode (toggle in the top-right corner). 5. Click **Load unpacked** and select the unzipped folder. 6. Open any LinkedIn Jobs page. ### Option 3 — Browser Extension (Firefox) 1. Download `linkedin-hide-viewed-jobs-firefox.zip` from the [latest release](https://github.com/sametcn99/linkedin-hide-viewed-jobs/releases). 2. Unzip the file to a permanent folder on your computer. 3. Open `about:debugging` in Firefox. 4. Click **This Firefox → Load Temporary Add-on**. 5. Select the `manifest.json` file from the unzipped folder. 6. Open any LinkedIn Jobs page. > Note: Firefox temporary add-ons are removed when the browser closes. For a > permanent install, the extension needs to be signed by Mozilla. ## Privacy - The script runs entirely on your device. - It makes no external network requests. - It does not collect, store, or transmit any data. - Your settings live in your browser's local storage and stay there. ## Technology Stack - **TypeScript** - **Vite** - **Browser Extension APIs (Chrome / Firefox)** - **Userscript (Tampermonkey / Violentmonkey)** ### [My Stars Atlas](https://sametcc.me/project/my-stars-atlas) --- title: My Stars Atlas publishedAt: "2026-07-20" summary: A generated, searchable catalog of starred GitHub repositories grouped into stable categories, built with a deterministic classification engine and published as a static site on GitHub Pages. tags: [TypeScript, GitHub API, GitHub Pages, GitHub Actions, Handlebars, Static Site, Awesome List, Automation] language: "en" type: "project" status: "published" --- # My Stars Atlas A generated catalog of starred GitHub repositories, grouped into stable categories. [Homepage](https://sametcn99.github.io/my-stars-atlas/) | [GitHub Repository](https://github.com/sametcn99/my-stars-atlas) ## About This Project This repository generates a curated, searchable catalog from a GitHub account's starred repositories. It combines a data pipeline, deterministic categorization rules, chunked JSON exports, and a static frontend so the result can be published on GitHub Pages without a server runtime. The project is useful if you want to: - publish your starred repositories as a clean public directory, - keep repository metadata and categories in version control, - customize how projects are grouped with your own rules and overrides, - reuse the structure for your own profile by forking or cloning the repository. ## How It Works The generator fetches starred repositories for `@sametcn99`, classifies each repository with stable rule-based categories, writes the results into chunked files under `dist/data/`, and rebuilds the README together with the static site assets. The frontend then reads the generated catalog and chunk files to provide filtering, search, sorting, and progressive loading in the browser. The main project-level settings live in `config/config.json`, while classification behavior stays in `config/categories.json` and `config/overrides.json`. ## Key Features - **Deterministic Classification Engine**: The taxonomy lives in configuration, not hardcoded per repository. Overrides win first, then rule-based scoring tries to find the strongest category. Only low-confidence items may trigger extra README fetches, which keeps the fallback precise and avoids paying that cost for the full dataset. - **Chunked JSON Exports**: Catalog data is split into chunked files under `dist/data/` for progressive loading in the browser. - **Static Frontend**: Filtering, search, sorting, and progressive loading — all client-side, no server runtime required. - **Automated Updates**: A GitHub Actions workflow keeps the catalog in sync with newly starred repositories on a schedule. - **Forkable & Reusable**: Fork the repo, edit `config/config.json` for your own username, add a GitHub token, and run the generator on your fork. ## Use This Project You can use this project in three common ways: 1. **Fork It** — Keep the existing setup and adapt it for your own GitHub account. Edit `config/config.json` for your own username, README copy, and site SEO settings. 2. **Clone It** — Get full local control or customize the data model, categorization rules, UI, or deployment flow. 3. **Contribute To It** — Improve category definitions, UI and accessibility, GitHub API efficiency, or documentation. ## Technology Stack - **TypeScript** - **Bun** - **Handlebars** (templating) - **GitHub Actions** (automation) - **GitHub Pages** (hosting) ### [Application Tracker](https://sametcc.me/project/application-tracker) --- title: Application Tracker publishedAt: "2026-05-15" summary: A self-hosted job search operating system that keeps applications, company notes, follow-ups, documents, and progress history together in a single private dashboard. tags: [TypeScript, Next.js, Prisma, PostgreSQL, Docker, Self-Hosted, Job Search, Application Tracking System, MinIO] language: "en" type: "project" status: "published" --- # Application Tracker Application Tracker is a self-hosted workspace for people who want one place to manage their job search. [GitHub Repository](https://github.com/sametcn99/application-tracker) | [Container Image](https://github.com/sametcn99/application-tracker/pkgs/container/application-tracker) ## Overview Application Tracker is designed for people who want more structure than a spreadsheet and more privacy than a hosted service. It helps you keep applications, company notes, follow-ups, documents, and progress history together in a single private dashboard. ## What You Can Do - **Track applications**: Keep role titles, companies, compensation notes, statuses, links, and next steps in one place. - **Organize company records**: Save company-specific notes and see every related application in context. - **Manage documents**: Attach files to applications so resumes, cover letters, and other materials stay easy to find. - **Follow your progress**: Use the dashboard and activity history to understand what changed and what needs attention next. - **Customize your workflow**: Use your own tags, source lists, and currencies instead of forcing everything into a fixed template. ## Install from a Local Clone Prerequisites: Docker 24+ and Docker Compose plugin. ```bash git clone https://github.com/sametcn99/application-tracker.git cd application-tracker cp .env.example .env docker compose up --build -d ``` After the containers start, open `http://localhost:3000` and sign in with the admin email and password you set in `.env`. ## Install from GitHub Packages If you prefer using the published container image instead of building locally, use the package published to GitHub Container Registry. ```bash docker pull ghcr.io/sametcn99/application-tracker: ``` Before starting the published image, provide these environment variables directly in your deployment platform, compose file, or container configuration: - `POSTGRES_USER` - `POSTGRES_PASSWORD` - `POSTGRES_DB` - `AUTH_SECRET` - `ADMIN_EMAIL` - `ADMIN_PASSWORD` - `ADMIN_NAME` - `MINIO_ROOT_USER` - `MINIO_ROOT_PASSWORD` The supporting PostgreSQL and MinIO services should use the same values so the application, database, and object storage all start with matching credentials. ## Technology Stack - **TypeScript / Next.js** - **Prisma** - **PostgreSQL** - **MinIO** (object storage) - **Docker Compose** (orchestration) - **Playwright / Vitest** (testing) ### [shadcn Theme Configuration for MUI](https://sametcc.me/gist/shadcn-ui-for-mui) --- title: "shadcn Theme Configuration for MUI" publishedAt: "2026-05-13" summary: "A complete MUI dark theme configuration styled to resemble shadcn's default dark theme, mapping zinc color tokens to MUI palette, typography, and component overrides." tags: [MUI, shadcn, React, Material UI, Theming] language: "en" type: "gist" status: "published" --- # shadcn Theme Configuration for MUI A single, app-wide dark theme for MUI that mirrors shadcn's default dark aesthetic. It maps zinc color tokens to MUI's palette, typography, and component overrides, producing bordered surfaces, near-white primary actions, and the subtle ring focus style shadcn is known for. ## Key Design Decisions - **Background**: zinc-950 (`#09090B`) for both default and paper surfaces - **Text**: zinc-200 (`#E4E4E7`) primary, zinc-400 (`#A1A1AA`) secondary - **Border/Divider**: zinc-800 (`#27272A`) - **Primary**: near-white (`#FAFAFA`) with dark contrast text — matching shadcn's dark-mode primary - **Secondary**: neutral surface (`#27272A`) - **Border Radius**: `0.5rem` (8px) applied via `theme.shape` and component overrides - **Focus Ring**: zinc-300 (`#D4D4D8`) with a subtle 3px box-shadow for that shadcn ring feel ## Full Theme ```tsx // theme.ts import { createTheme } from "@mui/material"; // Single, app-wide dark theme styled to resemble shadcn's default dark theme. // Key choices (mapped from shadcn tokens → MUI): // - background.default: zinc-950 (#09090B) // - background.paper: same base, minimal elevation look // - text.primary: zinc-200 (#E4E4E7), text.secondary: zinc-400 (#A1A1AA) // - divider/border: zinc-800 (#27272A) // - primary: near-white (#FAFAFA) with dark contrast (shadcn primary for dark) // - secondary: neutral surface (#27272A) // - radius: 0.5rem (~8px) applied via theme.shape and component overrides const zinc = { 50: "#FAFAFA", 100: "#F4F4F5", 200: "#E4E4E7", 300: "#D4D4D8", 400: "#A1A1AA", 500: "#71717A", 600: "#52525B", 700: "#3F3F46", 800: "#27272A", 900: "#18181B", 950: "#09090B", } as const; const brand = { white: zinc[50], black: zinc[950], border: zinc[800], ring: zinc[300], }; export const defaultTheme = createTheme({ cssVariables: true, palette: { mode: "dark", primary: { main: brand.white, // shadcn primary on dark is near-white light: zinc[100], dark: zinc[200], contrastText: brand.black, }, secondary: { main: zinc[800], light: zinc[700], dark: zinc[900], contrastText: brand.white, }, error: { main: "#EF4444" }, // red-500 warning: { main: "#F59E0B" }, // amber-500 info: { main: "#60A5FA" }, // blue-400 success: { main: "#22C55E" }, // green-500 background: { default: zinc[950], paper: zinc[950], }, text: { primary: zinc[200], secondary: zinc[400], disabled: zinc[600], }, divider: brand.border, grey: { 50: zinc[50], 100: zinc[100], 200: zinc[200], 300: zinc[300], 400: zinc[400], 500: zinc[500], 600: zinc[600], 700: zinc[700], 800: zinc[800], 900: zinc[900], A100: zinc[100], A200: zinc[200], A400: zinc[400], A700: zinc[700], }, contrastThreshold: 3, tonalOffset: 0.2, }, typography: { fontFamily: 'Inter, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif', button: { textTransform: "none", fontWeight: 500, letterSpacing: "-0.01em", }, }, shape: { borderRadius: 8, // 0.5rem }, components: { MuiCssBaseline: { styleOverrides: { body: { backgroundColor: zinc[950], color: zinc[200], }, "::selection": { backgroundColor: brand.ring, color: brand.black, }, }, }, // Surfaces MuiPaper: { styleOverrides: { root: { backgroundImage: "none", }, }, }, MuiCard: { defaultProps: { elevation: 0 }, styleOverrides: { root: { backgroundColor: zinc[950], border: `1px solid ${brand.border}`, borderRadius: 8, }, }, }, MuiDivider: { styleOverrides: { root: { borderColor: brand.border, }, }, }, // AppBar MuiAppBar: { defaultProps: { elevation: 0 }, styleOverrides: { colorDefault: { backgroundColor: zinc[950], color: zinc[200], borderBottom: `1px solid ${brand.border}`, }, }, }, // Buttons MuiButtonBase: { defaultProps: { disableRipple: true, }, }, MuiButton: { defaultProps: { disableElevation: true, size: "medium", }, styleOverrides: { root: { borderRadius: 8, textTransform: "none", fontWeight: 500, }, sizeSmall: { height: 36, paddingInline: 14, }, sizeMedium: { height: 40, paddingInline: 16, }, sizeLarge: { height: 44, paddingInline: 20, }, containedPrimary: { backgroundColor: brand.white, color: brand.black, "&:hover": { backgroundColor: zinc[200], }, "&:active": { filter: "brightness(0.98)" }, }, containedSecondary: { backgroundColor: zinc[800], color: brand.white, "&:hover": { backgroundColor: zinc[700], }, "&:active": { filter: "brightness(1.05)" }, }, outlined: { borderColor: brand.border, color: zinc[200], "&:hover": { borderColor: zinc[700], backgroundColor: zinc[800], }, }, text: { color: zinc[200], "&:hover": { backgroundColor: zinc[900], }, }, }, }, MuiIconButton: { styleOverrides: { root: { borderRadius: 8, color: zinc[200], "&:hover": { backgroundColor: zinc[900], }, }, }, }, // Inputs MuiInputLabel: { styleOverrides: { root: { color: zinc[400], "&.Mui-focused": { color: zinc[300], }, }, }, }, MuiOutlinedInput: { styleOverrides: { root: { borderRadius: 8, backgroundColor: "transparent", "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: zinc[700], }, "&.Mui-focused .MuiOutlinedInput-notchedOutline": { borderColor: brand.ring, boxShadow: `0 0 0 3px ${brand.ring}33`, // subtle ring }, }, notchedOutline: { borderColor: brand.border, }, input: { height: 40, padding: "10px 12px", }, }, }, MuiFilledInput: { styleOverrides: { root: { borderRadius: 8, backgroundColor: zinc[900], "&:hover": { backgroundColor: zinc[900] }, "&.Mui-focused": { backgroundColor: zinc[900], boxShadow: `0 0 0 3px ${brand.ring}33`, }, }, input: { height: 40 }, }, }, MuiTextField: { defaultProps: { variant: "outlined", size: "small", fullWidth: true, }, }, // Menus / Popovers / Dialogs share a bordered surface look MuiPopover: { styleOverrides: { paper: { backgroundColor: zinc[950], border: `1px solid ${brand.border}`, borderRadius: 8, }, }, }, MuiMenu: { styleOverrides: { paper: { backgroundColor: zinc[950], border: `1px solid ${brand.border}`, borderRadius: 8, }, }, }, MuiDialog: { styleOverrides: { paper: { backgroundColor: zinc[950], border: `1px solid ${brand.border}`, borderRadius: 12, }, }, }, // Chips MuiChip: { styleOverrides: { root: { borderRadius: 8, borderColor: brand.border, backgroundColor: zinc[900], color: zinc[200], }, }, }, // Tooltips MuiTooltip: { styleOverrides: { tooltip: { backgroundColor: zinc[900], color: zinc[200], border: `1px solid ${brand.border}`, borderRadius: 8, }, }, }, // Snackbar/Alert MuiAlert: { styleOverrides: { root: { borderRadius: 8, }, }, }, }, }); ``` ### [How to Synchronize Obsidian Vault Using Git (PC + Android)](https://sametcc.me/gist/how-to-synchronize-obsidian-vault-using-git) --- title: "How to Synchronize Obsidian Vault Using Git (PC + Android)" publishedAt: "2026-05-05" summary: "A comprehensive, step-by-step guide on synchronizing your Obsidian vault across PC and Android devices using Git, covering setup, workflow, conflict resolution, and best practices." tags: [Obsidian, Git, Android, Synchronization, Backup] language: "en" type: "gist" status: "published" --- # How to Synchronize Obsidian Vault Using Git (PC + Android) Obsidian stores all your notes as plain Markdown files inside a local folder called a "vault." Unlike cloud-based note-taking apps such as Notion or Google Keep, Obsidian does not include a built-in sync service. This design choice gives you full ownership and portability of your data, but it also means you need to handle cross-device synchronization yourself. If you use Obsidian on both a computer and an Android phone, you need a reliable way to keep those two vault copies consistent. This is where Git comes in. Git is a distributed version control system originally built for tracking changes in source code. However, since Obsidian notes are just plain text files written in Markdown, Git is a natural fit. By turning your vault into a Git repository and pushing it to a remote host like GitHub, you gain several concrete advantages: - **Full version history**: Every change you make is recorded as a commit. You can browse past versions of any note, compare differences between versions, or revert to an earlier state if you accidentally delete or overwrite something. This is far more powerful than a simple backup because you get granular, per-change tracking. - **Two-way synchronization**: You can edit notes on your PC, push them to GitHub, then pull them on your phone, and vice versa. Both devices stay up to date with the latest changes. There is no single "source of truth" device; GitHub acts as the central hub. - **No third-party cloud dependency**: Your notes live on GitHub inside a private repository that only you can access. Unlike commercial sync services, you are not locked into anyone's ecosystem, and there is no subscription fee for the tier most people need. - **Free and transparent**: Git and GitHub's free tier are more than sufficient for personal note synchronization. You can inspect exactly what changed, when it changed, and why, through Git's log and diff tools. - **Conflict visibility**: When two devices edit the same note simultaneously, Git does not silently overwrite one version. Instead, it flags the conflict and gives you full control over how to resolve it. You always know exactly what happened. That said, this approach has trade-offs you should be aware of before committing to it: - **It is not automatic**: You must manually run `git pull` and `git push` to sync. There is no background daemon watching your files. If you forget to push after editing on your PC, those changes will not appear on your phone until you do. - **It requires basic Git familiarity**: Concepts like staging, committing, pushing, pulling, and resolving merge conflicts are fundamental to this workflow. If you have never used Git before, there is a learning curve, but this guide covers everything you need. - **Binary files are not ideal in Git**: Obsidian notes are plain text, which Git handles brilliantly. However, if you store large numbers of images, PDFs, or audio files in your vault, your repository size will grow quickly. Git is designed for text; for large binary assets, consider using Git LFS or a separate cloud storage solution. If you prefer a set-and-forget sync that works silently in the background, consider using Obsidian's official Sync service or community plugins like Remotely Save instead. But if you value control, auditability, cost efficiency, and a complete version history, the Git-based workflow is hard to beat. --- ## Important Notes Read these carefully before proceeding. Understanding these points will save you from common pitfalls later. - **This method does not provide automatic synchronization.** Every time you finish editing on one device, you must explicitly run `git add`, `git commit`, and `git push`. On the other device, you must run `git pull` to receive those changes. If you forget to push, your other device will not see your latest notes. If you forget to pull before editing, you risk creating a merge conflict. Developing a habit of syncing at the start and end of each session is critical. - **This approach requires basic Git knowledge.** You do not need to be a Git expert, but you should understand what commit, push, pull, and merge conflict mean. This guide explains each step, but if you run into issues, knowing how to read Git error messages and search for solutions will be valuable. - **Avoid editing the same note on two devices without syncing in between.** If you edit the same file on your PC and your phone without pushing and pulling between those edits, Git will encounter a merge conflict. Conflicts are not destructive; Git preserves both versions and asks you to choose. However, resolving conflicts takes time and can be confusing if you are not prepared for them. --- ## 1. Create a GitHub Account GitHub will serve as the remote host for your Obsidian vault. It stores a copy of your repository in the cloud, acting as the synchronization point between your PC and your phone. Both devices push and pull from the same GitHub repository, so changes made on either device are reflected on the other. - Go to [github.com](https://github.com) and sign up for a free account. The free tier includes unlimited private repositories, which is all you need for personal note synchronization. - After creating your account, create a new repository. Give it a recognizable name such as `obsidian-vault` or `my-notes`. - **Choose "Private" visibility.** Obsidian vaults often contain personal notes, journals, passwords, and other sensitive information. A private repository ensures that only you (and anyone you explicitly grant access to) can read or modify the contents. Do not use a public repository unless you are certain your vault contains nothing private. - **Do not initialize the repository with a README, .gitignore, or license** if you are going to push an existing vault. This avoids conflicts between GitHub's initial commit and your local files. You can add these later if needed. --- ## 2. Install Git and GitHub CLI on Your Computer Before you can use Git from the command line, you need to install it on your computer. Additionally, installing the GitHub CLI (`gh`) simplifies authentication so you do not have to manage SSH keys or personal access tokens for day-to-day operations on your PC. ### Installing Git - **Windows**: Download the installer from [git-scm.com](https://git-scm.com) and run it. During installation, you can accept the default options. The installer includes Git Bash, which provides a Unix-style terminal you can use throughout this guide. - **macOS**: Git comes pre-installed on most macOS versions. If it is not available, install it via Homebrew with `brew install git`, or download the installer from [git-scm.com](https://git-scm.com). - **Linux**: Install Git through your distribution's package manager. For Debian/Ubuntu, run `sudo apt install git`. For Fedora, run `sudo dnf install git`. After installation, verify it works by running: ```bash git --version ``` You should see output like `git version 2.x.x`. ### Installing GitHub CLI The GitHub CLI (`gh`) lets you authenticate with GitHub directly from the terminal without manually creating SSH keys or tokens for every operation. - Download it from [cli.github.com](https://cli.github.com) for your operating system. - On macOS, you can also install it via Homebrew: `brew install gh`. ### Authenticating with GitHub Once both tools are installed, authenticate with your GitHub account: ```bash gh auth login ``` This command will guide you through an interactive setup: 1. Choose **GitHub.com** as the platform. 2. Select **HTTPS** as the preferred protocol (this is the simplest option and works well with the GitHub CLI). 3. Choose **Login with a web browser** when prompted. The CLI will give you a one-time code. Open the URL it displays in your browser, paste the code, and authorize the application. Once authenticated, the GitHub CLI stores your credentials securely. You can now push to and pull from GitHub repositories without entering your password each time. --- ## 3. Convert Your Obsidian Vault into a Git Repository This step turns your existing Obsidian vault folder into a Git repository on your PC. This means Git will start tracking every file inside the vault, recording each change you make. ### Navigate to your vault folder Open a terminal (or Git Bash on Windows) and navigate to your Obsidian vault directory: ```bash cd /path/to/your/obsidian-vault ``` Replace `/path/to/your/obsidian-vault` with the actual path to your vault. For example: - **Windows**: `cd C:\Users\YourName\Documents\MyVault` - **macOS**: `cd ~/Documents/MyVault` - **Linux**: `cd ~/Documents/MyVault` If you are not sure where your vault is located, open Obsidian, go to Settings, and look at the "Vault location" field. It will show you the exact folder path. ### Create a .gitignore file Before initializing Git, it is a good idea to create a `.gitignore` file. This file tells Git which files or folders to ignore, so they are not tracked or uploaded to GitHub. Obsidian generates some internal files that you typically do not need to sync: ```plaintext # Obsidian workspace and configuration .obsidian/workspace.json .obsidian/workspace-mobile.json # System files .DS_Store Thumbs.db # Temporary files *.tmp *.swp ``` Create this file in the root of your vault directory. The `workspace.json` files store window layout and open tab state, which differ between your PC and phone. If you track them, they will cause frequent and unnecessary merge conflicts. ### Initialize Git Initialize a Git repository in the vault folder: ```bash git init ``` This command creates a hidden `.git` directory inside your vault. This directory stores all of Git's tracking data, including the full history of every change you commit. It does not modify any of your note files. ### Stage all files Add every file in the vault to Git's staging area. The staging area is a holding zone where you prepare changes before committing them: ```bash git add . ``` The dot (`.`) means "everything in the current directory and all subdirectories." Git will scan your vault, find every file, and mark them for inclusion in the next commit. You can verify what was staged by running `git status`. ### Create your first commit A commit is a snapshot of your vault at a specific point in time. It records the current state of all staged files and attaches a message describing the change: ```bash git commit -m "initial obsidian vault" ``` This creates your first commit, which represents the initial state of your vault. Think of it as the starting point in your vault's history. From now on, every commit you make will build on top of this one. ### Create the repository on GitHub If you have not already created a repository on GitHub, you can do it from the command line using the GitHub CLI: ```bash gh repo create obsidian-vault --private --source=. --remote=origin --push ``` This single command does three things at once: 1. Creates a **private** repository named `obsidian-vault` on your GitHub account. 2. Links your local repository to the remote repository under the name `origin`. 3. Pushes your initial commit to GitHub immediately. If you already created the repository on GitHub's website, link it manually instead: ```bash git branch -M main git remote add origin https://github.com/username/obsidian-vault.git ``` - `git branch -M main` renames your current branch to "main," which is the modern convention (older Git versions default to "master"). - `git remote add origin` tells Git where the remote repository lives. Replace the URL with your actual repository URL. ### Push to the remote repository If you used `gh repo create` with the `--push` flag, your code is already on GitHub. If not, push manually: ```bash git push -u origin main ``` The `-u` flag sets the remote repository as the "upstream" for your current branch. This is a one-time setup. After this, you can simply run `git push` and `git pull` without specifying the remote or branch name each time. You should now be able to visit your repository on GitHub and see all your vault files in the browser. --- ## 4. Android Setup: Install Termux To use Git on your Android phone, you need a terminal emulator. Termux is the most capable option available. It provides a full Linux environment on Android, complete with a package manager that can install Git and other tools. ### Why Termux and not other apps? There are other terminal apps on Android, but Termux stands out because it includes its own package ecosystem. You can install not just Git, but also tools like `ssh`, `nano`, and `vim` directly through its package manager. It is actively maintained and widely used in the Android development community. ### Installation - **Recommended**: Download Termux from [F-Droid](https://f-droid.org) or the [GitHub Releases page](https://github.com/termux/termux-app/releases). These versions are up to date and receive regular updates. - **Not recommended**: The Google Play Store version of Termux is outdated and unmaintained. It has known bugs and incompatibilities with newer Android versions. Avoid it if possible. ### Initial setup After installing Termux, open it and run the following command to update all packages to their latest versions: ```bash pkg update && pkg upgrade ``` This ensures you are starting from the most current and stable package versions, which reduces the chance of running into bugs during setup. --- ## 5. Grant Termux Storage Access By default, Termux cannot access your phone's files. You need to grant it storage permission so it can read and write to your device's shared storage, where Obsidian stores its vaults. ```bash termux-setup-storage ``` When you run this command, Android will display a system prompt asking whether you want to allow Termux access to your photos, media, and files. Tap **Allow**. If you accidentally deny the permission, you can grant it later by going to your phone's Settings, finding Termux under Apps, and enabling the Storage permission manually. After granting permission, Termux creates a `~/storage` directory containing symlinks to various locations on your phone's file system. The most important one for this guide is `~/storage/shared`, which points to your phone's primary shared storage (the same storage you see when you connect your phone to a computer via USB). --- ## 6. Navigate to the Obsidian Folder Now that Termux has storage access, navigate to where your Obsidian vault is (or will be) stored: ```bash cd ~/storage/shared ``` This command takes you to the root of your phone's shared internal storage. From there, navigate to the folder where you want your vault to live. If you already have an Obsidian vault on your phone, go to that directory: ```bash cd Obsidian ``` If you do not yet have an Obsidian vault on your phone and want to create one from the GitHub repository, you can run the clone command directly in `~/storage/shared`. Obsidian detects folders that contain Markdown files and offers to open them as vaults. To see the contents of the current directory, use: ```bash ls -la ``` This helps you confirm you are in the right place before proceeding. --- ## 7. Install Git on Termux Install Git through Termux's package manager: ```bash pkg install git ``` Termux uses the `pkg` command as a wrapper around `apt`, so this works similarly to installing packages on Debian/Ubuntu Linux. The command downloads Git and any dependencies it needs. After installation finishes, verify that Git is available and working: ```bash git --version ``` You should see output like `git version 2.x.x`. If you get a "command not found" error, make sure the installation completed without errors and try running `pkg update` before reinstalling. --- ## 8. Configure Git Identity on Termux Before making any commits on your phone, tell Git who you are. Git attaches your name and email to every commit you make, making it easy to see who changed what. This is especially important when syncing across devices, so you can distinguish edits made on your PC from those made on your phone. ```bash git config --global user.name "Your Name" git config --global user.email "your-email@example.com" ``` Use the same name and email you configured on your PC so that commits from both devices are attributed to the same person in the Git log. ### Configure credential storage To avoid entering your GitHub credentials on every `git push` or `git pull`, configure Git to store them: ```bash git config --global credential.helper store ``` This command saves your credentials in a plain text file (`~/.git-credentials`) inside Termux's private storage. The next time you authenticate, Git will read from this file automatically. **Security note**: The `store` helper saves credentials in plaintext. On a non-rooted phone, this is reasonably secure because Termux's internal storage is isolated from other apps. However, on a rooted device, another app with root access could potentially read this file. If you are concerned about this, you can use the `cache` helper instead, which stores credentials only in memory for a limited time: ```bash git config --global credential.helper cache ``` The `cache` helper keeps credentials in memory for 15 minutes by default. You can extend this duration with `git config --global credential.helper "cache --timeout=3600"` (1 hour). --- ## 9. Create a GitHub Personal Access Token GitHub no longer accepts account passwords for Git operations over HTTPS. Instead, you must use a **Personal Access Token (PAT)**. This is a long, random string that acts as a password with scoped permissions, meaning you can restrict what the token is allowed to do. There are two types of tokens on GitHub: - **Classic tokens**: Simpler to create, with broad permissions. Good for quick setup but offer less fine-grained control. - **Fine-grained tokens**: Newer and more secure. They let you restrict access to specific repositories and specific permissions. This is the recommended type for this guide. ### Creating a fine-grained token 1. Log in to [github.com](https://github.com) on your computer. 2. Click your profile picture in the top right corner, then click **Settings**. 3. In the left sidebar, scroll down and click **Developer settings**. 4. Click **Personal access tokens**, then click **Fine-grained tokens**. 5. Click **Generate new token**. 6. Give the token a descriptive name, such as "Obsidian Vault - Android." This helps you remember what the token is for if you need to manage it later. 7. Set an expiration date. GitHub will revoke the token after this date. You can choose 30 days, 60 days, 90 days, or a custom date. For personal use, 90 days is a reasonable balance between convenience and security. You will need to generate a new token when this one expires. 8. Under **Repository access**, select **Only select repositories**, then choose your Obsidian vault repository. This ensures the token can only access that one repository and nothing else on your account, which significantly reduces the impact if the token is ever compromised. 9. Under **Permissions**, expand **Repository permissions** and set **Contents** to **Read and Write**. This is the minimum permission needed to push and pull changes from your repository. 10. Click **Generate token** at the bottom of the page. 11. **Copy the token immediately and store it somewhere secure** (such as a password manager). GitHub only displays the token once. After you leave the page, there is no way to see it again. If you lose it, you must generate a new one. --- ## 10. Clone the Repository on Your Phone Now that Git is installed, configured, and you have a personal access token, clone your Obsidian vault repository from GitHub to your phone. Navigate to the folder where you want the vault to be stored (see step 6), then run: ```bash git clone https://github.com/username/obsidian-vault.git ``` Replace `username` and `obsidian-vault` with your actual GitHub username and repository name. When prompted for credentials: - **Username**: Enter your GitHub username. - **Password**: Paste your Personal Access Token. **Do not enter your GitHub account password; it will not work.** GitHub has disabled password authentication for Git operations. If you make a mistake entering the token, Git will reject the authentication. You can retry the clone command. If you previously configured `credential.helper store`, your credentials will be saved after the first successful authentication, so you will not need to enter them again. After the clone completes, a new folder matching the repository name will appear in your current directory. Verify the contents: ```bash ls obsidian-vault/ ``` You should see all your Obsidian notes and folders. ### Opening the vault in Obsidian 1. Open the Obsidian app on your phone. 2. Tap **Open folder as vault**. 3. Navigate to the folder where you cloned the repository and select it. 4. Obsidian will detect the Markdown files and configure the vault automatically. --- ## 11. Using Synchronization With both your PC and phone connected to the same GitHub repository, you can now synchronize changes between them. The key principle is simple: **always pull before you start editing, and always push after you finish editing.** Following this habit prevents the vast majority of merge conflicts. ### Synchronizing from PC to phone On your PC, after making changes to your notes: ```bash git add . git commit -m "update notes - added meeting notes for Tuesday" git push ``` Let's break down each command: - `git add .`: Stages all modified and new files. This tells Git which changes you want to include in the next commit. The dot means "all changes in the current directory and subdirectories." If you only want to include specific files, you can list them individually, for example: `git add "Daily Notes/2025-01-15.md"`. - `git commit -m "..."`: Creates a commit with a descriptive message. The `-m` flag lets you write the message inline. Good commit messages are short but specific. "update notes" is vague; "add meeting notes for Tuesday" is much more useful when scanning the history later. - `git push`: Uploads your commits to GitHub. Your changes are now visible to any other device that pulls from the same repository. On your phone, open Termux, navigate to the repo folder, and pull the changes: ```bash cd ~/storage/shared/Obsidian/obsidian-vault git pull ``` `git pull` downloads all new commits from GitHub and merges them into your local copy. After this command finishes, the files on your phone match the files on your PC. ### Synchronizing from phone to PC The process works identically in reverse. After editing notes on your phone: ```bash cd ~/storage/shared/Obsidian/obsidian-vault git add . git commit -m "add quick note from phone" git push ``` Then on your PC: ```bash git pull ``` --- ## 12. Making Changes on Your Phone and Pushing Them Since synchronization is bidirectional, you can make full edits on your phone and push them back to GitHub. This is useful for adding quick notes, editing existing ones, or reviewing your notes on the go. ### Navigate to the repo folder Open Termux and navigate to the cloned repository: ```bash cd ~/storage/shared/Obsidian/obsidian-vault ``` If you close Termux and reopen it later, you will likely need to navigate to this folder again. You can create a shell alias to make this faster: ```bash echo "alias vault='cd ~/storage/shared/Obsidian/obsidian-vault'" >> ~/.bashrc source ~/.bashrc ``` After this, simply typing `vault` will take you directly to the repository folder. ### Stage your changes After making edits in Obsidian, return to Termux and stage the changes: ```bash git add . ``` You can check what changed before committing by running: ```bash git status ``` This shows you which files are modified, which are new, and which are staged. You can also see a diff of the changes: ```bash git diff ``` This displays the exact lines that were added or removed, which is helpful for reviewing your edits before committing. ### Create a commit Record the changes with a descriptive commit message: ```bash git commit -m "add quick note from phone" ``` Or, if you want to write a longer, more detailed message, omit the `-m` flag: ```bash git commit ``` This opens your default text editor (usually `nano` in Termux) where you can write a multi-line commit message. The first line is the subject, followed by a blank line, then the body with more details. ### Push to remote Send the commit to GitHub: ```bash git push ``` ### The golden rule: pull before you push Before pushing, especially if it has been a while since your last sync, it is good practice to pull first: ```bash git pull git add . git commit -m "merge remote changes before push" git push ``` Or more simply, just pull before making any new edits. This ensures your local copy is up to date with GitHub and reduces the chance of merge conflicts. Think of it as refreshing your working copy before you start making changes. --- ## 13. Handling Merge Conflicts Despite your best efforts, merge conflicts will occasionally happen. A conflict occurs when you edit the same file on both devices without syncing in between. Git cannot automatically merge these changes because it does not know which version you want to keep. ### What a conflict looks like When you run `git pull` and a conflict is detected, Git will output a message like: ```plaintext CONFLICT (content): Merge conflict in Daily Notes/2025-01-15.md Automatic merge failed; fix conflicts and then commit the result. ``` If you open the conflicting file, you will see conflict markers: ```plaintext <<<<<<< HEAD This is the version on your current device. ======= This is the version from the remote repository. >>>>>>> origin/main ``` The text between `<<<<<<< HEAD` and `=======` is your local version. The text between `=======` and `>>>>>>> origin/main` is the version from GitHub. ### How to resolve a conflict 1. Open the conflicting file in any text editor (or in Obsidian itself). 2. Decide which version you want to keep. You can keep one side, the other, or combine both. 3. Remove the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) and edit the content to your liking. 4. Save the file. 5. Stage and commit the resolution: ```bash git add "Daily Notes/2025-01-15.md" git commit -m "resolve merge conflict in daily note" git push ``` On the other device, simply run `git pull` to receive the resolved version. ### Tips to minimize conflicts - **Pull before you edit**: Always run `git pull` before opening Obsidian on either device. This ensures you are working on the latest version. - **Push after you edit**: Always run `git push` after finishing a writing session. This keeps GitHub up to date. - **Use smaller, more frequent commits**: Instead of one massive commit at the end of the day, commit after each meaningful change. This makes conflicts smaller and easier to resolve. - **Avoid editing the same note simultaneously on both devices**: If you know you edited something on your PC that you also need on your phone, push those changes before switching devices. --- ## 14. Automating Sync with Shell Aliases (Optional) To reduce the number of commands you type each time, you can create shell aliases that combine common Git operations into single commands. Add these to your `~/.bashrc` file on Termux (or your `.bashrc` / `.zshrc` on your PC): ```bash alias gsync='git add . && git commit -m "sync $(date +%Y-%m-%d\ %H:%M)" && git push' alias gpull='git pull' ``` After running `source ~/.bashrc`, you can simply type `gsync` to stage, commit, and push in one step, or `gpull` to pull the latest changes. The `$(date +%Y-%m-%d\ %H:%M)` part automatically inserts the current date and time into the commit message, so you always know when each sync happened. --- ## 15. Automating Sync with Termux Tasks Shell aliases are convenient, but you can take automation further by creating dedicated sync scripts and leveraging Termux's task ecosystem. This section covers scripts, home screen shortcuts, boot-time sync, and scheduled auto-sync. ### Create a sync script Instead of typing multiple Git commands every time, create a single script that handles the full sync workflow. Create a directory for your scripts: ```bash mkdir -p ~/bin ``` Then create a script called `obsidian-sync`: ```bash cat > ~/bin/obsidian-sync << 'EOF' #!/data/data/com.termux/files/usr/bin/bash VAULT_DIR="$HOME/storage/shared/Obsidian/obsidian-vault" LOG_FILE="$HOME/sync-log.txt" cd "$VAULT_DIR" || { echo "Vault directory not found"; exit 1; } echo "$(date '+%Y-%m-%d %H:%M:%S') — Starting sync" >> "$LOG_FILE" git pull --rebase >> "$LOG_FILE" 2>&1 if [ -n "$(git status --porcelain)" ]; then git add . git commit -m "sync $(date '+%Y-%m-%d %H:%M')" >> "$LOG_FILE" 2>&1 git push >> "$LOG_FILE" 2>&1 echo "$(date '+%Y-%m-%d %H:%M:%S') — Sync completed with changes" >> "$LOG_FILE" else echo "$(date '+%m-%d-%Y %H:%M:%S') — Already up to date, no changes" >> "$LOG_FILE" fi EOF chmod +x ~/bin/obsidian-sync ``` This script does three things in order: 1. **Pulls** the latest changes from GitHub with rebase to keep your history linear. 2. **Checks** whether you have local changes. If there are none, it skips the commit and push steps entirely. 3. **Commits and pushes** only if there are local changes to sync. A log file (`~/sync-log.txt`) records every sync attempt with timestamps, so you can review what happened if something goes wrong. You can run the script with: ```bash ~/bin/obsidian-sync ``` ### Create separate pull and push scripts Sometimes you only want to pull or push without the full sync cycle. Create two more scripts for those cases: ```bash cat > ~/bin/obsidian-pull << 'EOF' #!/data/data/com.termux/files/usr/bin/bash cd "$HOME/storage/shared/Obsidian/obsidian-vault" || exit 1 git pull EOF cat > ~/bin/obsidian-push << 'EOF' #!/data/data/com.termux/files/usr/bin/bash cd "$HOME/storage/shared/Obsidian/obsidian-vault" || exit 1 git add . git commit -m "sync $(date '+%Y-%m-%d %H:%M')" git push EOF chmod +x ~/bin/obsidian-pull ~/bin/obsidian-push ``` Now you can type `obsidian-pull` to fetch changes or `obsidian-push` to send your edits. ### Add scripts to PATH To run these scripts from anywhere without typing the full path, add `~/bin` to your PATH: ```bash echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc source ~/.bashrc ``` ### Home screen shortcuts with Termux:Widget Termux:Widget is a companion app that lets you create home screen shortcuts for Termux scripts. This means you can sync your vault with a single tap, without even opening Termux. 1. Install **Termux:Widget** from [F-Droid](https://f-droid.org) or the [GitHub Releases page](https://github.com/termux/termux-widget/releases). Make sure it matches the same source you installed Termux from (F-Droid or GitHub). 2. Create the widget scripts directory: ```bash mkdir -p ~/.termux/widget ``` 3. Create a widget script for syncing: ```bash cat > ~/.termux/widget/Obsidian\ Sync << 'EOF' #!/data/data/com.termux/files/usr/bin/bash ~/bin/obsidian-sync echo "Sync complete. Press Enter to close." read EOF chmod +x ~/.termux/widget/Obsidian\ Sync ``` 4. Add the widget to your home screen: - Long-press an empty area on your Android home screen. - Tap **Widgets**. - Find **Termux:Widget** and drag it to your home screen. - Select **Obsidian Sync** from the list. Now you can sync your Obsidian vault with a single tap from your home screen. ### Auto-sync on boot with Termux:Boot Termux:Boot is another companion app that runs scripts automatically when your phone starts up. You can use it to pull the latest changes every time your phone reboots, ensuring you always start with up-to-date notes. 1. Install **Termux:Boot** from [F-Droid](https://f-droid.org) or the [GitHub Releases page](https://github.com/termux/termux-boot/releases). 2. Create the boot scripts directory: ```bash mkdir -p ~/.termux/boot ``` 3. Create a boot script that pulls your vault on startup: ```bash cat > ~/.termux/boot/obsidian-boot-sync << 'EOF' #!/data/data/com.termux/files/usr/bin/bash sleep 30 cd "$HOME/storage/shared/Obsidian/obsidian-vault" || exit 1 git pull >> "$HOME/sync-log.txt" 2>&1 echo "$(date '+%Y-%m-%d %H:%M:%S') — Boot sync completed" >> "$HOME/sync-log.txt" EOF chmod +x ~/.termux/boot/obsidian-boot-sync ``` The `sleep 30` delay gives your phone time to establish a network connection before the script attempts to reach GitHub. If your phone connects to Wi-Fi slowly, increase this value. 4. Open the Termux:Boot app once to register it. You will see a Toast notification confirming it is active. After this, the script will run automatically every time your phone restarts. ### Scheduled auto-sync with cron For truly automatic synchronization at regular intervals, you can set up a cron job inside Termux. This pulls (and optionally pushes) your vault on a schedule, even if you forget to do it manually. 1. Install the cron daemon: ```bash pkg install cronie ``` 2. Start the cron daemon: ```bash crond ``` 3. Edit the crontab to define your schedule: ```bash crontab -e ``` 4. Add a line to sync every 30 minutes while your phone is awake: ```plaintext */30 * * * * /data/data/com.termux/files/home/bin/obsidian-sync ``` 5. Save and exit. If you are using the default `nano` editor, press `Ctrl+O`, `Enter`, then `Ctrl+X`. To make cron start automatically when Termux opens, add it to your `~/.bashrc`: ```bash echo 'crond' >> ~/.bashrc ``` **Note:** Cron in Termux only runs while Termux is active in the foreground or has a persistent notification. Android aggressively kills background processes to save battery. To keep Termux running, swipe down from the notification area and look for the Termux persistent notification. If you do not see it, open Termux, run `termux-wake-lock`, and then enable the persistent notification in Termux's notification settings. This prevents Android from suspending Termux and stopping your scheduled syncs. ### Summary of automation options | Method | Effort | Best for | |---|---|---| | Shell aliases (`gsync`, `gpull`) | Low | Quick manual sync from the terminal | | Sync scripts (`obsidian-sync`) | Low | Reliable one-command sync with logging | | Termux:Widget | Medium | Tapping a home screen shortcut to sync | | Termux:Boot | Medium | Auto-pulling changes on phone restart | | Cron job | Medium | Periodic automatic sync in the background | You can combine these approaches. For example, use Termux:Boot to pull on restart, a cron job for periodic background syncs, and Termux:Widget for on-demand pushes when you finish editing. --- ## 16. Adding Obsidian-Specific Files to .gitignore (Recommended) Obsidian generates several internal files that change frequently and are specific to each device. Tracking them in Git causes unnecessary noise and frequent conflicts. Consider adding the following to your `.gitignore`: ```plaintext # Obsidian workspace - stores open tabs and panel layout (different per device) .obsidian/workspace.json .obsidian/workspace-mobile.json # Obsidian plugins that are device-specific .obsidian/plugins/obsidian-local-rest-api/ .obsidian/plugins/phone-clipboard/ # OS-generated files .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db # Temporary editor files *.tmp *.swp *~ ``` The `workspace.json` files in particular cause constant merge conflicts because they record which tabs and panes you have open. Since these differ between your PC and phone, it is better to exclude them. Obsidian recreates these files automatically if they are missing. After updating your `.gitignore`, remove any previously tracked files that should now be ignored: ```bash git rm -r --cached .obsidian/workspace.json .obsidian/workspace-mobile.json git add .gitignore git commit -m "update gitignore and remove workspace files from tracking" git push ``` --- ## Conclusion With this setup, you have a robust, version-controlled synchronization system for your Obsidian notes: - **Full version history**: Every change is recorded as a commit. You can revert to any previous version, compare differences, and see exactly what changed and when. This is far more powerful than a simple backup. - **Bidirectional sync**: Both your PC and phone can push and pull changes. You can start a note on your computer, continue it on your phone, and finish it back on your computer. - **Complete audit trail**: The Git log provides a detailed timeline of every modification, including who made the change and the commit message explaining why. - **Data resilience**: Your notes exist in three places: your PC, your phone, and GitHub. If any one device fails, the other two still have a complete copy. Losing all three simultaneously would require a very specific disaster. - **Cost efficiency**: Git and GitHub's free tier handle personal note synchronization without any subscription fees. Remember to develop a consistent sync habit: **pull before you edit, push after you edit.** And if you want to reduce the manual effort, set up the Termux task automation described in section 15 — scripts, widgets, boot hooks, and cron jobs can handle most of the repetitive work for you. This simple routine prevents most conflicts and keeps your vault consistent across all devices. ### [Docker Usage Guide](https://sametcc.me/gist/docker-commands) --- title: "Docker Usage Guide" publishedAt: "2024-08-25" updatedAt: "2026-04-27" summary: "Comprehensive guide to Docker commands for containerization, image management, and container orchestration with practical examples and best practices." tags: [Docker, Containers, DevOps, CLI, Containerization] language: "en" type: "gist" status: "published" --- # Docker Usage Guide This document collects practical Docker commands you can use in daily development and troubleshooting. It starts with the basics and then moves into more advanced but still broadly useful commands. ## Core Concepts - **Image**: A read-only template used to create containers. - **Container**: A running or stopped instance of an image. - **Volume**: Persistent storage managed by Docker. - **Network**: A virtual network used for container-to-container or host-to-container communication. ## Basic Commands ### Check Docker Version and Info ```bash docker --version docker version docker info ``` - `docker --version` shows the installed CLI version. - `docker version` shows both client and server versions. - `docker info` shows engine details, storage driver, container count, and more. ### Search and Download Images ```bash docker search nginx docker pull nginx docker pull node:20-alpine ``` - Use `search` to discover images on Docker Hub. - Use `pull` to download a specific image and tag. ### List Local Images ```bash docker images docker image ls ``` ### Run a Container ```bash docker run nginx docker run -d nginx docker run -d -p 8080:80 --name web nginx ``` - `-d` runs the container in detached mode. - `-p 8080:80` maps host port `8080` to container port `80`. - `--name web` gives the container a readable name. ### List Running and All Containers ```bash docker ps docker ps -a docker container ls docker container ls -a ``` ### Stop, Start, Restart, Remove ```bash docker stop web docker start web docker restart web docker rm web docker rm -f web ``` - `rm -f` force removes a running container. ### Remove Images ```bash docker rmi nginx docker image rm nginx ``` ## Interactive Usage ### Start a Shell in a New Container ```bash docker run -it ubuntu bash docker run -it alpine sh ``` - `-it` combines interactive and TTY modes. ### Open a Shell in an Existing Container ```bash docker exec -it web sh docker exec -it web bash ``` Use `exec` when the container is already running and you want to inspect or debug it. ## Logs and Monitoring ### View Container Logs ```bash docker logs web docker logs -f web docker logs --tail 100 web docker logs -f --tail 50 web ``` - `-f` follows live log output. - `--tail` limits how many recent lines are shown. ### View Resource Usage ```bash docker stats docker stats web ``` This is useful for checking CPU, memory, network, and block I/O usage. ## Inspecting Containers and Images ### Detailed Inspection ```bash docker inspect web docker inspect nginx ``` This returns JSON with configuration, mounts, ports, IP address, and metadata. ### Useful Inspect Filters ```bash docker inspect -f '{{.NetworkSettings.IPAddress}}' web docker inspect -f '{{json .Mounts}}' web docker inspect -f '{{.State.Status}}' web ``` These filtered templates are much faster than reading the full JSON output. ### View Port Mappings ```bash docker port web ``` ## Working with Files ### Copy Files Between Host and Container ```bash docker cp ./local.txt web:/tmp/local.txt docker cp web:/var/log/nginx/access.log ./access.log ``` ### Mount a Host Directory ```bash docker run -d -p 8080:80 -v $(pwd):/app node:20-alpine ``` This mounts the current host directory into `/app` inside the container. ## Volumes ### Create and List Volumes ```bash docker volume create app_data docker volume ls ``` ### Use a Volume ```bash docker run -d --name db -v app_data:/var/lib/mysql mysql:8 ``` ### Inspect and Remove Volumes ```bash docker volume inspect app_data docker volume rm app_data ``` Volumes are preferred over container-local storage when data must survive container recreation. ## Networks ### List and Create Networks ```bash docker network ls docker network create app_net ``` ### Attach a Container to a Network ```bash docker run -d --name api --network app_net nginx docker network connect app_net web ``` ### Inspect and Remove Networks ```bash docker network inspect app_net docker network rm app_net ``` Custom networks are useful when multiple containers need to communicate by container name. ## Building Images ### Build from a Dockerfile ```bash docker build -t my-app . docker build -t my-app:1.0 . docker build -f Dockerfile.dev -t my-app:dev . ``` ### Tag and Push Images ```bash docker tag my-app:1.0 username/my-app:1.0 docker push username/my-app:1.0 ``` ## Advanced but Useful Commands ### Show Only Specific Fields with `--format` ```bash docker ps --format 'table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.Ports}}' docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' ``` This is very useful when you want readable CLI output without manual filtering. ### Filter Results ```bash docker ps -a --filter "name=web" docker images --filter "dangling=true" docker volume ls --filter "name=app" ``` ### Inspect Container Processes ```bash docker top web ``` This helps when debugging containers that appear healthy but are not doing the expected work. ### See Container Changes ```bash docker diff web ``` This shows filesystem changes inside a container since it was created. ### Commit a Container as an Image ```bash docker commit web debug/web:snapshot ``` This is helpful for temporary debugging workflows, though rebuilding from a Dockerfile is usually better for long-term use. ### Save and Load Images ```bash docker save -o my-app.tar my-app:1.0 docker load -i my-app.tar ``` Useful when moving images between environments without pulling from a registry. ### Export and Import Container Filesystems ```bash docker export web -o web.tar docker import web.tar imported-web:latest ``` Unlike `save` and `load`, this works with container filesystems instead of image layers. ### Run Commands in One-Off Containers ```bash docker run --rm alpine ls -la docker run --rm node:20-alpine node -v ``` - `--rm` removes the container automatically after the command finishes. ### Set Environment Variables ```bash docker run -e NODE_ENV=production my-app docker run --env-file .env my-app ``` ### Run PostgreSQL with Environment Configuration ```bash docker run -d \ --name postgres-db \ -e POSTGRES_DB=myapp \ -e POSTGRES_USER=myuser \ -e POSTGRES_PASSWORD=mypassword \ -p 5432:5432 \ -v postgres_data:/var/lib/postgresql/data \ postgres:16 ``` This starts PostgreSQL with a database, user, password, port mapping, and a persistent volume. Example using an environment file: ```bash docker run -d \ --name postgres-db \ --env-file .env.postgres \ -p 5432:5432 \ -v postgres_data:/var/lib/postgresql/data \ postgres:16 ``` Example `.env.postgres` content: ```env POSTGRES_DB=myapp POSTGRES_USER=myuser POSTGRES_PASSWORD=mypassword ``` Useful PostgreSQL follow-up commands: ```bash docker logs -f postgres-db docker exec -it postgres-db psql -U myuser -d myapp docker exec -it postgres-db sh ``` If you want a temporary PostgreSQL container with no persisted data, remove the volume mount: ```bash docker run -d \ --name postgres-temp \ -e POSTGRES_DB=myapp \ -e POSTGRES_USER=myuser \ -e POSTGRES_PASSWORD=mypassword \ -p 5432:5432 \ postgres:16 ``` ### Limit CPU and Memory ```bash docker run -d --memory="512m" --cpus="1.5" my-app ``` This is useful for testing behavior under constrained resources. ### Restart Policies ```bash docker run -d --restart unless-stopped nginx docker run -d --restart always nginx ``` This is practical for local servers and lightweight long-running services. ### Health Checks ```bash docker run -d \ --health-cmd="curl -f http://localhost || exit 1" \ --health-interval=30s \ --health-timeout=5s \ --health-retries=3 \ nginx ``` Health checks help detect containers that are running but not actually ready. ## Cleanup Commands ### Remove Stopped Containers ```bash docker container prune ``` ### Remove Unused Images ```bash docker image prune docker image prune -a ``` ### Remove Unused Volumes ```bash docker volume prune ``` ### Remove Unused Networks ```bash docker network prune ``` ### Clean Everything Unused ```bash docker system prune docker system prune -a --volumes ``` Be careful with prune commands because they can remove data you still need. ### Remove All Containers Shown by `docker ps -a` ```bash docker rm -f $(docker ps -aq) ``` This force-removes every container, including running ones. ### Remove All Containers and Related Artifacts If you want to remove all containers and also delete unused images, volumes, networks, and build cache: ```bash docker rm -f $(docker ps -aq) docker system prune -a --volumes -f ``` If you want a more explicit cleanup flow: ```bash docker rm -f $(docker ps -aq) docker volume rm $(docker volume ls -q) docker network prune -f docker image prune -a -f docker builder prune -a -f ``` Use the explicit version carefully. Removing all volumes deletes persisted database and application data. ### Safer Variant for Scripts If there may be no containers or no volumes, this avoids command errors: ```bash docker ps -aq | xargs -r docker rm -f docker volume ls -q | xargs -r docker volume rm docker system prune -a --volumes -f ``` ## Docker Compose Essentials If Docker Compose is available through the Docker CLI plugin: ```bash docker compose up docker compose up -d docker compose down docker compose ps docker compose logs -f docker compose exec app sh docker compose build ``` These are the most commonly used commands for multi-container applications. ### Docker Watch For most development workflows, Docker Watch is used through `docker compose watch`. ```bash docker compose watch docker compose watch app docker compose watch --no-up docker compose watch --dry-run ``` - `docker compose watch` watches the configured services for file changes. - `docker compose watch app` watches only the `app` service. - `--no-up` skips the initial build and startup step. - `--dry-run` shows what would happen without applying changes. Docker Watch is especially useful in development because it can sync changed files into the container or trigger a rebuild automatically. Example `compose.yml` configuration: ```yaml services: app: build: . ports: - "3000:3000" command: npm run dev develop: watch: - action: sync path: . target: /app ignore: - node_modules/ - action: rebuild path: package.json ``` What this example does: - Changes in the project folder are synced into `/app` in the container. - Changes to `package.json` trigger an image rebuild. - `node_modules/` is ignored to avoid unnecessary sync noise. Typical workflow: ```bash docker compose up -d docker compose watch ``` This is a good alternative to bind mounts when you want more controlled file sync behavior. ## Practical Troubleshooting Examples ### Check Why a Container Exits Immediately ```bash docker ps -a docker logs docker inspect -f '{{.State.ExitCode}}' ``` ### Check Network Reachability Between Containers ```bash docker exec -it api sh ping web ``` ### Check Mounted Files ```bash docker inspect -f '{{json .Mounts}}' web ``` ### Check Real Startup Command ```bash docker inspect -f '{{.Config.Cmd}}' web docker inspect -f '{{.Config.Entrypoint}}' web ``` ## Recommended Daily Commands If you only remember a short set, these are the most useful in daily work: ```bash docker ps -a docker logs -f docker exec -it sh docker inspect docker build -t my-app . docker run -d -p 8080:80 --name my-app my-app docker compose up -d docker system prune ``` ## Notes - Prefer named containers for easier management. - Prefer volumes for persistent data. - Prefer `--rm` for short-lived utility containers. - Use `inspect`, `logs`, and `exec` first when debugging. - Use prune commands carefully in environments with important local data. ### [Hiretea](https://sametcc.me/project/hiretea) --- title: Hiretea publishedAt: "2026-04-15" summary: A self-hosted platform for running technical assessments end to end, bundling Gitea as the code host and PostgreSQL as the data store with no external SaaS dependencies. tags: [TypeScript, Next.js, Prisma, PostgreSQL, Gitea, Docker, Self-Hosted, Technical Assessments, Recruiting] language: "en" type: "project" status: "published" --- # Hiretea Hiretea is a self-hosted platform for running technical assessments end to end. [GitHub Repository](https://github.com/sametcn99/hiretea) | [Releases](https://github.com/sametcn99/hiretea/releases) ## Overview Hiretea is a self-hosted platform for running technical assessments end to end. Define case templates against your own Gitea repositories, provision candidate accounts, assign per-candidate working repositories, capture reviewer signal, and keep an auditable trail of every state transition. It bundles Gitea as the code host and uses PostgreSQL as the data store — everything runs behind your own firewall, with no external SaaS dependencies. ## Who Is It For? - **Hiring teams** who want full control over their technical assessment pipeline without sending candidate data to third-party services. - **Engineering managers & recruiters** who need structured case assignments, reviewer guides, rubrics, and decision tracking in one place. - **Candidates** who receive a repository to work in, push code via standard `git`, and optionally open pull requests to submit their work for review. ## How It Works 1. **Bootstrap** — On first boot, the stack automatically sets up PostgreSQL, initializes Gitea with an admin account, registers an OAuth application, applies the database schema, and seeds the first workspace admin. A manual `/setup` page is also available as a fallback. 2. **Create templates** — Admins define case templates that point to Git repositories. A template can provision a brand-new repo, link an existing one, or copy from an existing repository. Each template carries reviewer assignments, review guides, and rubric criteria with weighted scores. 3. **Provision candidates** — Recruiters provision candidate accounts, which automatically creates a Gitea user with an initial password and a linked identity record. Invite links (with token hashing, expiration, and revocation) are generated for onboarding. 4. **Assign cases** — Assigning a template to a candidate creates a working repository (mirrored from the template), grants the candidate write access, registers a webhook for status tracking, and persists the case row in `READY` status. 5. **Candidate works** — The candidate pushes code to their working repository. Webhook deliveries automatically advance the case status: first push → `IN_PROGRESS`, review-submission PR → `REVIEWING`. 6. **Review & decide** — Reviewers access the review workflow, see the template guide and rubric, leave evaluation notes with scores and summaries, and finalize the case with a decision (`ADVANCE`, `HOLD`, or `REJECT`). 7. **Audit** — Every meaningful mutation (provisioning, status changes, access grants, invite lifecycle, webhook deliveries) is recorded in an append-only audit log. ## Key Features - **Case Templates**: Create reusable challenge definitions from Git repositories (provision new, link existing, or copy from existing). - **Candidate Provisioning**: Provision candidate accounts on Gitea, assign working repositories, and manage per-permission access grants. - **Invitations**: Token-based invite links for candidates and recruiters, with hash-at-rest tokens, expiration, revocation, and resend tracking. - **Review Workflow**: Assign reviewers, attach evaluation notes with rubric criteria and weighted scores, and track decisions (advance / hold / reject). - **Webhook-Driven Status Tracking**: Candidate case statuses advance automatically based on pushes and pull requests via signed Gitea webhooks. - **Audit Trail**: Every meaningful mutation is recorded in an append-only audit log, viewable from the dashboard. - **Candidate Completion Tracking**: Cases can be marked complete manually or automatically when deadlines pass, with observable login tracking from Gitea. - **Dark-Mode-First UI**: Built with Radix Themes, English-only interface. ## Quick Start ### Prerequisites - [Docker](https://docs.docker.com/get-docker/) and Docker Compose - [Bun](https://bun.sh/) (for running helper scripts) ### Setup 1. Copy the sample environment file: `cp .env.example .env` 2. Edit `.env` — at minimum, change these secrets for any non-local deployment: `NEXTAUTH_SECRET`, `BOOTSTRAP_TOKEN`, `GITEA_ADMIN_PASSWORD`, `GITEA_SECRET_KEY`, `GITEA_INTERNAL_TOKEN`, `GITEA_WEBHOOK_SECRET`. 3. Start the stack: `bun run docker:up` 4. Open [http://localhost:3000](http://localhost:3000) for Hiretea. 5. Open [http://localhost:3001](http://localhost:3001) for Gitea. Startup is fully automatic: PostgreSQL is provisioned, Gitea is initialized with an admin account and OAuth application, the database schema is applied, and the first workspace admin is seeded. ### Default Ports | Service | URL | Purpose | | ---------- | ------------------------ | ------------------ | | Hiretea | http://localhost:3000 | Application | | Gitea HTTP | http://localhost:3001 | Browser-facing Git | | Gitea SSH | ssh://git@localhost:2221 | Candidate git push | | PostgreSQL | localhost:5432 | Shared database | ## Technology Stack - **TypeScript / Next.js** - **Prisma** - **PostgreSQL 16** - **Gitea** (bundled code host) - **Radix Themes** (UI) - **Docker Compose** (orchestration) ### [Booking Calendar (Self-Hosted)](https://sametcc.me/project/booking-calendar) --- title: "Booking Calendar (Self-Hosted)" publishedAt: "2026-04-04" summary: A self-hosted PWA designed for single-admin appointment management, running on your own server with shareable booking links, smart notifications, and integrations for webhooks, CalDAV, and iCal. tags: [TypeScript, React, Bun, TypeORM, SQLite, PWA, Self-Hosted, Calendar, Booking Platform, Webhooks, SMTP] language: "en" type: "project" status: "published" --- # Booking Calendar (Self-Hosted) 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. [Documentation](https://booking-calendar-docs.vercel.app/) | [GitHub Repository](https://github.com/sametcn99/booking-calendar) | [Releases](https://github.com/sametcn99/booking-calendar/releases) | [Container Image](https://github.com/sametcn99/booking-calendar/pkgs/container/booking-calendar) ## Key Features - **Admin Control**: Full dashboard for managing slots, appointments, and unique booking links. - **Guest Experience**: Streamlined public booking page with mobile-first design. - **Conflict Prevention**: Built-in double-booking prevention with intelligent slot overlap checks. - **Smart Notifications**: - Email alerts with `.ics` calendar attachments for instant syncing. - Token-based one-click cancellation links for guests. - Real-time Web Push notifications for administrators. - **Advanced Sharing**: Public appointment detail pages with persistent shareable links. - **Community Events**: Support for community-driven events with public approval workflows. - **Integrations**: - **Webhooks**: HMAC-signed notifications for Discord, Slack, or custom automations. - **CalDAV**: Import remote busy times anonymously and write approved bookings back to your own calendar. - **iCal Export**: Export your entire schedule or specific ranges to external calendars. - **Performance & Security**: - **Native PWA**: Installable on both mobile and desktop. - **Rate Limiting**: IP-based protection against brute-force and spam. - **Async Processing**: Asynchronous email delivery to ensure zero-latency booking responses. - **Privacy**: Self-hosted SQLite persistence — your data never leaves your server. ## Quick Links - [Screenshots](https://booking-calendar-docs.vercel.app/pages/screenshots) - [Getting Started](https://booking-calendar-docs.vercel.app/guide/getting-started) - [Docker Deployment Guide](https://booking-calendar-docs.vercel.app/guide/deployment-docker) - [Environment Variables](https://booking-calendar-docs.vercel.app/guide/configuration) - [Contributing Guide](https://booking-calendar-docs.vercel.app/contributing) ## Technology Stack - **Frontend**: React 19, TypeScript, Base Web, Vite. - **Backend**: Bun native HTTP server, Layered Architecture. - **Database**: TypeORM + SQLite (file-based persistence). - **Notifications**: Nodemailer, Web Push (VAPID), Webhooks. ### [DreamTUI](https://sametcc.me/project/dreamtui) --- title: DreamTUI publishedAt: "2026-03-17" summary: A terminal-based generative dream engine that transforms written dreams into full-screen, animated ASCII art experiences using a two-stage AI pipeline. tags: [TypeScript, TUI, ASCII Art, AI, OpenAI, Anthropic, Google Gemini, Groq, Ollama, Terminal Application] language: "en" type: "project" status: "published" --- # DreamTUI A terminal-based generative dream engine. [GitHub Repository](https://github.com/sametcn99/dreamtui) | [Releases](https://github.com/sametcn99/dreamtui/releases) ## Overview DreamTUI transforms your written dreams into full-screen, animated ASCII art experiences. It uses a sophisticated two-stage AI pipeline to interpret the semantics of your dream and generate a procedural visualization that plays out directly in your terminal. > This is an experimental hobby project created to test the concept of AI-driven > ASCII visualization. It is currently in a prototype stage (and frankly, it > will probably stay that way forever). However, the project is open source and > contributions are highly welcome! ## Features ### AI-Powered Interpretation - **Dream Booster**: Enhances simple inputs into vivid, descriptive narratives. - **Semantic Analysis**: Extracts mood, symbols, tempo, and atmosphere from your text. ### ASCII Rendering Engine - **Layered Composition**: Combines background noise, symbols, and distortion effects. - **Procedural Animation**: Real-time animation loop reacting to dream parameters. - **Dynamic Distortions**: Visual wobbles and shifts representing dream instability. ### Modern TUI (Terminal User Interface) - **Interactive Screens**: Dedicated screens for Input, Animation, and Settings. - **Responsive Design**: Adapts gracefully to terminal resizing. - **Keyboard Controls**: Full keyboard navigation support. ### Flexible AI Providers Supports multiple LLM providers, configurable via the built-in Settings UI: - OpenAI - Anthropic - Google Gemini - Groq - Ollama (Local) ## Getting Started ### Prerequisites - [Bun](https://bun.sh/) (v1.0 or later recommended) - A terminal with TrueColor support. ### Installation ```bash git clone https://github.com/sametcn99/dreamtui.git cd dreamtui bun install bun start ``` ## Usage ### 1. The Input Screen When the app launches, you will see the Input Screen. - **Type your dream**: Enter a description of a dream (e.g., "I was flying over a neon city made of glass"). - **Submit**: Press `Enter` to start the dream generation process. - **Settings**: Press `Ctrl+S` to open the configuration menu. ### 2. The Dream Experience Once generated, the visualization will start automatically. - **Movement**: The ASCII art flows according to the dream's tempo (calm, chaotic, fast, slow). - **Controls**: - `Space`: Pause/Resume animation. - `R`: Restart the dream loop. - `Esc`: Exit back to the Input Screen. ### 3. Settings Configuration Access settings by pressing `Ctrl+S` on the Input Screen. - **Provider**: Select your preferred AI backend (e.g., OpenAI, Anthropic). - **API Key**: Enter your API key securely (stored locally). - **Model**: (Optional) Override the default model (e.g., `gpt-4o-mini`, `claude-3-opus`). - **Navigation**: Use `Tab` to cycle fields, `Ctrl+S` to Save, `Esc` to go back. ## Technology Stack - **TypeScript** - **Bun** - **OpenTUI** (terminal UI framework) - **Multiple LLM Providers** (OpenAI, Anthropic, Google Gemini, Groq, Ollama) ### [HTWind](https://sametcc.me/project/htwind) --- title: HTWind publishedAt: "2026-03-15" summary: A highly customizable, HTML-based widget manager for Windows built with .NET and WPF, bringing web tools and system helpers directly to your desktop with native PowerShell execution support. tags: [C#, .NET, WPF, XAML, HTML Widgets, Desktop Application, Windows, PowerShell, Fluent UI, Widget Manager] language: "en" type: "project" status: "published" --- # HTWind The missing HTML-based widget manager created with .NET. [Homepage](https://htwind.vercel.app/) | [GitHub Repository](https://github.com/sametcn99/HTWind) | [Releases](https://github.com/sametcn99/HTWind/releases) | [Microsoft Store](https://apps.microsoft.com/detail/9PN58CG1P20L) ## Overview HTWind is a highly customizable, HTML-based widget manager that brings your favorite web tools and system helpers directly to your Windows desktop. It also supports running PowerShell commands when you need quick system actions. Build custom widgets using plain HTML, CSS, and JavaScript — from a single HTML file for the fastest prototype, to a manifest-driven package using `htwind.widget.json` that can describe one widget or multiple widgets in one importable bundle. ## Highlights - **Native PowerShell Execution**: Run PowerShell scripts for system automation and quick tasks via the host bridge API. - **Desktop HTML Widgets**: HTML widgets with lock/unlock interaction modes. - **Widget Package Imports**: Import widget packages through `htwind.widget.json`. - **Local Asset Support**: Widgets can include local CSS, JavaScript, images, and fonts. - **Multi-Widget Packages**: Install several widgets from one manifest. - **Built-in Widget Library**: Clock, weather, system tools, file helpers, and more. - **Built-in Code Editor**: Widget code editor with syntax highlighting and live preview (hot reload). - **Tray Integration**: Show/hide app, background workflow. - **Window Management**: Pin-on-top, visibility toggle, and persisted widget geometry/state. - **Smart Visibility Suppression**: Hide widget windows while another app is fullscreen or maximized (per display, toggleable). - **Startup Toggle**: Optional launch on system startup (`HKCU\Software\Microsoft\Windows\CurrentVersion\Run`). - **Localization Infrastructure**: `resx` + `LocExtension`. - **Open-Source and Community-Driven**: Contributions, widgets, and feedback welcome. ## Visibility Suppression Modes HTWind includes per-display runtime suppression options in Settings to reduce distraction and improve performance while other apps are in focus. - **Hide widgets on fullscreen apps** (enabled by default): Widget windows are temporarily closed on the same display when another app enters fullscreen, then restored when fullscreen ends. - **Hide widgets on maximized apps** (optional): Widget windows are temporarily closed on the same display when another app is maximized, then restored when that app is no longer maximized. > These options do not change the widget `Visible` state in app data. > Suppression is runtime-only and windows are restored automatically. ## Widget Development You can build custom widgets using plain HTML/CSS/JavaScript. HTWind supports three practical widget packaging levels: 1. A single HTML file for the fastest possible prototype. 2. A widget folder with additional local assets such as CSS, JavaScript, images, or fonts. 3. A manifest-driven package using `htwind.widget.json`, which can describe one widget or multiple widgets in one importable bundle. ### Host Bridge API Widgets can call `window.HTWind.invoke("powershell.exec", args)` with supported args including `script` (required), `timeoutMs`, `maxOutputChars`, `shell` (`powershell` or `pwsh`), and `workingDirectory`. > Only `powershell.exec` is currently supported. Output is clipped by > `maxOutputChars` for safety. Scripts are executed with > `-NoProfile -NonInteractive -ExecutionPolicy Bypass`. ### Generate Widgets With LLM Help Use the dedicated HTWind system prompt to generate HTWind-compatible widgets (including PowerShell bridge usage) as a single HTML file with your preferred LLM. ## Installation ### Option 1: From GitHub Releases (Recommended) 1. Open [Releases](https://github.com/sametcn99/HTWind/releases) in this repository. 2. Download one of the assets: - `HTWind-setup-.exe` (installer, recommended) - `HTWind-portable-.zip` (portable alternative) 3. For installer mode, run the setup executable and follow the wizard. ### Option 2: Run from Source Prerequisites: Windows 10/11 and .NET SDK 10.0+. ```bash dotnet restore HTWind/HTWind.csproj dotnet build HTWind/HTWind.csproj dotnet run --project HTWind/HTWind.csproj ``` ## Technology Stack - **C# / .NET 10** - **WPF / XAML** - **Fluent UI** - **HTML / CSS / TypeScript / JavaScript** (widgets) - **PowerShell** (host bridge) ### [ArcDrop](https://sametcc.me/project/arcdrop) --- title: ArcDrop publishedAt: "2026-03-10" summary: A self-host-first bookmarking platform with cross-platform desktop clients and AI-assisted organization, built as a .NET 10 monorepo with MAUI, Blazor, and ASP.NET Core. tags: [C#, .NET, ASP.NET Core, Blazor, MAUI, PostgreSQL, Docker, Self-Hosted, Bookmarking, AI, Monorepo] language: "en" type: "project" status: "published" --- # ArcDrop Self-host-first bookmarking platform with cross-platform desktop clients and AI-assisted organization. [GitHub Repository](https://github.com/sametcn99/ArcDrop) ## Overview ArcDrop is a self-host-first bookmarking platform with cross-platform desktop clients and AI-assisted organization. It is structured as a .NET 10 monorepo spanning an ASP.NET Core API, a Blazor web host, and a .NET MAUI desktop client, all backed by PostgreSQL. ## Repository Status - Implements initial monorepo baseline. - Bootstraps API foundation. - Adds PostgreSQL schema, migration pipeline, and CRUD integration tests. - Adds fixed-admin JWT login baseline and authentication tests. - Adds self-host Docker Compose assets and runbook baseline. - Adds VS Code dev container onboarding for .NET 10, PostgreSQL, and shared workspace tooling. - Adds AI provider configuration lifecycle API (create/read/update/delete) with encrypted secret persistence. - Adds initial AI organization command and auditable operation logging endpoints. - Adds MAUI MVVM shell and route navigation foundation. - Adds MAUI bookmark add/list/detail/edit workflow slices using API-backed query and command services with resilient list fallback. - Adds Blazor web host pages for auth session management, bookmark CRUD, AI provider configuration, and AI organization operation lookup against backend APIs. - Adds Blazor web host inside MAUI folder with bookmark add/list/edit flows against the same API contracts. - Secures web auth flow with server-issued HttpOnly session cookie, protected route redirects, and persistent refresh-safe sign-in behavior. ## Quick Navigation - Monorepo rules: `docs/monorepo-structure.md` - API bootstrap and migration guide: `docs/api-bootstrap.md` - MAUI foundation guide: `docs/desktop-maui-foundation.md` - Blazor web app: `apps/web/ArcDrop.Web` - API tests project: `tests/ArcDrop.Api.Tests` - Self-host quickstart: `docs/self-host-quickstart.md` - Dev container setup: `docs/devcontainer-setup.md` - Contributor guide: `CONTRIBUTING.md` - Engineering guardrails: `.github/copilot-instructions.md` - Implementation plans: `.github/plan/` ## Technology Stack - **C# / .NET 10** - **ASP.NET Core** (API) - **Blazor** (web host) - **.NET MAUI** (desktop client, MVVM) - **PostgreSQL** (database) - **Docker Compose** (self-host) - **Dev Containers** (contributor onboarding) ### [GitHub Repo TUI Cloner (PowerShell)](https://sametcc.me/gist/git-clone-repo-tui) --- title: "GitHub Repo TUI Cloner (PowerShell)" publishedAt: "2026-02-19" summary: "An interactive PowerShell script to fetch and clone your owned GitHub repositories using a terminal UI (TUI)." tags: [GitHub, PowerShell, TUI, Git, Automation] language: "en" type: "gist" status: "published" --- # GitHub Repo TUI Cloner (PowerShell) This PowerShell script provides an interactive terminal user interface (TUI) to manage your GitHub repositories. It allows you to fetch a list of your owned repositories and selectively clone them to your local machine. ## Features - **Interactive TUI**: Easily browse and select repositories using keyboard controls. - **GitHub API Integration**: Fetches owned repositories using a personal access token (PAT). - **Shallow Cloning**: Defaults to shallow clones (`--depth 1`) for faster downloads, with an option for full history. - **Filtering**: Supports toggling archived and forked repositories in the selection. - **Batch Operations**: Select all, clear selection, or toggle specific types of repos. ## Requirements - **PowerShell**: Version 5.1 or 7+. - **Git**: Installed and available in your `PATH`. - **GitHub Token**: A Personal Access Token (PAT) with `repo` scope. - **Network**: Access to `api.github.com` and `github.com`. ## Usage 1. **Authentication**: - Recommended: Set your token in the environment variable `$env:GITHUB_TOKEN`. - Alternative: Edit the `$HARDCODED_GITHUB_TOKEN` variable inside the script. 2. **Run the script**: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File .\clone-tui.ps1 ``` 3. **Configuration**: The `CONFIG` section in the script allows you to change the destination folder, inclusion of archived repos, and clone depth. ## TUI Controls | Key | Action | | :---------- | :--------------------------------------------- | | **Up/Down** | Move cursor in the list | | **Space** | Toggle selection for the current repository | | **A** | Select all repositories | | **N** | Clear all selections | | **R** | Toggle selection for all archived repositories | | **F** | Toggle selection for all forked repositories | | **Enter** | Start cloning selected repositories | | **Q / Esc** | Quit without cloning | --- ## Full Script Below is the complete PowerShell script. You can save this as `clone-tui.ps1` and run it as described above. ```powershell Set-StrictMode -Version Latest # For local-only use, you can hardcode your token here. # Safer option: use $env:GITHUB_TOKEN instead. $HARDCODED_GITHUB_TOKEN = "REPLACE-TOKEN" # Internal configuration (parameterless mode). $CONFIG = @{ Dest = "." IncludeArchived = $true FullClone = $false ApiTimeoutSec = 25 } function Show-Help { Write-Host @" GitHub Repo TUI Cloner (PowerShell) Usage: .\clone-tui.ps1 Configuration: Edit the CONFIG hashtable in this script: - Dest - IncludeArchived - FullClone Controls: Up/Down Move in list Space Toggle selection A Select all N Clear selection R Toggle all archived repos F Toggle all forked repos Enter Clone selected repositories Q / Esc Exit Notes: - Default mode performs shallow clone (latest commit only). - Use -FullClone to clone complete history. - API timeout is controlled by CONFIG.ApiTimeoutSec. "@ } function Get-Token { if (-not [string]::IsNullOrWhiteSpace($HARDCODED_GITHUB_TOKEN)) { return $HARDCODED_GITHUB_TOKEN } if (-not [string]::IsNullOrWhiteSpace($env:GITHUB_TOKEN)) { return $env:GITHUB_TOKEN } throw "Token not found. Set HARDCODED_GITHUB_TOKEN or GITHUB_TOKEN." } function Get-OwnedRepos { param( [Parameter(Mandatory = $true)] [string]$Token, [switch]$IncludeArchived, [int]$TimeoutSec = 25 ) $headers = @{ Accept = "application/vnd.github+json" Authorization = "Bearer $Token" "X-GitHub-Api-Version" = "2022-11-28" "User-Agent" = "powershell-tui-clone" } $repos = @() $page = 1 Write-Host "Fetching repositories from GitHub API..." -ForegroundColor DarkGray while ($true) { $url = "https://api.github.com/user/repos?affiliation=owner&per_page=100&page=$page" Write-Host (" Requesting page {0}..." -f $page) -ForegroundColor DarkGray try { $rawData = Invoke-RestMethod -Uri $url -Headers $headers -Method Get -TimeoutSec $TimeoutSec } catch { throw ( "GitHub API request failed on page {0}. " + "Check internet/proxy/token. Inner error: {1}" ) -f $page, $_.Exception.Message } if ($null -eq $rawData) { break } $data = @($rawData) # GitHub API errors can arrive as an object (e.g. rate limit), not as repo array. if ($data.Count -gt 0) { $first = $data[0] $messageProp = $first.PSObject.Properties["message"] $nameProp = $first.PSObject.Properties["name"] if ($null -ne $messageProp -and $null -eq $nameProp) { throw ("GitHub API returned an error payload: {0}" -f $messageProp.Value) } } if ($data.Count -eq 0) { break } if ($IncludeArchived) { $repos += $data } else { $repos += ( $data | Where-Object { $archivedProp = $_.PSObject.Properties["archived"] if ($null -eq $archivedProp) { return $true } return -not [bool]$archivedProp.Value } ) } $page += 1 } return @($repos | Sort-Object -Property name) } function Draw-RepoList { param( [Parameter(Mandatory = $true)] [array]$Repos, [Parameter(Mandatory = $true)] [bool[]]$Selected, [int]$Cursor = 0, [int]$Top = 0 ) Clear-Host Write-Host "GitHub Repository Picker (TUI)" Write-Host "Space: toggle | A: all | N: none | R: archived | F: forks | Enter: clone | Q/Esc: quit" Write-Host "" $h = [Math]::Max(5, [Console]::WindowHeight - 6) $end = [Math]::Min($Repos.Count - 1, $Top + $h - 1) for ($i = $Top; $i -le $end; $i++) { $mark = if ($Selected[$i]) { "[x]" } else { "[ ]" } $pointer = if ($i -eq $Cursor) { ">" } else { " " } $line = "{0} {1} {2}" -f $pointer, $mark, $Repos[$i].name if ($i -eq $Cursor) { Write-Host $line -ForegroundColor Cyan } else { Write-Host $line } } Write-Host "" $selectedCount = @($Selected | Where-Object { $_ }).Count Write-Host ("Total: {0} | Selected: {1}" -f $Repos.Count, $selectedCount) } function Toggle-SelectionByFlag { param( [Parameter(Mandatory = $true)] [array]$Repos, [Parameter(Mandatory = $true)] [bool[]]$Selected, [Parameter(Mandatory = $true)] [string]$FlagName ) $matchingIndexes = @() for ($i = 0; $i -lt $Repos.Count; $i++) { $prop = $Repos[$i].PSObject.Properties[$FlagName] if ($null -ne $prop -and [bool]$prop.Value) { $matchingIndexes += $i } } if ($matchingIndexes.Count -eq 0) { return } $allSelected = $true foreach ($idx in $matchingIndexes) { if (-not $Selected[$idx]) { $allSelected = $false break } } $nextValue = -not $allSelected foreach ($idx in $matchingIndexes) { $Selected[$idx] = $nextValue } } function Select-ReposTui { param( [Parameter(Mandatory = $true)] [array]$Repos ) if ($Repos.Count -eq 0) { return @() } $selected = New-Object bool[] $Repos.Count $cursor = 0 $top = 0 while ($true) { $viewHeight = [Math]::Max(5, [Console]::WindowHeight - 6) if ($cursor -lt $top) { $top = $cursor } if ($cursor -ge ($top + $viewHeight)) { $top = $cursor - $viewHeight + 1 } Draw-RepoList -Repos $Repos -Selected $selected -Cursor $cursor -Top $top $key = [Console]::ReadKey($true) if ($key.Key -eq [ConsoleKey]::UpArrow -and $cursor -gt 0) { $cursor -= 1 continue } if ($key.Key -eq [ConsoleKey]::DownArrow -and $cursor -lt ($Repos.Count - 1)) { $cursor += 1 continue } if ($key.Key -eq [ConsoleKey]::Spacebar) { $selected[$cursor] = -not $selected[$cursor] continue } if ($key.Key -eq [ConsoleKey]::A) { for ($i = 0; $i -lt $selected.Length; $i++) { $selected[$i] = $true } continue } if ($key.Key -eq [ConsoleKey]::N) { for ($i = 0; $i -lt $selected.Length; $i++) { $selected[$i] = $false } continue } if ($key.Key -eq [ConsoleKey]::R) { Toggle-SelectionByFlag -Repos $Repos -Selected $selected -FlagName "archived" continue } if ($key.Key -eq [ConsoleKey]::F) { Toggle-SelectionByFlag -Repos $Repos -Selected $selected -FlagName "fork" continue } if ($key.Key -eq [ConsoleKey]::Enter) { $result = @() for ($i = 0; $i -lt $Repos.Count; $i++) { if ($selected[$i]) { $result += $Repos[$i] } } return $result } if ($key.Key -eq [ConsoleKey]::Q -or $key.Key -eq [ConsoleKey]::Escape) { return @() } } } function Clone-Repos { param( [Parameter(Mandatory = $true)] [array]$Repos, [Parameter(Mandatory = $true)] [string]$Dest, [switch]$FullClone ) if (-not (Test-Path -LiteralPath $Dest)) { New-Item -ItemType Directory -Path $Dest -Force | Out-Null } foreach ($repo in $Repos) { $target = Join-Path -Path $Dest -ChildPath $repo.name if (Test-Path -LiteralPath $target) { Write-Host ("Skipped (already exists): {0}" -f $repo.name) -ForegroundColor Yellow continue } Write-Host ("Cloning: {0}" -f $repo.name) -ForegroundColor Green if ($FullClone) { & git clone $repo.clone_url $target } else { $defaultBranchProp = $repo.PSObject.Properties["default_branch"] $defaultBranch = $null if ($null -ne $defaultBranchProp) { $defaultBranch = $defaultBranchProp.Value } if ([string]::IsNullOrWhiteSpace($defaultBranch)) { $defaultBranch = "main" } & git clone --depth 1 --single-branch --branch $defaultBranch $repo.clone_url $target } if ($LASTEXITCODE -ne 0) { Write-Host ("Clone failed: {0}" -f $repo.name) -ForegroundColor Red } } } try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 Show-Help Write-Host "" Write-Host "Starting with current CONFIG values..." -ForegroundColor DarkGray Write-Host (" Dest: {0}" -f $CONFIG.Dest) -ForegroundColor DarkGray Write-Host (" IncludeArchived: {0}" -f $CONFIG.IncludeArchived) -ForegroundColor DarkGray Write-Host (" FullClone: {0}" -f $CONFIG.FullClone) -ForegroundColor DarkGray Write-Host (" ApiTimeoutSec: {0}" -f $CONFIG.ApiTimeoutSec) -ForegroundColor DarkGray Write-Host "" $token = Get-Token $repos = @( Get-OwnedRepos -Token $token -IncludeArchived:$CONFIG.IncludeArchived -TimeoutSec $CONFIG.ApiTimeoutSec ) if ($repos.Count -eq 0) { Write-Host "No repositories found." exit 0 } $selectedRepos = @(Select-ReposTui -Repos $repos) if ($selectedRepos.Count -eq 0) { Write-Host "No selection made or operation cancelled." exit 0 } Write-Host ("Selected repositories: {0}" -f $selectedRepos.Count) Clone-Repos -Repos $selectedRepos -Dest $CONFIG.Dest -FullClone:$CONFIG.FullClone Write-Host "Done." } catch { Write-Host ("Error: {0}" -f $_.Exception.Message) -ForegroundColor Red exit 1 } ``` ### [TypeSpec for .NET and Next.js Teams](https://sametcc.me/gist/typespec-for-dotnet-nextjs) --- title: "TypeSpec for .NET and Next.js Teams" publishedAt: "2026-02-19" summary: "Practical TypeSpec workflow for contract-first API design with ASP.NET Core and Next.js, including OpenAPI generation and typed client usage." tags: [TypeSpec, .NET, Next.js, OpenAPI, API Design] language: "en" type: "gist" status: "published" --- # TypeSpec for .NET and Next.js Teams If your stack is `ASP.NET Core` on the backend and `Next.js` on the frontend, TypeSpec gives you a clean contract-first workflow. Write the API once in TypeSpec, emit OpenAPI, then: - implement the API in .NET from the same contract - generate a typed TypeScript client for Next.js - keep backend and frontend aligned with fewer breaking surprises --- ## What TypeSpec really is TypeSpec is a language for describing APIs, data shapes, and service behavior in a tool-friendly way. It is not your runtime framework. It does not replace ASP.NET Core or Next.js. It defines the contract those apps should follow. Think of it as: - a source language for API contracts - a way to generate artifacts (OpenAPI, docs, SDK inputs) - a governance layer for cross-team consistency For .NET + Next.js teams, this separation is useful because backend and frontend can evolve fast without guessing each other's changes. --- ## Why TypeSpec in this stack? For mixed .NET and TypeScript teams, API drift is usually the real problem: - backend DTO changes and frontend is not updated - endpoints evolve but docs lag behind - validation rules are duplicated in many places TypeSpec solves this by making the API contract the single source of truth. --- ## Core concepts you should understand first Before writing many files, get these concepts clear: - `model`: shape of data payloads - `interface`/`operation`: callable API actions - decorators (`@get`, `@route`, etc.): protocol and metadata - emitters: output targets such as OpenAPI 3 - namespaces: domain boundaries in bigger APIs If the team understands these five, onboarding becomes much easier. --- ## Contract-first vs code-first in practice With code-first, the backend implementation is usually the first source. Frontend catches up later through generated or handwritten docs. With contract-first (TypeSpec), the sequence becomes: 1. Define API behavior in TypeSpec. 2. Review contract in PR before implementation. 3. Generate OpenAPI/types. 4. Implement backend and frontend against generated outputs. This reduces the classic "it worked on backend but frontend broke" cycle. --- ## Suggested project layout ```text repo-root/ apps/ api-dotnet/ web-next/ contracts/ main.tsp tspconfig.yaml generated/ openapi/ openapi.json ts-client/ ``` - `contracts/` contains TypeSpec source - `generated/openapi/` is consumed by .NET - `generated/ts-client/` is consumed by Next.js Why this layout works: - keeps generated code out of hand-written app folders - makes CI checks straightforward - allows independent versioning for `contracts/` --- ## Installation guide (step by step) If your repo already has `.NET` and `Next.js`, the simplest setup is to add a small Node-based toolchain only for contracts. ### 1. Prerequisites - Node.js `18+` (prefer `20+`) - npm, pnpm, or yarn - existing .NET and Next.js projects in your monorepo Check versions: ```bash node -v npm -v dotnet --version ``` ### 2. Initialize a contracts package From repo root: ```bash mkdir contracts cd contracts npm init -y ``` ### 3. Install TypeSpec packages ```bash npm i -D @typespec/compiler @typespec/http @typespec/rest @typespec/openapi3 ``` ### 4. Initialize TypeSpec project files ```bash npx tsp init ``` Pick a minimal template, then keep `main.tsp` and `tspconfig.yaml` under `contracts/`. ### 5. Add scripts for repeatable usage In `contracts/package.json`: ```json { "scripts": { "tsp:compile": "tsp compile main.tsp --output-dir ../generated/openapi", "tsp:watch": "tsp compile main.tsp --watch --output-dir ../generated/openapi" } } ``` Now use: ```bash npm run tsp:compile npm run tsp:watch ``` --- ## Daily usage workflow For day-to-day development, keep this flow: 1. Update contract in `contracts/main.tsp`. 2. Run `npm run tsp:compile`. 3. Regenerate frontend types from emitted OpenAPI. 4. Run backend and frontend tests. 5. Commit both source and generated artifacts (if your repo policy requires). This workflow avoids stale clients and hidden API mismatches. --- ## Usage in .NET solution After TypeSpec emits `openapi.json`, integrate it into your API project. Common approach: 1. Copy `generated/openapi/openapi.json` into API static content during build. 2. Serve that file via Swagger UI endpoint. 3. Validate implementation behavior against contract in integration tests. Optional build copy in `.csproj`: ```xml PreserveNewest ``` This makes the emitted spec available at runtime without manual copying. --- ## Usage in Next.js app After each contract compile, regenerate frontend types: ```bash npx openapi-typescript ../generated/openapi/openapi.json -o ../generated/ts-client/schema.d.ts ``` Then consume those types in a single API layer (`lib/api.ts`) and avoid direct `fetch` spread across many components. Recommended pattern: - keep fetch wrappers in one folder - export typed methods (`getProjects`, `getProjectBySlug`) - keep pages/components focused on UI only This improves maintainability as your API grows. --- ## Local development commands (example) You can run contract compile + apps in separate terminals: ```bash # terminal 1 cd contracts npm run tsp:watch # terminal 2 cd apps/api-dotnet dotnet watch # terminal 3 cd apps/web-next npm run dev ``` If you use Turborepo/Nx, you can orchestrate these as one dev command. --- ## CI/CD integration checklist Minimum reliable pipeline: 1. Install Node dependencies for `contracts/`. 2. Run TypeSpec compile. 3. Generate frontend type artifacts. 4. Fail build if generated files changed unexpectedly. 5. Run `.NET` tests and Next.js checks. If your team often changes contracts, add OpenAPI diff reporting in pull requests. --- ## Minimal TypeSpec example Create `contracts/main.tsp`: ```typespec import "@typespec/http"; import "@typespec/rest"; import "@typespec/openapi3"; using TypeSpec.Http; using TypeSpec.Rest; @service({ title: "Portfolio API" }) namespace Portfolio; model Project { id: string; name: string; slug: string; tags?: string[]; } @route("/projects") interface Projects { @get list(): Project[]; @get @route("/{slug}") getBySlug(@path slug: string): Project; } ``` Create `contracts/tspconfig.yaml`: ```yaml emit: - "@typespec/openapi3" options: "@typespec/openapi3": output-file: "{output-dir}/openapi.json" ``` Compile: ```bash npx tsp compile contracts/main.tsp --output-dir generated/openapi ``` --- ## Designing good contracts (not just valid contracts) A contract can be syntactically correct and still painful in production. Design guidelines that help real teams: - Prefer stable, explicit field names over abbreviations. - Keep response models focused; avoid over-nested payloads. - Define error shapes intentionally, not ad-hoc per endpoint. - Separate public API models from internal persistence models. - Keep pagination, filtering, and sorting conventions consistent. For .NET teams, this usually means not exposing EF Core entities directly. For Next.js teams, it means predictable payload structure and safer caching logic. --- ## Versioning strategy that avoids pain Most contract issues appear during versioning, not initial development. Recommended approach: - treat breaking changes as explicit version events - publish changelog entries for contract updates - run backward compatibility checks in CI - deprecate first, remove later Common breaking changes to watch: - deleting fields used by frontend - changing scalar types (`string` to `number`) - changing required/optional semantics - changing endpoint paths or status code behavior --- ## Use the generated OpenAPI in .NET In `api-dotnet`, keep your implementation mapped to the generated contract. You can either: - use OpenAPI for Swagger UI and contract verification - generate C# clients for internal consumers Basic setup in ASP.NET Core: ```csharp builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); app.UseSwagger(); app.UseSwaggerUI(c => { c.SwaggerEndpoint("/openapi/openapi.json", "Portfolio API"); }); app.MapGet("/projects", () => { return Results.Ok(new[] { new { id = "1", name = "Mermaid Viewer", slug = "mermaid-viewer", tags = new[] { "blazor", "tools" } } }); }); ``` Tip: copy or serve `generated/openapi/openapi.json` from your API project during build to keep runtime docs synced. --- ## How this helps ASP.NET Core architecture TypeSpec does not dictate your .NET architecture, but it improves boundaries: - `Application` layer can align request/response DTOs to contract models - `Infrastructure` remains implementation detail - contract tests can verify endpoint behavior against generated OpenAPI If you use Minimal APIs, controllers, or vertical slices, TypeSpec still fits. The key is to map runtime behavior to contract intentionally. --- ## Generate and use a typed client in Next.js One common option is generating a TypeScript client from `openapi.json`. Example with `openapi-typescript`: ```bash npm i -D openapi-typescript npx openapi-typescript generated/openapi/openapi.json -o generated/ts-client/schema.d.ts ``` Then create a small fetch wrapper in `web-next/lib/api.ts`: ```typescript import type { paths } from "../../generated/ts-client/schema"; type GetProjectsResponse = paths["/projects"]["get"]["responses"][200]["content"]["application/json"]; export async function getProjects(): Promise { const res = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/projects`, { cache: "no-store", }); if (!res.ok) { throw new Error("Failed to fetch projects"); } return res.json() as Promise; } ``` This keeps your Next.js app strongly typed against the same backend contract. --- ## How this helps Next.js app design In Next.js, typed contracts improve: - server components and route handlers that fetch backend data - cache key reliability in data-fetching layers - safer refactors when endpoint payloads change - API integration tests with fewer runtime surprises A practical pattern is to centralize API calls in `lib/` and keep all pages or components consuming typed helper functions instead of raw `fetch` calls. --- ## Tooling ecosystem around TypeSpec TypeSpec becomes strongest when paired with automation: - formatting and linting for `.tsp` files - OpenAPI diff checks in pull requests - typed client generation on each contract change - release notes generated from contract deltas Even simple automation gives big returns in medium-size teams. --- ## CI flow that works well Add these checks in CI: 1. Compile TypeSpec and fail on diagnostics. 2. Regenerate `openapi.json`. 3. Regenerate TypeScript types/client. 4. Fail if generated artifacts changed but were not committed. This prevents silent contract drift across backend and frontend. --- ## Common mistakes and how to avoid them - Writing TypeSpec after backend code is done: you lose most contract-first benefits. - Treating generated files as manually editable: generated outputs should be reproducible, not handcrafted. - Mixing transport models with domain models: API contracts and business entities have different purposes. - Ignoring deprecation policy: frontend teams need a predictable migration window. --- ## When TypeSpec is probably overkill TypeSpec may be unnecessary if: - you have a very small project with one developer - API surface is tiny and unlikely to evolve - no separate frontend/backend team boundary exists In those cases, code-first OpenAPI generation may be enough. --- ## Migration path for an existing .NET + Next.js project If you already have production APIs, migrate incrementally: 1. Start with one bounded context (for example `projects`). 2. Model that context in TypeSpec. 3. Generate artifacts and compare with current OpenAPI. 4. Align backend behavior where mismatches exist. 5. Switch frontend data layer to generated types for that context. 6. Repeat per module. This avoids a risky all-at-once rewrite. --- ## Practical tips - Keep `contracts/` versioned and reviewed like production code. - Treat breaking TypeSpec changes as versioned API changes. - Start small: model only public endpoints first. - Add linting and formatting for TypeSpec in pre-commit hooks. If your team already uses .NET and Next.js, TypeSpec is one of the cleanest ways to make the API contract explicit, reusable, and hard to accidentally break. ### [Rendering HTML Details/Summary in MDX with Custom Components](https://sametcc.me/gist/mdx-details-custom-components) --- title: "Rendering HTML Details/Summary in MDX with Custom Components" publishedAt: "2026-01-30" summary: "A comprehensive guide on how to transform HTML details and summary elements into custom React components in Next.js MDX projects" tags: ["mdx", "react", "next.js", "typescript", "html"] language: "en" type: "gist" status: "published" --- # Rendering HTML Details/Summary in MDX with Custom Components When working with MDX in Next.js projects, you might encounter a common challenge: HTML `
` and `` elements don't render properly. This guide explains the problem and provides a complete solution using custom React components. ## Table of Contents - [The Problem](#the-problem) - [Why rehype-raw Doesn't Work](#why-rehype-raw-doesnt-work) - [The Solution](#the-solution) - [Step 1: Create Custom Components](#step-1-create-custom-components) - [Step 2: Transform HTML Tags Before Compilation](#step-2-transform-html-tags-before-compilation) - [Step 3: Register Components in MDX](#step-3-register-components-in-mdx) - [Why First Child as Summary?](#why-first-child-as-summary) - [Handling the open Attribute](#handling-the-open-attribute) - [Extending the Solution](#extending-the-solution) - [Summary](#summary) ## The Problem MDX compiles content into JSX, but it doesn't natively support raw HTML elements like `
` and ``. When you try to use these elements in your MDX content: ```html
Click to expand This content is hidden by default.
``` You'll likely encounter one of these issues: 1. **Compilation Error**: MDX throws an error because it can't process raw HTML nodes 2. **No Rendering**: The elements are ignored or rendered as plain text 3. **rehype-raw Conflict**: Using `rehype-raw` plugin causes conflicts with MDX JSX nodes The error message typically looks like: ``` Cannot compile `mdxJsxTextElement` node. It looks like you are using MDX nodes with `hast-util-raw` (or `rehype-raw`). ``` ## Why rehype-raw Doesn't Work You might think adding `rehype-raw` to your MDX configuration would solve this: ```typescript // ❌ This doesn't work with MDX import rehypeRaw from "rehype-raw"; const options = { mdxOptions: { rehypePlugins: [rehypeRaw], // Causes compilation errors }, }; ``` The `rehype-raw` plugin is designed for regular markdown, not MDX. MDX has its own JSX handling that conflicts with raw HTML processing. ## The Solution The solution involves three parts: 1. **Create custom React components** to handle the rendering 2. **Transform HTML tags to JSX components** before MDX compilation 3. **Register components** in the MDX component mapping ### Step 1: Create Custom Components Create `Details` and `Summary` components. Here's a basic implementation: ```tsx // components/mdx-components/Details.tsx "use client"; import { useState, Children, isValidElement, type ReactNode } from "react"; interface DetailsProps { children?: ReactNode; open?: boolean; } interface SummaryProps { children?: ReactNode; } /** * Renders HTML `` element. * Content is extracted by DetailsComponent for the header. */ export function SummaryComponent({ children }: SummaryProps) { return <>{children}; } SummaryComponent.displayName = "SummaryComponent"; /** * Renders HTML `
` element as a collapsible component. * Automatically extracts the first child (summary) as the header. */ export function DetailsComponent({ children, open = false }: DetailsProps) { const [isOpen, setIsOpen] = useState(open); // Separate summary from content // In HTML spec, the first child of
is always const childArray = Children.toArray(children); let summaryContent: ReactNode = "Details"; const contentChildren: ReactNode[] = []; childArray.forEach((child, index) => { // First child is always the summary if (index === 0 && isValidElement(child)) { const props = child.props as { children?: ReactNode }; summaryContent = props.children; } else { contentChildren.push(child); } }); return (
{isOpen &&
{contentChildren}
}
); } DetailsComponent.displayName = "DetailsComponent"; ``` Add some basic styles: ```css /* styles/details.css */ .details-container { margin: 1rem 0; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; } .details-summary { display: flex; align-items: center; gap: 0.5rem; width: 100%; padding: 1rem; background: #f5f5f5; border: none; cursor: pointer; font-size: 1rem; font-weight: 600; text-align: left; } .details-summary:hover { background: #ebebeb; } .details-icon { font-size: 0.75rem; } .details-content { padding: 1rem; } ``` ### Step 2: Transform HTML Tags Before Compilation The key insight is that we need to transform HTML tags **before** MDX compiles them. We also need to be careful not to transform tags inside code blocks or inline code. Create a context-aware transform function: ````tsx // components/mdx.tsx import { MDXRemote } from "next-mdx-remote/rsc"; import { useMDXComponents } from "../mdx-components"; /** * Transforms HTML details/summary tags to JSX Details/Summary components * before MDX compilation since MDX doesn't support raw HTML tags. * Skips transformation for content inside code blocks and inline code. */ function transformHtmlToJsx(source: string): string { // Regex to match code blocks (```...```) and inline code (`...`) const codeRegex = /(```[\s\S]*?```|`[^`\n]*`)/g; // Split by code blocks/inline code while keeping the delimiters const parts = source.split(codeRegex); return parts .map((part) => { // If this part is a code block or inline code, don't transform it if (part.match(codeRegex)) { return part; } // Transform HTML tags to JSX components for non-code parts return part .replace(/)/gi, "/gi, "
") .replace(/)/gi, "/gi, "
"); }) .join(""); } export function CustomMDX({ source }: { source: string }) { const components = useMDXComponents({}); const transformedSource = transformHtmlToJsx(source); return ( ); } ```` This refined transformation: - **Identifies code regions** using regex to prevent accidental transformation within examples. - **Converts `
` to `
`** only in prose. - **Converts `
` to `
`** only in prose. - **Converts `` to ``** only in prose. - **Converts `` to ``** only in prose. ### Step 3: Register Components in MDX Finally, register the components in your MDX component mapping: ```tsx // mdx-components.tsx import type { MDXComponents } from "mdx/types"; import { DetailsComponent, SummaryComponent, } from "./components/mdx-components/Details"; export function useMDXComponents(components: MDXComponents): MDXComponents { return { ...components, // Map JSX component names (capitalized) Details: DetailsComponent, Summary: SummaryComponent, // ... other components }; } ``` ## Why First Child as Summary? In the HTML specification, `
` element's first child should always be ``. The component leverages this by: 1. Taking the first child's content as the collapsible header 2. Putting all remaining children in the collapsible body This approach is more reliable than trying to identify the Summary component by type checking, which can fail due to how MDX compiles components. ## Handling the `open` Attribute HTML `
` supports an `open` attribute to show content by default: ```html
Already expanded This content is visible by default.
``` The component handles this with an initial state: ```tsx const [isOpen, setIsOpen] = useState(open); ``` ## Extending the Solution You can extend this pattern for other HTML elements that MDX doesn't handle well by adding more replacements inside the non-code part of your transform function: ```typescript // Inside the transformHtmlToJsx .map() callback: return ( part // Details/Summary .replace(/)/gi, "/gi, "
") .replace(/)/gi, "/gi, "
") // Dialog .replace(/)/gi, "/gi, "") // Mark .replace(/)/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 ![Logo](https://golter.vercel.app/opengraph-image) 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 ![Silent Hill Infographic](/uploads/content/tracing-edgar-allan-poes-ghost-in-the-fog-of-silent-hill/infographic.png) 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 ![Banner](https://github.com/sametcn99/env-protector/blob/main/assets/banner.png?raw=true) [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. ![Commands](https://raw.githubusercontent.com/sametcn99/env-protector/refs/heads/main/assets/commands.png) - **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. ![Confirmation Dialog](https://raw.githubusercontent.com/sametcn99/env-protector/refs/heads/main/assets/dialog.png) - **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). ![Masked Variables](https://raw.githubusercontent.com/sametcn99/env-protector/refs/heads/main/assets/masked.png) - **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. ![Add Environment Value](https://raw.githubusercontent.com/sametcn99/env-protector/refs/heads/main/assets/add.gif) ![Edit Environment Value](https://raw.githubusercontent.com/sametcn99/env-protector/refs/heads/main/assets/edit.gif) ![Remove Environment Value](https://raw.githubusercontent.com/sametcn99/env-protector/refs/heads/main/assets/remove.gif) ### [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. ![alt text](https://repository-images.githubusercontent.com/576042902/37e60d01-004b-4d49-89be-d1189cf4b9c1) 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