@@ -43,8 +43,8 @@ HAS_ANSIBLE := $(filter ansible,$(LANGUAGES))
4343# ---------------------------------------------------------------------------
4444# .PHONY declarations
4545# ---------------------------------------------------------------------------
46- .PHONY : help build lint format test security scan docs check install-hooks
47- .PHONY : _lint _format _test _security _scan _docs _check _check-config
46+ .PHONY : help build lint format test security scan docs changelog check install-hooks init
47+ .PHONY : _lint _format _test _security _scan _docs _changelog _check _check-config _init
4848
4949# ===========================================================================
5050# Public targets (run on host, delegate to Docker container)
@@ -59,6 +59,9 @@ help: ## Show this help
5959build : # # Build the container image locally
6060 docker build -t $(DEVRAIL_IMAGE ) :$(DEVRAIL_TAG ) .
6161
62+ changelog : # # Generate CHANGELOG.md from conventional commits
63+ $(DOCKER_RUN ) make _changelog
64+
6265check : # # Run all checks (lint, format, test, security, scan, docs)
6366 $(DOCKER_RUN ) make _check
6467
@@ -89,6 +92,9 @@ install-hooks: ## Install pre-commit hooks
8992 @pre-commit install --hook-type commit-msg
9093 @echo " Pre-commit hooks installed successfully. Hooks will run on every commit."
9194
95+ init : # # Scaffold config files for declared languages
96+ $(DOCKER_RUN ) make _init
97+
9298lint : # # Run all linters
9399 $(DOCKER_RUN ) make _lint
94100
@@ -493,6 +499,7 @@ _docs: _check-config
493499 fi ; \
494500 _tv trivy " trivy --version" ; \
495501 _tv gitleaks " gitleaks version" ; \
502+ _tv git-cliff " git-cliff --version" ; \
496503 printf ' }}\n' ; \
497504 } > .devrail-output/tool-versions.json; \
498505 generators=" $$ {generators}\" tool-versions\" ," ; \
@@ -505,6 +512,199 @@ _docs: _check-config
505512 fi ; \
506513 exit $$ overall_exit
507514
515+ # --- _changelog: generate CHANGELOG.md from conventional commits ---
516+ _changelog : _check-config
517+ @start_time=$$(date +%s%3N ) ; \
518+ config=" " ; \
519+ if [ -f " cliff.toml" ]; then \
520+ config=" cliff.toml" ; \
521+ elif [ -f " /opt/devrail/config/cliff.toml" ]; then \
522+ config=" /opt/devrail/config/cliff.toml" ; \
523+ fi ; \
524+ if [ -z " $$ config" ]; then \
525+ echo ' {"target":"changelog","status":"error","error":"no cliff.toml found","exit_code":2}' ; \
526+ exit 2; \
527+ fi ; \
528+ if ! git rev-parse --git-dir > /dev/null 2>&1 ; then \
529+ echo ' {"target":"changelog","status":"error","error":"not a git repository","exit_code":2}' ; \
530+ exit 2; \
531+ fi ; \
532+ git-cliff --config " $$ config" --output CHANGELOG.md; \
533+ cl_exit=$$? ; \
534+ end_time=$$(date +%s%3N ) ; \
535+ duration=$$((end_time - start_time ) ); \
536+ if [ $$ cl_exit -eq 0 ]; then \
537+ echo " {\" target\" :\" changelog\" ,\" status\" :\" pass\" ,\" duration_ms\" :$$ duration,\" config\" :\" $$ config\" ,\" output\" :\" CHANGELOG.md\" }" ; \
538+ else \
539+ echo " {\" target\" :\" changelog\" ,\" status\" :\" fail\" ,\" duration_ms\" :$$ duration,\" config\" :\" $$ config\" ,\" exit_code\" :$$ cl_exit}" ; \
540+ exit $$ cl_exit; \
541+ fi
542+
543+ # --- _init: scaffold config files for declared languages ---
544+ _init : _check-config
545+ @created=" " ; \
546+ skipped=" " ; \
547+ scaffold () { \
548+ _f=" $$ 1" ; shift ; \
549+ if [ ! -f " $$ _f" ]; then \
550+ printf ' %s\n' " $$ @" > " $$ _f" ; \
551+ created=" $$ {created}\" $$ _f\" ," ; \
552+ else \
553+ skipped=" $$ {skipped}\" $$ _f\" ," ; \
554+ fi ; \
555+ }; \
556+ scaffold .editorconfig \
557+ ' root = true' \
558+ ' ' \
559+ ' [*]' \
560+ ' charset = utf-8' \
561+ ' end_of_line = lf' \
562+ ' insert_final_newline = true' \
563+ ' trim_trailing_whitespace = true' \
564+ ' indent_style = space' \
565+ ' indent_size = 2' \
566+ ' ' \
567+ ' [Makefile]' \
568+ ' indent_style = tab' \
569+ ' ' \
570+ ' [*.py]' \
571+ ' indent_size = 4' \
572+ ' ' \
573+ ' [*.sh]' \
574+ ' indent_size = 2' ; \
575+ if [ -f " /opt/devrail/config/cliff.toml" ] && [ ! -f " cliff.toml" ]; then \
576+ cp /opt/devrail/config/cliff.toml cliff.toml; \
577+ created=" $$ {created}\" cliff.toml\" ," ; \
578+ elif [ -f " cliff.toml" ]; then \
579+ skipped=" $$ {skipped}\" cliff.toml\" ," ; \
580+ fi ; \
581+ if [ -n " $( HAS_PYTHON) " ]; then \
582+ scaffold ruff.toml \
583+ ' line-length = 120' \
584+ ' target-version = "py311"' \
585+ ' ' \
586+ ' [lint]' \
587+ ' select = ["E", "W", "F", "I", "UP", "B", "S", "C4", "SIM"]' \
588+ ' ' \
589+ ' [format]' \
590+ ' quote-style = "double"' \
591+ ' indent-style = "space"' ; \
592+ fi ; \
593+ if [ -n " $( HAS_BASH) " ]; then \
594+ scaffold .shellcheckrc \
595+ ' shell=bash' \
596+ ' enable=all' ; \
597+ fi ; \
598+ if [ -n " $( HAS_TERRAFORM) " ]; then \
599+ scaffold .tflint.hcl \
600+ ' config {' \
601+ ' call_module_type = "local"' \
602+ ' }' \
603+ ' ' \
604+ ' plugin "terraform" {' \
605+ ' enabled = true' \
606+ ' preset = "recommended"' \
607+ ' }' ; \
608+ fi ; \
609+ if [ -n " $( HAS_ANSIBLE) " ]; then \
610+ scaffold .ansible-lint \
611+ ' profile: production' \
612+ ' ' \
613+ ' exclude_paths:' \
614+ ' - .cache/' \
615+ ' - .github/' \
616+ ' - .gitlab/' \
617+ ' ' \
618+ ' skip_list:' \
619+ ' - yaml[truthy]' \
620+ ' ' \
621+ ' warn_list:' \
622+ ' - experimental' ; \
623+ fi ; \
624+ if [ -n " $( HAS_RUBY) " ]; then \
625+ scaffold .rubocop.yml \
626+ ' AllCops:' \
627+ ' TargetRubyVersion: 3.1' \
628+ ' NewCops: enable' \
629+ ' Exclude:' \
630+ ' - "db/schema.rb"' \
631+ ' - "bin/**/*"' \
632+ ' - "vendor/**/*"' \
633+ ' - "node_modules/**/*"' \
634+ ' ' \
635+ ' Style/Documentation:' \
636+ ' Enabled: false' \
637+ ' ' \
638+ ' Metrics/BlockLength:' \
639+ ' Exclude:' \
640+ ' - "spec/**/*"' \
641+ ' ' \
642+ ' Layout/LineLength:' \
643+ ' Max: 120' ; \
644+ scaffold .reek.yml \
645+ ' exclude_paths:' \
646+ ' - vendor' \
647+ ' - db/schema.rb' \
648+ ' - bin' \
649+ ' ' \
650+ ' detectors:' \
651+ ' IrresponsibleModule:' \
652+ ' enabled: false' ; \
653+ scaffold .rspec \
654+ ' --require spec_helper' \
655+ ' --format documentation' \
656+ ' --color' ; \
657+ fi ; \
658+ if [ -n " $( HAS_GO) " ]; then \
659+ scaffold .golangci.yml \
660+ ' version: "2"' \
661+ ' ' \
662+ ' linters:' \
663+ ' enable:' \
664+ ' - errcheck' \
665+ ' - govet' \
666+ ' - staticcheck' \
667+ ' - gosec' \
668+ ' - ineffassign' \
669+ ' - unused' \
670+ ' - gocritic' \
671+ ' - gofumpt' \
672+ ' - misspell' \
673+ ' - revive' \
674+ ' ' \
675+ ' issues:' \
676+ ' exclude-dirs:' \
677+ ' - vendor' \
678+ ' - node_modules' ; \
679+ fi ; \
680+ if [ -n " $( HAS_JAVASCRIPT) " ]; then \
681+ scaffold eslint.config.js \
682+ ' import eslint from "@eslint/js";' \
683+ ' import tseslint from "typescript-eslint";' \
684+ ' ' \
685+ ' export default tseslint.config(' \
686+ ' eslint.configs.recommended,' \
687+ ' tseslint.configs.recommended,' \
688+ ' {' \
689+ ' ignores: ["node_modules/", "dist/", "build/", "coverage/"],' \
690+ ' }' \
691+ ' );' ; \
692+ scaffold .prettierrc \
693+ ' {' \
694+ ' "semi": true,' \
695+ ' "singleQuote": false,' \
696+ ' "trailingComma": "es5",' \
697+ ' "printWidth": 80,' \
698+ ' "tabWidth": 2' \
699+ ' }' ; \
700+ scaffold .prettierignore \
701+ ' node_modules/' \
702+ ' dist/' \
703+ ' build/' \
704+ ' coverage/' ; \
705+ fi ; \
706+ echo " {\" target\" :\" init\" ,\" created\" :[$$ {created%,}],\" skipped\" :[$$ {skipped%,}]}"
707+
508708# --- _check: orchestrate all targets ---
509709_check : _check-config
510710 @overall_exit=0; \
0 commit comments