diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6023235..98e47c2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -22,7 +22,11 @@ on:
jobs:
build:
if: github.event_name == 'push' && contains(toJson(github.event.commits), '***NO_CI***') == false && contains(toJson(github.event.commits), '[ci skip]') == false && contains(toJson(github.event.commits), '[skip ci]') == false
- runs-on: windows-latest
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ dotnet: ['8.0.x', '9.0.x']
+ runs-on: ${{ matrix.os }}
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
DOTNET_CLI_TELEMETRY_OPTOUT: 1
@@ -39,23 +43,30 @@ jobs:
- name: setup .net core sdk
uses: actions/setup-dotnet@v4
with:
- dotnet-version: |
- 8.0.x
- 9.0.x
+ dotnet-version: ${{ matrix.dotnet }}
- name: dotnet build
run: dotnet build solrevdev.seedfolder.sln --configuration Release
+ - name: run integration tests (linux/mac)
+ if: matrix.os != 'windows-latest'
+ run: ./tests/integration-test.sh
+
+ - name: run integration tests (windows)
+ if: matrix.os == 'windows-latest'
+ run: powershell -File tests/integration-test.ps1
+
- name: dotnet pack
run: dotnet pack solrevdev.seedfolder.sln -c Release --no-build --include-source --include-symbols
- name: setup nuget
- if: github.event_name == 'push' && github.ref == 'refs/heads/master'
+ if: github.event_name == 'push' && github.ref == 'refs/heads/master' && matrix.os == 'ubuntu-latest' && matrix.dotnet == '8.0.x'
uses: nuget/setup-nuget@v1
with:
nuget-version: latest
- name: Publish NuGet
+ if: github.event_name == 'push' && github.ref == 'refs/heads/master' && matrix.os == 'ubuntu-latest' && matrix.dotnet == '8.0.x'
uses: rohith/publish-nuget@v2.1.1
with:
PROJECT_FILE_PATH: src/solrevdev.seedfolder.csproj # Relative to repository root
diff --git a/README.md b/README.md
index 1d8102c..2562a31 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,6 @@
[](https://github.com/solrevdev/seedfolder) [](https://github.com/solrevdev/seedfolder) [](https://twitter.com/solrevdev)
-
```
_ __ _ _
___ ___ ___ __| |/ _| ___ | | __| | ___ _ __
@@ -11,100 +10,532 @@
|___/\___|\___|\__,_|_| \___/|_|\__,_|\___|_|
```
-
## Overview
-This is a .NET Core Global Tool that will create a folder named after either the first argument passed to it or if no
-argument is passed it will ask you for the folder name. It will then copy some default standard dotfiles over.
+**SeedFolder** is a powerful .NET Global Tool that creates project directories and seeds them with curated template files for different project types. Whether you're starting a .NET project, Node.js app, Python script, Ruby gem, or documentation project, SeedFolder provides the right foundation with just one command.
+
+🚀 **Multi-Template System** - Support for 6 different project types
+⚡ **Professional CLI** - Comprehensive command-line interface with dry-run, force mode, and more
+🌐 **Cross-Platform** - Works on Windows, macOS, and Linux
+🔄 **Interactive Mode** - Guided project creation with template selection
+📊 **Progress Tracking** - Real-time feedback with visual progress indicators
+
+## Quick Start
+
+```bash
+# Install globally
+dotnet tool install --global solrevdev.seedfolder
+
+# Interactive mode - choose template and folder name
+seedfolder
+
+# Create specific project types
+seedfolder --template node myapp
+seedfolder -t python data-analysis
+seedfolder --type ruby my_gem
+
+# Preview before creating
+seedfolder --dry-run -t node myapp
+```
+
+## Supported Project Templates
+
+### 📝 **markdown** - Documentation Project (Default)
+Documentation and content projects:
+- `README.md` - Project documentation template
+- `.gitignore` - Documentation specific git ignore patterns
+- `.gitattributes` - Documentation-focused git attributes with LFS for images/videos
+- `.editorconfig` - Documentation optimized editor configuration
+
+### 🏗️ **dotnet** - .NET Project
+Complete .NET development environment with standard dotfiles:
+- `.dockerignore` - Docker ignore patterns
+- `.editorconfig` - Comprehensive C# editor configuration with .NET naming conventions
+- `.gitattributes` - Comprehensive .NET git attributes with LFS for binaries and C# language detection
+- `.gitignore` - .NET specific git ignore patterns
+- `.prettierignore` - Prettier ignore patterns
+- `.prettierrc` - Prettier configuration
+- `omnisharp.json` - OmniSharp configuration
-For example:
+### 📦 **node** - Node.js Project
+Modern Node.js project setup:
+- `package.json` - Node.js package configuration
+- `index.js` - Main application entry point
+- `.gitignore` - Node.js specific git ignore patterns
+- `.gitattributes` - JavaScript/TypeScript focused git attributes with web asset handling
+- `.editorconfig` - JavaScript/TypeScript optimized editor configuration
+- `.prettierignore` - Prettier ignore patterns
+- `.prettierrc` - Prettier configuration
+### 🐍 **python** - Python Project
+Python development environment:
+- `main.py` - Main application entry point
+- `requirements.txt` - Python dependencies
+- `.gitignore` - Python specific git ignore patterns
+- `.gitattributes` - Python-focused git attributes with LFS for wheels, data files, and ML models
+- `.editorconfig` - PEP 8 compliant editor configuration
+
+### 💎 **ruby** - Ruby Project
+Ruby development setup:
+- `Gemfile` - Ruby dependencies
+- `main.rb` - Main application entry point
+- `.gitignore` - Ruby specific git ignore patterns
+- `.gitattributes` - Ruby-focused git attributes with gem handling and ERB template support
+- `.editorconfig` - Ruby standard editor configuration
+
+### 🌐 **universal** - Basic Project
+Minimal project setup for any use case:
+- `README.md` - Project documentation template
+- `.gitignore` - Basic git ignore patterns
+- `.gitattributes` - Conservative cross-platform git attributes with basic binary handling
+- `.editorconfig` - Universal editor configuration
+
+## Command Line Interface
+
+```
+Usage: seedfolder [options] [folderName]
+
+Options:
+ --help, -h, -? Show this help message
+ --version, -v Show version information
+ --list-templates Show available template files
+ --template, --type, -t Specify project template type
+ --dry-run, --dry Preview operations without creating files
+ --force, -f Overwrite existing directory and files
+ --quiet, -q Suppress output (useful for scripting)
+
+Arguments:
+ folderName Name of the folder to create (optional)
+
+Template Types:
+ markdown Documentation project with README (default)
+ dotnet .NET project with standard dotfiles
+ node Node.js project with package.json
+ python Python project with requirements.txt
+ ruby Ruby project with Gemfile
+ universal Basic project with minimal files
+```
+
+## Usage Examples
+
+### Interactive Mode
```bash
+# Start interactive mode with template selection
seedfolder
-dotnet run --project src/solrevdev.seedfolder.csproj --framework net8.0
+
+# Example interactive session:
+ _ __ _ _
+ ___ ___ ___ __| |/ _| ___ | | __| | ___ _ __
+ / __|/ _ \/ _ \/ _` | |_ / _ \| |/ _` |/ _ \ '__|
+ \__ \ __/ __/ (_| | _| (_) | | (_| | __/ |
+ |___/\___|\___|\__,_|_| \___/|_|\__,_|\___|_|
+
+▲ Running in the path /current/directory
+▲ Available project templates:
+ 1. markdown - Documentation project with README
+ 2. dotnet - .NET project with standard dotfiles
+ 3. node - Node.js project with package.json
+ 4. python - Python project with requirements.txt
+ 5. ruby - Ruby project with Gemfile
+ 6. universal - Basic project with minimal files
+
+▲ Select template type (1-6) or press Enter for markdown: 3
+▲ Selected template: Node
▲ Do you want to prefix the folder with the date? [Y/n] y
-▲ What do you want the folder to be named? temp
-▲ Creating the directory 2020-12-10_temp
-▲ Copying .dockerignore to 2020-12-10_temp/.dockerignore
-▲ Copying .editorconfig to 2020-12-10_temp/.editorconfig
-▲ Copying .gitattributes to 2020-12-10_temp/.gitattributes
-▲ Copying .gitignore to 2020-12-10_temp/.gitignore
-▲ Copying .prettierignore to 2020-12-10_temp/.prettierignore
-▲ Copying .prettierrc to 2020-12-10_temp/.prettierrc
-▲ Copying omnisharp.json to 2020-12-10_temp/omnisharp.json
+▲ What do you want the folder to be named? my-awesome-app
+▲ Creating the directory 2024-01-15_my-awesome-app
+▲ [1/7] Copying package.json
+ ✅ Created 2024-01-15_my-awesome-app/package.json
+▲ [2/7] Copying index.js
+ ✅ Created 2024-01-15_my-awesome-app/index.js
+▲ [3/7] Copying .gitignore
+ ✅ Created 2024-01-15_my-awesome-app/.gitignore
+▲ [4/7] Copying .gitattributes
+ ✅ Created 2024-01-15_my-awesome-app/.gitattributes
+▲ [5/7] Copying .editorconfig
+ ✅ Created 2024-01-15_my-awesome-app/.editorconfig
+▲ [6/7] Copying .prettierignore
+ ✅ Created 2024-01-15_my-awesome-app/.prettierignore
+▲ [7/7] Copying .prettierrc
+ ✅ Created 2024-01-15_my-awesome-app/.prettierrc
▲ Done!
+▲ Successfully created 7 files in '2024-01-15_my-awesome-app' using Node template.
-seedfolder
-dotnet run --project src/solrevdev.seedfolder.csproj --framework net9.0
-▲ Do you want to prefix the folder with the date? [Y/n] n
-▲ What do you want the folder to be named? temp
-▲ Creating the directory temp
-▲ Copying .dockerignore to temp/.dockerignore
-▲ Copying .editorconfig to temp/.editorconfig
-▲ Copying .gitattributes to temp/.gitattributes
-▲ Copying .gitignore to temp/.gitignore
-▲ Copying .prettierignore to temp/.prettierignore
-▲ Copying .prettierrc to temp/.prettierrc
-▲ Copying omnisharp.json to temp/omnisharp.json
+▲ To initialize git and make your first commit, copy and paste these commands:
+
+cd "2024-01-15_my-awesome-app"
+git init
+git lfs install 2>/dev/null || echo "Git LFS not available"
+git add .
+git commit -m "Initial commit"
+```
+
+### Direct Template Selection
+```bash
+# Create different project types
+seedfolder --template node myapp
+▲ Using template type: Node
+ _ __ _ _
+ ___ ___ ___ __| |/ _| ___ | | __| | ___ _ __
+ / __|/ _ \/ _ \/ _` | |_ / _ \| |/ _` |/ _ \ '__|
+ \__ \ __/ __/ (_| | _| (_) | | (_| | __/ |
+ |___/\___|\___|\__,_|_| \___/|_|\__,_|\___|_|
+
+▲ Running in the path /current/directory
+▲ Creating the directory myapp
+▲ [1/7] Copying package.json
+ ✅ Created myapp/package.json
+▲ [2/7] Copying index.js
+ ✅ Created myapp/index.js
+▲ [3/7] Copying .gitignore
+ ✅ Created myapp/.gitignore
+▲ [4/7] Copying .gitattributes
+ ✅ Created myapp/.gitattributes
+▲ [5/7] Copying .editorconfig
+ ✅ Created myapp/.editorconfig
+▲ [6/7] Copying .prettierignore
+ ✅ Created myapp/.prettierignore
+▲ [7/7] Copying .prettierrc
+ ✅ Created myapp/.prettierrc
▲ Done!
+▲ Successfully created 7 files in 'myapp' using Node template.
+
+▲ To initialize git and make your first commit, copy and paste these commands:
+
+cd "myapp"
+git init
+git lfs install 2>/dev/null || echo "Git LFS not available"
+git add .
+git commit -m "Initial commit"
+
+# Create Python project (spaces converted to dashes)
+seedfolder -t python "machine learning project"
+▲ Using template type: Python
+ _ __ _ _
+ ___ ___ ___ __| |/ _| ___ | | __| | ___ _ __
+ / __|/ _ \/ _ \/ _` | |_ / _ \| |/ _` |/ _ \ '__|
+ \__ \ __/ __/ (_| | _| (_) | | (_| | __/ |
+ |___/\___|\___|\__,_|_| \___/|_|\__,_|\___|_|
+
+▲ Running in the path /current/directory
+▲ Creating the directory machine-learning-project
+▲ [1/5] Copying main.py
+ ✅ Created machine-learning-project/main.py
+▲ [2/5] Copying requirements.txt
+ ✅ Created machine-learning-project/requirements.txt
+▲ [3/5] Copying .gitignore
+ ✅ Created machine-learning-project/.gitignore
+▲ [4/5] Copying .gitattributes
+ ✅ Created machine-learning-project/.gitattributes
+▲ [5/5] Copying .editorconfig
+ ✅ Created machine-learning-project/.editorconfig
+▲ Done!
+▲ Successfully created 5 files in 'machine-learning-project' using Python template.
+
+▲ To initialize git and make your first commit, copy and paste these commands:
+
+cd "machine-learning-project"
+git init
+git lfs install 2>/dev/null || echo "Git LFS not available"
+git add .
+git commit -m "Initial commit"
+
+# Other examples
+seedfolder --type ruby my_gem_name # Creates Ruby project
+seedfolder -t markdown my-docs # Creates documentation project
+seedfolder --template universal basic-project # Creates universal project
+
+# Using folder name argument (skips interactive mode)
+seedfolder myproject # Creates markdown project (default)
+seedfolder --template python myapp # Creates Python project
+```
+
+### Preview and Force Operations
+```bash
+# Preview what would be created (dry-run mode)
+seedfolder --dry-run -t node myapp
+▲ Using template type: Node
+ _ __ _ _
+ ___ ___ ___ __| |/ _| ___ | | __| | ___ _ __
+ / __|/ _ \/ _ \/ _` | |_ / _ \| |/ _` |/ _ \ '__|
+ \__ \ __/ __/ (_| | _| (_) | | (_| | __/ |
+ |___/\___|\___|\__,_|_| \___/|_|\__,_|\___|_|
+
+▲ Running in the path /current/directory
+▲ DRY RUN: Would create directory 'myapp' with template 'Node'
+▲ Files that would be created:
+ • myapp/package.json
+ • myapp/index.js
+ • myapp/.gitignore
+ • myapp/.gitattributes
+ • myapp/.editorconfig
+ • myapp/.prettierignore
+ • myapp/.prettierrc
+▲ Use without --dry-run to actually create the files.
-seedfolder temp
-dotnet run --project src/solrevdev.seedfolder.csproj --framework net8.0 temp
-▲ Found 1 params to process.
-▲ Creating the directory temp
-▲ Copying .dockerignore to temp/.dockerignore
-▲ Copying .editorconfig to temp/.editorconfig
-▲ Copying .gitattributes to temp/.gitattributes
-▲ Copying .gitignore to temp/.gitignore
-▲ Copying .prettierignore to temp/.prettierignore
-▲ Copying .prettierrc to temp/.prettierrc
-▲ Copying omnisharp.json to temp/omnisharp.json
+# Force overwrite existing directory
+seedfolder --force existing-directory
+▲ Warning: Directory 'existing-directory' exists, will overwrite files.
+▲ Creating the directory existing-directory
+▲ [1/4] Copying README.md
+ ✅ Created existing-directory/README.md
+▲ [2/4] Copying .gitignore
+ ✅ Created existing-directory/.gitignore
+▲ [3/4] Copying .gitattributes
+ ✅ Created existing-directory/.gitattributes
+▲ [4/4] Copying .editorconfig
+ ✅ Created existing-directory/.editorconfig
▲ Done!
+▲ Successfully created 4 files in 'existing-directory' using Markdown template.
+
+▲ To initialize git and make your first commit, copy and paste these commands:
+
+cd "existing-directory"
+git init
+git lfs install 2>/dev/null || echo "Git LFS not available"
+git add .
+git commit -m "Initial commit"
+
+# Quiet mode for scripting (no output shown)
+seedfolder --quiet -t node myapp
+```
+
+### Template Information
+```bash
+# List all available templates with file details
+seedfolder --list-templates
+▲ Available project templates:
+
+ markdown - Documentation project with README
+ • README.md Project documentation
+ • .gitignore Documentation specific git ignore patterns
+ • .gitattributes Git attributes for documentation projects
+ • .editorconfig Editor configuration for Markdown
+
+ dotnet - Dotnet project with standard dotfiles
+ • .dockerignore Docker ignore patterns
+ • .editorconfig Editor configuration for .NET
+ • .gitattributes Git attributes
+ • .gitignore Git ignore patterns
+ • .prettierignore Prettier ignore patterns
+ • .prettierrc Prettier configuration
+ • omnisharp.json OmniSharp configuration
+
+ node - Node.js project with package.json
+ • package.json Node.js package configuration
+ • index.js Main application entry point
+ • .gitignore Node.js specific git ignore patterns
+ • .gitattributes Git attributes for Node.js projects
+ • .editorconfig Editor configuration for Node.js
+ • .prettierignore Prettier ignore patterns
+ • .prettierrc Prettier configuration
+ python - Python project with requirements.txt
+ • main.py Main application entry point
+ • requirements.txt Python dependencies
+ • .gitignore Python specific git ignore patterns
+ • .gitattributes Git attributes for Python projects
+ • .editorconfig Editor configuration for Python
+
+ ruby - Ruby project with Gemfile
+ • Gemfile Ruby dependencies
+ • main.rb Main application entry point
+ • .gitignore Ruby specific git ignore patterns
+ • .gitattributes Git attributes for Ruby projects
+ • .editorconfig Editor configuration for Ruby
+
+ universal - Basic project with minimal files
+ • README.md Project documentation
+ • .gitignore Basic git ignore patterns
+ • .gitattributes Git attributes for universal projects
+ • .editorconfig Editor configuration for universal projects
+
+▲ Usage examples:
+ seedfolder --template node myproject
+ seedfolder -t python myapp
+ seedfolder --type ruby mygem
+
+# Show version information
+seedfolder --version
+▲ seedfolder version 1.3.1
+
+# Show help
+seedfolder --help
+▲ seedfolder version 1.3.1
+
+▲ Usage: seedfolder [options] [folderName]
+
+Options:
+ --help, -h, -? Show this help message
+ --version, -v Show version information
+ --list-templates Show available template files
+ --template, --type, -t Specify project template type
+ --dry-run, --dry Preview operations without creating files
+ --force, -f Overwrite existing directory and files
+ --quiet, -q Suppress output (useful for scripting)
+
+Arguments:
+ folderName Name of the folder to create (optional)
+
+Template Types:
+ dotnet .NET project with standard dotfiles
+ node Node.js project with package.json
+ python Python project with requirements.txt
+ ruby Ruby project with Gemfile
+ markdown Documentation project with README (default)
+ universal Basic project with minimal files
+
+Examples:
+ seedfolder # Interactive mode with template selection
+ seedfolder myproject # Create 'myproject' folder with markdown template
+ seedfolder --template node myapp # Create Node.js project
+ seedfolder -t python "my project" # Create Python project (spaces converted to dashes)
+ seedfolder --type ruby mygem # Create Ruby project
+ seedfolder --dry-run -t node myapp # Preview Node.js project creation
+ seedfolder --force myproject # Overwrite existing 'myproject' directory
+ seedfolder --quiet -t python myapp # Create Python project with no output
```
-It will also copy the following dotfiles from the `src/Data` folder over:
+## Advanced Features
-* .dockerignore
-* .editorconfig
-* .gitattributes
-* .gitignore
-* .prettierignore
-* .prettierrc
+### 🛡️ **Error Handling & Validation**
+- **Disk Space Validation** - Checks for minimum 10MB free space before operations
+- **Path Validation** - Automatically sanitizes folder names and handles invalid characters
+- **Permission Checks** - Graceful handling of permission errors with actionable suggestions
+- **Rollback Support** - Failed operations provide clear rollback information
+
+### 📊 **Progress Indicators**
+Real-time progress tracking with visual feedback:
+```bash
+▲ [1/5] Copying main.py
+ ✅ Created test-python/main.py
+▲ [2/5] Copying requirements.txt
+ ✅ Created test-python/requirements.txt
+▲ [3/5] Copying .gitignore
+ ✅ Created test-python/.gitignore
+▲ [4/5] Copying .gitattributes
+ ✅ Created test-python/.gitattributes
+▲ [5/5] Copying .editorconfig
+ ✅ Created test-python/.editorconfig
+▲ Done!
+▲ Successfully created 5 files in 'test-python' using Python template.
+
+▲ To initialize git and make your first commit, copy and paste these commands:
+
+cd "test-python"
+git init
+git lfs install 2>/dev/null || echo "Git LFS not available"
+git add .
+git commit -m "Initial commit"
+```
+
+### 🔄 **Smart Input Handling**
+- **Space Conversion** - Spaces in folder names automatically converted to dashes
+- **Path Sanitization** - Invalid filesystem characters replaced with safe alternatives
+- **Date Prefixing** - Optional YYYY-MM-DD prefix for organized project structure
## Requirements
-This tool requires .NET 8.0 or .NET 9.0 SDK to be installed on your system.
+This tool requires **.NET 8.0 or .NET 9.0 SDK** to be installed on your system.
+
+- **Supported Platforms**: Windows, macOS, Linux
+- **Runtime**: .NET 8.0 or later
+- **Installation**: Via .NET Global Tools
## Installation
-Locally without publishing it on NuGet
+### From NuGet (Recommended)
+```bash
+dotnet tool install --global solrevdev.seedfolder
+```
-```powershell
+### Local Development Installation
+```bash
+# Clone the repository
+git clone https://github.com/solrevdev/seedfolder.git
+cd seedfolder
-dotnet pack
-dotnet tool install --global --add-source ./nupkg solrevdev.seedfolder
+# Build and install locally
+dotnet pack -c release -o artifacts/nupkg
+dotnet tool uninstall -g solrevdev.seedfolder # If previously installed
+dotnet tool install -g --add-source artifacts/nupkg solrevdev.seedfolder
+```
+### Uninstall
+```bash
+dotnet tool uninstall --global solrevdev.seedfolder
```
-Normally via NuGet
+## Development
-```powershell
-dotnet tool install --global solrevdev.seedfolder
+### Local Testing
+```bash
+## Run directly during development
+
+# Interactive mode
+dotnet run --project src/solrevdev.seedfolder.csproj --framework net9.0
+
+## With arguments
+
+# Show the application's version (pass `--version` to the app via `--`)
+dotnet run --project src/solrevdev.seedfolder.csproj --framework net9.0 -- --version
+
+# Show the application's help text (pass `--help` to the app via `--`)
+dotnet run --project src/solrevdev.seedfolder.csproj --framework net9.0 -- --help
+
+# Create a Node template project using the app (arguments after `--` are for the app)
+dotnet run --project src/solrevdev.seedfolder.csproj --framework net9.0 -- --template node myapp
+
+# If you omit `--` and run `dotnet run --project ... --help`, the .NET CLI help will be shown.
```
-To uninstall
+### Building and Testing
+```bash
+# Build the project
+dotnet build
-```powershell
-dotnet tool uninstall -g solrevdev.seedfolder
+# Run tests
+dotnet test
+
+# Create package
+dotnet pack -c release
```
-## Publish to Nuget
+## Roadmap
+
+### ✅ Completed (v1.3.x)
+- Multi-template system with 6 project types
+- Professional CLI interface with comprehensive help
+- Cross-platform testing and CI/CD
+- Interactive template selection
+- Dry-run mode and force operations
+- Progress indicators and enhanced error handling
+
+### 🔮 Future Enhancements
+- **External Template Sources** - Support for custom template directories
+- **Configuration File Support** - User preferences and default settings
+- **Template Management System** - Validation, versioning, and template marketplace
-Uses a GitHub secret to store a `NUGET_API_KEY` API Key created over at NuGet in order to build and deploy via GitHub actions to NuGet.
+## Contributing
-When you commit bump the version in the `csproj` file
+Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
+
+## Publishing to NuGet
+
+The project uses GitHub Actions for automatic publishing to NuGet. Simply bump the version in `src/solrevdev.seedfolder.csproj`:
```xml
-1.0.1
+1.3.2
```
+
+Commit to the `master` branch and GitHub Actions will automatically build and publish to NuGet.
+
+## License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+## Support
+
+- 🐛 **Issues**: [GitHub Issues](https://github.com/solrevdev/seedfolder/issues)
+- 💬 **Discussions**: [GitHub Discussions](https://github.com/solrevdev/seedfolder/discussions)
+- 🐦 **Twitter**: [@solrevdev](https://twitter.com/solrevdev)
diff --git a/src/Data/Gemfile b/src/Data/Gemfile
new file mode 100644
index 0000000..02a2c57
--- /dev/null
+++ b/src/Data/Gemfile
@@ -0,0 +1,3 @@
+source 'https://rubygems.org'
+
+gem 'bundler'
\ No newline at end of file
diff --git a/src/Data/README.md b/src/Data/README.md
new file mode 100644
index 0000000..bae4ea1
--- /dev/null
+++ b/src/Data/README.md
@@ -0,0 +1,19 @@
+# Project Name
+
+Brief description of your project.
+
+## Installation
+
+Instructions for installing and setting up your project.
+
+## Usage
+
+Examples of how to use your project.
+
+## Contributing
+
+Guidelines for contributing to the project.
+
+## License
+
+Information about the project license.
\ No newline at end of file
diff --git a/src/Data/editorconfig-dotnet b/src/Data/editorconfig-dotnet
new file mode 100644
index 0000000..6da0e9d
--- /dev/null
+++ b/src/Data/editorconfig-dotnet
@@ -0,0 +1,201 @@
+# EditorConfig is awesome:
+http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 4 spaces as indentation
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+guidelines = 140
+
+
+# C# files
+[*.cs]
+# New line preferences
+csharp_new_line_before_open_brace = all
+csharp_new_line_before_else = true
+csharp_new_line_before_catch = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_switch_labels = true
+csharp_indent_labels = one_less_than_current
+
+# avoid this. unless absolutely necessary
+dotnet_style_qualification_for_field = false:suggestion
+dotnet_style_qualification_for_property = false:suggestion
+dotnet_style_qualification_for_method = false:suggestion
+dotnet_style_qualification_for_event = false:suggestion
+
+# only use var when it's obvious what the variable type is
+csharp_style_var_for_built_in_types = true:suggestion
+csharp_style_var_when_type_is_apparent = true:suggestion
+csharp_style_var_elsewhere = true:suggestion
+
+# use language keywords instead of BCL types
+dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
+dotnet_style_predefined_type_for_member_access = true:suggestion
+
+# name all constant fields using PascalCase
+dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
+dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
+dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
+
+dotnet_naming_symbols.constant_fields.applicable_kinds = field
+dotnet_naming_symbols.constant_fields.required_modifiers = const
+
+dotnet_naming_style.pascal_case_style.capitalization = pascal_case
+
+# static fields should have _ prefix
+dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
+dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
+dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
+
+dotnet_naming_symbols.static_fields.applicable_kinds = field
+dotnet_naming_symbols.static_fields.required_modifiers = static
+
+dotnet_naming_style.static_prefix_style.required_prefix = _
+dotnet_naming_style.static_prefix_style.capitalization = camel_case
+
+# internal and private fields should be _camelCase
+dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
+dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
+dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
+
+dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
+dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
+
+dotnet_naming_style.camel_case_underscore_style.required_prefix = _
+dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
+
+# Code style defaults
+dotnet_sort_system_directives_first = true
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = false
+dotnet_separate_import_directive_groups = false
+
+# IDE0005: Remove unnessecary using statements
+dotnet_diagnostic.IDE0005.severity = error
+
+# Expression-level preferences
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+
+# Expression-bodied members
+csharp_style_expression_bodied_methods = true:suggestion
+csharp_style_expression_bodied_constructors = true:suggestion
+csharp_style_expression_bodied_operators = true:suggestion
+csharp_style_expression_bodied_properties = true:suggestion
+csharp_style_expression_bodied_indexers = true:suggestion
+csharp_style_expression_bodied_accessors = true:suggestion
+
+# Inlined variable declarations
+csharp_style_inlined_variable_declaration = true : suggestion
+
+# Pattern matching
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+
+# Null checking preferences
+csharp_style_throw_expression = true:suggestion
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = do_not_ignore
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# ---- Latest csharp features ----- #
+
+# new using statement
+csharp_prefer_simple_using_statement = false:suggestion
+
+# Simplify new expression (IDE0090)
+csharp_style_implicit_object_creation_when_type_is_apparent = false:suggestion
+
+# Use range operator (IDE0057)
+csharp_style_prefer_range_operator = false:suggestion
+csharp_style_prefer_index_operator = false:suggestion
+
+# file scoped namespaces
+csharp_style_namespace_declarations = file_scoped
+dotnet_diagnostic.IDE0161.severity = error
+
+# async methods should have "Async" suffix
+dotnet_naming_rule.async_methods_end_in_async.symbols = any_async_methods
+dotnet_naming_rule.async_methods_end_in_async.style = end_in_async
+dotnet_naming_rule.async_methods_end_in_async.severity = error
+
+dotnet_naming_symbols.any_async_methods.applicable_kinds = method
+dotnet_naming_symbols.any_async_methods.applicable_accessibilities = *
+dotnet_naming_symbols.any_async_methods.required_modifiers = async
+
+dotnet_naming_style.end_in_async.required_prefix =
+dotnet_naming_style.end_in_async.required_suffix = Async
+dotnet_naming_style.end_in_async.capitalization = pascal_case
+dotnet_naming_style.end_in_async.word_separator =
+
+# IDE0290 Use primary constructor
+# https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#primary-constructors
+# https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/primary-constructors
+# will disable this rule for now until I decide if I like it and want to use it.
+# dotnet_diagnostic.IDE0290.severity = none | error | suggestion
+
+# IDE0290 Use primary constructor
+dotnet_diagnostic.IDE0290.severity = none
+
+# ---- Latest csharp features ----- #
+
+# CA1031: Do not catch general exception types
+dotnet_diagnostic.CA1031.severity = none
+
+[*.{asm,inc}]
+indent_size = 8
+
+# Xml project files
+[*.{csproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
+indent_size = 2
+
+# Xml config files
+[*.{props,targets,config,nuspec}]
+indent_size = 2
+
+[CMakeLists.txt]
+indent_size = 2
+
+[*.cmd]
+indent_size = 2
diff --git a/src/Data/editorconfig-markdown b/src/Data/editorconfig-markdown
new file mode 100644
index 0000000..579335e
--- /dev/null
+++ b/src/Data/editorconfig-markdown
@@ -0,0 +1,27 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 2 spaces as indentation for documentation projects
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+
+# Markdown files
+[*.{md,markdown}]
+trim_trailing_whitespace = false
+indent_size = 2
+
+# YAML frontmatter in Markdown
+[*.{yml,yaml}]
+indent_size = 2
+
+# JSON files (for configuration)
+[*.json]
+indent_size = 2
\ No newline at end of file
diff --git a/src/Data/editorconfig-node b/src/Data/editorconfig-node
new file mode 100644
index 0000000..61e5625
--- /dev/null
+++ b/src/Data/editorconfig-node
@@ -0,0 +1,34 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 2 spaces as indentation for Node.js projects
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+
+# JavaScript files
+[*.{js,jsx,ts,tsx}]
+indent_size = 2
+
+# JSON files
+[*.json]
+indent_size = 2
+
+# YAML files
+[*.{yml,yaml}]
+indent_size = 2
+
+# Markdown files
+[*.md]
+trim_trailing_whitespace = false
+
+# Package files
+[package.json]
+indent_size = 2
\ No newline at end of file
diff --git a/src/Data/editorconfig-python b/src/Data/editorconfig-python
new file mode 100644
index 0000000..8a3bf2a
--- /dev/null
+++ b/src/Data/editorconfig-python
@@ -0,0 +1,36 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 4 spaces as indentation (PEP 8)
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
+max_line_length = 88
+
+# Python files
+[*.py]
+indent_size = 4
+max_line_length = 88
+
+# YAML files
+[*.{yml,yaml}]
+indent_size = 2
+
+# TOML files (pyproject.toml, etc.)
+[*.toml]
+indent_size = 4
+
+# Requirements files
+[requirements*.txt]
+indent_size = 4
+
+# Markdown files
+[*.md]
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/src/Data/editorconfig-ruby b/src/Data/editorconfig-ruby
new file mode 100644
index 0000000..0e5487d
--- /dev/null
+++ b/src/Data/editorconfig-ruby
@@ -0,0 +1,34 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 2 spaces as indentation (Ruby standard)
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+
+# Ruby files
+[*.{rb,rake}]
+indent_size = 2
+
+# Gemfile and related files
+[{Gemfile,Rakefile,*.gemspec}]
+indent_size = 2
+
+# YAML files
+[*.{yml,yaml}]
+indent_size = 2
+
+# ERB templates
+[*.erb]
+indent_size = 2
+
+# Markdown files
+[*.md]
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/src/Data/editorconfig-universal b/src/Data/editorconfig-universal
new file mode 100644
index 0000000..b775407
--- /dev/null
+++ b/src/Data/editorconfig-universal
@@ -0,0 +1,26 @@
+# EditorConfig is awesome: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Default settings:
+# A newline ending every file
+# Use 2 spaces as indentation (universal default)
+[*]
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+charset = utf-8
+trim_trailing_whitespace = true
+
+# Markdown files
+[*.md]
+trim_trailing_whitespace = false
+
+# YAML files
+[*.{yml,yaml}]
+indent_size = 2
+
+# JSON files
+[*.json]
+indent_size = 2
\ No newline at end of file
diff --git a/src/Data/gitattributes-markdown b/src/Data/gitattributes-markdown
new file mode 100644
index 0000000..6988e7b
--- /dev/null
+++ b/src/Data/gitattributes-markdown
@@ -0,0 +1,45 @@
+###############################
+# Git Line Endings #
+###############################
+
+# Set default behavior to automatically normalize line endings.
+* text=auto eol=lf
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+
+# Force bash scripts to always use lf line endings
+*.sh text eol=lf
+
+###############################
+# Git Large File System (LFS) #
+###############################
+
+# Archives
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+
+# Images (common in documentation)
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.svg filter=lfs diff=lfs merge=lfs -text
+*.webp filter=lfs diff=lfs merge=lfs -text
+
+# Documents
+*.pdf filter=lfs diff=lfs merge=lfs -text
+
+# Videos (for documentation)
+*.mp4 filter=lfs diff=lfs merge=lfs -text
+*.mov filter=lfs diff=lfs merge=lfs -text
+*.gif filter=lfs diff=lfs merge=lfs -text
+
+# Markdown language detection
+*.md linguist-language=Markdown
+*.markdown linguist-language=Markdown
+*.mdown linguist-language=Markdown
+*.mkd linguist-language=Markdown
+*.mkdn linguist-language=Markdown
+*.mdx linguist-language=MDX
\ No newline at end of file
diff --git a/src/Data/gitattributes-node b/src/Data/gitattributes-node
new file mode 100644
index 0000000..9a57c27
--- /dev/null
+++ b/src/Data/gitattributes-node
@@ -0,0 +1,46 @@
+###############################
+# Git Line Endings #
+###############################
+
+# Set default behavior to automatically normalize line endings.
+* text=auto eol=lf
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+
+# Force bash scripts to always use lf line endings
+*.sh text eol=lf
+
+###############################
+# Git Large File System (LFS) #
+###############################
+
+# Archives
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.br filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+
+# Images
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.ico filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.svg filter=lfs diff=lfs merge=lfs -text
+*.webp filter=lfs diff=lfs merge=lfs -text
+
+# Fonts
+*.woff filter=lfs diff=lfs merge=lfs -text
+*.woff2 filter=lfs diff=lfs merge=lfs -text
+*.ttf filter=lfs diff=lfs merge=lfs -text
+*.eot filter=lfs diff=lfs merge=lfs -text
+
+# Videos
+*.mp4 filter=lfs diff=lfs merge=lfs -text
+*.mov filter=lfs diff=lfs merge=lfs -text
+
+# JavaScript/TypeScript language detection
+*.ts linguist-language=TypeScript
+*.tsx linguist-language=TypeScript
+*.vue linguist-language=Vue
\ No newline at end of file
diff --git a/src/Data/gitattributes-python b/src/Data/gitattributes-python
new file mode 100644
index 0000000..fa16fce
--- /dev/null
+++ b/src/Data/gitattributes-python
@@ -0,0 +1,52 @@
+###############################
+# Git Line Endings #
+###############################
+
+# Set default behavior to automatically normalize line endings.
+* text=auto eol=lf
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+
+# Force bash scripts to always use lf line endings
+*.sh text eol=lf
+
+###############################
+# Git Large File System (LFS) #
+###############################
+
+# Archives
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+
+# Python wheels and eggs
+*.whl filter=lfs diff=lfs merge=lfs -text
+*.egg filter=lfs diff=lfs merge=lfs -text
+
+# Data files (common in Python projects)
+*.csv filter=lfs diff=lfs merge=lfs -text
+*.json filter=lfs diff=lfs merge=lfs -text
+*.h5 filter=lfs diff=lfs merge=lfs -text
+*.hdf5 filter=lfs diff=lfs merge=lfs -text
+*.pkl filter=lfs diff=lfs merge=lfs -text
+*.pickle filter=lfs diff=lfs merge=lfs -text
+
+# Images
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.svg filter=lfs diff=lfs merge=lfs -text
+
+# Models and datasets
+*.model filter=lfs diff=lfs merge=lfs -text
+*.weights filter=lfs diff=lfs merge=lfs -text
+*.pth filter=lfs diff=lfs merge=lfs -text
+*.pt filter=lfs diff=lfs merge=lfs -text
+
+# Python language detection
+*.py linguist-language=Python
+*.pyx linguist-language=Python
+*.pxd linguist-language=Python
+*.pxi linguist-language=Python
\ No newline at end of file
diff --git a/src/Data/gitattributes-ruby b/src/Data/gitattributes-ruby
new file mode 100644
index 0000000..619fae9
--- /dev/null
+++ b/src/Data/gitattributes-ruby
@@ -0,0 +1,43 @@
+###############################
+# Git Line Endings #
+###############################
+
+# Set default behavior to automatically normalize line endings.
+* text=auto eol=lf
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+
+# Force bash scripts to always use lf line endings
+*.sh text eol=lf
+
+###############################
+# Git Large File System (LFS) #
+###############################
+
+# Archives
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+
+# Ruby gems
+*.gem filter=lfs diff=lfs merge=lfs -text
+
+# Images
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.svg filter=lfs diff=lfs merge=lfs -text
+
+# Documents
+*.pdf filter=lfs diff=lfs merge=lfs -text
+
+# Ruby language detection
+*.rb linguist-language=Ruby
+*.rake linguist-language=Ruby
+*.gemspec linguist-language=Ruby
+*.ru linguist-language=Ruby
+*.erb linguist-language=HTML+ERB
+*.slim linguist-language=Slim
+*.haml linguist-language=Haml
\ No newline at end of file
diff --git a/src/Data/gitattributes-universal b/src/Data/gitattributes-universal
new file mode 100644
index 0000000..15ff642
--- /dev/null
+++ b/src/Data/gitattributes-universal
@@ -0,0 +1,36 @@
+###############################
+# Git Line Endings #
+###############################
+
+# Set default behavior to automatically normalize line endings.
+* text=auto eol=lf
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+
+# Force bash scripts to always use lf line endings
+*.sh text eol=lf
+
+###############################
+# Git Large File System (LFS) #
+###############################
+
+# Archives
+*.7z filter=lfs diff=lfs merge=lfs -text
+*.gz filter=lfs diff=lfs merge=lfs -text
+*.tar filter=lfs diff=lfs merge=lfs -text
+*.zip filter=lfs diff=lfs merge=lfs -text
+
+# Images
+*.gif filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
+*.jpeg filter=lfs diff=lfs merge=lfs -text
+*.png filter=lfs diff=lfs merge=lfs -text
+*.svg filter=lfs diff=lfs merge=lfs -text
+
+# Documents
+*.pdf filter=lfs diff=lfs merge=lfs -text
+
+# Common binaries
+*.exe filter=lfs diff=lfs merge=lfs -text
+*.dll filter=lfs diff=lfs merge=lfs -text
+*.so filter=lfs diff=lfs merge=lfs -text
\ No newline at end of file
diff --git a/src/Data/gitignore-markdown b/src/Data/gitignore-markdown
new file mode 100644
index 0000000..46094d3
--- /dev/null
+++ b/src/Data/gitignore-markdown
@@ -0,0 +1,18 @@
+# Documentation/Markdown specific ignores
+.DS_Store
+Thumbs.db
+
+# Editor files
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# Generated files
+_site/
+.sass-cache/
+.jekyll-cache/
+.jekyll-metadata
+node_modules/
+dist/
\ No newline at end of file
diff --git a/src/Data/gitignore-node b/src/Data/gitignore-node
new file mode 100644
index 0000000..73ee119
--- /dev/null
+++ b/src/Data/gitignore-node
@@ -0,0 +1,39 @@
+# Node.js specific ignores
+node_modules/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Coverage directory used by tools like istanbul
+coverage/
+*.lcov
+
+# Dependency directories
+jspm_packages/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# production builds
+dist/
+build/
\ No newline at end of file
diff --git a/src/Data/gitignore-python b/src/Data/gitignore-python
new file mode 100644
index 0000000..4e02745
--- /dev/null
+++ b/src/Data/gitignore-python
@@ -0,0 +1,57 @@
+# Python specific ignores
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Virtual environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
\ No newline at end of file
diff --git a/src/Data/gitignore-ruby b/src/Data/gitignore-ruby
new file mode 100644
index 0000000..fefe0b7
--- /dev/null
+++ b/src/Data/gitignore-ruby
@@ -0,0 +1,39 @@
+# Ruby specific ignores
+*.gem
+*.rbc
+/.config
+/coverage/
+/InstalledFiles
+/pkg/
+/spec/reports/
+/spec/examples.txt
+/test/tmp/
+/test/version_tmp/
+/tmp/
+
+# Used by dotenv library to load environment variables.
+.env
+
+# Ignore Byebug command history file.
+.byebug_history
+
+# Ignore bundler config.
+/.bundle
+
+# Ignore the default SQLite database.
+/db/*.sqlite3
+/db/*.sqlite3-journal
+
+# Ignore all logfiles and tempfiles.
+/log/*
+/tmp/*
+!/log/.keep
+!/tmp/.keep
+
+# Ignore uploaded files in development
+/storage/*
+!/storage/.keep
+
+# IDE
+.vscode/
+.idea/
\ No newline at end of file
diff --git a/src/Data/index.js b/src/Data/index.js
new file mode 100644
index 0000000..84dcd5b
--- /dev/null
+++ b/src/Data/index.js
@@ -0,0 +1 @@
+console.log('Hello, World!');
\ No newline at end of file
diff --git a/src/Data/main.py b/src/Data/main.py
new file mode 100644
index 0000000..a8a1402
--- /dev/null
+++ b/src/Data/main.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python3
+"""
+Main application entry point.
+"""
+
+def main():
+ """Main function."""
+ print("Hello, World!")
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/src/Data/main.rb b/src/Data/main.rb
new file mode 100644
index 0000000..b8a8539
--- /dev/null
+++ b/src/Data/main.rb
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+
+puts 'Hello, World!'
\ No newline at end of file
diff --git a/src/Data/package.json b/src/Data/package.json
new file mode 100644
index 0000000..a9af525
--- /dev/null
+++ b/src/Data/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "my-project",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "start": "node index.js",
+ "dev": "node --watch index.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "MIT"
+}
\ No newline at end of file
diff --git a/src/Data/requirements.txt b/src/Data/requirements.txt
new file mode 100644
index 0000000..663bd1f
--- /dev/null
+++ b/src/Data/requirements.txt
@@ -0,0 +1 @@
+requests
\ No newline at end of file
diff --git a/src/Program.cs b/src/Program.cs
index 53ef264..d3a65be 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -3,34 +3,168 @@
using System.Text;
using System.Reflection;
using System.Globalization;
+using System.Runtime.InteropServices;
+
namespace solrevdev.seedfolder;
+// Template metadata structure for future extensibility
+internal record TemplateFile(string ResourceName, string FileName, string Description = "");
+
+// Enum for supported project types
+internal enum ProjectType
+{
+ Dotnet,
+ Node,
+ Python,
+ Ruby,
+ Markdown,
+ Universal
+}
+
internal static class Program
{
- public static async Task Main(string[] args)
+ public static async Task Main(string[] args)
{
- ShowHeader();
-
- WriteLine($"▲ Running in the path {Directory.GetCurrentDirectory()}");
-
var folderName = "";
+ var projectType = ProjectType.Markdown; // Default to markdown
+ var isDryRun = false;
+ var isForce = false;
+ var isQuiet = false;
if (args?.Length > 0)
{
- var opts = new[] { "--help", "-h", "-?", "--version", "-v" };
- if (opts.Contains(args[0].ToLower(CultureInfo.InvariantCulture)))
+ var argIndex = 0;
+ while (argIndex < args.Length)
{
- ShowHelp();
- return;
+ var arg = args[argIndex].ToLower(CultureInfo.InvariantCulture);
+
+ // Handle flags
+ if (arg is "--help" or "-h" or "-?")
+ {
+ ShowHelp();
+ return 0;
+ }
+
+ if (arg is "--version" or "-v")
+ {
+ ShowVersion();
+ return 0;
+ }
+
+ if (arg is "--list-templates" or "--list")
+ {
+ ShowTemplates();
+ return 0;
+ }
+
+ if (arg is "--dry-run" or "--dry")
+ {
+ isDryRun = true;
+ argIndex++;
+ continue;
+ }
+
+ if (arg is "--force" or "-f")
+ {
+ isForce = true;
+ argIndex++;
+ continue;
+ }
+
+ if (arg is "--quiet" or "-q")
+ {
+ isQuiet = true;
+ argIndex++;
+ continue;
+ }
+
+ if (arg is "--template" or "--type" or "-t")
+ {
+ if (argIndex + 1 >= args.Length)
+ {
+ WriteLine("▲ Error: --template requires a template type.", ConsoleColor.DarkRed);
+ WriteLine("▲ Available types: dotnet, node, python, ruby, markdown, universal", ConsoleColor.DarkYellow);
+ return 1;
+ }
+
+ var templateArg = args[argIndex + 1].ToLower(CultureInfo.InvariantCulture);
+ if (!TryParseProjectType(templateArg, out projectType))
+ {
+ WriteLine($"▲ Error: Unknown template type '{args[argIndex + 1]}'.", ConsoleColor.DarkRed);
+ WriteLine("▲ Available template types:", ConsoleColor.DarkYellow);
+ WriteLine(" dotnet - .NET project with standard dotfiles", ConsoleColor.DarkYellow);
+ WriteLine(" node - Node.js project with package.json", ConsoleColor.DarkYellow);
+ WriteLine(" python - Python project with requirements.txt", ConsoleColor.DarkYellow);
+ WriteLine(" ruby - Ruby project with Gemfile", ConsoleColor.DarkYellow);
+ WriteLine(" markdown - Documentation project with README", ConsoleColor.DarkYellow);
+ WriteLine(" universal - Basic project with minimal files", ConsoleColor.DarkYellow);
+ WriteLine("▲ Use --list-templates to see all available templates and their files.", ConsoleColor.Cyan);
+ return 1;
+ }
+
+ argIndex += 2;
+
+ // Get folder name if provided
+ if (argIndex < args.Length)
+ {
+ folderName = args[argIndex];
+ argIndex++;
+ }
+
+ if (!isQuiet)
+ WriteLine($"▲ Using template type: {projectType}");
+ break;
+ }
+ else
+ {
+ // This is the folder name
+ folderName = args[argIndex];
+ argIndex++;
+ break;
+ }
}
+ }
- WriteLine($"▲ Found {args?.Length} params to process. ");
- folderName = args[0];
+ if (!isQuiet)
+ {
+ ShowHeader();
+ WriteLine($"▲ Running in the path {Directory.GetCurrentDirectory()}");
}
var sb = new StringBuilder();
if (string.IsNullOrWhiteSpace(folderName))
{
+ // Interactive template selection
+ if (projectType == ProjectType.Dotnet) // Only prompt if no template was specified
+ {
+ if (!isQuiet)
+ {
+ WriteLine("▲ Available project templates:");
+ WriteLine(" 1. markdown - Documentation project with README");
+ WriteLine(" 2. dotnet - .NET project with standard dotfiles");
+ WriteLine(" 3. node - Node.js project with package.json");
+ WriteLine(" 4. python - Python project with requirements.txt");
+ WriteLine(" 5. ruby - Ruby project with Gemfile");
+ WriteLine(" 6. universal - Basic project with minimal files");
+ WriteLine("");
+ }
+
+ var templateChoice = Prompt.GetString("▲ Select template type (1-6) or press Enter for markdown", "1");
+
+ projectType = templateChoice switch
+ {
+ "2" or "dotnet" => ProjectType.Dotnet,
+ "3" or "node" => ProjectType.Node,
+ "4" or "python" => ProjectType.Python,
+ "5" or "ruby" => ProjectType.Ruby,
+ "6" or "universal" => ProjectType.Universal,
+ _ => ProjectType.Markdown
+ };
+
+ if (!isQuiet)
+ WriteLine($"▲ Selected template: {projectType}");
+ }
+
var prefixWithDate = Prompt.GetYesNo("▲ Do you want to prefix the folder with the date?", defaultAnswer: true);
if (prefixWithDate)
{
@@ -41,82 +175,346 @@ public static async Task Main(string[] args)
folderName = Prompt.GetString("▲ What do you want the folder to be named?");
}
+ // Validate and sanitize folder name
if (string.IsNullOrWhiteSpace(folderName))
{
- WriteLine("▲ You must enter a folder name.", ConsoleColor.DarkRed);
- return;
+ WriteLine("▲ Error: You must enter a folder name.", ConsoleColor.DarkRed);
+ return 1;
}
- else
+
+ folderName = RemoveSpaces(folderName);
+ folderName = SafeNameForFileSystem(folderName);
+
+ if (string.IsNullOrWhiteSpace(folderName))
{
- folderName = RemoveSpaces(folderName);
- folderName = SafeNameForFileSystem(folderName);
- sb.Append(folderName);
+ WriteLine("▲ Error: Folder name contains only invalid characters.", ConsoleColor.DarkRed);
+ return 1;
}
+
+ sb.Append(folderName);
var finalFolderName = sb.ToString();
+
+ // Check if directory already exists
if (Directory.Exists(finalFolderName))
{
- WriteLine($"▲ Sorry but {finalFolderName} already exists.", ConsoleColor.DarkRed);
- return;
+ if (!isForce)
+ {
+ WriteLine($"▲ Error: Directory '{finalFolderName}' already exists.", ConsoleColor.DarkRed);
+ WriteLine("▲ Use --force to overwrite existing directory.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ else if (!isQuiet)
+ {
+ WriteLine($"▲ Warning: Directory '{finalFolderName}' exists, will overwrite files.", ConsoleColor.DarkYellow);
+ }
}
- WriteLine($"▲ Creating the directory {finalFolderName}");
- Directory.CreateDirectory(finalFolderName);
+ // Define template files based on project type
+ var templateFiles = GetTemplateFiles(projectType);
- WriteLine($"▲ Copying .dockerignore to {finalFolderName}{Path.DirectorySeparatorChar}.dockerignore");
- await WriteFileAsync("dockerignore", $"{finalFolderName}{Path.DirectorySeparatorChar}.dockerignore").ConfigureAwait(false);
+ if (isDryRun)
+ {
+ WriteLine($"▲ DRY RUN: Would create directory '{finalFolderName}' with template '{projectType}'", ConsoleColor.Cyan);
+ WriteLine("▲ Files that would be created:", ConsoleColor.Cyan);
+ foreach (var templateFile in templateFiles)
+ {
+ var destinationPath = Path.Combine(finalFolderName, templateFile.FileName);
+ WriteLine($" • {destinationPath}", ConsoleColor.Cyan);
+ }
+ WriteLine("▲ Use without --dry-run to actually create the files.", ConsoleColor.Cyan);
+ return 0;
+ }
- WriteLine($"▲ Copying .editorconfig to {finalFolderName}{Path.DirectorySeparatorChar}.editorconfig");
- await WriteFileAsync("editorconfig", $"{finalFolderName}{Path.DirectorySeparatorChar}.editorconfig").ConfigureAwait(false);
+ // Create directory with enhanced error handling
+ if (!isQuiet)
+ WriteLine($"▲ Creating the directory {finalFolderName}");
+
+ try
+ {
+ Directory.CreateDirectory(finalFolderName);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ WriteLine($"▲ Error: Permission denied creating directory '{finalFolderName}'.", ConsoleColor.DarkRed);
+ WriteLine("▲ Please check that you have write permissions to this location.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ catch (DirectoryNotFoundException)
+ {
+ WriteLine($"▲ Error: Parent directory path not found for '{finalFolderName}'.", ConsoleColor.DarkRed);
+ WriteLine("▲ Please ensure the parent directory exists.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ catch (PathTooLongException)
+ {
+ WriteLine($"▲ Error: Directory path is too long: '{finalFolderName}'.", ConsoleColor.DarkRed);
+ WriteLine("▲ Please use a shorter folder name or path.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ catch (Exception ex)
+ {
+ WriteLine($"▲ Error creating directory: {ex.Message}", ConsoleColor.DarkRed);
+ WriteLine("▲ Please check your permissions and try again.", ConsoleColor.DarkYellow);
+ return 1;
+ }
- WriteLine($"▲ Copying .gitattributes to {finalFolderName}{Path.DirectorySeparatorChar}.gitattributes");
- await WriteFileAsync("gitattributes", $"{finalFolderName}{Path.DirectorySeparatorChar}.gitattributes").ConfigureAwait(false);
+ // Validate disk space before creating files
+ if (!ValidateDiskSpace(finalFolderName))
+ {
+ WriteLine("▲ Error: Insufficient disk space to create project files.", ConsoleColor.DarkRed);
+ WriteLine("▲ Please free up disk space and try again.", ConsoleColor.DarkYellow);
+ return 1;
+ }
- WriteLine($"▲ Copying .gitignore to {finalFolderName}{Path.DirectorySeparatorChar}.gitignore");
- await WriteFileAsync("gitignore", $"{finalFolderName}{Path.DirectorySeparatorChar}.gitignore").ConfigureAwait(false);
+ // Copy template files using cross-platform path handling with progress indicators
+ var fileCount = templateFiles.Length;
+ for (int i = 0; i < fileCount; i++)
+ {
+ var templateFile = templateFiles[i];
+ var destinationPath = Path.Combine(finalFolderName, templateFile.FileName);
+
+ if (!isQuiet)
+ {
+ var progress = $"[{i + 1}/{fileCount}]";
+ WriteLine($"▲ {progress} Copying {templateFile.FileName}");
+ }
+
+ try
+ {
+ await WriteFileAsync(templateFile.ResourceName, destinationPath).ConfigureAwait(false);
+
+ if (!isQuiet)
+ WriteLine($" ✅ Created {destinationPath}", ConsoleColor.DarkGreen);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ WriteLine($"▲ Error: Permission denied writing {templateFile.FileName}.", ConsoleColor.DarkRed);
+ WriteLine($"▲ Please check write permissions for '{destinationPath}'.", ConsoleColor.DarkYellow);
+ WriteLine($"▲ Failed at file {i + 1} of {fileCount}. Some files may have been created.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ catch (DirectoryNotFoundException)
+ {
+ WriteLine($"▲ Error: Directory not found for {templateFile.FileName}.", ConsoleColor.DarkRed);
+ WriteLine($"▲ The directory may have been deleted during operation.", ConsoleColor.DarkYellow);
+ WriteLine($"▲ Failed at file {i + 1} of {fileCount}. Some files may have been created.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ catch (IOException ioEx)
+ {
+ WriteLine($"▲ Error: I/O error writing {templateFile.FileName}: {ioEx.Message}", ConsoleColor.DarkRed);
+ WriteLine($"▲ This could be due to disk space, file locks, or permission issues.", ConsoleColor.DarkYellow);
+ WriteLine($"▲ Failed at file {i + 1} of {fileCount}. Some files may have been created.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ catch (Exception ex)
+ {
+ WriteLine($"▲ Error copying {templateFile.FileName}: {ex.Message}", ConsoleColor.DarkRed);
+ WriteLine($"▲ Failed at file {i + 1} of {fileCount}. Some files may have been created.", ConsoleColor.DarkYellow);
+ return 1;
+ }
+ }
- WriteLine($"▲ Copying .prettierignore to {finalFolderName}{Path.DirectorySeparatorChar}.prettierignore");
- await WriteFileAsync("prettierignore", $"{finalFolderName}{Path.DirectorySeparatorChar}.prettierignore").ConfigureAwait(false);
+ if (!isQuiet)
+ {
+ WriteLine("▲ Done!", ConsoleColor.DarkGreen);
+ WriteLine($"▲ Successfully created {fileCount} files in '{finalFolderName}' using {projectType} template.", ConsoleColor.DarkGreen);
+ WriteLine("");
+ ShowGitSetupInstructions(finalFolderName);
+ }
+
+ return 0;
+ }
- WriteLine($"▲ Copying .prettierrc to {finalFolderName}{Path.DirectorySeparatorChar}.prettierrc");
- await WriteFileAsync("prettierrc", $"{finalFolderName}{Path.DirectorySeparatorChar}.prettierrc").ConfigureAwait(false);
+ private static bool TryParseProjectType(string input, out ProjectType projectType)
+ {
+ projectType = input switch
+ {
+ "dotnet" or "net" or "csharp" => ProjectType.Dotnet,
+ "node" or "nodejs" or "javascript" or "js" => ProjectType.Node,
+ "python" or "py" => ProjectType.Python,
+ "ruby" or "rb" => ProjectType.Ruby,
+ "markdown" or "md" or "docs" => ProjectType.Markdown,
+ "universal" or "basic" or "minimal" => ProjectType.Universal,
+ _ => ProjectType.Dotnet
+ };
+
+ return input is "dotnet" or "net" or "csharp" or "node" or "nodejs" or "javascript" or "js"
+ or "python" or "py" or "ruby" or "rb" or "markdown" or "md" or "docs"
+ or "universal" or "basic" or "minimal";
+ }
- WriteLine($"▲ Copying omnisharp.json to {finalFolderName}{Path.DirectorySeparatorChar}omnisharp.json");
- await WriteFileAsync("omnisharp.json", $"{finalFolderName}{Path.DirectorySeparatorChar}omnisharp.json").ConfigureAwait(false);
+ private static TemplateFile[] GetTemplateFiles(ProjectType projectType)
+ {
+ return projectType switch
+ {
+ ProjectType.Node => GetNodeTemplate(),
+ ProjectType.Python => GetPythonTemplate(),
+ ProjectType.Ruby => GetRubyTemplate(),
+ ProjectType.Markdown => GetMarkdownTemplate(),
+ ProjectType.Universal => GetUniversalTemplate(),
+ _ => GetDotnetTemplate()
+ };
+ }
- WriteLine("▲ Done!");
+ private static TemplateFile[] GetDotnetTemplate()
+ {
+ return new TemplateFile[]
+ {
+ new("dockerignore", ".dockerignore", "Docker ignore patterns"),
+ new("editorconfig-dotnet", ".editorconfig", "Editor configuration for .NET"),
+ new("gitattributes", ".gitattributes", "Git attributes"),
+ new("gitignore", ".gitignore", "Git ignore patterns"),
+ new("prettierignore", ".prettierignore", "Prettier ignore patterns"),
+ new("prettierrc", ".prettierrc", "Prettier configuration"),
+ new("omnisharp.json", "omnisharp.json", "OmniSharp configuration")
+ };
}
- private static void ShowHelp()
+ private static TemplateFile[] GetNodeTemplate()
{
- var version = typeof(Program).GetTypeInfo().Assembly.GetCustomAttribute().InformationalVersion;
+ return new TemplateFile[]
+ {
+ new("package.json", "package.json", "Node.js package configuration"),
+ new("index.js", "index.js", "Main application entry point"),
+ new("gitignore-node", ".gitignore", "Node.js specific git ignore patterns"),
+ new("gitattributes-node", ".gitattributes", "Git attributes for Node.js projects"),
+ new("editorconfig-node", ".editorconfig", "Editor configuration for Node.js"),
+ new("prettierignore", ".prettierignore", "Prettier ignore patterns"),
+ new("prettierrc", ".prettierrc", "Prettier configuration")
+ };
+ }
- WriteLine($"▲ Version {version}");
- const string help = @"▲ Usage: dotnet run [folderName]
+ private static TemplateFile[] GetPythonTemplate()
+ {
+ return new TemplateFile[]
+ {
+ new("main.py", "main.py", "Main application entry point"),
+ new("requirements.txt", "requirements.txt", "Python dependencies"),
+ new("gitignore-python", ".gitignore", "Python specific git ignore patterns"),
+ new("gitattributes-python", ".gitattributes", "Git attributes for Python projects"),
+ new("editorconfig-python", ".editorconfig", "Editor configuration for Python")
+ };
+ }
-Passing no folderName will then interactively ask you for the folder name. otherwise it will use the folderName you pass and create a new directory in your current folder.
+ private static TemplateFile[] GetRubyTemplate()
+ {
+ return new TemplateFile[]
+ {
+ new("Gemfile", "Gemfile", "Ruby dependencies"),
+ new("main.rb", "main.rb", "Main application entry point"),
+ new("gitignore-ruby", ".gitignore", "Ruby specific git ignore patterns"),
+ new("gitattributes-ruby", ".gitattributes", "Git attributes for Ruby projects"),
+ new("editorconfig-ruby", ".editorconfig", "Editor configuration for Ruby")
+ };
+ }
-For example:
+ private static TemplateFile[] GetMarkdownTemplate()
+ {
+ return new TemplateFile[]
+ {
+ new("README.md", "README.md", "Project documentation"),
+ new("gitignore-markdown", ".gitignore", "Documentation specific git ignore patterns"),
+ new("gitattributes-markdown", ".gitattributes", "Git attributes for documentation projects"),
+ new("editorconfig-markdown", ".editorconfig", "Editor configuration for Markdown")
+ };
+ }
-seedfolder
-▲ Do you want to prefix the folder with the date? [Y/n] y
-▲ What do you want the folder to be named? temp
-▲ Creating the directory 2020-12-10_temp
-▲ Done!
+ private static TemplateFile[] GetUniversalTemplate()
+ {
+ return new TemplateFile[]
+ {
+ new("README.md", "README.md", "Project documentation"),
+ new("gitignore", ".gitignore", "Basic git ignore patterns"),
+ new("gitattributes-universal", ".gitattributes", "Git attributes for universal projects"),
+ new("editorconfig-universal", ".editorconfig", "Editor configuration for universal projects")
+ };
+ }
-seedfolder
-▲ Do you want to prefix the folder with the date? [Y/n] n
-▲ What do you want the folder to be named? temp
-▲ Creating the directory temp
-▲ Done!
+ private static TemplateFile[] GetDefaultTemplate()
+ {
+ return GetDotnetTemplate();
+ }
-seedfolder temp
-▲ Found 1 params to process.
-▲ Creating the directory temp
-▲ Done!
+ private static void ShowTemplates()
+ {
+ WriteLine("▲ Available project templates:");
+ WriteLine("");
+
+ var templates = new[]
+ {
+ ("markdown", "Documentation project with README", GetMarkdownTemplate()),
+ ("dotnet", "Dotnet project with standard dotfiles", GetDotnetTemplate()),
+ ("node", "Node.js project with package.json", GetNodeTemplate()),
+ ("python", "Python project with requirements.txt", GetPythonTemplate()),
+ ("ruby", "Ruby project with Gemfile", GetRubyTemplate()),
+ ("universal", "Basic project with minimal files", GetUniversalTemplate())
+ };
+
+ foreach (var (name, description, files) in templates)
+ {
+ WriteLine($" {name,-12} - {description}");
+ foreach (var file in files)
+ {
+ WriteLine($" • {file.FileName,-20} {file.Description}");
+ }
+ WriteLine("");
+ }
+
+ WriteLine("▲ Usage examples:");
+ WriteLine(" seedfolder --template node myproject");
+ WriteLine(" seedfolder -t python myapp");
+ WriteLine(" seedfolder --type ruby mygem");
+ }
-seedfolder will also copy various dotfiles to that folder.
-";
+ private static void ShowVersion()
+ {
+ var version = typeof(Program).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? "Unknown";
+ WriteLine($"▲ seedfolder version {version}");
+ }
+
+ private static void ShowHelp()
+ {
+ var version = typeof(Program).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? "Unknown";
+
+ WriteLine($"▲ seedfolder version {version}");
+ WriteLine("");
+ const string help = @"▲ Usage: seedfolder [options] [folderName]
+
+Options:
+ --help, -h, -? Show this help message
+ --version, -v Show version information
+ --list-templates Show available template files
+ --template, --type, -t Specify project template type
+ --dry-run, --dry Preview operations without creating files
+ --force, -f Overwrite existing directory and files
+ --quiet, -q Suppress output (useful for scripting)
+
+Arguments:
+ folderName Name of the folder to create (optional)
+
+Template Types:
+ dotnet .NET project with standard dotfiles
+ node Node.js project with package.json
+ python Python project with requirements.txt
+ ruby Ruby project with Gemfile
+ markdown Documentation project with README (default)
+ universal Basic project with minimal files
+
+If no folder name is provided, seedfolder will interactively ask for the folder name
+and whether to prefix it with the current date.
+
+Examples:
+
+ seedfolder # Interactive mode with template selection
+ seedfolder myproject # Create 'myproject' folder with markdown template
+ seedfolder --template node myapp # Create Node.js project
+ seedfolder -t python ""my project"" # Create Python project (spaces converted to dashes)
+ seedfolder --type ruby mygem # Create Ruby project
+ seedfolder --dry-run -t node myapp # Preview Node.js project creation
+ seedfolder --force myproject # Overwrite existing 'myproject' directory
+ seedfolder --quiet -t python myapp # Create Python project with no output";
WriteLine(help);
}
@@ -137,15 +535,25 @@ private static void WriteLine(string text, ConsoleColor color = default)
private static async Task WriteFileAsync(string filename, string destination)
{
var assembly = Assembly.GetEntryAssembly();
- var resourceStream = assembly.GetManifestResourceStream($"solrevdev.seedfolder.Data.{filename}");
- if (resourceStream != null)
+ var resourceName = $"solrevdev.seedfolder.Data.{filename}";
+ var resourceStream = assembly?.GetManifestResourceStream(resourceName);
+
+ if (resourceStream == null)
{
- using (var reader = new StreamReader(resourceStream, Encoding.UTF8))
- {
- var fileContents = await reader.ReadToEndAsync().ConfigureAwait(false);
- File.WriteAllText(destination, fileContents);
- }
+ throw new InvalidOperationException($"Could not find embedded resource: {resourceName}");
}
+
+ using var reader = new StreamReader(resourceStream, Encoding.UTF8);
+ var fileContents = await reader.ReadToEndAsync().ConfigureAwait(false);
+
+ // Ensure destination directory exists
+ var destinationDir = Path.GetDirectoryName(destination);
+ if (!string.IsNullOrEmpty(destinationDir) && !Directory.Exists(destinationDir))
+ {
+ Directory.CreateDirectory(destinationDir);
+ }
+
+ await File.WriteAllTextAsync(destination, fileContents).ConfigureAwait(false);
}
private static void ShowHeader()
@@ -168,7 +576,61 @@ private static void AppendBlankLines(int howMany = 2)
private static string SafeNameForFileSystem(string name, char replace = '-')
{
+ if (string.IsNullOrWhiteSpace(name))
+ return string.Empty;
+
var invalids = Path.GetInvalidFileNameChars();
- return new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray());
+ var result = new string(name.Select(c => invalids.Contains(c) ? replace : c).ToArray());
+
+ // Remove any leading/trailing dashes and handle edge cases
+ result = result.Trim(replace);
+
+ // Ensure we don't end up with an empty string after cleaning
+ return string.IsNullOrWhiteSpace(result) ? string.Empty : result;
+ }
+
+ private static bool ValidateDiskSpace(string directoryPath)
+ {
+ try
+ {
+ var drive = new DriveInfo(Path.GetPathRoot(Path.GetFullPath(directoryPath)) ?? Directory.GetCurrentDirectory());
+
+ // Check if we have at least 10MB of free space (conservative estimate)
+ const long minimumSpaceRequired = 10 * 1024 * 1024; // 10MB in bytes
+
+ return drive.AvailableFreeSpace >= minimumSpaceRequired;
+ }
+ catch
+ {
+ // If we can't determine disk space, assume we have enough (better to try and fail gracefully)
+ return true;
+ }
+ }
+
+ private static void ShowGitSetupInstructions(string folderName)
+ {
+ WriteLine("▲ To initialize git and make your first commit, copy and paste these commands:", ConsoleColor.Cyan);
+ WriteLine("");
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ // Windows commands
+ WriteLine($"cd \"{folderName}\"", ConsoleColor.DarkYellow);
+ WriteLine("git init", ConsoleColor.DarkYellow);
+ WriteLine("git lfs install 2>nul || echo Git LFS not available", ConsoleColor.DarkYellow);
+ WriteLine("git add .", ConsoleColor.DarkYellow);
+ WriteLine("git commit -m \"Initial commit\"", ConsoleColor.DarkYellow);
+ }
+ else
+ {
+ // Unix-like systems (Linux, macOS)
+ WriteLine($"cd \"{folderName}\"", ConsoleColor.DarkYellow);
+ WriteLine("git init", ConsoleColor.DarkYellow);
+ WriteLine("git lfs install 2>/dev/null || echo \"Git LFS not available\"", ConsoleColor.DarkYellow);
+ WriteLine("git add .", ConsoleColor.DarkYellow);
+ WriteLine("git commit -m \"Initial commit\"", ConsoleColor.DarkYellow);
+ }
+
+ WriteLine("");
}
}
diff --git a/src/solrevdev.seedfolder.csproj b/src/solrevdev.seedfolder.csproj
index a90d73d..eee9e54 100644
--- a/src/solrevdev.seedfolder.csproj
+++ b/src/solrevdev.seedfolder.csproj
@@ -37,7 +37,18 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -47,10 +58,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/integration-test.ps1 b/tests/integration-test.ps1
new file mode 100644
index 0000000..68d9d19
--- /dev/null
+++ b/tests/integration-test.ps1
@@ -0,0 +1,110 @@
+# Integration test for seedfolder - validates all template types work correctly (PowerShell)
+
+$ErrorActionPreference = "Stop"
+
+Write-Host "🚀 Starting seedfolder integration tests..." -ForegroundColor Green
+
+# Navigate to project root
+Set-Location "$(Split-Path $PSScriptRoot -Parent)"
+
+# Build the project
+Write-Host "📦 Building project..." -ForegroundColor Yellow
+dotnet build src/solrevdev.seedfolder.csproj --configuration Release --framework net8.0
+
+# Create test directory
+$TestDir = "test-output"
+if (Test-Path $TestDir) { Remove-Item $TestDir -Recurse -Force }
+New-Item -ItemType Directory -Path $TestDir | Out-Null
+Set-Location $TestDir
+
+Write-Host "✅ Testing all template types..." -ForegroundColor Green
+
+try {
+ # Test dotnet template (default)
+ Write-Host "🔹 Testing dotnet template..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet test-dotnet
+ if (!(Test-Path "test-dotnet/.gitignore")) { throw "dotnet .gitignore missing" }
+ if (!(Test-Path "test-dotnet/omnisharp.json")) { throw "dotnet omnisharp.json missing" }
+
+ # Test node template
+ Write-Host "🔹 Testing node template..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node test-node
+ if (!(Test-Path "test-node/package.json")) { throw "node package.json missing" }
+ if (!(Test-Path "test-node/index.js")) { throw "node index.js missing" }
+
+ # Test python template
+ Write-Host "🔹 Testing python template..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --template python test-python
+ if (!(Test-Path "test-python/main.py")) { throw "python main.py missing" }
+ if (!(Test-Path "test-python/requirements.txt")) { throw "python requirements.txt missing" }
+
+ # Test ruby template
+ Write-Host "🔹 Testing ruby template..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --type ruby test-ruby
+ if (!(Test-Path "test-ruby/Gemfile")) { throw "ruby Gemfile missing" }
+ if (!(Test-Path "test-ruby/main.rb")) { throw "ruby main.rb missing" }
+
+ # Test markdown template
+ Write-Host "🔹 Testing markdown template..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t markdown test-markdown
+ if (!(Test-Path "test-markdown/README.md")) { throw "markdown README.md missing" }
+
+ # Test universal template
+ Write-Host "🔹 Testing universal template..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t universal test-universal
+ if (!(Test-Path "test-universal/README.md")) { throw "universal README.md missing" }
+
+ # Test dry-run mode
+ Write-Host "🔹 Testing dry-run mode..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --dry-run -t node test-dry-run
+ if (Test-Path "test-dry-run") { throw "dry-run should not create directory" }
+
+ # Test force mode
+ Write-Host "🔹 Testing force mode..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node test-force-existing
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --force -t python test-force-existing
+ if (!(Test-Path "test-force-existing/main.py")) { throw "force mode should overwrite with python template" }
+
+ # Test space handling
+ Write-Host "🔹 Testing space handling..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node "test space project"
+ if (!(Test-Path "test-space-project")) { throw "space handling failed" }
+
+ # Test error handling - invalid template type
+ Write-Host "🔹 Testing error handling - invalid template..." -ForegroundColor Cyan
+ $result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- -t invalidtype test-error 2>$null
+ if ($LASTEXITCODE -eq 0) { throw "Should have failed with invalid template type" }
+
+ # Test error handling - missing template argument
+ Write-Host "🔹 Testing error handling - missing template argument..." -ForegroundColor Cyan
+ $result = dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --template 2>$null
+ if ($LASTEXITCODE -eq 0) { throw "Should have failed with missing template argument" }
+
+ # Test help command
+ Write-Host "🔹 Testing help command..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --help >$null
+ if ($LASTEXITCODE -ne 0) { throw "help command failed" }
+
+ # Test version command
+ Write-Host "🔹 Testing version command..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --version >$null
+ if ($LASTEXITCODE -ne 0) { throw "version command failed" }
+
+ # Test list templates command
+ Write-Host "🔹 Testing list templates command..." -ForegroundColor Cyan
+ dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --list-templates >$null
+ if ($LASTEXITCODE -ne 0) { throw "list templates command failed" }
+
+ Write-Host "🎉 All integration tests passed!" -ForegroundColor Green
+ Write-Host "✅ Tested templates: dotnet, node, python, ruby, markdown, universal" -ForegroundColor Green
+ Write-Host "✅ Tested features: dry-run, force, quiet, space handling, error handling, help, version, list-templates" -ForegroundColor Green
+}
+catch {
+ Write-Host "❌ Test failed: $_" -ForegroundColor Red
+ exit 1
+}
+finally {
+ # Clean up
+ Set-Location ".."
+ if (Test-Path $TestDir) { Remove-Item $TestDir -Recurse -Force }
+}
\ No newline at end of file
diff --git a/tests/integration-test.sh b/tests/integration-test.sh
new file mode 100755
index 0000000..72c112a
--- /dev/null
+++ b/tests/integration-test.sh
@@ -0,0 +1,111 @@
+#!/bin/bash
+# Integration test for seedfolder - validates all template types work correctly
+
+set -e
+
+echo "🚀 Starting seedfolder integration tests..."
+
+# Navigate to project root
+cd "$(dirname "$0")/.."
+
+# Build the project
+echo "📦 Building project..."
+dotnet build src/solrevdev.seedfolder.csproj --configuration Release --framework net8.0
+
+# Create test directory
+TEST_DIR="test-output"
+rm -rf "$TEST_DIR"
+mkdir -p "$TEST_DIR"
+cd "$TEST_DIR"
+
+echo "✅ Testing all template types..."
+
+# Test markdown template (default)
+echo "🔹 Testing markdown template (default)..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet test-default-markdown
+[[ -f "test-default-markdown/README.md" ]] || { echo "❌ default markdown README.md missing"; exit 1; }
+[[ -f "test-default-markdown/.gitignore" ]] || { echo "❌ default markdown .gitignore missing"; exit 1; }
+
+# Test dotnet template
+echo "🔹 Testing dotnet template..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --template dotnet test-dotnet
+[[ -f "test-dotnet/.gitignore" ]] || { echo "❌ dotnet .gitignore missing"; exit 1; }
+[[ -f "test-dotnet/omnisharp.json" ]] || { echo "❌ dotnet omnisharp.json missing"; exit 1; }
+
+# Test node template
+echo "🔹 Testing node template..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node test-node
+[[ -f "test-node/package.json" ]] || { echo "❌ node package.json missing"; exit 1; }
+[[ -f "test-node/index.js" ]] || { echo "❌ node index.js missing"; exit 1; }
+
+# Test python template
+echo "🔹 Testing python template..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --template python test-python
+[[ -f "test-python/main.py" ]] || { echo "❌ python main.py missing"; exit 1; }
+[[ -f "test-python/requirements.txt" ]] || { echo "❌ python requirements.txt missing"; exit 1; }
+
+# Test ruby template
+echo "🔹 Testing ruby template..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --type ruby test-ruby
+[[ -f "test-ruby/Gemfile" ]] || { echo "❌ ruby Gemfile missing"; exit 1; }
+[[ -f "test-ruby/main.rb" ]] || { echo "❌ ruby main.rb missing"; exit 1; }
+
+# Test markdown template
+echo "🔹 Testing markdown template..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t markdown test-markdown
+[[ -f "test-markdown/README.md" ]] || { echo "❌ markdown README.md missing"; exit 1; }
+
+# Test universal template
+echo "🔹 Testing universal template..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t universal test-universal
+[[ -f "test-universal/README.md" ]] || { echo "❌ universal README.md missing"; exit 1; }
+
+# Test dry-run mode
+echo "🔹 Testing dry-run mode..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --dry-run -t node test-dry-run
+[[ ! -d "test-dry-run" ]] || { echo "❌ dry-run should not create directory"; exit 1; }
+
+# Test force mode
+echo "🔹 Testing force mode..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node test-force-existing
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet --force -t python test-force-existing
+[[ -f "test-force-existing/main.py" ]] || { echo "❌ force mode should overwrite with python template"; exit 1; }
+
+# Test space handling
+echo "🔹 Testing space handling..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --quiet -t node "test space project"
+[[ -d "test-space-project" ]] || { echo "❌ space handling failed"; exit 1; }
+
+# Test error handling - invalid template type
+echo "🔹 Testing error handling - invalid template..."
+if dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- -t invalidtype test-error 2>/dev/null; then
+ echo "❌ Should have failed with invalid template type"
+ exit 1
+fi
+
+# Test error handling - missing template argument
+echo "🔹 Testing error handling - missing template argument..."
+if dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --template 2>/dev/null; then
+ echo "❌ Should have failed with missing template argument"
+ exit 1
+fi
+
+# Test help command
+echo "🔹 Testing help command..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --help > /dev/null || { echo "❌ help command failed"; exit 1; }
+
+# Test version command
+echo "🔹 Testing version command..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --version > /dev/null || { echo "❌ version command failed"; exit 1; }
+
+# Test list templates command
+echo "🔹 Testing list templates command..."
+dotnet run --project ../src/solrevdev.seedfolder.csproj --framework net8.0 -- --list-templates > /dev/null || { echo "❌ list templates command failed"; exit 1; }
+
+# Clean up
+cd ..
+rm -rf "$TEST_DIR"
+
+echo "🎉 All integration tests passed!"
+echo "✅ Tested templates: dotnet, node, python, ruby, markdown, universal"
+echo "✅ Tested features: dry-run, force, quiet, space handling, error handling, help, version, list-templates"
\ No newline at end of file