Skip to content

Commit a9e4d26

Browse files
authored
feat: Add environment variable support for CLI arguments (#125)
1 parent 0984782 commit a9e4d26

7 files changed

Lines changed: 613 additions & 12 deletions

File tree

docs/environment-variables.md

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# Environment Variables
2+
3+
StructKit supports environment variables to configure CLI arguments without requiring them to be specified on the command line. This is particularly useful for CI/CD pipelines, containerized environments, and automation workflows.
4+
5+
## Overview
6+
7+
Environment variables allow you to set default values for CLI arguments. Command-line arguments always take precedence over environment variables, ensuring flexibility when needed.
8+
9+
## High Priority Environment Variables
10+
11+
### `STRUCTKIT_GLOBAL_SYSTEM_PROMPT`
12+
13+
Sets the global system prompt for OpenAI integration.
14+
15+
**CLI Equivalent:** `--global-system-prompt` / `-p`
16+
17+
**Use Case:** Typically a long or sensitive value that users want to set once. Avoids repeating the same prompt across multiple invocations. Ideal for CI/CD workflows and container initialization.
18+
19+
**Example:**
20+
```bash
21+
export STRUCTKIT_GLOBAL_SYSTEM_PROMPT="You are a helpful assistant for generating project structures."
22+
structkit generate my-structure ./output
23+
```
24+
25+
### `STRUCTKIT_INPUT_STORE`
26+
27+
Sets the path to the input store for template variables.
28+
29+
**CLI Equivalent:** `--input-store` / `-n`
30+
31+
**Default:** `/tmp/structkit/input.json`
32+
33+
**Use Case:** Allows users to set a consistent default location for input data. Useful for workflows that need persistent input across multiple runs.
34+
35+
**Example:**
36+
```bash
37+
export STRUCTKIT_INPUT_STORE="/home/user/structkit-inputs/data.json"
38+
structkit generate my-structure ./output
39+
```
40+
41+
### `STRUCTKIT_BACKUP_PATH`
42+
43+
Sets the default backup location for file backups.
44+
45+
**CLI Equivalent:** `--backup` / `-b`
46+
47+
**Use Case:** Set a default backup location project-wide or environment-wide. Saves typing in repetitive operations. Useful for ensuring backups go to a specific location (e.g., mounted volume in containers).
48+
49+
**Example:**
50+
```bash
51+
export STRUCTKIT_BACKUP_PATH="/backups/structkit"
52+
structkit generate my-structure ./output
53+
```
54+
55+
## Medium Priority Environment Variables
56+
57+
### `STRUCTKIT_FILE_STRATEGY`
58+
59+
Sets the default strategy for handling existing files.
60+
61+
**CLI Equivalent:** `--file-strategy` / `-f`
62+
63+
**Valid Values:** `overwrite`, `skip`, `append`, `rename`, `backup`
64+
65+
**Default:** `overwrite`
66+
67+
**Use Case:** Let users set a preferred default strategy. Could prevent accidental data loss if set to 'skip' or 'backup' by default.
68+
69+
**Example:**
70+
```bash
71+
export STRUCTKIT_FILE_STRATEGY="backup"
72+
structkit generate my-structure ./output
73+
```
74+
75+
### `STRUCTKIT_NON_INTERACTIVE`
76+
77+
Enables or disables interactive mode for all commands.
78+
79+
**CLI Equivalent:** `--non-interactive`
80+
81+
**Valid Values:** `true`, `1`, `yes` (case-insensitive) for enabled; any other value for disabled
82+
83+
**Default:** `false`
84+
85+
**Use Case:** Boolean flag useful for CI/CD pipelines. Could be set in environment and applied across all commands.
86+
87+
**Example:**
88+
```bash
89+
export STRUCTKIT_NON_INTERACTIVE=true
90+
structkit generate my-structure ./output
91+
```
92+
93+
### `STRUCTKIT_OUTPUT_MODE`
94+
95+
Sets the default output mode for the generate command.
96+
97+
**CLI Equivalent:** `--output` / `-o`
98+
99+
**Valid Values:** `console`, `file`
100+
101+
**Default:** `file`
102+
103+
**Use Case:** Some users might prefer 'console' output by default. Useful for pipeline integration.
104+
105+
**Example:**
106+
```bash
107+
export STRUCTKIT_OUTPUT_MODE="console"
108+
structkit generate my-structure ./output
109+
```
110+
111+
## Shared Environment Variables
112+
113+
### `STRUCTKIT_STRUCTURES_PATH`
114+
115+
Sets the path to custom structure definitions.
116+
117+
**CLI Equivalent:** `--structures-path` / `-s`
118+
119+
**Use Case:** Allows specifying custom structures directory that applies across all commands (generate, list, info, generate-schema).
120+
121+
**Example:**
122+
```bash
123+
export STRUCTKIT_STRUCTURES_PATH="/home/user/my-structures"
124+
structkit list
125+
structkit generate my-custom-structure ./output
126+
```
127+
128+
### `STRUCTKIT_LOG_LEVEL`
129+
130+
Sets the logging level for all commands.
131+
132+
**CLI Equivalent:** `--log` (generate command)
133+
134+
**Valid Values:** `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
135+
136+
**Default:** `INFO`
137+
138+
**Use Case:** Control verbosity of output for debugging or production deployments.
139+
140+
**Example:**
141+
```bash
142+
export STRUCTKIT_LOG_LEVEL="DEBUG"
143+
structkit generate my-structure ./output
144+
```
145+
146+
## Precedence Rules
147+
148+
Command-line arguments **always take precedence** over environment variables. This allows environment variables to set sensible defaults while maintaining the ability to override them when needed.
149+
150+
**Precedence Order (highest to lowest):**
151+
1. Command-line arguments
152+
2. Environment variables
153+
3. Built-in defaults
154+
155+
**Example:**
156+
```bash
157+
# Set default via environment variable
158+
export STRUCTKIT_FILE_STRATEGY="backup"
159+
160+
# Override with CLI argument
161+
structkit generate --file-strategy skip my-structure ./output
162+
# Uses 'skip', not 'backup'
163+
164+
# Use default from environment
165+
structkit generate my-structure ./output
166+
# Uses 'backup' from STRUCTKIT_FILE_STRATEGY
167+
```
168+
169+
## Docker and Containerization
170+
171+
Environment variables are particularly useful when running StructKit in containers:
172+
173+
**Docker Example:**
174+
```bash
175+
docker run \
176+
-e STRUCTKIT_STRUCTURES_PATH=/custom/structures \
177+
-e STRUCTKIT_NON_INTERACTIVE=true \
178+
-e STRUCTKIT_FILE_STRATEGY=backup \
179+
-v /custom/structures:/custom/structures \
180+
-v $(pwd):/workdir \
181+
ghcr.io/httpdss/structkit:main generate my-structure /workdir/output
182+
```
183+
184+
**Docker Compose Example:**
185+
```yaml
186+
version: '3'
187+
services:
188+
structkit:
189+
image: ghcr.io/httpdss/structkit:main
190+
environment:
191+
STRUCTKIT_STRUCTURES_PATH: /custom/structures
192+
STRUCTKIT_NON_INTERACTIVE: "true"
193+
STRUCTKIT_FILE_STRATEGY: backup
194+
STRUCTKIT_LOG_LEVEL: DEBUG
195+
volumes:
196+
- /custom/structures:/custom/structures
197+
- ./output:/workdir
198+
command: generate my-structure /workdir/output
199+
```
200+
201+
## CI/CD Pipeline Integration
202+
203+
### GitHub Actions - Basic Example
204+
205+
```yaml
206+
name: Generate Project Structure
207+
208+
on: [push, pull_request]
209+
210+
jobs:
211+
generate:
212+
runs-on: ubuntu-latest
213+
env:
214+
STRUCTKIT_NON_INTERACTIVE: "true"
215+
STRUCTKIT_BACKUP_PATH: /tmp/backups
216+
STRUCTKIT_FILE_STRATEGY: backup
217+
steps:
218+
- uses: actions/checkout@v3
219+
220+
- name: Install StructKit
221+
run: pip install structkit
222+
223+
- name: Generate structure
224+
run: structkit generate my-structure ./generated-project
225+
226+
- name: Upload generated files
227+
uses: actions/upload-artifact@v3
228+
with:
229+
name: generated-project
230+
path: generated-project/
231+
```
232+
233+
### GitHub Actions - Advanced Example with Custom Structures
234+
235+
```yaml
236+
name: Generate with Custom Structures
237+
238+
on:
239+
push:
240+
branches: [main, develop]
241+
pull_request:
242+
branches: [main]
243+
244+
jobs:
245+
generate:
246+
runs-on: ubuntu-latest
247+
env:
248+
STRUCTKIT_NON_INTERACTIVE: "true"
249+
STRUCTKIT_LOG_LEVEL: DEBUG
250+
STRUCTKIT_OUTPUT_MODE: console
251+
steps:
252+
- uses: actions/checkout@v3
253+
254+
- name: Set up Python
255+
uses: actions/setup-python@v4
256+
with:
257+
python-version: '3.11'
258+
259+
- name: Install StructKit
260+
run: pip install structkit
261+
262+
- name: Generate default structure
263+
run: structkit generate python-basic ./my-project
264+
265+
- name: Generate with custom backup strategy
266+
env:
267+
STRUCTKIT_BACKUP_PATH: ./backups
268+
STRUCTKIT_FILE_STRATEGY: backup
269+
run: structkit generate terraform-module ./my-infrastructure
270+
271+
- name: Create summary
272+
run: |
273+
echo "## Generated Structures" >> $GITHUB_STEP_SUMMARY
274+
echo "- Python project generated" >> $GITHUB_STEP_SUMMARY
275+
echo "- Terraform module generated" >> $GITHUB_STEP_SUMMARY
276+
```
277+
278+
## Best Practices
279+
280+
1. **Use Environment Variables for Defaults** - Set environment variables for values that don't change frequently
281+
2. **Override When Needed** - Use CLI arguments for one-off changes or specific use cases
282+
3. **Document Configuration** - Document which environment variables are used in your project
283+
4. **Sensitive Data** - Store sensitive data (like API keys) in environment variables, not in configuration files
284+
5. **Validation** - Test environment variable configuration to ensure it works as expected
285+
286+
## Troubleshooting
287+
288+
### Environment variable not being picked up
289+
290+
1. Verify the environment variable is set: `echo $VARIABLE_NAME`
291+
2. Ensure you're using the correct variable name (case-sensitive on Linux/macOS)
292+
3. If running in Docker, check that the environment variable is passed correctly with `-e`
293+
4. Restart your terminal or shell session after setting the variable
294+
295+
### CLI argument not overriding environment variable
296+
297+
This should not happen - CLI arguments always take precedence. If you're experiencing this:
298+
1. Verify the CLI argument is correctly formatted
299+
2. Check that you're using the correct argument name (e.g., `--file-strategy` not `--strategy`)
300+
3. Ensure there are no spaces or special characters in the argument value
301+
302+
### Boolean environment variables not working correctly
303+
304+
For `STRUCTKIT_NON_INTERACTIVE`, only `true`, `1`, and `yes` (case-insensitive, e.g., `"True"`, `"TRUE"`, `"YeS"`) are recognized as true values. All other values are treated as false, including:
305+
- `"true "` (with trailing space)
306+
- `"on"` or `"enable"`
307+
308+
Use one of the recognized values for reliable behavior.

structkit/commands/generate.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,27 @@ def __init__(self, parser):
2121
'-s',
2222
'--structures-path',
2323
type=str,
24-
help='Path to structure definitions',
24+
help='Path to structure definitions (env: STRUCTKIT_STRUCTURES_PATH)',
2525
default=os.getenv('STRUCTKIT_STRUCTURES_PATH', None)
2626
)
27-
parser.add_argument('-n', '--input-store', type=str, help='Path to the input store', default='/tmp/structkit/input.json')
27+
parser.add_argument('-n', '--input-store', type=str, help='Path to the input store (env: STRUCTKIT_INPUT_STORE)', default=os.getenv('STRUCTKIT_INPUT_STORE', '/tmp/structkit/input.json'))
2828
parser.add_argument('-d', '--dry-run', action='store_true', help='Perform a dry run without creating any files or directories')
2929
parser.add_argument('--diff', action='store_true', help='Show unified diffs for files that would change during dry-run or console output')
3030
parser.add_argument('-v', '--vars', type=str, help='Template variables in the format KEY1=value1,KEY2=value2')
31-
parser.add_argument('-b', '--backup', type=str, help='Path to the backup folder')
31+
parser.add_argument('-b', '--backup', type=str, help='Path to the backup folder (env: STRUCTKIT_BACKUP_PATH)', default=os.getenv('STRUCTKIT_BACKUP_PATH', None))
3232
parser.add_argument(
3333
'-f',
3434
'--file-strategy',
3535
type=str,
3636
choices=['overwrite', 'skip', 'append', 'rename', 'backup'],
37-
default='overwrite',
38-
help='Strategy for handling existing files').completer = file_strategy_completer
39-
parser.add_argument('-p', '--global-system-prompt', type=str, help='Global system prompt for OpenAI')
40-
parser.add_argument('--non-interactive', action='store_true', help='Run the command in non-interactive mode')
37+
default=os.getenv('STRUCTKIT_FILE_STRATEGY', 'overwrite'),
38+
help='Strategy for handling existing files (env: STRUCTKIT_FILE_STRATEGY)').completer = file_strategy_completer
39+
parser.add_argument('-p', '--global-system-prompt', type=str, help='Global system prompt for OpenAI (env: STRUCTKIT_GLOBAL_SYSTEM_PROMPT)', default=os.getenv('STRUCTKIT_GLOBAL_SYSTEM_PROMPT', None))
40+
parser.add_argument('--non-interactive', action='store_true', help='Run the command in non-interactive mode (env: STRUCTKIT_NON_INTERACTIVE)', default=os.getenv('STRUCTKIT_NON_INTERACTIVE', '').lower() in ('true', '1', 'yes'))
4141
parser.add_argument('--mappings-file', type=str, action='append',
4242
help='Path to a YAML file containing mappings to be used in templates (can be specified multiple times)')
4343
parser.add_argument('-o', '--output', type=str,
44-
choices=['console', 'file'], default='file', help='Output mode')
44+
choices=['console', 'file'], default=os.getenv('STRUCTKIT_OUTPUT_MODE', 'file'), help='Output mode (env: STRUCTKIT_OUTPUT_MODE)')
4545
parser.set_defaults(func=self.execute)
4646

4747
def _parse_template_vars(self, vars_str):

structkit/commands/generate_schema.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class GenerateSchemaCommand(Command):
77
def __init__(self, parser):
88
super().__init__(parser)
99
parser.description = "Generate JSON schema for available structures"
10-
parser.add_argument('-s', '--structures-path', type=str, help='Path to structure definitions')
10+
parser.add_argument('-s', '--structures-path', type=str, help='Path to structure definitions (env: STRUCTKIT_STRUCTURES_PATH)', default=os.getenv('STRUCTKIT_STRUCTURES_PATH', None))
1111
parser.add_argument('-o', '--output', type=str, help='Output file path for the schema (default: stdout)')
1212
parser.set_defaults(func=self.execute)
1313

structkit/commands/info.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, parser):
1313
parser.description = "Show information about the package or structure definition"
1414
parser.add_argument('structure_definition', type=str, help='Name of the structure definition')
1515
parser.add_argument(
16-
'-s', '--structures-path', type=str, help='Path to structure definitions',
16+
'-s', '--structures-path', type=str, help='Path to structure definitions (env: STRUCTKIT_STRUCTURES_PATH)',
1717
default=os.getenv('STRUCTKIT_STRUCTURES_PATH', None)
1818
)
1919
parser.add_argument('--mcp', action='store_true', help='Enable MCP (Model Context Protocol) integration')

structkit/commands/list.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def __init__(self, parser):
99
super().__init__(parser)
1010
parser.description = "List available structures"
1111
parser.add_argument(
12-
'-s', '--structures-path', type=str, help='Path to structure definitions',
12+
'-s', '--structures-path', type=str, help='Path to structure definitions (env: STRUCTKIT_STRUCTURES_PATH)',
1313
default=os.getenv('STRUCTKIT_STRUCTURES_PATH', None)
1414
)
1515
parser.add_argument('--names-only', action='store_true', help='Print only structure names, one per line (for shell completion)')

tests/test_commands.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def test_generate_schema_command_init(parser):
9090
assert 'output' in actions
9191

9292
# Check help text
93-
assert actions['structures_path'].help == 'Path to structure definitions'
93+
assert actions['structures_path'].help == 'Path to structure definitions (env: STRUCTKIT_STRUCTURES_PATH)'
9494
assert actions['output'].help == 'Output file path for the schema (default: stdout)'
9595

9696

0 commit comments

Comments
 (0)