@zairakai/js-dev-tools 1.0.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.
Files changed (69) hide show
  1. package/.editorconfig +86 -0
  2. package/.gitlab/ci/pipeline-js-app.yml +75 -0
  3. package/.gitlab/ci/pipeline-js-package.yml +360 -0
  4. package/LICENSE +21 -0
  5. package/README.md +206 -0
  6. package/config/.markdownlint.json +7 -0
  7. package/config/.markdownlintignore +5 -0
  8. package/config/.prettierignore +11 -0
  9. package/config/.stylelintignore +7 -0
  10. package/config/eslint.config.js +198 -0
  11. package/config/knip.config.js +40 -0
  12. package/config/prettier.config.js +121 -0
  13. package/config/stylelint.config.js +56 -0
  14. package/config/tsconfig.base.json +20 -0
  15. package/config/vitest.config.js +25 -0
  16. package/index.js +24 -0
  17. package/package.json +148 -0
  18. package/scripts/build.sh +58 -0
  19. package/scripts/ci-quality.sh +51 -0
  20. package/scripts/config.sh +254 -0
  21. package/scripts/doctor.sh +74 -0
  22. package/scripts/eslint-fix.sh +38 -0
  23. package/scripts/eslint.sh +49 -0
  24. package/scripts/install-bats.sh +227 -0
  25. package/scripts/install-hooks.sh +167 -0
  26. package/scripts/install-markdownlint.sh +31 -0
  27. package/scripts/install-shellcheck.sh +232 -0
  28. package/scripts/knip.sh +40 -0
  29. package/scripts/markdownlint-fix.sh +8 -0
  30. package/scripts/markdownlint.sh +84 -0
  31. package/scripts/outdated.sh +70 -0
  32. package/scripts/prettier-fix.sh +52 -0
  33. package/scripts/prettier.sh +53 -0
  34. package/scripts/setup-project.sh +816 -0
  35. package/scripts/stylelint-fix.sh +64 -0
  36. package/scripts/stylelint.sh +66 -0
  37. package/scripts/test.sh +40 -0
  38. package/scripts/typecheck.sh +41 -0
  39. package/scripts/validate-shellcheck.sh +70 -0
  40. package/stubs/githooks/commit-msg +34 -0
  41. package/stubs/githooks/pre-commit +58 -0
  42. package/stubs/githooks/pre-push +57 -0
  43. package/stubs/githooks/prepare-commit-msg +39 -0
  44. package/stubs/gitlab-ci/gitlab-ci.yml.stub +18 -0
  45. package/stubs/gitlab-ci/gitlab-pipeline-js-app.yml.stub +16 -0
  46. package/stubs/governance/CODE_OF_CONDUCT.md.stub +8 -0
  47. package/stubs/governance/CONTRIBUTING.md.stub +39 -0
  48. package/stubs/governance/SECURITY.md.stub +41 -0
  49. package/stubs/quality/eslint.config.js.stub +18 -0
  50. package/stubs/quality/knip.config.js.stub +14 -0
  51. package/stubs/style/prettier.config.js.stub +16 -0
  52. package/stubs/style/stylelint.config.js.stub +17 -0
  53. package/stubs/testing/vitest.config.js.stub +18 -0
  54. package/stubs/typescript/tsconfig.json.stub +10 -0
  55. package/tools/make/bats.mk +49 -0
  56. package/tools/make/code-style.mk +20 -0
  57. package/tools/make/core.mk +56 -0
  58. package/tools/make/help.mk +32 -0
  59. package/tools/make/knip.mk +7 -0
  60. package/tools/make/markdownlint.mk +17 -0
  61. package/tools/make/quality.mk +19 -0
  62. package/tools/make/shellcheck.mk +14 -0
  63. package/tools/make/stylelint.mk +11 -0
  64. package/tools/make/test.mk +19 -0
  65. package/tools/make/typescript.mk +10 -0
  66. package/tools/make/utils.mk +44 -0
  67. package/tools/make/variables.mk +42 -0
  68. package/tools/scripts/git-cleanup.sh +53 -0
  69. package/tools/scripts/git-update.sh +37 -0
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ # shellcheck disable=SC1091
6
+ source "$SCRIPT_DIR/config.sh"
7
+
8
+ log_header "ESLint Check"
9
+
10
+ ensure_bin "$ESLINT_BIN" "eslint" || exit 1
11
+
12
+ if [[ -n "$ESLINT_CONFIG" ]]; then
13
+ log_step "Using configuration: ${ESLINT_CONFIG#"$PROJECT_ROOT"/}"
14
+ else
15
+ log_warning "No ESLint configuration found — using ESLint defaults"
16
+ fi
17
+
18
+ # Lint target depends on project type (overridable via env)
19
+ if [[ "$PROJECT_TYPE" == "laravel-app" ]]; then
20
+ LINT_TARGET="${LINT_TARGET:-resources/js/}"
21
+ else
22
+ # Default to src/, but fallback to . if src/ doesn't exist
23
+ if [[ -z "${LINT_TARGET:-}" ]]; then
24
+ if [[ -d "${PROJECT_ROOT}/src" ]]; then
25
+ LINT_TARGET="src/"
26
+ else
27
+ LINT_TARGET="."
28
+ fi
29
+ fi
30
+ fi
31
+
32
+ log_step "Checking: $LINT_TARGET"
33
+
34
+ # Skip gracefully if no matching files exist
35
+ if ! has_files "${PROJECT_ROOT}/${LINT_TARGET}**/*.{js,ts,vue,jsx,tsx}"; then
36
+ log_info "No JS/TS files found in ${LINT_TARGET} — skipping"
37
+ exit 0
38
+ fi
39
+
40
+ ESLINT_ARGS=()
41
+
42
+ # Only pass --config when the config is not at project root (ESLint 9 auto-discovers root)
43
+ if [[ -n "$ESLINT_CONFIG" ]] && [[ "$ESLINT_CONFIG" != "${PROJECT_ROOT}/eslint.config.js" ]]; then
44
+ ESLINT_ARGS+=(--config "$ESLINT_CONFIG")
45
+ fi
46
+
47
+ "$ESLINT_BIN" "${ESLINT_ARGS[@]}" "$LINT_TARGET"
48
+
49
+ log_success "ESLint check passed"
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Zairakai NPM Dev Tools - BATS Installer
4
+ # Installs BATS (Bash Automated Testing System) for shell script testing
5
+ #
6
+ # Platform: Linux/macOS/WSL
7
+ #
8
+ # Usage:
9
+ # bash scripts/install-bats.sh
10
+ #
11
+
12
+ set -euo pipefail
13
+
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ # shellcheck disable=SC1091
16
+ source "${SCRIPT_DIR}/config.sh"
17
+
18
+ # ============================================================================
19
+ # Configuration
20
+ # ============================================================================
21
+
22
+ CI_MODE="${CI:-false}"
23
+ TOOL_VERSIONS_FILE="${DEV_TOOLS_ROOT}/.tool-versions"
24
+ INSTALL_DIR="${HOME}/.local/share/bats"
25
+ BIN_DIR="${HOME}/.local/bin"
26
+
27
+ # ============================================================================
28
+ # Helpers
29
+ # ============================================================================
30
+
31
+ get_bats_version() {
32
+ local version=""
33
+
34
+ if [[ -f "${TOOL_VERSIONS_FILE}" ]]; then
35
+ version="$(grep "^bats=" "${TOOL_VERSIONS_FILE}" | awk -F "=" '{print $2}' || echo "")"
36
+ if [[ -n "${version}" ]]; then
37
+ log_info "Found .tool-versions: ${version}"
38
+ fi
39
+ fi
40
+
41
+ if [[ -z "${version}" ]]; then
42
+ version="1.11.0"
43
+ log_info "Using default version: ${version}"
44
+ fi
45
+
46
+ if [[ ! "${version}" =~ ^v ]]; then
47
+ version="v${version}"
48
+ fi
49
+
50
+ echo "${version}"
51
+ }
52
+
53
+ version_ge() {
54
+ local ver1="${1#v}"
55
+ local ver2="${2#v}"
56
+
57
+ if [[ "${ver1}" == "${ver2}" ]]; then
58
+ return 0
59
+ fi
60
+
61
+ local IFS=.
62
+ # shellcheck disable=SC2206
63
+ local ver1_arr=($ver1) ver2_arr=($ver2)
64
+
65
+ local i
66
+ for ((i=${#ver1_arr[@]}; i<${#ver2_arr[@]}; i++)); do
67
+ ver1_arr[i]=0
68
+ done
69
+
70
+ for ((i=0; i<${#ver1_arr[@]}; i++)); do
71
+ if [[ -z "${ver2_arr[i]:-}" ]]; then
72
+ ver2_arr[i]=0
73
+ fi
74
+
75
+ if ((10#${ver1_arr[i]} > 10#${ver2_arr[i]})); then
76
+ return 0
77
+ fi
78
+
79
+ if ((10#${ver1_arr[i]} < 10#${ver2_arr[i]})); then
80
+ return 1
81
+ fi
82
+ done
83
+
84
+ return 0
85
+ }
86
+
87
+ check_bats_installed() {
88
+ local required_version="$1"
89
+
90
+ if ! command_exists bats; then
91
+ return 1
92
+ fi
93
+
94
+ local current_version
95
+ current_version="$(bats --version 2>/dev/null | awk '{print $2}' || echo "")"
96
+
97
+ if [[ -z "${current_version}" ]]; then
98
+ log_warning "Could not determine BATS version"
99
+ return 1
100
+ fi
101
+
102
+ log_info "Current version: ${current_version}"
103
+
104
+ if version_ge "${current_version}" "${required_version}"; then
105
+ log_success "BATS ${current_version} is sufficient (need >=${required_version})"
106
+ return 0
107
+ fi
108
+
109
+ log_warning "Version ${current_version} is outdated (need >=${required_version})"
110
+
111
+ if [[ "${CI_MODE}" != "true" ]]; then
112
+ echo ""
113
+ read -rp "Upgrade to ${required_version}? (y/N) " -n 1
114
+ echo ""
115
+
116
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
117
+ log_info "Keeping existing installation"
118
+ return 0
119
+ fi
120
+ else
121
+ log_info "CI mode: upgrading automatically"
122
+ fi
123
+
124
+ return 1
125
+ }
126
+
127
+ install_bats_core() {
128
+ local version="$1"
129
+
130
+ log_step "Installing BATS ${version} from source…"
131
+ mkdir -p "${INSTALL_DIR}" "${BIN_DIR}"
132
+
133
+ if [[ -d "${INSTALL_DIR}/bats-core" ]]; then
134
+ log_info "BATS repository exists, updating…"
135
+ git -C "${INSTALL_DIR}/bats-core" fetch --tags --quiet
136
+ git -C "${INSTALL_DIR}/bats-core" checkout "${version}" --quiet
137
+ else
138
+ log_info "Cloning BATS repository…"
139
+ git clone --quiet --depth 1 --branch "${version}" \
140
+ https://github.com/bats-core/bats-core.git "${INSTALL_DIR}/bats-core"
141
+ fi
142
+
143
+ "${INSTALL_DIR}/bats-core/install.sh" "${HOME}/.local"
144
+ log_success "BATS core installed to: ${BIN_DIR}/bats"
145
+
146
+ if [[ ":$PATH:" != *":${BIN_DIR}:"* ]]; then
147
+ echo ""
148
+ log_warning "${BIN_DIR} is not in your PATH"
149
+ echo ""
150
+ echo "Add this to your ~/.bashrc or ~/.zshrc:"
151
+ echo ""
152
+ echo -e " ${CYAN}export PATH=\"\${HOME}/.local/bin:\${PATH}\"${NC}"
153
+ echo ""
154
+ fi
155
+ }
156
+
157
+ install_bats_libraries() {
158
+ log_step "Installing BATS support libraries…"
159
+
160
+ local libraries=(
161
+ "bats-support:https://github.com/bats-core/bats-support.git"
162
+ "bats-assert:https://github.com/bats-core/bats-assert.git"
163
+ "bats-file:https://github.com/bats-core/bats-file.git"
164
+ )
165
+
166
+ local lib_info lib_name lib_url lib_path
167
+ for lib_info in "${libraries[@]}"; do
168
+ lib_name="${lib_info%%:*}"
169
+ lib_url="${lib_info#*:}"
170
+ lib_path="${INSTALL_DIR}/${lib_name}"
171
+
172
+ if [[ -d "${lib_path}" ]]; then
173
+ log_info "${lib_name} already installed"
174
+ else
175
+ log_info "Installing ${lib_name}…"
176
+ git clone --quiet --depth 1 "${lib_url}" "${lib_path}"
177
+ log_success "${lib_name} installed"
178
+ fi
179
+ done
180
+ }
181
+
182
+ # ============================================================================
183
+ # Main
184
+ # ============================================================================
185
+
186
+ main() {
187
+ local required_version
188
+ required_version="$(get_bats_version)"
189
+
190
+ echo ""
191
+ log_header "BATS Installer"
192
+ echo ""
193
+
194
+ if check_bats_installed "${required_version}"; then
195
+ echo ""
196
+ log_info "No action needed — BATS is already available"
197
+ echo ""
198
+ exit 0
199
+ fi
200
+
201
+ echo ""
202
+ log_warning "BATS not found or outdated — installing…"
203
+ echo ""
204
+
205
+ install_bats_core "${required_version}"
206
+ install_bats_libraries
207
+
208
+ echo ""
209
+ log_success "BATS installation complete"
210
+ echo ""
211
+
212
+ if command_exists bats; then
213
+ bats --version
214
+ echo ""
215
+ log_info "Libraries installed in: ${INSTALL_DIR}"
216
+ echo ""
217
+ log_info "Run tests with:"
218
+ echo -e " ${CYAN}make bats${NC}"
219
+ echo -e " ${CYAN}bats tests/bats/ --recursive${NC}"
220
+ echo ""
221
+ else
222
+ log_error "BATS installation verification failed"
223
+ exit 1
224
+ fi
225
+ }
226
+
227
+ main "$@"
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Zairakai NPM Dev Tools - Git Hooks Installer
4
+ # Installs quality check hooks into .git/hooks
5
+ #
6
+ # Usage:
7
+ # bash scripts/install-hooks.sh # Install hooks
8
+ # bash scripts/install-hooks.sh --remove # Remove hooks
9
+ #
10
+
11
+ set -euo pipefail
12
+
13
+ # Load central configuration
14
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
15
+ # shellcheck disable=SC1091
16
+ source "${SCRIPT_DIR}/config.sh"
17
+
18
+ # ============================================================================
19
+ # Configuration
20
+ # ============================================================================
21
+
22
+ HOOKS_SOURCE_DIR="${DEV_TOOLS_ROOT}/stubs/githooks"
23
+ HOOKS_TARGET_DIR="${PROJECT_ROOT}/.git/hooks"
24
+
25
+ # Available hooks (auto-discovered from stubs/githooks directory)
26
+ HOOKS=(
27
+ "commit-msg"
28
+ "prepare-commit-msg"
29
+ "pre-commit"
30
+ "pre-push"
31
+ )
32
+
33
+ # ============================================================================
34
+ # Functions
35
+ # ============================================================================
36
+
37
+ install_hooks() {
38
+ log_header "📦 Installing Git Hooks"
39
+
40
+ if [[ ! -d ".git" ]]; then
41
+ log_error "Not a git repository"
42
+ log_info "Git hooks can only be installed in a git repository"
43
+ exit 1
44
+ fi
45
+
46
+ if [[ ! -d "$HOOKS_SOURCE_DIR" ]]; then
47
+ log_error "Hooks source directory not found: $HOOKS_SOURCE_DIR"
48
+ exit 1
49
+ fi
50
+
51
+ mkdir -p "$HOOKS_TARGET_DIR"
52
+
53
+ local installed=0
54
+
55
+ for hook in "${HOOKS[@]}"; do
56
+ local source="${HOOKS_SOURCE_DIR}/${hook}"
57
+ local target="${HOOKS_TARGET_DIR}/${hook}"
58
+
59
+ if [[ ! -f "$source" ]]; then
60
+ log_warning "Hook not found: ${hook} (skipping)"
61
+ continue
62
+ fi
63
+
64
+ echo -e " ${BLUE}•${NC} ${hook}"
65
+
66
+ # Backup existing hook if present
67
+ if [[ -f "$target" ]] && [[ ! -L "$target" ]]; then
68
+ local backup
69
+ backup="${target}.backup-$(date +%Y%m%d-%H%M%S)"
70
+ mv "$target" "$backup"
71
+ log_warning " Existing hook backed up to: ${backup}"
72
+ fi
73
+
74
+ # Remove existing symlink
75
+ rm -f "$target"
76
+
77
+ # Install hook
78
+ ln -s "$source" "$target"
79
+ log_success " ${hook} installed"
80
+
81
+ installed=$((installed + 1))
82
+ done
83
+
84
+ echo ""
85
+
86
+ if [[ $installed -eq 0 ]]; then
87
+ log_warning "No hooks were installed"
88
+ exit 1
89
+ fi
90
+
91
+ log_header "✅ ${installed} Hook(s) Installed Successfully"
92
+
93
+ echo ""
94
+ log_info "Installed hooks:"
95
+ for hook in "${HOOKS[@]}"; do
96
+ case "$hook" in
97
+ commit-msg)
98
+ echo -e " ${GREEN}✓${NC} ${hook} ${YELLOW}→${NC} Validate Conventional Commits format"
99
+ ;;
100
+ prepare-commit-msg)
101
+ echo -e " ${GREEN}✓${NC} ${hook} ${YELLOW}→${NC} Auto-prefix ticket number from branch"
102
+ ;;
103
+ pre-commit)
104
+ echo -e " ${GREEN}✓${NC} ${hook} ${YELLOW}→${NC} Quality checks (ESLint, Prettier, Markdown)"
105
+ ;;
106
+ pre-push)
107
+ echo -e " ${GREEN}✓${NC} ${hook} ${YELLOW}→${NC} Full quality gate before push"
108
+ ;;
109
+ *)
110
+ echo -e " ${GREEN}✓${NC} ${hook}"
111
+ ;;
112
+ esac
113
+ done
114
+
115
+ echo ""
116
+ log_info "To bypass hooks (emergency only):"
117
+ echo -e " ${CYAN}git commit --no-verify${NC}"
118
+ echo ""
119
+ }
120
+
121
+ remove_hooks() {
122
+ log_header "🗑️ Removing Git Hooks"
123
+
124
+ if [[ ! -d ".git" ]]; then
125
+ log_error "Not a git repository"
126
+ exit 1
127
+ fi
128
+
129
+ local removed=0
130
+
131
+ for hook in "${HOOKS[@]}"; do
132
+ local target="${HOOKS_TARGET_DIR}/${hook}"
133
+
134
+ if [[ -L "$target" ]]; then
135
+ # Only remove if it's a symlink (managed by us)
136
+ echo -e " ${BLUE}•${NC} ${hook}"
137
+ rm -f "$target"
138
+ log_success " ${hook} removed"
139
+ removed=$((removed + 1))
140
+ elif [[ -f "$target" ]]; then
141
+ log_warning " ${hook} exists but is not a symlink (skipping)"
142
+ fi
143
+ done
144
+
145
+ echo ""
146
+
147
+ if [[ $removed -eq 0 ]]; then
148
+ log_info "No hooks were removed"
149
+ exit 0
150
+ fi
151
+
152
+ log_header "✅ ${removed} Hook(s) Removed"
153
+ echo ""
154
+ }
155
+
156
+ # ============================================================================
157
+ # Main
158
+ # ============================================================================
159
+
160
+ case "${1:-install}" in
161
+ --remove|-r)
162
+ remove_hooks
163
+ ;;
164
+ --install|-i|install|*)
165
+ install_hooks
166
+ ;;
167
+ esac
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Zairakai NPM Dev Tools - Markdownlint Installer
4
+ # Installs markdownlint-cli2 as a local npm dev dependency.
5
+ #
6
+ # Usage:
7
+ # bash scripts/install-markdownlint.sh
8
+ #
9
+
10
+ set -euo pipefail
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
+ # shellcheck source=config.sh
14
+ source "${SCRIPT_DIR}/config.sh"
15
+
16
+ log_header "Markdownlint Installer"
17
+
18
+ if [[ -f "$MARKDOWNLINT_BIN" ]]; then
19
+ version=$("$MARKDOWNLINT_BIN" --version 2>/dev/null || echo "unknown")
20
+ log_success "markdownlint-cli2 already installed: ${version}"
21
+ log_info "Run: make markdownlint"
22
+ exit 0
23
+ fi
24
+
25
+ log_info "markdownlint-cli2 not found in node_modules/.bin/"
26
+ log_step "Installing markdownlint-cli2 as a dev dependency…"
27
+
28
+ "$PM" install --save-dev markdownlint-cli2
29
+
30
+ log_success "markdownlint-cli2 installed"
31
+ log_info "Run: make markdownlint"
@@ -0,0 +1,232 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Zairakai NPM Dev Tools - ShellCheck Installer
4
+ # Installs ShellCheck for shell script static analysis
5
+ #
6
+ # Platform: Linux/WSL only
7
+ #
8
+ # Features:
9
+ # - Reads version from .tool-versions
10
+ # - Supports apt/dnf/pacman/apk
11
+ # - CI-aware
12
+ #
13
+ # Usage:
14
+ # bash scripts/install-shellcheck.sh
15
+ #
16
+
17
+ set -euo pipefail
18
+
19
+ # Load central configuration
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+ # shellcheck source=config.sh
22
+ source "${SCRIPT_DIR}/config.sh"
23
+
24
+ # ============
25
+ # Configuration
26
+ # ============
27
+
28
+ CI_MODE="${CI:-false}"
29
+ TOOL_VERSIONS_FILE="${SCRIPT_DIR}/../.tool-versions"
30
+
31
+ # ============
32
+ # Functions
33
+ # ============
34
+
35
+ # Get ShellCheck version from .tool-versions or use default
36
+ get_shellcheck_version() {
37
+ local version=""
38
+
39
+ if [[ -f "${TOOL_VERSIONS_FILE}" ]]; then
40
+ version=$(grep "^shellcheck=" "${TOOL_VERSIONS_FILE}" | awk -F "=" '{print $2}' || echo "")
41
+
42
+ if [[ -n "${version}" ]]; then
43
+ log_info "Found .tool-versions: ${version}"
44
+ fi
45
+ fi
46
+
47
+ # Fallback to default if not found
48
+ if [[ -z "${version}" ]]; then
49
+ version="0.10.0"
50
+ log_info "Using default version: ${version}"
51
+ fi
52
+
53
+ echo "${version}"
54
+ }
55
+
56
+ # Compare versions (returns 0 if ver1 >= ver2)
57
+ version_ge() {
58
+ local ver1="$1"
59
+ local ver2="$2"
60
+
61
+ if [[ "${ver1}" == "${ver2}" ]]; then
62
+ return 0
63
+ fi
64
+
65
+ local IFS=.
66
+ local i ver1_arr=("$ver1") ver2_arr=("$ver2")
67
+
68
+ # Fill empty positions with zeros
69
+ for ((i=${#ver1_arr[@]}; i<${#ver2_arr[@]}; i++)); do
70
+ ver1_arr[i]=0
71
+ done
72
+
73
+ for ((i=0; i<${#ver1_arr[@]}; i++)); do
74
+ if [[ -z ${ver2_arr[i]} ]]; then
75
+ ver2_arr[i]=0
76
+ fi
77
+
78
+ if ((10#${ver1_arr[i]} > 10#${ver2_arr[i]})); then
79
+ return 0
80
+ fi
81
+
82
+ if ((10#${ver1_arr[i]} < 10#${ver2_arr[i]})); then
83
+ return 1
84
+ fi
85
+ done
86
+
87
+ return 0
88
+ }
89
+
90
+ # Check if ShellCheck is already installed with sufficient version
91
+ check_shellcheck_installed() {
92
+ local required_version="$1"
93
+
94
+ if ! command_exists shellcheck; then
95
+ return 1
96
+ fi
97
+
98
+ local current_version
99
+ current_version=$(shellcheck --version | grep "version:" | awk '{print $2}' || echo "")
100
+
101
+ if [[ -z "${current_version}" ]]; then
102
+ log_warning "Could not determine ShellCheck version"
103
+ return 1
104
+ fi
105
+
106
+ log_info "Current version: ${current_version}"
107
+
108
+ if version_ge "${current_version}" "${required_version}"; then
109
+ log_success "ShellCheck ${current_version} is sufficient (need >=${required_version})"
110
+ return 0
111
+ fi
112
+
113
+ log_warning "Version ${current_version} is outdated (need >=${required_version})"
114
+
115
+ # Ask for upgrade confirmation if not in CI
116
+ if [[ "${CI_MODE}" != "true" ]]; then
117
+ echo ""
118
+ read -rp "Upgrade to ${required_version}? (y/N) " -n 1
119
+ echo ""
120
+
121
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
122
+ log_info "Keeping existing installation"
123
+ return 0
124
+ fi
125
+ else
126
+ log_info "CI mode: upgrading automatically"
127
+ fi
128
+
129
+ return 1
130
+ }
131
+
132
+ # Detect package manager and install ShellCheck
133
+ install_shellcheck() {
134
+ local version="$1"
135
+
136
+ log_header "🐚 Installing ShellCheck ${version}"
137
+ echo ""
138
+
139
+ local use_sudo=true
140
+
141
+ # Don't use sudo in CI or if running as root
142
+ if [[ "${CI_MODE}" == "true" ]] || [[ "$(id -u)" -eq 0 ]]; then
143
+ use_sudo=false
144
+ fi
145
+
146
+ # Detect and use package manager
147
+ if command_exists apt-get; then
148
+ log_step "Installing via apt-get..."
149
+
150
+ if [[ "${use_sudo}" == "true" ]]; then
151
+ sudo apt-get update -qq
152
+ sudo apt-get install -y -qq shellcheck
153
+ else
154
+ apt-get update -qq
155
+ apt-get install -y -qq shellcheck
156
+ fi
157
+
158
+ elif command_exists dnf; then
159
+ log_step "Installing via dnf..."
160
+
161
+ if [[ "${use_sudo}" == "true" ]]; then
162
+ sudo dnf install -y -q ShellCheck
163
+ else
164
+ dnf install -y -q ShellCheck
165
+ fi
166
+
167
+ elif command_exists pacman; then
168
+ log_step "Installing via pacman..."
169
+
170
+ if [[ "${use_sudo}" == "true" ]]; then
171
+ sudo pacman -S --noconfirm --quiet shellcheck
172
+ else
173
+ pacman -S --noconfirm --quiet shellcheck
174
+ fi
175
+
176
+ elif command_exists apk; then
177
+ log_step "Installing via apk..."
178
+ apk add --no-cache -q shellcheck
179
+
180
+ else
181
+ log_error "No supported package manager found (apt-get, dnf, pacman, apk)"
182
+ echo ""
183
+ log_info "Install manually from: https://github.com/koalaman/shellcheck"
184
+ return 1
185
+ fi
186
+
187
+ log_success "ShellCheck installed successfully"
188
+
189
+ echo ""
190
+ log_header "✅ ShellCheck Installation Complete"
191
+ echo ""
192
+
193
+ # Verify installation
194
+ if command_exists shellcheck; then
195
+ shellcheck --version
196
+ echo ""
197
+ log_info "Run linter with:"
198
+ echo -e " ${CYAN}make shellcheck${NC}"
199
+ echo -e " ${CYAN}shellcheck scripts/*.sh${NC}"
200
+ echo ""
201
+ else
202
+ log_error "ShellCheck installation verification failed"
203
+ return 1
204
+ fi
205
+ }
206
+
207
+ # ============
208
+ # Main
209
+ # ============
210
+
211
+ main() {
212
+ local required_version
213
+ required_version=$(get_shellcheck_version)
214
+
215
+ echo ""
216
+
217
+ # Check if already installed with sufficient version
218
+ if check_shellcheck_installed "${required_version}"; then
219
+ echo ""
220
+ log_info "No action needed - ShellCheck is already available"
221
+ echo ""
222
+ exit 0
223
+ fi
224
+
225
+ echo ""
226
+ log_warning "ShellCheck not found or outdated - installing..."
227
+ echo ""
228
+
229
+ install_shellcheck "${required_version}"
230
+ }
231
+
232
+ main "$@"