Advanced Features
This guide covers the advanced features of @pixpilot/scaffoldfy that enable powerful and flexible configuration automation.
Table of Contents
Configuration-Level Enabled
Control whether an entire configuration should be executed at the root level. This is useful for creating conditional configurations that only run under specific circumstances.
Overview
The root-level enabled property allows you to enable or disable an entire configuration, including all of its prompts, variables, and tasks. If the configuration is disabled, execution stops immediately without any user interaction or file operations.
Basic Usage
Simple Boolean
{
"$schema": "https://unpkg.com/@pixpilot/scaffoldfy/schema",
"name": "my-config",
"enabled": true,
"tasks": []
}
Conditional Format
{
"$schema": "https://unpkg.com/@pixpilot/scaffoldfy/schema",
"name": "conditional-config",
"enabled": {
"type": "condition",
"value": "projectType === 'monorepo'"
},
"tasks": []
}
Executable Enabled
Determine if a configuration should run by executing a shell command:
{
"$schema": "https://unpkg.com/@pixpilot/scaffoldfy/schema",
"name": "git-config",
"enabled": {
"type": "exec",
"value": "git rev-parse --is-inside-work-tree"
},
"tasks": []
}
The command output is parsed as a boolean:
- Empty string,
"0","false", or"no"(case-insensitive) =false - Everything else =
true - Failed commands =
false
Use Cases
1. Environment-Based Configurations
Only run certain configurations in specific environments:
{
"name": "development-tools",
"enabled": {
"type": "exec",
"value": "test \"$NODE_ENV\" = \"development\" && echo true || echo false"
},
"tasks": [
{
"id": "install-dev-tools",
"name": "Install Development Tools",
"type": "exec",
"config": {
"command": "npm install --save-dev prettier eslint"
}
}
]
}
2. Dependency-Based Configurations
Enable configurations only when certain dependencies exist:
{
"name": "react-setup",
"description": "Setup React-specific configurations",
"enabled": {
"type": "exec",
"value": "test -f node_modules/react/package.json && echo true || echo false"
},
"tasks": []
}
3. Conditional Based on Other Configurations
Enable configurations based on variables from dependency configurations:
{
"name": "typescript-config",
"description": "TypeScript configuration",
"dependencies": ["project-info"],
"enabled": {
"type": "condition",
"value": "useTypeScript === true"
},
"tasks": [
{
"id": "create-tsconfig",
"name": "Create tsconfig.json",
"type": "write",
"config": {
"file": "tsconfig.json",
"template": "{ \"compilerOptions\": { \"strict\": true } }"
}
}
]
}
In this example, if the project-info configuration has a prompt useTypeScript, this configuration will only run if the user answered true.
4. Git Repository Check
Only run Git-related configurations in Git repositories:
{
"name": "git-hooks",
"enabled": {
"type": "exec",
"value": "git rev-parse --git-dir"
},
"tasks": [
{
"id": "setup-hooks",
"name": "Setup Git Hooks",
"type": "exec",
"config": {
"command": "npx husky install"
}
}
]
}
Evaluation Timing
The configuration-level enabled property evaluation depends on the context:
For Main Configurations:
- ✅ Evaluated first - Before variable resolution
- ✅ Evaluated first - Before prompt collection
- ✅ Evaluated first - Before task execution
- ✅ Evaluated first - Before validation
If the main configuration is disabled, nothing from that configuration will be processed or executed.
For Extended Configurations (via extends):
Extended configurations use lazy evaluation, meaning their enabled condition is evaluated sequentially during execution with access to previous values:
- Prompts: Each prompt from an extended configuration is evaluated before asking - has access to all previous prompt answers
- Variables: Each variable from an extended configuration is evaluated before resolving - has access to all prompts + previous variables
- Tasks: Each task from an extended configuration is evaluated before execution - has access to all prompts and variables
Example: Conditional Extended Configuration
{
"name": "pixpilot-info",
"dependencies": ["project-info"],
"variables": [
{
"id": "pixpilot_project",
"value": {
"type": "conditional",
"condition": "repoOwner === 'pixpilots' || orgName === 'pixpilots'",
"ifTrue": true,
"ifFalse": false
}
}
]
}
{
"name": "pixpilot-copilot-instructions",
"dependencies": ["pixpilot-info"],
"enabled": {
"condition": "pixpilot_project == true"
},
"tasks": [
{
"id": "create-copilot-instructions",
"name": "Create Copilot Instructions",
"type": "write",
"config": {
"file": ".github/copilot-instructions.md",
"templateFile": "copilot-instructions.md"
}
}
]
}
In this example:
project-infoconfiguration asks forrepoOwnerandorgNamepixpilot-infoconfiguration computespixpilot_projectvariable based on those answerspixpilot-copilot-instructionsconfiguration is only enabled ifpixpilot_projectistrue- The tasks, prompts, and variables from disabled configurations are automatically skipped
Configuration Variables in Enabled
You can use variables in the enabled expression, but be aware that variables are resolved during execution. For exec-type enabled, you can use configuration interpolation:
{
"name": "conditional-config",
"variables": [
{
"id": "targetEnv",
"value": {
"type": "exec",
"value": "echo $NODE_ENV"
}
}
],
"enabled": { "type": "condition", "value": "targetEnv === 'production'" },
"tasks": []
}
However, for early evaluation, prefer using exec-type enabled directly:
{
"name": "conditional-config",
"enabled": {
"type": "exec",
"value": "test \"$NODE_ENV\" = \"production\" && echo true || echo false"
},
"tasks": []
}
Best Practices
- Use exec for external checks - When checking file existence, environment variables, or system state
- Use conditions for prompt-based logic - When the decision depends on user input from dependency configurations
- Keep conditions simple - Complex logic should be in scripts, not conditions
- Document dependencies - If your configuration depends on variables from other configurations, list them in
dependencies
Conditional Execution
Execute tasks conditionally based on user input, configuration values, or any JavaScript expression.
Overview
Every task type supports an optional condition field that determines whether the task should be executed. If the condition evaluates to false, the task is skipped.
Basic Usage
{
"type": "update-json",
"config": {
"file": "package.json",
"updates": {
"private": true
},
"condition": "makePrivate === true"
}
}
Condition Syntax
Conditions are JavaScript expressions that have access to all configuration variables:
Supported Operators:
- Comparison:
===,!==,<,>,<=,>= - Logical:
&&(and),||(or),!(not) - Ternary:
condition ? true : false
Available Variables:
- Built-in config variables:
projectName,author,repoUrl, etc. - Root-level prompt values: Available to all tasks
- Root-level variable values: Available to all tasks
Using with Prompts
Combine conditional execution with interactive prompts for dynamic behavior:
{
"prompts": [
{
"id": "useTypeScript",
"type": "confirm",
"message": "Use TypeScript?",
"default": true
}
],
"tasks": [
{
"id": "setup-typescript",
"type": "write",
"config": {
"file": "tsconfig.json",
"templateFile": ".templates/tsconfig.hbs",
"condition": "useTypeScript === true"
}
}
]
}
Complex Conditions
Create sophisticated conditional logic:
{
"type": "delete",
"config": {
"paths": ["examples", "test-fixtures", "docs/api"],
"condition": "!keepExamples && environment !== 'development'"
}
}
Numeric Comparisons
{
"prompts": [
{
"id": "nodeVersion",
"type": "number",
"message": "Minimum Node.js version?",
"default": 18
}
],
"type": "exec",
"config": {
"command": "npm install --save-dev typescript@latest",
"condition": "nodeVersion >= 16"
}
}
Multiple Conditions
Combine multiple conditions with logical operators:
{
"type": "write",
"config": {
"file": ".github/workflows/ci.yml",
"templateFile": ".templates/ci-advanced.hbs",
"condition": "enableCI === true && (useTypeScript === true || useLinting === true)"
}
}
Dry Run with Conditions
When using --dry-run, you’ll see which tasks would be skipped:
✓ Task: Update Package Info
✗ Task: Setup TypeScript (condition not met: useTypeScript === false)
✓ Task: Create README
Best Practices
- Keep conditions simple - Complex logic is harder to debug
- Use root-level prompts - All prompts are available to all tasks
- Document conditions - Add clear descriptions explaining why tasks are conditional
- Test both paths - Run with conditions true and false to verify behavior
- Use meaningful prompt IDs - Make conditions self-documenting (e.g.,
enableFeatureX)
Examples
Conditional cleanup:
{
"prompts": [
{
"id": "cleanupTemplate",
"type": "confirm",
"message": "Remove template files after initialization?",
"default": true
}
],
"tasks": [
{
"id": "cleanup-template-files",
"type": "delete",
"config": {
"paths": [".templates", "template-tasks.json"],
"condition": "cleanupTemplate === true"
}
}
]
}
Environment-specific configuration:
{
"prompts": [
{
"id": "environment",
"type": "select",
"message": "Deployment environment?",
"choices": ["development", "staging", "production"]
}
],
"tasks": [
{
"id": "create-env",
"type": "write",
"config": {
"file": ".env",
"templateFile": ".templates/env-production.hbs",
"condition": "environment === 'production'"
}
}
]
}
Conditional Variables
Create dynamic variables that evaluate expressions and return different values based on conditions. This enables intelligent automation that adapts to user input or system state.
Overview
Conditional variables evaluate JavaScript expressions and return different values based on whether the condition is true or false. They have access to:
- Other variables (static or executable)
- Prompt values (user input)
- Any value in the configuration context
Basic Syntax
{
"id": "variableName",
"value": {
"type": "conditional",
"condition": "expression === 'value'",
"ifTrue": "value when true",
"ifFalse": "value when false"
}
}
Properties
| Property | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Must be "conditional" |
condition |
string | Yes | JavaScript expression to evaluate |
ifTrue |
any | Yes | Value returned when condition is true |
ifFalse |
any | Yes | Value returned when condition is false |
Evaluation Timing
Conditional variables are resolved in two passes:
- First pass: Before prompts - resolves non-conditional variables
- Second pass: After prompts - resolves conditional variables with full context
This allows conditional variables to reference prompt values.
Common Use Cases
1. Derive Boolean Flags
{
"variables": [
{
"id": "pixpilot_project",
"value": {
"type": "conditional",
"condition": "repoOwner === 'pixpilot' || orgName === 'pixpilot'",
"ifTrue": true,
"ifFalse": false
}
}
]
}
2. Choose Configuration Files
{
"variables": [
{
"id": "configFile",
"value": {
"type": "conditional",
"condition": "language === 'typescript'",
"ifTrue": "tsconfig.json",
"ifFalse": "jsconfig.json"
}
}
]
}
3. Set Different Values Based on Environment
{
"variables": [
{
"id": "apiEndpoint",
"value": {
"type": "conditional",
"condition": "environment === 'production'",
"ifTrue": "https://api.prod.example.com",
"ifFalse": "https://api.dev.example.com"
}
}
]
}
Dynamic Configuration Enabling
Combine conditional variables with configuration-level enabled to create configurations that automatically activate based on project context:
Config: pixpilot-info (derives the flag)
{
"name": "pixpilot-info",
"dependencies": ["project-info"],
"variables": [
{
"id": "pixpilot_project",
"value": {
"type": "conditional",
"condition": "repoOwner === 'pixpilot' || orgName === 'pixpilot'",
"ifTrue": true,
"ifFalse": false
}
}
]
}
Config: pixpilot-copilot-instructions (uses the flag)
{
"name": "pixpilot-copilot-instructions",
"dependencies": ["project-info", "pixpilot-info"],
"enabled": {
"condition": "pixpilot_project == true"
},
"tasks": [
{
"id": "create-copilot-instructions",
"name": "Create Copilot Instructions",
"type": "write",
"config": {
"file": ".github/copilot-instructions.md",
"templateFile": "copilot-instructions.md"
}
}
]
}
Result: The pixpilot-copilot-instructions configuration only runs when the repository owner or organization is “pixpilot”.
Complex Conditions
Conditional variables support complex JavaScript expressions:
{
"variables": [
{
"id": "shouldUseYarn",
"value": {
"type": "conditional",
"condition": "packageManager === 'yarn' && nodeVersion >= 14",
"ifTrue": true,
"ifFalse": false
}
},
{
"id": "installCommand",
"value": {
"type": "conditional",
"condition": "shouldUseYarn",
"ifTrue": "yarn install",
"ifFalse": "npm install"
}
}
]
}
Best Practices
- Keep conditions simple: Use clear, readable expressions
- Document the logic: Add comments in
descriptionfield - Chain dependencies: Let conditional variables build on each other
- Fail gracefully: Provide sensible default values in
ifFalse
Handlebars Templates
Use powerful Handlebars templating for advanced template generation with conditionals, loops, and helpers.
Overview
Scaffoldfy automatically uses Handlebars for template files ending in .hbs. This gives you access to advanced templating features like conditionals, loops, and built-in helpers.
Automatic Detection
No configuration needed - just use the .hbs extension:
{
"type": "write",
"config": {
"file": "README.md",
"templateFile": ".templates/readme.hbs"
}
}
Template Processing Rules
| Template Type | Extension | Processing |
|---|---|---|
| Handlebars | .hbs |
Full Handlebars templating |
| Simple | Other extensions | Simple `` replacement |
| Inline | N/A | Simple `` replacement |
Basic Handlebars Template
Create .templates/readme.hbs:
License
Use it in your task:
```json
{
"type": "write",
"config": {
"file": "README.md",
"templateFile": ".templates/readme.hbs"
}
}
Conditionals
Use and for conditional content:
### Loops
Use `` to iterate over arrays:
```handlebars
# Contributors
- ()
# Features
-
Pass arrays through prompts or config:
{
"prompts": [
{
"id": "features",
"type": "input",
"message": "List features (comma-separated):"
}
]
}
Nested Conditionals
Combine conditionals for complex logic:
Built-in Helpers
Handlebars provides useful built-in helpers:
Complex Example
.templates/package-json.hbs:
File Organization
Recommended structure:
project-root/
├── .templates/
│ ├── readme.hbs
│ ├── package-json.hbs
│ ├── tsconfig.hbs
│ └── ci-workflow.hbs
└── template-tasks.json
Complete Workflow Example
{
"prompts": [
{
"id": "projectName",
"type": "input",
"message": "Project name?",
"required": true
},
{
"id": "description",
"type": "input",
"message": "Description?"
},
{
"id": "useTypeScript",
"type": "confirm",
"message": "Use TypeScript?",
"default": true
},
{
"id": "enableTesting",
"type": "confirm",
"message": "Include testing?",
"default": true
}
],
"tasks": [
{
"id": "collect-info",
"name": "Collect Project Info",
"type": "write",
"config": {
"file": "package.json",
"templateFile": ".templates/package-json.hbs"
}
},
{
"id": "create-readme",
"name": "Create README",
"type": "write",
"config": {
"file": "README.md",
"templateFile": ".templates/readme.hbs"
}
},
{
"id": "setup-typescript",
"name": "Setup TypeScript",
"type": "write",
"config": {
"file": "tsconfig.json",
"templateFile": ".templates/tsconfig.hbs",
"condition": "useTypeScript === true"
}
}
]
}
Simple vs Handlebars
When to use Handlebars (.hbs):
- Need conditionals or loops
- Complex template logic
- Multiple variations of output
- Rich documentation generation
When to use simple templates:
- Basic variable replacement
- Inline templates
- Simple configuration files
- Quick file generation
Best Practices
- Use
.hbsextension - Makes it clear which templates use Handlebars - Organize templates - Keep all
.hbsfiles in a.templates/directory - Test templates - Use
--dry-runto preview generated files - Keep logic simple - Complex logic belongs in prompts or conditions, not templates
- Document variables - Comment what variables your templates expect
- Validate JSON - When generating JSON files, ensure proper comma handling
Troubleshooting
Template not using Handlebars?
- Ensure file ends with
.hbs - Check that
templateFile(nottemplate) is used
Syntax errors?
- Validate Handlebars syntax
- Check matching
with - Ensure proper nesting
Missing variables?
- Verify prompt IDs match template variables
- All root-level prompts are available to all tasks
- Ensure built-in variables are spelled correctly
Learn More
For complete Handlebars documentation:
Combining Features
The real power comes from combining these features:
{
"prompts": [
{
"id": "framework",
"type": "select",
"message": "Choose framework:",
"choices": ["react", "vue", "svelte"]
},
{
"id": "useTypeScript",
"type": "confirm",
"message": "Use TypeScript?",
"default": true
}
],
"tasks": [
{
"id": "setup",
"type": "write",
"config": {
"file": "package.json",
"templateFile": ".templates/package-.hbs"
}
},
{
"id": "setup-typescript",
"type": "write",
"config": {
"file": "tsconfig.json",
"templateFile": ".templates/tsconfig-.hbs",
"condition": "useTypeScript === true"
}
}
]
}
This example:
- Uses root-level prompts for shared values (
framework,useTypeScript) - Uses conditional execution to only setup TypeScript when needed
- Uses Handlebars templates with conditionals and dynamic file selection
- Creates a flexible, user-friendly template initialization
Next Steps
- Getting Started Guide - Learn the basics
- Task Types Reference - Explore all task types
- Interactive Prompts - Master user input
- Configuration Inheritance - Compose configurations
- Plugin System - Create custom task types
- Dry Run Mode - Preview changes safely