@vyuhlabs/dxkit 2.4.8 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +235 -0
- package/README.md +360 -439
- package/dist/analyzers/security/aggregator.d.ts.map +1 -1
- package/dist/analyzers/security/aggregator.js +4 -46
- package/dist/analyzers/security/aggregator.js.map +1 -1
- package/dist/analyzers/tools/fingerprint.d.ts +91 -26
- package/dist/analyzers/tools/fingerprint.d.ts.map +1 -1
- package/dist/analyzers/tools/fingerprint.js +111 -22
- package/dist/analyzers/tools/fingerprint.js.map +1 -1
- package/dist/analyzers/tools/generic.d.ts.map +1 -1
- package/dist/analyzers/tools/generic.js +6 -1
- package/dist/analyzers/tools/generic.js.map +1 -1
- package/dist/analyzers/tools/gitleaks.d.ts +24 -1
- package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
- package/dist/analyzers/tools/gitleaks.js +20 -11
- package/dist/analyzers/tools/gitleaks.js.map +1 -1
- package/dist/analyzers/types.d.ts +6 -4
- package/dist/analyzers/types.d.ts.map +1 -1
- package/dist/baseline/baseline-file.d.ts +104 -0
- package/dist/baseline/baseline-file.d.ts.map +1 -0
- package/dist/baseline/baseline-file.js +110 -0
- package/dist/baseline/baseline-file.js.map +1 -0
- package/dist/baseline/check-renderers.d.ts +108 -0
- package/dist/baseline/check-renderers.d.ts.map +1 -0
- package/dist/baseline/check-renderers.js +379 -0
- package/dist/baseline/check-renderers.js.map +1 -0
- package/dist/baseline/check.d.ts +127 -0
- package/dist/baseline/check.d.ts.map +1 -0
- package/dist/baseline/check.js +462 -0
- package/dist/baseline/check.js.map +1 -0
- package/dist/baseline/content-hash.d.ts +83 -0
- package/dist/baseline/content-hash.d.ts.map +1 -0
- package/dist/baseline/content-hash.js +131 -0
- package/dist/baseline/content-hash.js.map +1 -0
- package/dist/baseline/create.d.ts +96 -0
- package/dist/baseline/create.d.ts.map +1 -0
- package/dist/baseline/create.js +339 -0
- package/dist/baseline/create.js.map +1 -0
- package/dist/baseline/entry-to-located.d.ts +35 -0
- package/dist/baseline/entry-to-located.d.ts.map +1 -0
- package/dist/baseline/entry-to-located.js +72 -0
- package/dist/baseline/entry-to-located.js.map +1 -0
- package/dist/baseline/finding-identity.d.ts +47 -0
- package/dist/baseline/finding-identity.d.ts.map +1 -0
- package/dist/baseline/finding-identity.js +292 -0
- package/dist/baseline/finding-identity.js.map +1 -0
- package/dist/baseline/git-aware-match.d.ts +146 -0
- package/dist/baseline/git-aware-match.d.ts.map +1 -0
- package/dist/baseline/git-aware-match.js +439 -0
- package/dist/baseline/git-aware-match.js.map +1 -0
- package/dist/baseline/policy.d.ts +171 -0
- package/dist/baseline/policy.d.ts.map +1 -0
- package/dist/baseline/policy.js +206 -0
- package/dist/baseline/policy.js.map +1 -0
- package/dist/baseline/producers/health.d.ts +30 -0
- package/dist/baseline/producers/health.d.ts.map +1 -0
- package/dist/baseline/producers/health.js +42 -0
- package/dist/baseline/producers/health.js.map +1 -0
- package/dist/baseline/producers/index.d.ts +164 -0
- package/dist/baseline/producers/index.d.ts.map +1 -0
- package/dist/baseline/producers/index.js +200 -0
- package/dist/baseline/producers/index.js.map +1 -0
- package/dist/baseline/producers/licenses.d.ts +23 -0
- package/dist/baseline/producers/licenses.d.ts.map +1 -0
- package/dist/baseline/producers/licenses.js +46 -0
- package/dist/baseline/producers/licenses.js.map +1 -0
- package/dist/baseline/producers/quality.d.ts +39 -0
- package/dist/baseline/producers/quality.d.ts.map +1 -0
- package/dist/baseline/producers/quality.js +84 -0
- package/dist/baseline/producers/quality.js.map +1 -0
- package/dist/baseline/producers/secret-hmac.d.ts +45 -0
- package/dist/baseline/producers/secret-hmac.d.ts.map +1 -0
- package/dist/baseline/producers/secret-hmac.js +70 -0
- package/dist/baseline/producers/secret-hmac.js.map +1 -0
- package/dist/baseline/producers/security.d.ts +59 -0
- package/dist/baseline/producers/security.d.ts.map +1 -0
- package/dist/baseline/producers/security.js +135 -0
- package/dist/baseline/producers/security.js.map +1 -0
- package/dist/baseline/producers/tests.d.ts +36 -0
- package/dist/baseline/producers/tests.d.ts.map +1 -0
- package/dist/baseline/producers/tests.js +69 -0
- package/dist/baseline/producers/tests.js.map +1 -0
- package/dist/baseline/salt.d.ts +45 -0
- package/dist/baseline/salt.d.ts.map +1 -0
- package/dist/baseline/salt.js +113 -0
- package/dist/baseline/salt.js.map +1 -0
- package/dist/baseline/show.d.ts +79 -0
- package/dist/baseline/show.d.ts.map +1 -0
- package/dist/baseline/show.js +233 -0
- package/dist/baseline/show.js.map +1 -0
- package/dist/baseline/types.d.ts +482 -0
- package/dist/baseline/types.d.ts.map +1 -0
- package/dist/baseline/types.js +53 -0
- package/dist/baseline/types.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +360 -81
- package/dist/cli.js.map +1 -1
- package/dist/codebase-scanner.d.ts.map +1 -1
- package/dist/codebase-scanner.js +0 -1
- package/dist/codebase-scanner.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +0 -4
- package/dist/constants.js.map +1 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +22 -25
- package/dist/doctor.js.map +1 -1
- package/dist/fail-on.d.ts +84 -0
- package/dist/fail-on.d.ts.map +1 -0
- package/dist/fail-on.js +128 -0
- package/dist/fail-on.js.map +1 -0
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +2 -141
- package/dist/generator.js.map +1 -1
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +0 -9
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +0 -15
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +1 -1
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +0 -6
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +0 -11
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +0 -15
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +0 -6
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +0 -4
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +2 -28
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +26 -4
- package/dist/languages/typescript.js.map +1 -1
- package/dist/lib.d.ts +2 -3
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +3 -6
- package/dist/lib.js.map +1 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +0 -10
- package/dist/prompts.js.map +1 -1
- package/dist/report-schema.d.ts +42 -0
- package/dist/report-schema.d.ts.map +1 -0
- package/dist/report-schema.js +54 -0
- package/dist/report-schema.js.map +1 -0
- package/dist/ship-installers.d.ts +106 -0
- package/dist/ship-installers.d.ts.map +1 -0
- package/dist/ship-installers.js +415 -0
- package/dist/ship-installers.js.map +1 -0
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +0 -4
- package/dist/update.js.map +1 -1
- package/package.json +17 -11
- package/templates/.claude/agents/onboarding.md +5 -4
- package/templates/.claude/agents-available/codebase-explorer.md +1 -1
- package/templates/.claude/agents-available/debugger.md +2 -2
- package/templates/.claude/agents-available/health-auditor.md +2 -2
- package/templates/.claude/commands/doctor.md +20 -12
- package/templates/.claude/skills/build/SKILL.md.template +22 -30
- package/templates/.claude/skills/deploy/SKILL.md.template +5 -25
- package/templates/.claude/skills/doctor/SKILL.md +24 -47
- package/templates/.claude/skills/gcloud/SKILL.md +5 -5
- package/templates/.claude/skills/learned/SKILL.md +1 -1
- package/templates/.claude/skills/pulumi/SKILL.md +2 -2
- package/templates/.claude/skills/quality/SKILL.md.template +4 -23
- package/templates/.claude/skills/review/SKILL.md.template +4 -3
- package/templates/.claude/skills/scaffold/SKILL.md.template +5 -15
- package/templates/.claude/skills/secrets/SKILL.md +20 -21
- package/templates/.claude/skills/session/SKILL.md +20 -31
- package/templates/.claude/skills/test/SKILL.md.template +1 -7
- package/templates/.devcontainer/devcontainer.json +81 -0
- package/templates/.devcontainer/install-agent-clis.sh +42 -0
- package/templates/.devcontainer/post-create.sh +67 -0
- package/templates/.githooks/pre-commit +55 -0
- package/templates/.githooks/pre-push +63 -0
- package/templates/.github/workflows/dxkit-baseline-refresh.yml +78 -0
- package/templates/.github/workflows/dxkit-guardrails.yml +98 -0
- package/templates/CLAUDE.md.template +62 -196
- package/dist/project-yaml.d.ts +0 -13
- package/dist/project-yaml.d.ts.map +0 -1
- package/dist/project-yaml.js +0 -188
- package/dist/project-yaml.js.map +0 -1
- package/templates/.ai/README.md +0 -117
- package/templates/.ai/prompts/execution-prompt.md +0 -9
- package/templates/.ai/prompts/planning-prompt.md +0 -18
- package/templates/.ai/prompts/session-end-template.md +0 -182
- package/templates/.ai/prompts/session-end.md +0 -132
- package/templates/.ai/prompts/session-start.md +0 -109
- package/templates/.ai/prompts/step-by-step.md +0 -113
- package/templates/.ai/sessions/.gitkeep +0 -0
- package/templates/.claude/commands/setup-pr-review.md +0 -72
- package/templates/.devcontainer/Dockerfile.dev.template +0 -89
- package/templates/.devcontainer/devcontainer.json.template +0 -184
- package/templates/.devcontainer/docker-compose.yml.template +0 -105
- package/templates/.devcontainer/init-scripts/01-init.sql.template +0 -12
- package/templates/.devcontainer/post-create.sh.template +0 -298
- package/templates/.github/workflows/ci.yml.template +0 -399
- package/templates/.github/workflows/quality.yml.template +0 -376
- package/templates/.pre-commit-config.yaml.template +0 -106
- package/templates/.project/config/edit_config.py +0 -275
- package/templates/.project/config/project_config.py +0 -894
- package/templates/.project/scripts/codegen/generate-all.sh +0 -20
- package/templates/.project/scripts/codegen/validate-all.sh +0 -17
- package/templates/.project/scripts/docs/generate-all.sh +0 -30
- package/templates/.project/scripts/docs/serve.sh +0 -20
- package/templates/.project/scripts/quality/fix-all.sh +0 -138
- package/templates/.project/scripts/quality/lint-go.sh +0 -34
- package/templates/.project/scripts/quality/lint-python.sh +0 -54
- package/templates/.project/scripts/quality/run-all.sh +0 -497
- package/templates/.project/scripts/session/commit.sh +0 -70
- package/templates/.project/scripts/session/create-pr.sh +0 -165
- package/templates/.project/scripts/session/end.sh +0 -207
- package/templates/.project/scripts/session/start.sh +0 -233
- package/templates/.project/scripts/setup/doctor.sh +0 -404
- package/templates/.project/scripts/setup/interactive-setup.sh +0 -585
- package/templates/.project/scripts/sync/sync-template.sh +0 -328
- package/templates/.project/scripts/test/run-all.sh +0 -179
- package/templates/.project/scripts/test/run-quick.sh +0 -25
- package/templates/Makefile +0 -514
- package/templates/config/versions.yaml +0 -57
- package/templates/configs/go/.golangci.yml.template +0 -172
- package/templates/configs/go/go.mod.template +0 -15
- package/templates/configs/java/README.md +0 -6
- package/templates/configs/kotlin/README.md +0 -6
- package/templates/configs/node/package.json.template +0 -67
- package/templates/configs/node/tsconfig.json.template +0 -53
- package/templates/configs/python/pyproject.toml.template +0 -92
- package/templates/configs/python/pytest.ini.template +0 -64
- package/templates/configs/python/ruff.toml.template +0 -79
- package/templates/configs/ruby/README.md +0 -6
- package/templates/configs/rust/Cargo.toml.template +0 -51
- package/templates/configs/shared/.editorconfig +0 -67
- package/templates/scripts/validate-templates.sh +0 -449
|
@@ -1,894 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Project Configuration Manager
|
|
4
|
-
==============================
|
|
5
|
-
|
|
6
|
-
Reads and manages .project.yaml configuration for multi-language projects.
|
|
7
|
-
|
|
8
|
-
USAGE
|
|
9
|
-
-----
|
|
10
|
-
# Display project info dashboard
|
|
11
|
-
python3 project_config.py info
|
|
12
|
-
|
|
13
|
-
# Get/set a specific value
|
|
14
|
-
python3 project_config.py get languages.python.version
|
|
15
|
-
python3 project_config.py set languages.python.quality.coverage 85
|
|
16
|
-
|
|
17
|
-
# Quality presets
|
|
18
|
-
python3 project_config.py preset # list presets
|
|
19
|
-
python3 project_config.py preset strict # apply strict preset
|
|
20
|
-
python3 project_config.py preset relaxed # apply relaxed preset
|
|
21
|
-
|
|
22
|
-
# Language management
|
|
23
|
-
python3 project_config.py lang-list # list languages
|
|
24
|
-
python3 project_config.py lang-add python # enable python
|
|
25
|
-
python3 project_config.py lang-remove go # disable go
|
|
26
|
-
|
|
27
|
-
# Sync config to language files
|
|
28
|
-
python3 project_config.py sync # create/update config files
|
|
29
|
-
python3 project_config.py sync --dry-run # preview changes
|
|
30
|
-
|
|
31
|
-
# Export as shell variables (for CI/scripts)
|
|
32
|
-
python3 project_config.py export
|
|
33
|
-
python3 project_config.py export python # only python vars
|
|
34
|
-
|
|
35
|
-
# Initialize default config
|
|
36
|
-
python3 project_config.py init
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
from __future__ import annotations
|
|
40
|
-
|
|
41
|
-
import contextlib
|
|
42
|
-
import json
|
|
43
|
-
import sys
|
|
44
|
-
from importlib.util import find_spec
|
|
45
|
-
from pathlib import Path
|
|
46
|
-
from typing import Any
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# Check if yaml is available
|
|
50
|
-
HAS_YAML = find_spec("yaml") is not None
|
|
51
|
-
if HAS_YAML:
|
|
52
|
-
import yaml
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
CONFIG_FILE = ".project.yaml"
|
|
56
|
-
|
|
57
|
-
# Quality presets
|
|
58
|
-
QUALITY_PRESETS = {
|
|
59
|
-
"strict": {
|
|
60
|
-
"description": "Production-ready: High coverage, all checks enabled",
|
|
61
|
-
"python": {"coverage": 90, "lint": True, "typecheck": True, "format": True},
|
|
62
|
-
"go": {"coverage": 80, "lint": True, "format": True},
|
|
63
|
-
"node": {"coverage": 85, "lint": True, "typecheck": True, "format": True},
|
|
64
|
-
"rust": {"coverage": 75, "lint": True, "format": True},
|
|
65
|
-
},
|
|
66
|
-
"standard": {
|
|
67
|
-
"description": "Balanced: Moderate thresholds, essential checks",
|
|
68
|
-
"python": {"coverage": 80, "lint": True, "typecheck": True, "format": True},
|
|
69
|
-
"go": {"coverage": 70, "lint": True, "format": True},
|
|
70
|
-
"node": {"coverage": 75, "lint": True, "typecheck": True, "format": True},
|
|
71
|
-
"rust": {"coverage": 60, "lint": True, "format": True},
|
|
72
|
-
},
|
|
73
|
-
"relaxed": {
|
|
74
|
-
"description": "Rapid prototyping: Lower thresholds, flexible checks",
|
|
75
|
-
"python": {"coverage": 50, "lint": True, "typecheck": False, "format": True},
|
|
76
|
-
"go": {"coverage": 40, "lint": True, "format": True},
|
|
77
|
-
"node": {"coverage": 50, "lint": True, "typecheck": False, "format": True},
|
|
78
|
-
"rust": {"coverage": 30, "lint": True, "format": True},
|
|
79
|
-
},
|
|
80
|
-
"off": {
|
|
81
|
-
"description": "No enforcement: All quality checks disabled",
|
|
82
|
-
"python": {"coverage": 0, "lint": False, "typecheck": False, "format": False},
|
|
83
|
-
"go": {"coverage": 0, "lint": False, "format": False},
|
|
84
|
-
"node": {"coverage": 0, "lint": False, "typecheck": False, "format": False},
|
|
85
|
-
"rust": {"coverage": 0, "lint": False, "format": False},
|
|
86
|
-
},
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
# Default configuration template
|
|
90
|
-
DEFAULT_CONFIG = {
|
|
91
|
-
"project": {
|
|
92
|
-
"name": "my-project",
|
|
93
|
-
"description": "A new project",
|
|
94
|
-
},
|
|
95
|
-
"languages": {
|
|
96
|
-
"python": {
|
|
97
|
-
"enabled": False,
|
|
98
|
-
"version": "3.12",
|
|
99
|
-
"src_dir": "src", # Source directory (e.g., "src", ".", or "src/mypackage")
|
|
100
|
-
"quality": {
|
|
101
|
-
"coverage": 80,
|
|
102
|
-
"lint": True,
|
|
103
|
-
"typecheck": True,
|
|
104
|
-
"format": True,
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
"go": {
|
|
108
|
-
"enabled": False,
|
|
109
|
-
"version": "1.24.0",
|
|
110
|
-
"quality": {
|
|
111
|
-
"coverage": 70,
|
|
112
|
-
"lint": True,
|
|
113
|
-
"format": True,
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
"node": {
|
|
117
|
-
"enabled": False,
|
|
118
|
-
"version": "20",
|
|
119
|
-
"quality": {
|
|
120
|
-
"coverage": 75,
|
|
121
|
-
"lint": True,
|
|
122
|
-
"typecheck": True,
|
|
123
|
-
"format": True,
|
|
124
|
-
},
|
|
125
|
-
},
|
|
126
|
-
"rust": {
|
|
127
|
-
"enabled": False,
|
|
128
|
-
"version": "stable",
|
|
129
|
-
"quality": {
|
|
130
|
-
"coverage": 60,
|
|
131
|
-
"lint": True,
|
|
132
|
-
"format": True,
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
},
|
|
136
|
-
"precommit": True,
|
|
137
|
-
"infrastructure": {
|
|
138
|
-
"postgres": {
|
|
139
|
-
"enabled": False,
|
|
140
|
-
"version": "16",
|
|
141
|
-
},
|
|
142
|
-
"redis": {
|
|
143
|
-
"enabled": False,
|
|
144
|
-
"version": "7",
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
# ANSI colors
|
|
150
|
-
CYAN = "\033[36m"
|
|
151
|
-
GREEN = "\033[32m"
|
|
152
|
-
RED = "\033[31m"
|
|
153
|
-
DIM = "\033[2m"
|
|
154
|
-
BOLD = "\033[1m"
|
|
155
|
-
RESET = "\033[0m"
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def load_config(config_path: str = CONFIG_FILE) -> dict:
|
|
159
|
-
"""Load configuration from YAML file."""
|
|
160
|
-
path = Path(config_path)
|
|
161
|
-
|
|
162
|
-
if not path.exists():
|
|
163
|
-
return DEFAULT_CONFIG.copy()
|
|
164
|
-
|
|
165
|
-
if not HAS_YAML:
|
|
166
|
-
print("Warning: PyYAML not installed, using defaults", file=sys.stderr)
|
|
167
|
-
return DEFAULT_CONFIG.copy()
|
|
168
|
-
|
|
169
|
-
with open(path) as f:
|
|
170
|
-
config = yaml.safe_load(f) or {}
|
|
171
|
-
|
|
172
|
-
# Merge with defaults to ensure all keys exist
|
|
173
|
-
return deep_merge(DEFAULT_CONFIG.copy(), config)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def save_config(config: dict, config_path: str = CONFIG_FILE) -> None:
|
|
177
|
-
"""Save configuration to YAML file."""
|
|
178
|
-
if not HAS_YAML:
|
|
179
|
-
print("Error: PyYAML required for saving config", file=sys.stderr)
|
|
180
|
-
sys.exit(1)
|
|
181
|
-
|
|
182
|
-
path = Path(config_path)
|
|
183
|
-
with open(path, "w") as f:
|
|
184
|
-
yaml.dump(config, f, default_flow_style=False, sort_keys=False, indent=2)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
def deep_merge(base: dict, override: dict) -> dict:
|
|
188
|
-
"""Deep merge two dictionaries."""
|
|
189
|
-
result = base.copy()
|
|
190
|
-
for key, value in override.items():
|
|
191
|
-
if key in result and isinstance(result[key], dict) and isinstance(value, dict):
|
|
192
|
-
result[key] = deep_merge(result[key], value)
|
|
193
|
-
else:
|
|
194
|
-
result[key] = value
|
|
195
|
-
return result
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def get_nested(config: dict, path: str) -> Any:
|
|
199
|
-
"""Get a nested value by dot-separated path."""
|
|
200
|
-
keys = path.split(".")
|
|
201
|
-
value = config
|
|
202
|
-
for key in keys:
|
|
203
|
-
if isinstance(value, dict) and key in value:
|
|
204
|
-
value = value[key]
|
|
205
|
-
else:
|
|
206
|
-
return None
|
|
207
|
-
return value
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
def set_nested(config: dict, path: str, value: Any) -> dict:
|
|
211
|
-
"""Set a nested value by dot-separated path."""
|
|
212
|
-
keys = path.split(".")
|
|
213
|
-
current = config
|
|
214
|
-
for key in keys[:-1]:
|
|
215
|
-
if key not in current:
|
|
216
|
-
current[key] = {}
|
|
217
|
-
current = current[key]
|
|
218
|
-
|
|
219
|
-
# Try to parse value as appropriate type
|
|
220
|
-
if isinstance(value, str):
|
|
221
|
-
if value.lower() == "true":
|
|
222
|
-
value = True
|
|
223
|
-
elif value.lower() == "false":
|
|
224
|
-
value = False
|
|
225
|
-
else:
|
|
226
|
-
with contextlib.suppress(ValueError):
|
|
227
|
-
value = int(value)
|
|
228
|
-
|
|
229
|
-
current[keys[-1]] = value
|
|
230
|
-
return config
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def cmd_info(config: dict) -> None:
|
|
234
|
-
"""Display project configuration dashboard."""
|
|
235
|
-
width = 57
|
|
236
|
-
|
|
237
|
-
def hline(w: int) -> str:
|
|
238
|
-
return "─" * w
|
|
239
|
-
|
|
240
|
-
def check(val: bool) -> str:
|
|
241
|
-
return f"{GREEN}✓{RESET}" if val else f"{DIM}✗{RESET}"
|
|
242
|
-
|
|
243
|
-
project_name = config.get("project", {}).get("name", "unknown")
|
|
244
|
-
languages = config.get("languages", {})
|
|
245
|
-
precommit = config.get("precommit", False)
|
|
246
|
-
infra = config.get("infrastructure", {})
|
|
247
|
-
|
|
248
|
-
print()
|
|
249
|
-
print(f"{CYAN}┌{hline(width - 2)}┐{RESET}")
|
|
250
|
-
print(f"{CYAN}│{RESET} {BOLD}{project_name:<{width - 4}}{RESET} {CYAN}│{RESET}")
|
|
251
|
-
print(f"{CYAN}├{hline(width - 2)}┤{RESET}")
|
|
252
|
-
|
|
253
|
-
# Languages section
|
|
254
|
-
print(f"{CYAN}│{RESET} {BOLD}Languages{RESET}{' ' * (width - 12)}{CYAN}│{RESET}")
|
|
255
|
-
print(f"{CYAN}│{RESET}{' ' * (width - 2)}{CYAN}│{RESET}")
|
|
256
|
-
|
|
257
|
-
lang_tools = {
|
|
258
|
-
"python": ("Python", True), # has typecheck (mypy)
|
|
259
|
-
"go": ("Go", False), # typecheck built-in
|
|
260
|
-
"node": ("Node.js", True), # has typecheck (tsc)
|
|
261
|
-
"rust": ("Rust", False), # typecheck built-in
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
for lang_key, (lang_name, has_typecheck) in lang_tools.items():
|
|
265
|
-
lang = languages.get(lang_key, {})
|
|
266
|
-
enabled = lang.get("enabled", False)
|
|
267
|
-
version = lang.get("version", "")
|
|
268
|
-
quality = lang.get("quality", {})
|
|
269
|
-
|
|
270
|
-
if enabled:
|
|
271
|
-
cov = quality.get("coverage", 0)
|
|
272
|
-
lint = quality.get("lint", False)
|
|
273
|
-
typecheck = quality.get("typecheck", False)
|
|
274
|
-
|
|
275
|
-
extras = []
|
|
276
|
-
extras.append(f"cov:{cov}%")
|
|
277
|
-
if lint:
|
|
278
|
-
extras.append(f"{GREEN}lint{RESET}")
|
|
279
|
-
if has_typecheck and typecheck:
|
|
280
|
-
extras.append(f"{GREEN}type{RESET}")
|
|
281
|
-
|
|
282
|
-
extras_str = " ".join(extras)
|
|
283
|
-
line = f" {GREEN}✓{RESET} {lang_name:<10} {CYAN}{version:<8}{RESET} {extras_str}"
|
|
284
|
-
else:
|
|
285
|
-
line = f" {DIM}✗ {lang_name:<10}{RESET}"
|
|
286
|
-
|
|
287
|
-
# Pad to width (accounting for ANSI codes)
|
|
288
|
-
visible_len = len(line.replace(GREEN, "").replace(CYAN, "").replace(DIM, "").replace(RESET, "").replace(BOLD, ""))
|
|
289
|
-
padding = width - 2 - visible_len
|
|
290
|
-
print(f"{CYAN}│{RESET}{line}{' ' * max(0, padding)}{CYAN}│{RESET}")
|
|
291
|
-
|
|
292
|
-
print(f"{CYAN}├{hline(width - 2)}┤{RESET}")
|
|
293
|
-
|
|
294
|
-
# Global settings
|
|
295
|
-
print(f"{CYAN}│{RESET} {BOLD}Settings{RESET}{' ' * (width - 11)}{CYAN}│{RESET}")
|
|
296
|
-
print(f"{CYAN}│{RESET}{' ' * (width - 2)}{CYAN}│{RESET}")
|
|
297
|
-
|
|
298
|
-
precommit_str = f"{GREEN}enabled{RESET}" if precommit else f"{DIM}disabled{RESET}"
|
|
299
|
-
line = f" Pre-commit: {precommit_str}"
|
|
300
|
-
visible_len = len(line.replace(GREEN, "").replace(DIM, "").replace(RESET, ""))
|
|
301
|
-
padding = width - 2 - visible_len
|
|
302
|
-
print(f"{CYAN}│{RESET}{line}{' ' * max(0, padding)}{CYAN}│{RESET}")
|
|
303
|
-
|
|
304
|
-
print(f"{CYAN}├{hline(width - 2)}┤{RESET}")
|
|
305
|
-
|
|
306
|
-
# Infrastructure section
|
|
307
|
-
print(f"{CYAN}│{RESET} {BOLD}Infrastructure{RESET}{' ' * (width - 17)}{CYAN}│{RESET}")
|
|
308
|
-
print(f"{CYAN}│{RESET}{' ' * (width - 2)}{CYAN}│{RESET}")
|
|
309
|
-
|
|
310
|
-
pg = infra.get("postgres", {}).get("enabled", False)
|
|
311
|
-
redis = infra.get("redis", {}).get("enabled", False)
|
|
312
|
-
|
|
313
|
-
line = f" {check(pg)} PostgreSQL {check(redis)} Redis"
|
|
314
|
-
visible_len = len(line.replace(GREEN, "").replace(DIM, "").replace(RESET, ""))
|
|
315
|
-
padding = width - 2 - visible_len
|
|
316
|
-
print(f"{CYAN}│{RESET}{line}{' ' * max(0, padding)}{CYAN}│{RESET}")
|
|
317
|
-
|
|
318
|
-
print(f"{CYAN}└{hline(width - 2)}┘{RESET}")
|
|
319
|
-
print()
|
|
320
|
-
print(f"{DIM}Config: {CONFIG_FILE} | Edit: make config{RESET}")
|
|
321
|
-
print()
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
def cmd_get(config: dict, path: str) -> None:
|
|
325
|
-
"""Get a configuration value."""
|
|
326
|
-
value = get_nested(config, path)
|
|
327
|
-
if value is None:
|
|
328
|
-
print(f"Key not found: {path}", file=sys.stderr)
|
|
329
|
-
sys.exit(1)
|
|
330
|
-
print(value)
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
def cmd_set(config: dict, path: str, value: str) -> None:
|
|
334
|
-
"""Set a configuration value."""
|
|
335
|
-
config = set_nested(config, path, value)
|
|
336
|
-
save_config(config)
|
|
337
|
-
print(f"Set {path} = {value}")
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
def cmd_export(config: dict, lang_filter: str | None = None) -> None:
|
|
341
|
-
"""Export configuration as shell variables."""
|
|
342
|
-
project = config.get("project", {})
|
|
343
|
-
languages = config.get("languages", {})
|
|
344
|
-
infra = config.get("infrastructure", {})
|
|
345
|
-
|
|
346
|
-
exports = []
|
|
347
|
-
|
|
348
|
-
# Project
|
|
349
|
-
exports.append(f"PROJECT_NAME='{project.get('name', '')}'")
|
|
350
|
-
exports.append(f"PROJECT_DESCRIPTION='{project.get('description', '')}'")
|
|
351
|
-
|
|
352
|
-
# Languages
|
|
353
|
-
for lang_key, lang in languages.items():
|
|
354
|
-
if lang_filter and lang_key != lang_filter:
|
|
355
|
-
continue
|
|
356
|
-
|
|
357
|
-
prefix = lang_key.upper()
|
|
358
|
-
exports.append(f"INCLUDE_{prefix}={str(lang.get('enabled', False)).lower()}")
|
|
359
|
-
exports.append(f"{prefix}_VERSION='{lang.get('version', '')}'")
|
|
360
|
-
|
|
361
|
-
quality = lang.get("quality", {})
|
|
362
|
-
exports.append(f"{prefix}_COVERAGE_THRESHOLD={quality.get('coverage', 0)}")
|
|
363
|
-
exports.append(f"{prefix}_LINT_ENABLED={str(quality.get('lint', False)).lower()}")
|
|
364
|
-
if "typecheck" in quality:
|
|
365
|
-
exports.append(f"{prefix}_TYPECHECK_ENABLED={str(quality.get('typecheck', False)).lower()}")
|
|
366
|
-
exports.append(f"{prefix}_FORMAT_ENABLED={str(quality.get('format', False)).lower()}")
|
|
367
|
-
|
|
368
|
-
# Precommit
|
|
369
|
-
exports.append(f"INCLUDE_PRECOMMIT={str(config.get('precommit', False)).lower()}")
|
|
370
|
-
|
|
371
|
-
# Infrastructure
|
|
372
|
-
for infra_key, infra_config in infra.items():
|
|
373
|
-
prefix = infra_key.upper()
|
|
374
|
-
exports.append(f"INCLUDE_{prefix}={str(infra_config.get('enabled', False)).lower()}")
|
|
375
|
-
exports.append(f"{prefix}_VERSION='{infra_config.get('version', '')}'")
|
|
376
|
-
|
|
377
|
-
for export in exports:
|
|
378
|
-
print(export)
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
def cmd_init(config: dict) -> None:
|
|
382
|
-
"""Initialize configuration file with defaults."""
|
|
383
|
-
if Path(CONFIG_FILE).exists():
|
|
384
|
-
print(f"Config file already exists: {CONFIG_FILE}")
|
|
385
|
-
print("Use 'make config' to edit")
|
|
386
|
-
return
|
|
387
|
-
|
|
388
|
-
save_config(DEFAULT_CONFIG)
|
|
389
|
-
print(f"Created {CONFIG_FILE}")
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
def cmd_preset(config: dict, preset_name: str | None = None) -> None:
|
|
393
|
-
"""Apply a quality preset or list available presets."""
|
|
394
|
-
if preset_name is None:
|
|
395
|
-
# List available presets
|
|
396
|
-
print()
|
|
397
|
-
print(f"{BOLD}Available Quality Presets{RESET}")
|
|
398
|
-
print("=" * 50)
|
|
399
|
-
print()
|
|
400
|
-
for name, preset in QUALITY_PRESETS.items():
|
|
401
|
-
print(f" {CYAN}{name:<12}{RESET} {preset['description']}")
|
|
402
|
-
print()
|
|
403
|
-
print(f"{DIM}Usage: python3 project_config.py preset <name>{RESET}")
|
|
404
|
-
print(f"{DIM} or: make quality-strict / make quality-relaxed{RESET}")
|
|
405
|
-
print()
|
|
406
|
-
return
|
|
407
|
-
|
|
408
|
-
if preset_name not in QUALITY_PRESETS:
|
|
409
|
-
print(f"Unknown preset: {preset_name}", file=sys.stderr)
|
|
410
|
-
print(f"Available: {', '.join(QUALITY_PRESETS.keys())}", file=sys.stderr)
|
|
411
|
-
sys.exit(1)
|
|
412
|
-
|
|
413
|
-
preset = QUALITY_PRESETS[preset_name]
|
|
414
|
-
languages = config.get("languages", {})
|
|
415
|
-
|
|
416
|
-
# Apply preset to all enabled languages
|
|
417
|
-
applied = []
|
|
418
|
-
for lang_key in languages:
|
|
419
|
-
if lang_key in preset:
|
|
420
|
-
languages[lang_key]["quality"] = preset[lang_key].copy()
|
|
421
|
-
if languages[lang_key].get("enabled", False):
|
|
422
|
-
applied.append(lang_key)
|
|
423
|
-
|
|
424
|
-
config["languages"] = languages
|
|
425
|
-
save_config(config)
|
|
426
|
-
|
|
427
|
-
print()
|
|
428
|
-
print(f"{GREEN}✓{RESET} Applied '{preset_name}' preset")
|
|
429
|
-
print(f" {preset['description']}")
|
|
430
|
-
print()
|
|
431
|
-
if applied:
|
|
432
|
-
print(f" Updated: {', '.join(applied)}")
|
|
433
|
-
else:
|
|
434
|
-
print(f" {DIM}No languages enabled. Enable with: make lang-add LANG=python{RESET}")
|
|
435
|
-
print()
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
def cmd_lang_add(config: dict, lang: str) -> None:
|
|
439
|
-
"""Enable a language in the project."""
|
|
440
|
-
valid_langs = ["python", "go", "node", "rust"]
|
|
441
|
-
if lang not in valid_langs:
|
|
442
|
-
print(f"Unknown language: {lang}", file=sys.stderr)
|
|
443
|
-
print(f"Available: {', '.join(valid_langs)}", file=sys.stderr)
|
|
444
|
-
sys.exit(1)
|
|
445
|
-
|
|
446
|
-
languages = config.get("languages", {})
|
|
447
|
-
if lang not in languages:
|
|
448
|
-
languages[lang] = DEFAULT_CONFIG["languages"].get(lang, {"enabled": True})
|
|
449
|
-
|
|
450
|
-
if languages[lang].get("enabled", False):
|
|
451
|
-
print(f"{lang} is already enabled")
|
|
452
|
-
return
|
|
453
|
-
|
|
454
|
-
languages[lang]["enabled"] = True
|
|
455
|
-
config["languages"] = languages
|
|
456
|
-
save_config(config)
|
|
457
|
-
|
|
458
|
-
version = languages[lang].get("version", "")
|
|
459
|
-
quality = languages[lang].get("quality", {})
|
|
460
|
-
cov = quality.get("coverage", 0)
|
|
461
|
-
|
|
462
|
-
print()
|
|
463
|
-
print(f"{GREEN}✓{RESET} Enabled {lang}")
|
|
464
|
-
print(f" Version: {version}")
|
|
465
|
-
print(f" Coverage: {cov}%")
|
|
466
|
-
print()
|
|
467
|
-
print(f"{DIM}Run 'make info' to see full configuration{RESET}")
|
|
468
|
-
print()
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
def cmd_lang_remove(config: dict, lang: str) -> None:
|
|
472
|
-
"""Disable a language in the project."""
|
|
473
|
-
languages = config.get("languages", {})
|
|
474
|
-
|
|
475
|
-
if lang not in languages:
|
|
476
|
-
print(f"Unknown language: {lang}", file=sys.stderr)
|
|
477
|
-
sys.exit(1)
|
|
478
|
-
|
|
479
|
-
if not languages[lang].get("enabled", False):
|
|
480
|
-
print(f"{lang} is already disabled")
|
|
481
|
-
return
|
|
482
|
-
|
|
483
|
-
languages[lang]["enabled"] = False
|
|
484
|
-
config["languages"] = languages
|
|
485
|
-
save_config(config)
|
|
486
|
-
|
|
487
|
-
print()
|
|
488
|
-
print(f"{GREEN}✓{RESET} Disabled {lang}")
|
|
489
|
-
print()
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
def cmd_lang_list(config: dict) -> None:
|
|
493
|
-
"""List available languages and their status."""
|
|
494
|
-
languages = config.get("languages", {})
|
|
495
|
-
print()
|
|
496
|
-
print(f"{BOLD}Languages{RESET}")
|
|
497
|
-
print("=" * 40)
|
|
498
|
-
print()
|
|
499
|
-
for lang_key, lang_config in languages.items():
|
|
500
|
-
enabled = lang_config.get("enabled", False)
|
|
501
|
-
version = lang_config.get("version", "")
|
|
502
|
-
status = f"{GREEN}enabled{RESET}" if enabled else f"{DIM}disabled{RESET}"
|
|
503
|
-
print(f" {lang_key:<10} {version:<10} {status}")
|
|
504
|
-
print()
|
|
505
|
-
print(f"{DIM}Add: make lang-add LANG=python{RESET}")
|
|
506
|
-
print(f"{DIM}Remove: make lang-remove LANG=python{RESET}")
|
|
507
|
-
print()
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
def cmd_sync(config: dict, dry_run: bool = False) -> None:
|
|
511
|
-
"""Sync config to language-specific files."""
|
|
512
|
-
languages = config.get("languages", {})
|
|
513
|
-
project_name = config.get("project", {}).get("name", "my-project")
|
|
514
|
-
|
|
515
|
-
synced = []
|
|
516
|
-
created = []
|
|
517
|
-
|
|
518
|
-
for lang_key, lang_config in languages.items():
|
|
519
|
-
if not lang_config.get("enabled", False):
|
|
520
|
-
continue
|
|
521
|
-
|
|
522
|
-
version = lang_config.get("version", "")
|
|
523
|
-
quality = lang_config.get("quality", {})
|
|
524
|
-
|
|
525
|
-
if lang_key == "python":
|
|
526
|
-
src_dir = lang_config.get("src_dir", "src")
|
|
527
|
-
result = _sync_python(project_name, version, quality, src_dir, dry_run)
|
|
528
|
-
synced.extend(result.get("synced", []))
|
|
529
|
-
created.extend(result.get("created", []))
|
|
530
|
-
|
|
531
|
-
elif lang_key == "go":
|
|
532
|
-
result = _sync_go(project_name, version, quality, dry_run)
|
|
533
|
-
synced.extend(result.get("synced", []))
|
|
534
|
-
created.extend(result.get("created", []))
|
|
535
|
-
|
|
536
|
-
elif lang_key == "node":
|
|
537
|
-
result = _sync_node(project_name, version, quality, dry_run)
|
|
538
|
-
synced.extend(result.get("synced", []))
|
|
539
|
-
created.extend(result.get("created", []))
|
|
540
|
-
|
|
541
|
-
elif lang_key == "rust":
|
|
542
|
-
result = _sync_rust(project_name, version, quality, dry_run)
|
|
543
|
-
synced.extend(result.get("synced", []))
|
|
544
|
-
created.extend(result.get("created", []))
|
|
545
|
-
|
|
546
|
-
print()
|
|
547
|
-
if dry_run:
|
|
548
|
-
print(f"{CYAN}[DRY RUN]{RESET} Would sync the following:")
|
|
549
|
-
else:
|
|
550
|
-
print(f"{GREEN}✓{RESET} Config synced")
|
|
551
|
-
|
|
552
|
-
if created:
|
|
553
|
-
print(f"\n {BOLD}Created:{RESET}")
|
|
554
|
-
for f in created:
|
|
555
|
-
print(f" + {f}")
|
|
556
|
-
|
|
557
|
-
if synced:
|
|
558
|
-
print(f"\n {BOLD}Updated:{RESET}")
|
|
559
|
-
for f in synced:
|
|
560
|
-
print(f" ~ {f}")
|
|
561
|
-
|
|
562
|
-
if not created and not synced:
|
|
563
|
-
print(f" {DIM}No changes needed{RESET}")
|
|
564
|
-
|
|
565
|
-
print()
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
def _sync_python(name: str, version: str, quality: dict, src_dir: str, dry_run: bool) -> dict:
|
|
569
|
-
"""Sync Python configuration files."""
|
|
570
|
-
result = {"synced": [], "created": []}
|
|
571
|
-
pyproject_path = Path("pyproject.toml")
|
|
572
|
-
coverage = quality.get("coverage", 80)
|
|
573
|
-
lint = quality.get("lint", True)
|
|
574
|
-
typecheck = quality.get("typecheck", True)
|
|
575
|
-
fmt = quality.get("format", True)
|
|
576
|
-
|
|
577
|
-
# Generate pyproject.toml content
|
|
578
|
-
content = f'''[project]
|
|
579
|
-
name = "{name}"
|
|
580
|
-
version = "0.1.0"
|
|
581
|
-
description = ""
|
|
582
|
-
requires-python = ">={version}"
|
|
583
|
-
|
|
584
|
-
[project.optional-dependencies]
|
|
585
|
-
dev = [
|
|
586
|
-
"pytest>=7.4.0",
|
|
587
|
-
"pytest-cov>=4.1.0",
|
|
588
|
-
"pytest-asyncio>=0.21.0",
|
|
589
|
-
"ruff>=0.8.0",
|
|
590
|
-
"mypy>=1.8.0",
|
|
591
|
-
]
|
|
592
|
-
|
|
593
|
-
[build-system]
|
|
594
|
-
requires = ["hatchling"]
|
|
595
|
-
build-backend = "hatchling.build"
|
|
596
|
-
|
|
597
|
-
[tool.hatch.build.targets.wheel]
|
|
598
|
-
include = ["**/*.py"]
|
|
599
|
-
exclude = [
|
|
600
|
-
"tests/**",
|
|
601
|
-
"**/test_*.py",
|
|
602
|
-
"**/*_test.py",
|
|
603
|
-
"conftest.py",
|
|
604
|
-
".template/**",
|
|
605
|
-
".project/**",
|
|
606
|
-
"scripts/**",
|
|
607
|
-
"docs/**",
|
|
608
|
-
"examples/**",
|
|
609
|
-
]
|
|
610
|
-
|
|
611
|
-
[tool.pytest.ini_options]
|
|
612
|
-
testpaths = ["tests"]
|
|
613
|
-
addopts = "--cov={src_dir} --cov-report=term-missing --cov-fail-under={coverage}"
|
|
614
|
-
|
|
615
|
-
[tool.coverage.run]
|
|
616
|
-
source = ["{src_dir}"]
|
|
617
|
-
branch = true
|
|
618
|
-
|
|
619
|
-
[tool.coverage.report]
|
|
620
|
-
fail_under = {coverage}
|
|
621
|
-
show_missing = true
|
|
622
|
-
'''
|
|
623
|
-
|
|
624
|
-
if lint or fmt:
|
|
625
|
-
content += f'''
|
|
626
|
-
[tool.ruff]
|
|
627
|
-
line-length = 88
|
|
628
|
-
target-version = "py{version.replace(".", "")[:3]}"
|
|
629
|
-
'''
|
|
630
|
-
if lint:
|
|
631
|
-
content += '''
|
|
632
|
-
[tool.ruff.lint]
|
|
633
|
-
select = ["E", "F", "W", "I", "UP", "B", "C4"]
|
|
634
|
-
ignore = []
|
|
635
|
-
'''
|
|
636
|
-
|
|
637
|
-
if typecheck:
|
|
638
|
-
content += f'''
|
|
639
|
-
[tool.mypy]
|
|
640
|
-
python_version = "{version}"
|
|
641
|
-
strict = true
|
|
642
|
-
warn_return_any = true
|
|
643
|
-
warn_unused_configs = true
|
|
644
|
-
'''
|
|
645
|
-
|
|
646
|
-
if dry_run:
|
|
647
|
-
if pyproject_path.exists():
|
|
648
|
-
result["synced"].append("pyproject.toml")
|
|
649
|
-
else:
|
|
650
|
-
result["created"].append("pyproject.toml")
|
|
651
|
-
else:
|
|
652
|
-
existed = pyproject_path.exists()
|
|
653
|
-
with open(pyproject_path, "w") as f:
|
|
654
|
-
f.write(content)
|
|
655
|
-
if existed:
|
|
656
|
-
result["synced"].append("pyproject.toml")
|
|
657
|
-
else:
|
|
658
|
-
result["created"].append("pyproject.toml")
|
|
659
|
-
|
|
660
|
-
return result
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
def _sync_go(name: str, version: str, quality: dict, dry_run: bool) -> dict:
|
|
664
|
-
"""Sync Go configuration files."""
|
|
665
|
-
result = {"synced": [], "created": []}
|
|
666
|
-
gomod_path = Path("go.mod")
|
|
667
|
-
golangci_path = Path(".golangci.yml")
|
|
668
|
-
lint = quality.get("lint", True)
|
|
669
|
-
|
|
670
|
-
# go.mod
|
|
671
|
-
gomod_content = f'''module {name}
|
|
672
|
-
|
|
673
|
-
go {version}
|
|
674
|
-
'''
|
|
675
|
-
|
|
676
|
-
if dry_run:
|
|
677
|
-
if gomod_path.exists():
|
|
678
|
-
result["synced"].append("go.mod")
|
|
679
|
-
else:
|
|
680
|
-
result["created"].append("go.mod")
|
|
681
|
-
else:
|
|
682
|
-
existed = gomod_path.exists()
|
|
683
|
-
with open(gomod_path, "w") as f:
|
|
684
|
-
f.write(gomod_content)
|
|
685
|
-
if existed:
|
|
686
|
-
result["synced"].append("go.mod")
|
|
687
|
-
else:
|
|
688
|
-
result["created"].append("go.mod")
|
|
689
|
-
|
|
690
|
-
# .golangci.yml for linting
|
|
691
|
-
if lint:
|
|
692
|
-
golangci_content = '''run:
|
|
693
|
-
timeout: 5m
|
|
694
|
-
|
|
695
|
-
linters:
|
|
696
|
-
enable:
|
|
697
|
-
- errcheck
|
|
698
|
-
- gosimple
|
|
699
|
-
- govet
|
|
700
|
-
- ineffassign
|
|
701
|
-
- staticcheck
|
|
702
|
-
- unused
|
|
703
|
-
- gofmt
|
|
704
|
-
- goimports
|
|
705
|
-
|
|
706
|
-
linters-settings:
|
|
707
|
-
errcheck:
|
|
708
|
-
check-type-assertions: true
|
|
709
|
-
'''
|
|
710
|
-
if dry_run:
|
|
711
|
-
if golangci_path.exists():
|
|
712
|
-
result["synced"].append(".golangci.yml")
|
|
713
|
-
else:
|
|
714
|
-
result["created"].append(".golangci.yml")
|
|
715
|
-
else:
|
|
716
|
-
existed = golangci_path.exists()
|
|
717
|
-
with open(golangci_path, "w") as f:
|
|
718
|
-
f.write(golangci_content)
|
|
719
|
-
if existed:
|
|
720
|
-
result["synced"].append(".golangci.yml")
|
|
721
|
-
else:
|
|
722
|
-
result["created"].append(".golangci.yml")
|
|
723
|
-
|
|
724
|
-
return result
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
def _sync_node(name: str, version: str, quality: dict, dry_run: bool) -> dict:
|
|
728
|
-
"""Sync Node.js configuration files."""
|
|
729
|
-
result = {"synced": [], "created": []}
|
|
730
|
-
pkg_path = Path("package.json")
|
|
731
|
-
tsconfig_path = Path("tsconfig.json")
|
|
732
|
-
coverage = quality.get("coverage", 75)
|
|
733
|
-
lint = quality.get("lint", True)
|
|
734
|
-
typecheck = quality.get("typecheck", True)
|
|
735
|
-
|
|
736
|
-
# package.json
|
|
737
|
-
pkg_content = {
|
|
738
|
-
"name": name,
|
|
739
|
-
"version": "0.1.0",
|
|
740
|
-
"type": "module",
|
|
741
|
-
"engines": {"node": f">={version}"},
|
|
742
|
-
"scripts": {
|
|
743
|
-
"build": "tsc" if typecheck else "echo 'No build step'",
|
|
744
|
-
"test": f"vitest run --coverage --coverage.thresholds.statements={coverage}",
|
|
745
|
-
"lint": "eslint src" if lint else "echo 'Lint disabled'",
|
|
746
|
-
"format": "prettier --write src",
|
|
747
|
-
},
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
if dry_run:
|
|
751
|
-
if pkg_path.exists():
|
|
752
|
-
result["synced"].append("package.json")
|
|
753
|
-
else:
|
|
754
|
-
result["created"].append("package.json")
|
|
755
|
-
else:
|
|
756
|
-
existed = pkg_path.exists()
|
|
757
|
-
with open(pkg_path, "w") as f:
|
|
758
|
-
json.dump(pkg_content, f, indent=2)
|
|
759
|
-
f.write("\n")
|
|
760
|
-
if existed:
|
|
761
|
-
result["synced"].append("package.json")
|
|
762
|
-
else:
|
|
763
|
-
result["created"].append("package.json")
|
|
764
|
-
|
|
765
|
-
# tsconfig.json for TypeScript
|
|
766
|
-
if typecheck:
|
|
767
|
-
tsconfig_content = {
|
|
768
|
-
"compilerOptions": {
|
|
769
|
-
"target": "ES2022",
|
|
770
|
-
"module": "NodeNext",
|
|
771
|
-
"moduleResolution": "NodeNext",
|
|
772
|
-
"strict": True,
|
|
773
|
-
"esModuleInterop": True,
|
|
774
|
-
"skipLibCheck": True,
|
|
775
|
-
"outDir": "dist",
|
|
776
|
-
"rootDir": "src",
|
|
777
|
-
},
|
|
778
|
-
"include": ["src"],
|
|
779
|
-
"exclude": ["node_modules", "dist"],
|
|
780
|
-
}
|
|
781
|
-
|
|
782
|
-
if dry_run:
|
|
783
|
-
if tsconfig_path.exists():
|
|
784
|
-
result["synced"].append("tsconfig.json")
|
|
785
|
-
else:
|
|
786
|
-
result["created"].append("tsconfig.json")
|
|
787
|
-
else:
|
|
788
|
-
existed = tsconfig_path.exists()
|
|
789
|
-
with open(tsconfig_path, "w") as f:
|
|
790
|
-
json.dump(tsconfig_content, f, indent=2)
|
|
791
|
-
f.write("\n")
|
|
792
|
-
if existed:
|
|
793
|
-
result["synced"].append("tsconfig.json")
|
|
794
|
-
else:
|
|
795
|
-
result["created"].append("tsconfig.json")
|
|
796
|
-
|
|
797
|
-
return result
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
def _sync_rust(name: str, version: str, quality: dict, dry_run: bool) -> dict:
|
|
801
|
-
"""Sync Rust configuration files."""
|
|
802
|
-
result = {"synced": [], "created": []}
|
|
803
|
-
cargo_path = Path("Cargo.toml")
|
|
804
|
-
|
|
805
|
-
# Cargo.toml
|
|
806
|
-
cargo_content = f'''[package]
|
|
807
|
-
name = "{name.replace("-", "_")}"
|
|
808
|
-
version = "0.1.0"
|
|
809
|
-
edition = "2021"
|
|
810
|
-
rust-version = "{version if version != "stable" else "1.75"}"
|
|
811
|
-
|
|
812
|
-
[dependencies]
|
|
813
|
-
|
|
814
|
-
[dev-dependencies]
|
|
815
|
-
|
|
816
|
-
[lints.rust]
|
|
817
|
-
unsafe_code = "forbid"
|
|
818
|
-
|
|
819
|
-
[lints.clippy]
|
|
820
|
-
all = "warn"
|
|
821
|
-
pedantic = "warn"
|
|
822
|
-
'''
|
|
823
|
-
|
|
824
|
-
if dry_run:
|
|
825
|
-
if cargo_path.exists():
|
|
826
|
-
result["synced"].append("Cargo.toml")
|
|
827
|
-
else:
|
|
828
|
-
result["created"].append("Cargo.toml")
|
|
829
|
-
else:
|
|
830
|
-
existed = cargo_path.exists()
|
|
831
|
-
with open(cargo_path, "w") as f:
|
|
832
|
-
f.write(cargo_content)
|
|
833
|
-
if existed:
|
|
834
|
-
result["synced"].append("Cargo.toml")
|
|
835
|
-
else:
|
|
836
|
-
result["created"].append("Cargo.toml")
|
|
837
|
-
|
|
838
|
-
return result
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
def main() -> int:
|
|
842
|
-
"""CLI entry point."""
|
|
843
|
-
if len(sys.argv) < 2:
|
|
844
|
-
print(__doc__)
|
|
845
|
-
return 1
|
|
846
|
-
|
|
847
|
-
cmd = sys.argv[1]
|
|
848
|
-
config = load_config()
|
|
849
|
-
|
|
850
|
-
if cmd == "info":
|
|
851
|
-
cmd_info(config)
|
|
852
|
-
elif cmd == "get":
|
|
853
|
-
if len(sys.argv) < 3:
|
|
854
|
-
print("Usage: project_config.py get <path>", file=sys.stderr)
|
|
855
|
-
return 1
|
|
856
|
-
cmd_get(config, sys.argv[2])
|
|
857
|
-
elif cmd == "set":
|
|
858
|
-
if len(sys.argv) < 4:
|
|
859
|
-
print("Usage: project_config.py set <path> <value>", file=sys.stderr)
|
|
860
|
-
return 1
|
|
861
|
-
cmd_set(config, sys.argv[2], sys.argv[3])
|
|
862
|
-
elif cmd == "export":
|
|
863
|
-
lang_filter = sys.argv[2] if len(sys.argv) > 2 else None
|
|
864
|
-
cmd_export(config, lang_filter)
|
|
865
|
-
elif cmd == "init":
|
|
866
|
-
cmd_init(config)
|
|
867
|
-
elif cmd == "preset":
|
|
868
|
-
preset_name = sys.argv[2] if len(sys.argv) > 2 else None
|
|
869
|
-
cmd_preset(config, preset_name)
|
|
870
|
-
elif cmd == "lang-add":
|
|
871
|
-
if len(sys.argv) < 3:
|
|
872
|
-
print("Usage: project_config.py lang-add <language>", file=sys.stderr)
|
|
873
|
-
print("Languages: python, go, node, rust", file=sys.stderr)
|
|
874
|
-
return 1
|
|
875
|
-
cmd_lang_add(config, sys.argv[2])
|
|
876
|
-
elif cmd == "lang-remove":
|
|
877
|
-
if len(sys.argv) < 3:
|
|
878
|
-
print("Usage: project_config.py lang-remove <language>", file=sys.stderr)
|
|
879
|
-
return 1
|
|
880
|
-
cmd_lang_remove(config, sys.argv[2])
|
|
881
|
-
elif cmd == "lang-list":
|
|
882
|
-
cmd_lang_list(config)
|
|
883
|
-
elif cmd == "sync":
|
|
884
|
-
dry_run = "--dry-run" in sys.argv
|
|
885
|
-
cmd_sync(config, dry_run)
|
|
886
|
-
else:
|
|
887
|
-
print(f"Unknown command: {cmd}", file=sys.stderr)
|
|
888
|
-
return 1
|
|
889
|
-
|
|
890
|
-
return 0
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
if __name__ == "__main__":
|
|
894
|
-
sys.exit(main())
|