Complete guide for setting up, developing, testing, and contributing to the Package Script Writer project.
- Prerequisites
- Getting Started
- Development Workflow
- Testing
- Building and Deployment
- Code Style
- Contributing
- Troubleshooting
| Software | Minimum Version | Purpose |
|---|---|---|
| .NET SDK | 9.0+ | Application runtime |
| Git | 2.30+ | Version control |
| Visual Studio / VS Code / Rider | Latest | IDE |
| Tool | Purpose |
|---|---|
| REST Client (VS Code Extension) | API testing |
| Postman | API testing |
| Docker | Container testing |
| SQL Server | Database testing (if needed) |
Windows:
winget install Microsoft.DotNet.SDK.9macOS:
brew install dotnetLinux (Ubuntu):
wget https://dot.net/v1/dotnet-install.sh
chmod +x dotnet-install.sh
./dotnet-install.sh --channel 10.0Verify Installation:
dotnet --version
# Output: 10.0.xWindows:
winget install Git.GitmacOS:
brew install gitLinux:
sudo apt-get install gitgit clone https://github.com/prjseal/Package-Script-Writer.git
cd Package-Script-Writercd src/PSW
dotnet restoreOutput:
Restore completed in 2.5 sec for Package-Script-Writer.csproj
dotnet run --project ./src/PSW/PSW.csprojdotnet watch run --project ./src/PSW/PSW.csprojBenefits of dotnet watch:
- Auto-reloads on file changes
- Hot reload for Razor pages
- Faster iteration
Open browser and navigate to:
- HTTPS:
https://localhost:5001 - HTTP:
http://localhost:5000(redirects to HTTPS)
Expected Output:
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
src/PSW/
├── Components/ # View Components
├── Controllers/ # MVC & API Controllers
├── Models/ # Data models
├── Services/ # Business logic
├── Views/ # Razor views
├── wwwroot/ # Static files
│ ├── css/
│ ├── js/
│ └── images/
├── appsettings.json # Configuration
└── Program.cs # Entry point
- Update
MarketplacePackageService.cs - Add new API endpoint configuration
- Update cache keys
- Test API integration
- Update
TemplateDictionary.cs - Add template-specific logic in
ScriptGeneratorService.cs - Update UI in
OptionsViewComponent.cshtml - Test script generation
- Edit methods in
ScriptGeneratorService.cs - Update tests
- Verify output format
- Test with multiple configurations
The project includes a comprehensive integration test suite using xUnit, Microsoft.AspNetCore.Mvc.Testing, HttpClient, and FluentAssertions. Integration tests validate API endpoints in a realistic environment by spinning up a test server and making actual HTTP requests.
Using .NET CLI (recommended):
# Run all tests
dotnet test
# Run tests with detailed output
dotnet test --verbosity normal
# Run tests with code coverage
dotnet test --collect:"XPlat Code Coverage"
# Run specific test class
dotnet test --filter "FullyQualifiedName~ScriptGeneratorApiTests"Using Visual Studio:
- Open Test Explorer (Test → Test Explorer)
- Click "Run All" or select specific tests
Using Visual Studio Code:
- Install the .NET Test Explorer extension
- Tests appear in Test Explorer panel
- Click play button to run tests
The current test suite (PSW.IntegrationTests project) covers:
- ✅ GET /api/ScriptGeneratorApi/test - API health check
- ✅ GET /api/ScriptGeneratorApi/clearcache - Cache clearing functionality
- ✅ POST /api/ScriptGeneratorApi/generatescript - Script generation with valid data
- ✅ POST /api/ScriptGeneratorApi/generatescript - Script generation with empty request (error handling)
- ✅ POST /api/ScriptGeneratorApi/getpackageversions - Package version retrieval
Test Project Structure:
src/PSW.IntegrationTests/
├── ScriptGeneratorApiTests.cs # Main test class
├── CustomWebApplicationFactory.cs # Test server configuration
└── GlobalUsings.cs # Common using statements
Example Test:
[Fact]
public async Task GenerateScript_WithValidRequest_ReturnsScript()
{
// Arrange
var request = new
{
model = new
{
templateName = "Umbraco.Templates",
templateVersion = "14.3.0",
projectName = "TestProject"
}
};
// Act
var response = await _client.PostAsJsonAsync(
"/api/ScriptGeneratorApi/generatescript", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
var result = await response.Content.ReadFromJsonAsync<JsonElement>();
result.GetProperty("script").GetString().Should().NotBeNullOrEmpty();
}See Integration Tests README for detailed information on writing tests.
Every pull request automatically runs all tests via GitHub Actions:
Workflow: .github/workflows/website-build-and-test.yml
Features:
- 🔄 Runs on every pull request
- ✅ Builds the solution
- 🧪 Executes all integration tests
- 📊 Reports test results
- 🚫 Blocks PR merge if tests fail
Workflow Triggers:
on:
pull_request:
branches: [ main ]
workflow_dispatch:Manual Workflow Run:
# Via GitHub CLI
gh workflow run website-build-and-test.ymlView Test Results:
- Go to the Pull Request on GitHub
- Check "Checks" tab
- View "Integration Tests" workflow results
The easiest way to test the API is through the built-in Swagger UI with full OpenAPI documentation:
- Start the application:
dotnet watch run --project ./src/PSW/ - Navigate to: https://localhost:5001/api/docs
- Click on any endpoint to expand it
- Click "Try it out" button
- Fill in the parameters
- Click "Execute" to send the request
- View the response directly in the browser
Swagger UI Features:
- 📖 Interactive API documentation with OpenAPI annotations
- 🧪 Built-in request testing
- 📝 Request/response examples with schemas
- 🔍 Model schema exploration
- 📄 Complete API specification download
- Install REST Client extension
- Open file:
Api Request/API Testing.http
### Test Generate Script
POST https://localhost:5001/api/scriptgeneratorapi/generatescript
Content-Type: application/json
{
"model": {
"templateName": "Umbraco.Templates",
"templateVersion": "14.3.0",
"projectName": "TestProject"
}
}
### Test Get Package Versions
POST https://localhost:5001/api/scriptgeneratorapi/getpackageversions
Content-Type: application/json
{
"packageId": "Umbraco.Community.BlockPreview",
"includePrerelease": false
}
### Test Clear Cache
GET https://localhost:5001/api/scriptgeneratorapi/clearcache- Run application:
dotnet watch run --project ./src/PSW/ - Click "Send Request" in VS Code
Test Generate Script:
curl -X POST https://localhost:5001/api/scriptgeneratorapi/generatescript \
-H "Content-Type: application/json" \
-k \
-d '{
"model": {
"templateName": "Umbraco.Templates",
"templateVersion": "14.3.0",
"projectName": "TestProject"
}
}'Note: -k flag ignores SSL certificate validation (development only)
- Import collection from
Api Request/folder (if available) - Set base URL:
https://localhost:5001 - Configure to ignore SSL errors (Settings → SSL certificate verification → OFF)
- Run requests
- Template selection changes version dropdown
- Package selection loads version dropdown
- Search filters packages correctly
- Copy button copies script to clipboard
- Save configuration to localStorage works
- Restore configuration from localStorage works
- URL updates on form changes
- Loading configuration from URL works
- Docker options appear/disappear based on template
- Unattended install options appear/disappear
- One-liner output formats correctly
- Remove comments option works
- All Umbraco versions generate valid scripts
- All integration tests pass
- Chrome (latest)
- Firefox (latest)
- Safari (latest)
- Edge (latest)
- Mobile Chrome
- Mobile Safari
To add new integration tests to the PSW.IntegrationTests project:
1. Create Test Class:
public class MyNewApiTests : IClassFixture<CustomWebApplicationFactory>
{
private readonly HttpClient _client;
public MyNewApiTests(CustomWebApplicationFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task MyEndpoint_WithValidData_ReturnsSuccess()
{
// Arrange
var request = new { /* test data */ };
// Act
var response = await _client.PostAsJsonAsync("/api/myendpoint", request);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
}2. Use FluentAssertions for readable assertions:
// Instead of:
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
// Use:
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Should().NotBeNull();
result.Should().Contain("expected text");3. Test both success and failure scenarios:
[Fact]
public async Task Endpoint_WithInvalidData_ReturnsBadRequest()
{
var response = await _client.PostAsJsonAsync("/api/endpoint", null);
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
}- Use IClassFixture: Share
WebApplicationFactoryacross tests for better performance - Test behavior, not implementation: Focus on API contracts and expected responses
- Use realistic data: Test with data representing actual usage scenarios
- Isolate tests: Each test should be independent
- Use meaningful names:
MethodName_Scenario_ExpectedResult - Test error cases: Verify proper error handling
- Run tests frequently: Use
dotnet watch testduring development - Check CI results: Ensure tests pass in GitHub Actions
dotnet build ./src/PSW/PSW.csprojOutput: bin/Debug/net10.0/
dotnet publish ./src/PSW/PSW.csproj -c Release -o ./publishOutput: ./publish/ (ready for deployment)
Build Artifacts:
- PSW.dll
- appsettings.json
- appsettings.Production.json
- wwwroot/ (static files)
- Dependencies
Dockerfile Example:
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["src/PSW/PSW.csproj", "src/PSW/"]
RUN dotnet restore "src/PSW/PSW.csproj"
COPY . .
WORKDIR "/src/src/PSW"
RUN dotnet build "PSW.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "PSW.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "PSW.dll"]Build Docker Image:
docker build -t psw:latest .Run Container:
docker run -d -p 8080:80 -p 8443:443 --name psw psw:latest- Create App Service (Linux, .NET 9)
- Configure deployment (GitHub Actions, Azure DevOps)
- Set environment variables
- Deploy
Azure CLI:
az webapp create --name psw --resource-group myResourceGroup --plan myAppServicePlan --runtime "DOTNETCORE:9.0"
az webapp deployment source config --name psw --resource-group myResourceGroup --repo-url https://github.com/prjseal/Package-Script-Writer --branch main- Install ASP.NET Core Hosting Bundle
- Create IIS site
- Copy published files to site directory
- Configure application pool (.NET CLR Version: No Managed Code)
- Install .NET Runtime
- Configure systemd service
- Setup Nginx reverse proxy
- Enable and start service
Auto-format on Build:
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="dotnet format" />
</Target>Manual Format:
dotnet format ./src/PSW/PSW.csprojNaming Conventions:
- PascalCase: Classes, methods, properties
- camelCase: Local variables, parameters
- _camelCase: Private fields
- UPPER_CASE: Constants
Example:
public class ScriptGeneratorService : IScriptGeneratorService
{
private readonly PSWConfig _pswConfig;
private const int DEFAULT_TIMEOUT = 30;
public string GenerateScript(PackagesViewModel model)
{
var outputList = new List<string>();
// ...
}
}Naming Conventions:
- camelCase: Variables, functions
- PascalCase: Constructors (not used in current code)
Example:
var psw = {
controls: {
templateName: document.getElementById('TemplateName')
},
updateOutput: function() {
var model = psw.buildModelFromForm();
// ...
}
};File: .editorconfig (recommended)
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true
[*.{cs,csx}]
csharp_new_line_before_open_brace = all
csharp_indent_case_contents = true
csharp_space_after_cast = false
[*.{js,json}]
indent_size = 2
[*.{html,cshtml}]
indent_size = 2-
Raise an Issue (if one doesn't exist)
- Bug reports: Include steps to reproduce, expected vs actual behavior
- Feature requests: Describe the feature and use case
-
Discuss Before Coding
- Comment on the issue to discuss approach
- Wait for maintainer feedback
- Ensure feature aligns with project goals
-
Fork Repository
# Fork via GitHub UI git clone https://github.com/YOUR_USERNAME/Package-Script-Writer.git -
Create Feature Branch
git checkout -b feature/my-new-feature # or git checkout -b fix/issue-123 -
Make Changes
- Follow code style guidelines
- Add comments for complex logic
- Update documentation if needed
-
Test Changes
- Test manually
- Run existing tests (if any)
- Verify in multiple browsers
-
Commit Changes
git add . git commit -m "Add feature: description of feature"
Commit Message Format:
feat: Add support for community templatesfix: Correct Docker version detectiondocs: Update API documentationrefactor: Simplify script generation logictest: Add tests for QueryStringService
-
Push to Fork
git push origin feature/my-new-feature
-
Create Pull Request
- Go to original repository
- Click "New Pull Request"
- Select your fork and branch
- Fill in PR template:
- What problem does this solve?
- How did you fix it?
- What is the result?
- Link related issue: "Fixes #123"
-
Review Process
- Maintainer will review
- Address feedback if requested
- Once approved, PR will be merged
- Issue exists and is linked to PR
- Code follows project style
- Changes are tested manually
- Documentation updated (if needed)
- Commit messages are clear
- PR description explains changes
- No merge conflicts
- Ready for review
Reviewers check for:
- Correct functionality
- Code quality and maintainability
- Performance implications
- Security concerns
- Test coverage
- Documentation completeness
Error:
Failed to bind to address https://127.0.0.1:5001: address already in use.
Solution:
# Find process using port 5001
lsof -i :5001 # macOS/Linux
netstat -ano | findstr :5001 # Windows
# Kill process
kill -9 <PID> # macOS/Linux
taskkill /PID <PID> /F # Windows
# Or run on different port
dotnet run --urls "https://localhost:5555"Error:
Unable to configure HTTPS endpoint. No server certificate was specified.
Solution:
# Trust development certificate
dotnet dev-certs https --trust
# Clean and recreate
dotnet dev-certs https --clean
dotnet dev-certs https --trustError:
Unable to resolve package dependencies.
Solution:
# Clear NuGet cache
dotnet nuget locals all --clear
# Restore with verbose output
dotnet restore --verbosity detailedSymptoms: Changes not reflected without manual restart
Solution:
- Ensure using
dotnet watch run - Check file watchers limit (Linux):
# Increase inotify limit echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf sudo sysctl -p
- Restart
dotnet watch
Symptoms: Stale package data, versions not updating
Solution:
# Call cache clear endpoint
curl https://localhost:5001/api/scriptgeneratorapi/clearcache
# Or restart application- Documentation: Check this guide and other documentation files
- Issues: Search existing issues on GitHub
- Create Issue: If problem persists, create new issue with:
- Clear description
- Steps to reproduce
- Expected vs actual behavior
- Environment details (.NET version, OS)
- Use Hot Reload:
dotnet watch runfor faster iteration - Browser DevTools: Use F12 for JavaScript debugging
- Network Tab: Inspect API requests/responses
- Breakpoints: Use debugger in Visual Studio/VS Code/Rider
- Logging: Add temporary logging for debugging
- Cache Clearing: Clear cache when testing package versions
- ASP.NET Core: https://docs.microsoft.com/aspnet/core
- .NET CLI: https://docs.microsoft.com/dotnet/core/tools
- C# Language: https://docs.microsoft.com/dotnet/csharp
- Razor Pages: https://docs.microsoft.com/aspnet/core/razor-pages
- Umbraco Documentation: https://docs.umbraco.com
- Umbraco Marketplace: https://marketplace.umbraco.com
- Umbraco Templates: https://www.nuget.org/packages/Umbraco.Templates
-
VS Code Extensions:
- C# (Microsoft)
- REST Client (Huachao Mao)
- GitLens (GitKraken)
- Prettier (Code formatter)
-
Visual Studio Extensions:
- ReSharper (JetBrains)
- Web Essentials
- Productivity Power Tools