From 2f46c14e186696a958186393234a50c9ed255a26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Hlad=C3=ADk?= Date: Fri, 26 Dec 2025 17:29:41 +0100 Subject: [PATCH] feat(author): author info added --- .github/copilot-instructions.md | 101 ++++++++++++++++--- README.md | 170 +++++++++++++++++++++++++++++++- src/root.zig | 57 +++++++++-- 3 files changed, 302 insertions(+), 26 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 279fea4..900c719 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,7 +2,15 @@ ## Project Overview -A Zig library (`version_info` module) with CLI executable, demonstrating Zig's dual library+executable pattern. Minimal NPM integration for metadata only - this is NOT a Node.js project. +A Zig library (`version_info` module) with CLI executable that generates TypeScript version information files from package.json. Demonstrates Zig's dual library+executable pattern with minimal NPM integration for metadata only - this is NOT a Node.js project. + +### Core Functionality + +- Reads version from `package.json` +- Generates ISO 8601 timestamps +- Extracts git information (branch and commit hash) from `.git` folder +- Outputs TypeScript file with `VERSION_INFO` constant +- Handles non-git repositories gracefully (optional git info) ## Architecture @@ -10,15 +18,17 @@ A Zig library (`version_info` module) with CLI executable, demonstrating Zig's d - **Library module** ([src/root.zig](../src/root.zig)): Public API via `version_info` module - Entry point for consumers using `@import("version_info")` + - Public functions: `printVersion()`, `generateVersionInfo()`, `getGitInfo()` - Only public declarations in root.zig are accessible to external code - **Executable** ([src/main.zig](../src/main.zig)): CLI wrapper that imports and uses the library + - Parses command-line arguments (`--version`, `--input`, `--output`) + - Delegates to library functions - Separate business logic from CLI concerns - - Root module imports library module as dependency This separation allows consumers to either: 1. Use as a library by importing the `version_info` module -2. Use as a standalone CLI tool +2. Use as a standalone CLI tool via `zig-out/bin/version_info` ## Development Workflows @@ -28,11 +38,12 @@ This separation allows consumers to either: # Build executable (output: zig-out/bin/version_info) zig build -# Run executable directly +# Run executable directly (generates version-info.ts) zig build run # Run with arguments -zig build run -- arg1 arg2 +zig build run -- --input package.json --output version-info.ts +zig build run -- --version # Run all tests (runs both library and executable tests in parallel) zig build test @@ -49,33 +60,81 @@ rm -rf zig-cache zig-out - `b.addModule()` exposes modules to package consumers - `b.createModule()` creates internal modules (not exposed) +## Core Implementation Details + +### Git Information Reading + +- Reads `.git/HEAD` to determine current branch reference +- Parses `refs/heads/branch-name` format +- Reads commit hash from `.git/refs/heads/{branch}` file +- Handles detached HEAD state +- Returns `null` if not in a git repository (graceful degradation) + +### TypeScript Output Format + +```typescript +export const VERSION_INFO = { + version: "0.0.3", + date: "2025-12-26T16:00:00.000Z", + git: { + branch: "master", + commit: "324822ac3893dd6159ab8cb4477d45edeacf11f6", + }, +}; +``` + +Git section is **optional** - omitted when not in a git repository. + +### Memory Management Patterns + +- `getGitInfo()` allocates memory for branch and commit strings +- Caller must free using `allocator.free()` for both fields +- Use explicit cleanup after file writing to avoid use-after-free +- Example pattern: + ```zig + const git_info = try getGitInfo(allocator); + // ... use git_info ... + if (git_info) |info| { + allocator.free(info.branch); + allocator.free(info.commit); + } + ``` + ## Testing ### Test Organization -- Library tests: in [src/root.zig](../src/root.zig) (e.g., `test "basic add functionality"`) -- Executable tests: in [src/main.zig](../src/main.zig) (e.g., `test "simple test"`, `test "fuzz example"`) +- Library tests: in [src/root.zig](../src/root.zig) +- Executable tests: in [src/main.zig](../src/main.zig) - Both test suites run in parallel via `zig build test` -### Fuzzing Support +### Testing Git Functionality -Use `--fuzz` flag for fuzz testing: `zig build test --fuzz` -Example in [src/main.zig](../src/main.zig#L20-L27) shows `std.testing.fuzz()` pattern +- Requires actual `.git` folder for integration testing +- Test null case by running in non-git directory +- Memory leak tests automatically verify proper cleanup ## Conventions ### Memory Management - Explicit allocator passing (see `std.testing.allocator` usage) -- **Critical**: Always `defer` cleanup (e.g., `defer list.deinit(gpa)`) +- **Critical**: Always `defer` cleanup or explicit free at appropriate scope +- Avoid `defer` in optional capture blocks - use explicit cleanup after usage - Zig detects memory leaks in tests automatically ### Output Handling -- `std.debug.print()` → stderr (debugging) +- `std.debug.print()` → stderr (debugging/logging with colored output) - `std.fs.File.stdout()` → stdout (actual program output) - Buffered writers require explicit `.flush()` calls +### Colored Console Output + +- Uses ANSI escape codes defined in `Color` struct +- Helper `log()` function for consistent emoji + color formatting +- Example: `log(Color.BRIGHT_GREEN, "✅", "Successfully generated {s}", .{path});` + ### Module Imports ```zig @@ -102,12 +161,14 @@ const version_info = @import("version_info"); // Local module ### NPM Integration -[package.json](../package.json) contains metadata only (author, license). No Node.js runtime usage. +[package.json](../package.json) contains metadata only (author, license, scripts). No Node.js runtime usage. + +Scripts are for building/publishing the Zig executable as an NPM package. ### Publishing -- `.paths` in build.zig.zon defines published files (build.zig, build.zig.zon, src/) -- Consider adding LICENSE/README.md to .paths before publishing +- `.paths` in build.zig.zon defines published files (build.zig, build.zig.zon, src/, LICENSE, README.md) +- Binary published via NPM with bin entry pointing to `zig-out/bin/version_info` ## Common Patterns @@ -116,6 +177,14 @@ const version_info = @import("version_info"); // Local module 1. Add to [src/root.zig](../src/root.zig) with `pub` keyword 2. Write corresponding `test` block 3. Re-export from root.zig if defined in other files +4. Ensure proper memory management for allocated resources + +### Error Handling + +- Use `try` for propagating errors +- Catch specific errors with `catch |err|` for graceful degradation +- Return optional types (`?T`) for operations that may fail non-critically +- Example: `getGitInfo()` returns `?GitInfo` and handles `FileNotFound` gracefully ### Creating New Dependencies @@ -126,3 +195,5 @@ Edit [build.zig](../build.zig) `.imports` array in `exe.root_module` or module d - Zig build is declarative: mutations to build graph (`b`), not direct compilation - Test executables test ONE module at a time (requires separate test instances) - Always specify target when creating modules used in tests +- Optional captures create new variable scope - avoid `defer` within them +- File operations must handle both git and non-git environments gracefully diff --git a/README.md b/README.md index cccdd2a..49bbd60 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,173 @@ -## Version Info +# Package Version Info + +[![npm version](https://badge.fury.io/js/package-version-info.svg)](https://badge.fury.io/js/package-version-info) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +A blazingly fast CLI tool written in Zig that generates TypeScript version information files from your `package.json`. Perfect for embedding version, build date, and git information into your applications. + +## ✨ Features + +- 🚀 **Fast**: Written in Zig for maximum performance +- 📦 **Zero Dependencies**: No runtime dependencies required +- 👤 **Author Information**: Extracts author details from package.json +- 🌿 **Git Integration**: Automatically extracts branch and commit information +- ⚡ **TypeScript Output**: Generates type-safe TypeScript constants +- 🎯 **Graceful Degradation**: Works with or without a git repository or author info +- 🔧 **Configurable**: Customize input and output paths + +## 🚀 Quick Start + +### Installation + +```bash +npm install package-version-info --save-dev +# or +yarn add package-version-info --dev +``` + +### Usage + +```bash +# Generate version-info.ts from package.json +npx package-version-info + +# Show version +npx package-version-info --version + +# Custom paths +npx package-version-info --input package.json --output src/version-info.ts +``` + +### Output Example + +**Full Output (with author and git):** + +```typescript +export const VERSION_INFO = { + version: "1.0.0", + date: "2025-12-26T16:00:00.000Z", + author: { + name: "Dominik Hladík", + email: "dominik.hladik@seznam.cz", + url: "https://github.com/Celtian", + }, + git: { + branch: "master", + commit: "324822ac3893dd6159ab8cb4477d45edeacf11f6", + }, +}; +``` + +**Minimal Output (without author and git):** + +```typescript +export const VERSION_INFO = { + version: "1.0.0", + date: "2025-12-26T16:00:00.000Z", +}; +``` + +## 📖 Usage in Your Application + +Once generated, import and use the version info in your application: + +```typescript +import { VERSION_INFO } from "./version-info"; + +console.log(`Version: ${VERSION_INFO.version}`); +console.log(`Build Date: ${VERSION_INFO.date}`); + +if (VERSION_INFO.author) { + console.log(`Author: ${VERSION_INFO.author.name}`); + console.log(`Email: ${VERSION_INFO.author.email}`); + console.log(`URL: ${VERSION_INFO.author.url}`); +} + +if (VERSION_INFO.git) { + console.log(`Branch: ${VERSION_INFO.git.branch}`); + console.log(`Commit: ${VERSION_INFO.git.commit}`); +} +``` + +## 🔧 CLI Options + +| Option | Alias | Default | Description | +| ----------- | ----- | ----------------- | ------------------------------ | +| `--version` | `-v` | - | Display version information | +| `--input` | `-i` | `package.json` | Path to package.json file | +| `--output` | `-o` | `version-info.ts` | Path to output TypeScript file | + +## 🛠️ Integration with Build Tools + +### NPM Scripts + +Add to your `package.json`: + +```json +{ + "scripts": { + "prebuild": "package-version-info", + "build": "your-build-command" + } +} +``` + +### With TypeScript Projects + +```json +{ + "scripts": { + "version-info": "package-version-info --output src/version-info.ts", + "prebuild": "npm run version-info", + "build": "tsc" + } +} +``` + +## 🏗️ Development + +This project is written in Zig. To build from source: + +### Prerequisites + +- [Zig](https://ziglang.org/) >= 0.15.2 + +### Build Commands + +```bash +# Build the executable +zig build + +# Run the executable +zig build run + +# Run with arguments +zig build run -- --input package.json --output version-info.ts + +# Run tests +zig build test + +# Clean build artifacts +rm -rf zig-cache zig-out +``` + +## 🎯 Why Zig? + +- **Performance**: Compiled binary with minimal overhead +- **Size**: Small executable footprint +- **Cross-platform**: Easy to build for multiple platforms +- **Memory Safety**: No runtime exceptions or undefined behavior ## 📦 Dependencies -_None_ +_None_ - This is a standalone binary with zero runtime dependencies. + +## 🤝 Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. ## 🪪 License Copyright © 2025 [Dominik Hladik](https://github.com/Celtian) -All contents are licensed under the [MIT license]. - -[mit license]: LICENSE +All contents are licensed under the [MIT license](LICENSE). diff --git a/src/root.zig b/src/root.zig index ef99ade..19132b7 100644 --- a/src/root.zig +++ b/src/root.zig @@ -44,6 +44,13 @@ pub fn printVersion(allocator: std.mem.Allocator, package_json_path: []const u8) logNewLine(); } +/// Structure to hold author information +pub const AuthorInfo = struct { + name: []const u8, + email: []const u8, + url: []const u8, +}; + /// Structure to hold git information pub const GitInfo = struct { branch: []const u8, @@ -118,22 +125,29 @@ fn writeTypescriptHeader(writer: anytype) !void { } /// Writes the VERSION_INFO export statement to the output -fn writeVersionInfo(writer: anytype, version: []const u8, date_str: []const u8, git_info: ?GitInfo) !void { +fn writeVersionInfo(writer: anytype, version: []const u8, date_str: []const u8, author_info: ?AuthorInfo, git_info: ?GitInfo) !void { try writer.print("export const VERSION_INFO = {{\n", .{}); try writer.print(" version: \"{s}\",\n", .{version}); try writer.print(" date: \"{s}\"", .{date_str}); + if (author_info) |author| { + try writer.print(",\n", .{}); + try writer.print(" author: {{\n", .{}); + try writer.print(" name: \"{s}\",\n", .{author.name}); + try writer.print(" email: \"{s}\",\n", .{author.email}); + try writer.print(" url: \"{s}\"\n", .{author.url}); + try writer.print(" }}", .{}); + } + if (git_info) |info| { try writer.print(",\n", .{}); try writer.print(" git: {{\n", .{}); try writer.print(" branch: \"{s}\",\n", .{info.branch}); try writer.print(" commit: \"{s}\"\n", .{info.commit}); - try writer.print(" }}\n", .{}); - } else { - try writer.print("\n", .{}); + try writer.print(" }}", .{}); } - try writer.print("}};\n", .{}); + try writer.print("\n}};\n", .{}); } /// Formats a Unix timestamp in milliseconds to ISO 8601 format (YYYY-MM-DDTHH:MM:SS.sssZ) @@ -191,6 +205,37 @@ pub fn generateVersionInfo(allocator: std.mem.Allocator, package_json_path: []co const version = parsed.value.object.get("version").?.string; log(Color.BLUE, "📦", "Version: {s}", .{version}); + logNewLine(); + log(Color.BRIGHT_BLUE, "👤", "Reading author info...", .{}); + var author_info: ?AuthorInfo = null; + if (parsed.value.object.get("author")) |author_value| { + if (author_value == .object) { + const author_obj = author_value.object; + if (author_obj.get("name")) |name| { + const author_name = name.string; + const author_email = if (author_obj.get("email")) |email| email.string else ""; + const author_url = if (author_obj.get("url")) |url| url.string else ""; + + author_info = AuthorInfo{ + .name = author_name, + .email = author_email, + .url = author_url, + }; + + log(Color.BRIGHT_BLUE, "✍️ ", "Name: {s}", .{author_name}); + if (author_email.len > 0) { + log(Color.BRIGHT_BLUE, "📧", "Email: {s}", .{author_email}); + } + if (author_url.len > 0) { + log(Color.BRIGHT_BLUE, "🔗", "URL: {s}", .{author_url}); + } + } + } + } + if (author_info == null) { + log(Color.YELLOW, "⚠️ ", "No author info found in package.json", .{}); + } + logNewLine(); log(Color.CYAN, "⏰", "Generating timestamp...", .{}); const millis = std.time.milliTimestamp(); @@ -225,7 +270,7 @@ pub fn generateVersionInfo(allocator: std.mem.Allocator, package_json_path: []co const writer = &stdout_writer.interface; try writeTypescriptHeader(writer); - try writeVersionInfo(writer, version, date_str, git_info); + try writeVersionInfo(writer, version, date_str, author_info, git_info); try writer.flush();