@sun-asterisk/sunlint 1.0.5
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 +202 -0
- package/LICENSE +21 -0
- package/README.md +490 -0
- package/cli-legacy.js +355 -0
- package/cli.js +35 -0
- package/config/default.json +22 -0
- package/config/presets/beginner.json +36 -0
- package/config/presets/ci.json +46 -0
- package/config/presets/recommended.json +24 -0
- package/config/presets/strict.json +32 -0
- package/config/rules-registry.json +681 -0
- package/config/sunlint-schema.json +166 -0
- package/config/typescript/custom-rules-new.js +0 -0
- package/config/typescript/custom-rules.js +9 -0
- package/config/typescript/eslint.config.js +110 -0
- package/config/typescript/package-lock.json +1585 -0
- package/config/typescript/package.json +13 -0
- package/config/typescript/security-rules/index.js +90 -0
- package/config/typescript/security-rules/s005-no-origin-auth.js +95 -0
- package/config/typescript/security-rules/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/config/typescript/security-rules/s008-crypto-agility.js +62 -0
- package/config/typescript/security-rules/s009-no-insecure-crypto.js +103 -0
- package/config/typescript/security-rules/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/config/typescript/security-rules/s011-no-insecure-uuid.js +66 -0
- package/config/typescript/security-rules/s012-hardcode-secret.js +71 -0
- package/config/typescript/security-rules/s014-insecure-tls-version.js +50 -0
- package/config/typescript/security-rules/s015-insecure-tls-certificate.js +43 -0
- package/config/typescript/security-rules/s016-sensitive-query-parameter.js +59 -0
- package/config/typescript/security-rules/s017-no-sql-injection.js +193 -0
- package/config/typescript/security-rules/s018-positive-input-validation.js +56 -0
- package/config/typescript/security-rules/s019-no-raw-user-input-in-email.js +113 -0
- package/config/typescript/security-rules/s020-no-eval-dynamic-execution.js +89 -0
- package/config/typescript/security-rules/s022-output-encoding.js +78 -0
- package/config/typescript/security-rules/s023-no-json-injection.js +300 -0
- package/config/typescript/security-rules/s025-server-side-input-validation.js +217 -0
- package/config/typescript/security-rules/s026-json-schema-validation.js +68 -0
- package/config/typescript/security-rules/s027-no-hardcoded-secrets.js +80 -0
- package/config/typescript/security-rules/s029-require-csrf-protection.js +79 -0
- package/config/typescript/security-rules/s030-no-directory-browsing.js +78 -0
- package/config/typescript/security-rules/s033-require-samesite-cookie.js +80 -0
- package/config/typescript/security-rules/s034-require-host-cookie-prefix.js +77 -0
- package/config/typescript/security-rules/s035-cookie-specific-path.js +74 -0
- package/config/typescript/security-rules/s036-no-unsafe-file-include.js +68 -0
- package/config/typescript/security-rules/s037-require-anti-cache-headers.js +70 -0
- package/config/typescript/security-rules/s038-no-version-disclosure.js +74 -0
- package/config/typescript/security-rules/s039-no-session-token-in-url.js +63 -0
- package/config/typescript/security-rules/s041-require-session-invalidate-on-logout.js +211 -0
- package/config/typescript/security-rules/s042-require-periodic-reauthentication.js +294 -0
- package/config/typescript/security-rules/s043-terminate-sessions-on-password-change.js +254 -0
- package/config/typescript/security-rules/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/config/typescript/security-rules/s045-anti-automation-controls.js +46 -0
- package/config/typescript/security-rules/s046-secure-notification-on-auth-change.js +44 -0
- package/config/typescript/security-rules/s048-password-credential-recovery.js +54 -0
- package/config/typescript/security-rules/s050-session-token-weak-hash.js +94 -0
- package/config/typescript/security-rules/s052-secure-random-authentication-code.js +66 -0
- package/config/typescript/security-rules/s054-verification-default-account.js +109 -0
- package/config/typescript/security-rules/s057-utc-logging.js +54 -0
- package/config/typescript/security-rules/s058-no-ssrf.js +73 -0
- package/config/typescript/test-s005-working.ts +22 -0
- package/config/typescript/tsconfig.json +29 -0
- package/core/ai-analyzer.js +169 -0
- package/core/analysis-orchestrator.js +705 -0
- package/core/cli-action-handler.js +230 -0
- package/core/cli-program.js +106 -0
- package/core/config-manager.js +396 -0
- package/core/config-merger.js +136 -0
- package/core/config-override-processor.js +74 -0
- package/core/config-preset-resolver.js +65 -0
- package/core/config-source-loader.js +152 -0
- package/core/config-validator.js +126 -0
- package/core/dependency-manager.js +105 -0
- package/core/eslint-engine-service.js +312 -0
- package/core/eslint-instance-manager.js +104 -0
- package/core/eslint-integration-service.js +363 -0
- package/core/git-utils.js +170 -0
- package/core/multi-rule-runner.js +239 -0
- package/core/output-service.js +250 -0
- package/core/report-generator.js +320 -0
- package/core/rule-mapping-service.js +309 -0
- package/core/rule-selection-service.js +121 -0
- package/core/sunlint-engine-service.js +23 -0
- package/core/typescript-analyzer.js +262 -0
- package/core/typescript-engine.js +313 -0
- package/docs/AI.md +163 -0
- package/docs/ARCHITECTURE.md +78 -0
- package/docs/CI-CD-GUIDE.md +315 -0
- package/docs/COMMAND-EXAMPLES.md +256 -0
- package/docs/DEBUG.md +86 -0
- package/docs/DISTRIBUTION.md +153 -0
- package/docs/ESLINT-INTEGRATION-STRATEGY.md +392 -0
- package/docs/ESLINT_INTEGRATION.md +238 -0
- package/docs/FOLDER_STRUCTURE.md +59 -0
- package/docs/HEURISTIC_VS_AI.md +113 -0
- package/docs/README.md +32 -0
- package/docs/RELEASE_GUIDE.md +230 -0
- package/docs/RULE-RESPONSIBILITY-MATRIX.md +204 -0
- package/eslint-integration/.eslintrc.js +98 -0
- package/eslint-integration/cli.js +35 -0
- package/eslint-integration/eslint-plugin-custom/c002-no-duplicate-code.js +204 -0
- package/eslint-integration/eslint-plugin-custom/c003-no-vague-abbreviations.js +246 -0
- package/eslint-integration/eslint-plugin-custom/c006-function-name-verb-noun.js +207 -0
- package/eslint-integration/eslint-plugin-custom/c010-limit-block-nesting.js +90 -0
- package/eslint-integration/eslint-plugin-custom/c013-no-dead-code.js +43 -0
- package/eslint-integration/eslint-plugin-custom/c014-abstract-dependency-preferred.js +38 -0
- package/eslint-integration/eslint-plugin-custom/c017-limit-constructor-logic.js +39 -0
- package/eslint-integration/eslint-plugin-custom/c018-no-generic-throw.js +335 -0
- package/eslint-integration/eslint-plugin-custom/c023-no-duplicate-variable-name-in-scope.js +142 -0
- package/eslint-integration/eslint-plugin-custom/c027-limit-function-nesting.js +50 -0
- package/eslint-integration/eslint-plugin-custom/c029-catch-block-logging.js +80 -0
- package/eslint-integration/eslint-plugin-custom/c030-use-custom-error-classes.js +294 -0
- package/eslint-integration/eslint-plugin-custom/c034-no-implicit-return.js +34 -0
- package/eslint-integration/eslint-plugin-custom/c035-no-empty-catch.js +32 -0
- package/eslint-integration/eslint-plugin-custom/c041-no-config-inline.js +64 -0
- package/eslint-integration/eslint-plugin-custom/c042-boolean-name-prefix.js +406 -0
- package/eslint-integration/eslint-plugin-custom/c043-no-console-or-print.js +300 -0
- package/eslint-integration/eslint-plugin-custom/c047-no-duplicate-retry-logic.js +239 -0
- package/eslint-integration/eslint-plugin-custom/c048-no-var-declaration.js +31 -0
- package/eslint-integration/eslint-plugin-custom/c076-one-assert-per-test.js +184 -0
- package/eslint-integration/eslint-plugin-custom/index.js +155 -0
- package/eslint-integration/eslint-plugin-custom/package.json +13 -0
- package/eslint-integration/eslint-plugin-custom/package.json.bak +9 -0
- package/eslint-integration/eslint-plugin-custom/s003-no-unvalidated-redirect.js +86 -0
- package/eslint-integration/eslint-plugin-custom/s005-no-origin-auth.js +95 -0
- package/eslint-integration/eslint-plugin-custom/s006-activation-recovery-secret-not-plaintext.js +69 -0
- package/eslint-integration/eslint-plugin-custom/s008-crypto-agility.js +62 -0
- package/eslint-integration/eslint-plugin-custom/s009-no-insecure-crypto.js +103 -0
- package/eslint-integration/eslint-plugin-custom/s010-no-insecure-random-in-sensitive-context.js +123 -0
- package/eslint-integration/eslint-plugin-custom/s011-no-insecure-uuid.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s012-hardcode-secret.js +71 -0
- package/eslint-integration/eslint-plugin-custom/s014-insecure-tls-version.js +50 -0
- package/eslint-integration/eslint-plugin-custom/s015-insecure-tls-certificate.js +43 -0
- package/eslint-integration/eslint-plugin-custom/s016-sensitive-query-parameter.js +59 -0
- package/eslint-integration/eslint-plugin-custom/s017-no-sql-injection.js +193 -0
- package/eslint-integration/eslint-plugin-custom/s018-positive-input-validation.js +56 -0
- package/eslint-integration/eslint-plugin-custom/s019-no-raw-user-input-in-email.js +113 -0
- package/eslint-integration/eslint-plugin-custom/s020-no-eval-dynamic-execution.js +89 -0
- package/eslint-integration/eslint-plugin-custom/s022-output-encoding.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s023-no-json-injection.js +300 -0
- package/eslint-integration/eslint-plugin-custom/s025-server-side-input-validation.js +217 -0
- package/eslint-integration/eslint-plugin-custom/s026-json-schema-validation.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s027-no-hardcoded-secrets.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s029-require-csrf-protection.js +79 -0
- package/eslint-integration/eslint-plugin-custom/s030-no-directory-browsing.js +78 -0
- package/eslint-integration/eslint-plugin-custom/s033-require-samesite-cookie.js +80 -0
- package/eslint-integration/eslint-plugin-custom/s034-require-host-cookie-prefix.js +77 -0
- package/eslint-integration/eslint-plugin-custom/s035-cookie-specific-path.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s036-no-unsafe-file-include.js +68 -0
- package/eslint-integration/eslint-plugin-custom/s037-require-anti-cache-headers.js +70 -0
- package/eslint-integration/eslint-plugin-custom/s038-no-version-disclosure.js +74 -0
- package/eslint-integration/eslint-plugin-custom/s039-no-session-token-in-url.js +63 -0
- package/eslint-integration/eslint-plugin-custom/s041-require-session-invalidate-on-logout.js +211 -0
- package/eslint-integration/eslint-plugin-custom/s042-require-periodic-reauthentication.js +294 -0
- package/eslint-integration/eslint-plugin-custom/s043-terminate-sessions-on-password-change.js +254 -0
- package/eslint-integration/eslint-plugin-custom/s044-require-full-session-for-sensitive-operations.js +292 -0
- package/eslint-integration/eslint-plugin-custom/s045-anti-automation-controls.js +46 -0
- package/eslint-integration/eslint-plugin-custom/s046-secure-notification-on-auth-change.js +44 -0
- package/eslint-integration/eslint-plugin-custom/s047-secure-random-passwords.js +108 -0
- package/eslint-integration/eslint-plugin-custom/s048-password-credential-recovery.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s050-session-token-weak-hash.js +94 -0
- package/eslint-integration/eslint-plugin-custom/s052-secure-random-authentication-code.js +66 -0
- package/eslint-integration/eslint-plugin-custom/s054-verification-default-account.js +109 -0
- package/eslint-integration/eslint-plugin-custom/s055-verification-rest-check-the-incoming-content-type.js +143 -0
- package/eslint-integration/eslint-plugin-custom/s057-utc-logging.js +54 -0
- package/eslint-integration/eslint-plugin-custom/s058-no-ssrf.js +73 -0
- package/eslint-integration/eslint-plugin-custom/t002-interface-prefix-i.js +42 -0
- package/eslint-integration/eslint-plugin-custom/t003-ts-ignore-reason.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t004-interface-public-only.js +160 -0
- package/eslint-integration/eslint-plugin-custom/t007-no-fn-in-constructor.js +52 -0
- package/eslint-integration/eslint-plugin-custom/t011-no-real-time-dependency.js +175 -0
- package/eslint-integration/eslint-plugin-custom/t019-no-empty-type.js +95 -0
- package/eslint-integration/eslint-plugin-custom/t025-no-nested-union-tuple.js +48 -0
- package/eslint-integration/eslint-plugin-custom/t026-limit-nested-generics.js +377 -0
- package/eslint-integration/eslint.config.js +125 -0
- package/eslint-integration/eslint.config.simple.js +24 -0
- package/eslint-integration/node_modules/eslint-plugin-custom/package.json +0 -0
- package/eslint-integration/package.json +23 -0
- package/eslint-integration/sample.ts +53 -0
- package/eslint-integration/test-s003.js +5 -0
- package/eslint-integration/tsconfig.json +27 -0
- package/examples/.github/workflows/code-quality.yml +111 -0
- package/examples/.sunlint.json +42 -0
- package/examples/README.md +47 -0
- package/examples/package.json +33 -0
- package/package.json +100 -0
- package/rules/C006_function_naming/analyzer.js +338 -0
- package/rules/C006_function_naming/config.json +86 -0
- package/rules/C019_log_level_usage/analyzer.js +359 -0
- package/rules/C019_log_level_usage/config.json +121 -0
- package/rules/C029_catch_block_logging/analyzer.js +339 -0
- package/rules/C029_catch_block_logging/config.json +59 -0
- package/rules/C031_validation_separation/README.md +72 -0
- package/rules/C031_validation_separation/analyzer.js +186 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# ESLint Integration Feature
|
|
2
|
+
|
|
3
|
+
## 🎯 **Overview**
|
|
4
|
+
|
|
5
|
+
SunLint ESLint Integration cho phép teams **merge** existing ESLint configuration với SunLint rules trong **single execution pipeline**. Thay vì chạy parallel, SunLint sẽ **orchestrate** và **combine** cả 2 rule sets.
|
|
6
|
+
|
|
7
|
+
### **Problem Solved**
|
|
8
|
+
- ✅ Teams có existing ESLint (20 rules) + muốn add SunLint (93 rules) = **113 rules total**
|
|
9
|
+
- ✅ Single command execution thay vì multiple tool chains
|
|
10
|
+
- ✅ No degradation của existing ESLint workflow
|
|
11
|
+
- ✅ Combined reporting cho easier debugging
|
|
12
|
+
|
|
13
|
+
## 📖 **Configuration**
|
|
14
|
+
|
|
15
|
+
### **Method 1: package.json Configuration**
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"scripts": {
|
|
19
|
+
"lint:integrated": "sunlint --all --eslint-integration --input=src"
|
|
20
|
+
},
|
|
21
|
+
"sunlint": {
|
|
22
|
+
"eslintIntegration": {
|
|
23
|
+
"enabled": true,
|
|
24
|
+
"mergeRules": true,
|
|
25
|
+
"preserveUserConfig": true
|
|
26
|
+
},
|
|
27
|
+
"rules": {
|
|
28
|
+
"C006": "warn",
|
|
29
|
+
"C019": "error"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### **Method 2: .sunlint.json Configuration**
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"eslintIntegration": {
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"mergeRules": true,
|
|
41
|
+
"preserveUserConfig": true,
|
|
42
|
+
"runAfterSunLint": false
|
|
43
|
+
},
|
|
44
|
+
"rules": {
|
|
45
|
+
"C006": "warn",
|
|
46
|
+
"C019": "error",
|
|
47
|
+
"S047": "warn"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### **Method 3: CLI Flags**
|
|
53
|
+
```bash
|
|
54
|
+
# Enable integration
|
|
55
|
+
sunlint --all --eslint-integration --input=src
|
|
56
|
+
|
|
57
|
+
# Merge rules (default: true)
|
|
58
|
+
sunlint --all --eslint-integration --eslint-merge-rules --input=src
|
|
59
|
+
|
|
60
|
+
# Preserve user config (default: true)
|
|
61
|
+
sunlint --all --eslint-integration --eslint-preserve-config --input=src
|
|
62
|
+
|
|
63
|
+
# Run ESLint after SunLint (alternative to merge)
|
|
64
|
+
sunlint --all --eslint-integration --eslint-run-after --input=src
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## 🔧 **Integration Modes**
|
|
68
|
+
|
|
69
|
+
### **Mode 1: Merged Execution (Default)**
|
|
70
|
+
```bash
|
|
71
|
+
sunlint --all --eslint-integration --input=src
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**How it works:**
|
|
75
|
+
1. SunLint discovers existing `.eslintrc.json`
|
|
76
|
+
2. Merges SunLint rules + User ESLint rules
|
|
77
|
+
3. Creates combined ESLint configuration
|
|
78
|
+
4. Runs single ESLint execution with **merged ruleset**
|
|
79
|
+
5. Categorizes results by rule source (SunLint vs User)
|
|
80
|
+
|
|
81
|
+
**Output:**
|
|
82
|
+
```
|
|
83
|
+
🔗 ESLint Integration Summary:
|
|
84
|
+
📋 SunLint violations: 4
|
|
85
|
+
🔧 User ESLint violations: 6
|
|
86
|
+
📊 Total combined violations: 10
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### **Mode 2: Sequential Execution**
|
|
90
|
+
```bash
|
|
91
|
+
sunlint --all --eslint-integration --eslint-run-after --input=src
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**How it works:**
|
|
95
|
+
1. Run SunLint rules first
|
|
96
|
+
2. Run user ESLint rules after
|
|
97
|
+
3. Combine results for reporting
|
|
98
|
+
4. Maintain separation of concerns
|
|
99
|
+
|
|
100
|
+
## 🚀 **Usage Examples**
|
|
101
|
+
|
|
102
|
+
### **Basic Integration**
|
|
103
|
+
```bash
|
|
104
|
+
# Analyze with both SunLint + existing ESLint rules
|
|
105
|
+
sunlint --typescript --eslint-integration --input=src
|
|
106
|
+
|
|
107
|
+
# Git integration + ESLint integration
|
|
108
|
+
sunlint --all --eslint-integration --changed-files
|
|
109
|
+
|
|
110
|
+
# CI pipeline
|
|
111
|
+
sunlint --all --eslint-integration --changed-files --format=summary --fail-on-new-violations
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### **Team Migration Scripts**
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"scripts": {
|
|
118
|
+
"lint": "npm run lint:integrated",
|
|
119
|
+
"lint:integrated": "sunlint --all --eslint-integration --input=src",
|
|
120
|
+
"lint:changed": "sunlint --all --eslint-integration --changed-files",
|
|
121
|
+
"lint:staged": "sunlint --all --eslint-integration --staged-files",
|
|
122
|
+
"ci:lint": "sunlint --all --eslint-integration --changed-files --format=summary"
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### **GitHub Actions Integration**
|
|
128
|
+
```yaml
|
|
129
|
+
name: Code Quality Check
|
|
130
|
+
on: [pull_request]
|
|
131
|
+
|
|
132
|
+
jobs:
|
|
133
|
+
lint:
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
steps:
|
|
136
|
+
- uses: actions/checkout@v3
|
|
137
|
+
- uses: actions/setup-node@v3
|
|
138
|
+
- run: npm ci
|
|
139
|
+
- name: Run Integrated Linting
|
|
140
|
+
run: |
|
|
141
|
+
sunlint --all --eslint-integration --changed-files \
|
|
142
|
+
--diff-base=origin/main \
|
|
143
|
+
--format=summary \
|
|
144
|
+
--fail-on-new-violations
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## 🏗️ **Architecture**
|
|
148
|
+
|
|
149
|
+
### **ESLintIntegrationService**
|
|
150
|
+
- **Responsibility**: Detect, load, and merge ESLint configurations
|
|
151
|
+
- **Methods**:
|
|
152
|
+
- `hasExistingESLintConfig()`: Auto-detect existing ESLint setup
|
|
153
|
+
- `loadExistingESLintConfig()`: Load user's ESLint configuration
|
|
154
|
+
- `createMergedConfig()`: Merge SunLint + User rules
|
|
155
|
+
- `runIntegratedAnalysis()`: Execute combined analysis
|
|
156
|
+
|
|
157
|
+
### **Configuration Merging Strategy**
|
|
158
|
+
```javascript
|
|
159
|
+
mergedConfig = {
|
|
160
|
+
extends: [...sunlintExtends, ...userExtends],
|
|
161
|
+
plugins: [...sunlintPlugins, ...userPlugins],
|
|
162
|
+
rules: {
|
|
163
|
+
...sunlintRules,
|
|
164
|
+
...userRules // User rules override SunLint in case of conflicts
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### **Result Categorization**
|
|
170
|
+
```javascript
|
|
171
|
+
{
|
|
172
|
+
results: [...],
|
|
173
|
+
categorized: {
|
|
174
|
+
sunlint: [/* SunLint violations */],
|
|
175
|
+
user: [/* User ESLint violations */],
|
|
176
|
+
combined: [/* All violations */]
|
|
177
|
+
},
|
|
178
|
+
integration: {
|
|
179
|
+
totalRules: 113,
|
|
180
|
+
sunlintRules: 93,
|
|
181
|
+
userRules: 20
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## 🎯 **Benefits**
|
|
187
|
+
|
|
188
|
+
### **For Development Teams**
|
|
189
|
+
- ✅ **No workflow disruption**: Existing ESLint continues working
|
|
190
|
+
- ✅ **Single command**: One execution for all quality checks
|
|
191
|
+
- ✅ **Incremental adoption**: Can enable/disable integration easily
|
|
192
|
+
- ✅ **Conflict resolution**: User rules take precedence over SunLint
|
|
193
|
+
|
|
194
|
+
### **For CI/CD Pipelines**
|
|
195
|
+
- ✅ **Faster execution**: Single tool execution vs multiple tools
|
|
196
|
+
- ✅ **Unified reporting**: Combined results, easier to track
|
|
197
|
+
- ✅ **Git integration**: Works with `--changed-files`, `--staged-files`
|
|
198
|
+
- ✅ **Baseline comparison**: `--fail-on-new-violations`
|
|
199
|
+
|
|
200
|
+
### **For Enterprise Adoption**
|
|
201
|
+
- ✅ **Backward compatibility**: No existing config changes required
|
|
202
|
+
- ✅ **Gradual migration**: Teams can test integration without commitment
|
|
203
|
+
- ✅ **Centralized enforcement**: SunLint rules + team-specific ESLint rules
|
|
204
|
+
- ✅ **Compliance reporting**: Combined violation tracking
|
|
205
|
+
|
|
206
|
+
## 📊 **Example Scenario**
|
|
207
|
+
|
|
208
|
+
**Before Integration:**
|
|
209
|
+
```bash
|
|
210
|
+
# Team workflow (2 separate commands)
|
|
211
|
+
npm run lint:eslint # 20 rules, 6 violations
|
|
212
|
+
npm run lint:sunlint # 93 rules, 4 violations
|
|
213
|
+
# Total: 10 violations, 2 command executions
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**After Integration:**
|
|
217
|
+
```bash
|
|
218
|
+
# Single integrated command
|
|
219
|
+
npm run lint:integrated # 113 rules, 10 violations
|
|
220
|
+
# Total: 10 violations, 1 command execution
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## 🔍 **Demo**
|
|
224
|
+
|
|
225
|
+
Run the integration demo:
|
|
226
|
+
```bash
|
|
227
|
+
./demo-eslint-integration.sh
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
This demonstrates:
|
|
231
|
+
1. Existing ESLint workflow (20 rules)
|
|
232
|
+
2. SunLint-only analysis (93 rules)
|
|
233
|
+
3. **Integrated analysis (113 rules total)**
|
|
234
|
+
4. Available npm scripts for team adoption
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
**🎉 Result**: Teams can now run **113 total rules** (93 SunLint + 20 existing ESLint) in **single command execution** without disrupting existing workflows!
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Sunlint Folder Structure
|
|
2
|
+
|
|
3
|
+
## Rules Directory Naming Convention
|
|
4
|
+
|
|
5
|
+
All rule folders follow the consistent pattern: `C{ID}_{descriptive_name}`
|
|
6
|
+
|
|
7
|
+
### Current Rules Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
rules/
|
|
11
|
+
├── C006_function_naming/ # Function naming conventions
|
|
12
|
+
│ ├── analyzer.js # Rule implementation
|
|
13
|
+
│ └── config.json # Rule configuration
|
|
14
|
+
├── C019_log_level_usage/ # Log level usage validation
|
|
15
|
+
│ ├── analyzer.js
|
|
16
|
+
│ └── config.json
|
|
17
|
+
├── C029_catch_block_logging/ # Catch block error logging
|
|
18
|
+
│ ├── analyzer.js
|
|
19
|
+
│ └── config.json
|
|
20
|
+
└── C031_validation_separation/ # Validation logic separation (planned)
|
|
21
|
+
├── analyzer.js
|
|
22
|
+
└── config.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Benefits of This Naming Convention
|
|
26
|
+
|
|
27
|
+
1. **Consistency** - All folders follow the same pattern
|
|
28
|
+
2. **Resilience** - If rule IDs change, descriptive names provide context
|
|
29
|
+
3. **Readability** - Easy to understand rule purpose from folder name
|
|
30
|
+
4. **Maintainability** - Clear organization for developers
|
|
31
|
+
|
|
32
|
+
### Adding New Rules
|
|
33
|
+
|
|
34
|
+
When adding a new rule, follow this pattern:
|
|
35
|
+
|
|
36
|
+
1. Create folder: `C{ID}_{snake_case_description}/`
|
|
37
|
+
2. Add `analyzer.js` with rule implementation
|
|
38
|
+
3. Add `config.json` with rule configuration
|
|
39
|
+
4. Update `rules-registry.json` with correct paths
|
|
40
|
+
5. Add tests in `test/fixtures/`
|
|
41
|
+
|
|
42
|
+
### Example
|
|
43
|
+
|
|
44
|
+
For a new rule C040 about "API Response Format":
|
|
45
|
+
```
|
|
46
|
+
rules/C040_api_response_format/
|
|
47
|
+
├── analyzer.js
|
|
48
|
+
└── config.json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Registry entry:
|
|
52
|
+
```json
|
|
53
|
+
"C040": {
|
|
54
|
+
"name": "API Response Format",
|
|
55
|
+
"description": "Hàm xử lý API nên return response object chuẩn",
|
|
56
|
+
"analyzer": "./rules/C040_api_response_format/analyzer.js",
|
|
57
|
+
"config": "./rules/C040_api_response_format/config.json"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# 🎯 Heuristic vs AI Analysis Guide
|
|
2
|
+
|
|
3
|
+
## Quick Commands
|
|
4
|
+
|
|
5
|
+
### 🔍 **Heuristic Mode (Fast & Accurate)**
|
|
6
|
+
```bash
|
|
7
|
+
# Force heuristic analysis (ignore config)
|
|
8
|
+
node cli.js --rule=C019 --input=src --no-ai
|
|
9
|
+
|
|
10
|
+
# Disable in config file
|
|
11
|
+
# In .sunlint.json: "ai": { "enabled": false }
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### 🤖 **AI Mode (Context-Aware)**
|
|
15
|
+
```bash
|
|
16
|
+
# Force AI analysis (with API key)
|
|
17
|
+
export OPENAI_API_KEY="your-key"
|
|
18
|
+
node cli.js --rule=C019 --input=src --ai
|
|
19
|
+
|
|
20
|
+
# Enable in config file
|
|
21
|
+
# In .sunlint.json: "ai": { "enabled": true }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Performance Comparison
|
|
25
|
+
|
|
26
|
+
| Mode | Speed | Accuracy | Cost | Use Case |
|
|
27
|
+
|------|-------|----------|------|----------|
|
|
28
|
+
| **Heuristic** | ⚡ ~100ms | 95%+ | Free | Daily development |
|
|
29
|
+
| **AI** | 🐌 ~20-30s | 98%+ | $0.001/file | Complex analysis |
|
|
30
|
+
|
|
31
|
+
## Configuration Priority
|
|
32
|
+
|
|
33
|
+
1. **CLI flags** (highest priority)
|
|
34
|
+
- `--ai` → Force enable AI
|
|
35
|
+
- `--no-ai` → Force disable AI
|
|
36
|
+
|
|
37
|
+
2. **Config file** (.sunlint.json)
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"ai": {
|
|
41
|
+
"enabled": true/false,
|
|
42
|
+
"provider": "openai",
|
|
43
|
+
"model": "gpt-4o-mini"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
3. **Default** → Heuristic mode
|
|
49
|
+
|
|
50
|
+
## Use Cases
|
|
51
|
+
|
|
52
|
+
### ✅ **Use Heuristic When:**
|
|
53
|
+
- Daily code reviews
|
|
54
|
+
- CI/CD pipelines
|
|
55
|
+
- Quick local checks
|
|
56
|
+
- Large codebases
|
|
57
|
+
- Limited internet/API quotas
|
|
58
|
+
|
|
59
|
+
### ✅ **Use AI When:**
|
|
60
|
+
- Complex rule violations
|
|
61
|
+
- Need context understanding
|
|
62
|
+
- Training new developers
|
|
63
|
+
- Quality audits
|
|
64
|
+
- Unusual code patterns
|
|
65
|
+
|
|
66
|
+
## Debug Commands
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Check which mode is active
|
|
70
|
+
node cli.js --rule=C019 --input=src --debug
|
|
71
|
+
|
|
72
|
+
# Look for these outputs:
|
|
73
|
+
# Heuristic: "🔍 Running pattern analysis"
|
|
74
|
+
# AI: "🤖 AI analysis enabled" + "🎯 AI found X violations"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Troubleshooting
|
|
78
|
+
|
|
79
|
+
### AI Not Working?
|
|
80
|
+
1. Check API key: `echo $OPENAI_API_KEY`
|
|
81
|
+
2. Use `--ai --debug` to see errors
|
|
82
|
+
3. Verify config: `"ai": { "enabled": true }`
|
|
83
|
+
|
|
84
|
+
### Heuristic Not Working?
|
|
85
|
+
1. Use `--no-ai --debug`
|
|
86
|
+
2. Check output for "🔍 Running pattern analysis"
|
|
87
|
+
|
|
88
|
+
## Best Practices
|
|
89
|
+
|
|
90
|
+
1. **Development**: Use `--no-ai` for speed
|
|
91
|
+
2. **CI/CD**: Use heuristic mode by default
|
|
92
|
+
3. **Code Review**: Use AI for complex cases
|
|
93
|
+
4. **Training**: Use AI mode to understand violations better
|
|
94
|
+
|
|
95
|
+
## Example Workflows
|
|
96
|
+
|
|
97
|
+
### Daily Development
|
|
98
|
+
```bash
|
|
99
|
+
# Quick check before commit
|
|
100
|
+
node cli.js --quality --input=src --no-ai
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Code Review
|
|
104
|
+
```bash
|
|
105
|
+
# Deep analysis with AI
|
|
106
|
+
node cli.js --quality --input=src --ai --format=summary
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### CI Pipeline
|
|
110
|
+
```bash
|
|
111
|
+
# Fast, reliable heuristic
|
|
112
|
+
node cli.js --all --input=src --no-ai --format=json
|
|
113
|
+
```
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# 📚 Sunlint Documentation
|
|
2
|
+
|
|
3
|
+
## Quick Links
|
|
4
|
+
|
|
5
|
+
- **[AI Features](AI.md)** - AI-powered analysis guide
|
|
6
|
+
- **[Debug Guide](DEBUG.md)** - How to debug rules and CLI
|
|
7
|
+
- **[Architecture](ARCHITECTURE.md)** - System architecture and modular design
|
|
8
|
+
- **[Distribution](DISTRIBUTION.md)** - Installation and deployment methods
|
|
9
|
+
|
|
10
|
+
## Development Guides
|
|
11
|
+
|
|
12
|
+
- **[Folder Structure](FOLDER_STRUCTURE.md)** - Project organization
|
|
13
|
+
- **[ESLint Integration Strategy](ESLINT-INTEGRATION-STRATEGY.md)** - ESLint vs SunLint strategy
|
|
14
|
+
- **[CI/CD Guide](CI-CD-GUIDE.md)** - Continuous integration setup
|
|
15
|
+
- **[Command Examples](COMMAND-EXAMPLES.md)** - CLI usage examples
|
|
16
|
+
|
|
17
|
+
## Performance & Analysis
|
|
18
|
+
|
|
19
|
+
- **[Heuristic vs AI](HEURISTIC_VS_AI.md)** - Performance comparison guide
|
|
20
|
+
- **[Rule Responsibility Matrix](RULE-RESPONSIBILITY-MATRIX.md)** - Rules mapping and coverage
|
|
21
|
+
|
|
22
|
+
## Main Documentation
|
|
23
|
+
|
|
24
|
+
- **[README.md](../README.md)** - Main project documentation
|
|
25
|
+
- **[CONTRIBUTING.md](../CONTRIBUTING.md)** - Contribution guidelines
|
|
26
|
+
- **[CHANGELOG.md](../CHANGELOG.md)** - Version history
|
|
27
|
+
- **[LICENSE](../LICENSE)** - License information
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
- **[.sunlint-simple.json](../.sunlint-simple.json)** - Simple configuration example
|
|
32
|
+
- **[sunlint.config.json](../sunlint.config.json)** - Full configuration with all features
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# 🚀 SunLint v1.0.5 Release Guide
|
|
2
|
+
|
|
3
|
+
## 📦 **Dual Release Strategy**
|
|
4
|
+
|
|
5
|
+
SunLint v1.0.5 supports **two deployment methods** to meet different enterprise needs:
|
|
6
|
+
|
|
7
|
+
### **1. GitHub Package Registry (Private/Enterprise)**
|
|
8
|
+
- **Purpose**: Private enterprise distribution
|
|
9
|
+
- **Benefits**: Free private packages, organization control
|
|
10
|
+
- **Target**: Internal teams, enterprise customers
|
|
11
|
+
|
|
12
|
+
### **2. Global Tarball Release (Public)**
|
|
13
|
+
- **Purpose**: Public distribution via GitHub Releases
|
|
14
|
+
- **Benefits**: No NPM Registry fees, direct download
|
|
15
|
+
- **Target**: Open source community, public usage
|
|
16
|
+
|
|
17
|
+
## 🔧 **Release Process**
|
|
18
|
+
|
|
19
|
+
### **Automated Release (Recommended)**
|
|
20
|
+
|
|
21
|
+
1. **Trigger GitHub Action**:
|
|
22
|
+
- Go to GitHub Actions → "Release SunLint" workflow
|
|
23
|
+
- Click "Run workflow"
|
|
24
|
+
- Select parameters:
|
|
25
|
+
- **Version**: `1.0.5`
|
|
26
|
+
- **Release Type**: `both` (GitHub Package + Tarball)
|
|
27
|
+
|
|
28
|
+
2. **Automated Steps**:
|
|
29
|
+
- ✅ Run tests
|
|
30
|
+
- ✅ Update version numbers
|
|
31
|
+
- ✅ Build package tarball
|
|
32
|
+
- ✅ Publish to GitHub Package Registry
|
|
33
|
+
- ✅ Create GitHub Release with tarball
|
|
34
|
+
- ✅ Generate installation documentation
|
|
35
|
+
|
|
36
|
+
### **Manual Release (Alternative)**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. Prepare release
|
|
40
|
+
cd coding-quality/extensions/sunlint
|
|
41
|
+
npm test
|
|
42
|
+
npm run clean
|
|
43
|
+
|
|
44
|
+
# 2. Update version
|
|
45
|
+
npm version 1.0.5 --no-git-tag-version
|
|
46
|
+
|
|
47
|
+
# 3. GitHub Package Registry
|
|
48
|
+
cp package-github.json package.json
|
|
49
|
+
npm publish --registry=https://npm.pkg.github.com
|
|
50
|
+
|
|
51
|
+
# 4. Global tarball
|
|
52
|
+
npm pack
|
|
53
|
+
mv *.tgz sunlint-1.0.5.tgz
|
|
54
|
+
|
|
55
|
+
# 5. Create GitHub release (manual upload)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 📖 **Installation Methods**
|
|
59
|
+
|
|
60
|
+
### **Method 1: GitHub Package Registry**
|
|
61
|
+
|
|
62
|
+
**Setup (one-time)**:
|
|
63
|
+
```bash
|
|
64
|
+
# Configure GitHub Package Registry
|
|
65
|
+
echo "@sun-asterisk:registry=https://npm.pkg.github.com" >> ~/.npmrc
|
|
66
|
+
echo "//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}" >> ~/.npmrc
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Install**:
|
|
70
|
+
```bash
|
|
71
|
+
# Global installation
|
|
72
|
+
npm install -g @sun-asterisk/sunlint
|
|
73
|
+
|
|
74
|
+
# Project installation
|
|
75
|
+
npm install --save-dev @sun-asterisk/sunlint
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### **Method 2: Direct Tarball**
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Global installation from release
|
|
82
|
+
npm install -g https://github.com/sun-asterisk/engineer-excellence/releases/download/sunlint-v1.0.5/sunlint-1.0.5.tgz
|
|
83
|
+
|
|
84
|
+
# Project installation
|
|
85
|
+
npm install --save-dev https://github.com/sun-asterisk/engineer-excellence/releases/download/sunlint-v1.0.5/sunlint-1.0.5.tgz
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### **Method 3: Setup Script**
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# One-line setup for GitHub Package Registry
|
|
92
|
+
curl -fsSL https://raw.githubusercontent.com/sun-asterisk/engineer-excellence/main/coding-quality/extensions/sunlint/scripts/setup-github-registry.sh | GITHUB_TOKEN=your_token bash
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## 🎯 **Team Integration Examples**
|
|
96
|
+
|
|
97
|
+
### **Enterprise Team (GitHub Package Registry)**
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"name": "my-enterprise-project",
|
|
102
|
+
"scripts": {
|
|
103
|
+
"lint": "sunlint --all --input=src",
|
|
104
|
+
"lint:changed": "sunlint --all --changed-files",
|
|
105
|
+
"lint:eslint": "sunlint --all --eslint-integration --input=src",
|
|
106
|
+
"ci:lint": "sunlint --all --changed-files --fail-on-new-violations"
|
|
107
|
+
},
|
|
108
|
+
"devDependencies": {
|
|
109
|
+
"@sun-asterisk/sunlint": "^1.0.5"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**.npmrc** (project-level):
|
|
115
|
+
```
|
|
116
|
+
@sun-asterisk:registry=https://npm.pkg.github.com
|
|
117
|
+
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### **Open Source Project (Direct Tarball)**
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"name": "my-open-source-project",
|
|
125
|
+
"scripts": {
|
|
126
|
+
"lint": "sunlint --all --input=src",
|
|
127
|
+
"lint:eslint": "sunlint --all --eslint-integration --input=src"
|
|
128
|
+
},
|
|
129
|
+
"devDependencies": {
|
|
130
|
+
"@sun-asterisk/sunlint": "https://github.com/sun-asterisk/engineer-excellence/releases/download/sunlint-v1.0.5/sunlint-1.0.5.tgz"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 🚢 **CI/CD Integration**
|
|
136
|
+
|
|
137
|
+
### **GitHub Actions with GitHub Package Registry**
|
|
138
|
+
|
|
139
|
+
```yaml
|
|
140
|
+
name: Code Quality
|
|
141
|
+
on: [pull_request]
|
|
142
|
+
|
|
143
|
+
jobs:
|
|
144
|
+
lint:
|
|
145
|
+
runs-on: ubuntu-latest
|
|
146
|
+
steps:
|
|
147
|
+
- uses: actions/checkout@v4
|
|
148
|
+
- uses: actions/setup-node@v4
|
|
149
|
+
with:
|
|
150
|
+
node-version: '18'
|
|
151
|
+
registry-url: 'https://npm.pkg.github.com'
|
|
152
|
+
|
|
153
|
+
- name: Configure GitHub Package Registry
|
|
154
|
+
run: |
|
|
155
|
+
echo "@sun-asterisk:registry=https://npm.pkg.github.com" >> ~/.npmrc
|
|
156
|
+
echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> ~/.npmrc
|
|
157
|
+
|
|
158
|
+
- run: npm ci
|
|
159
|
+
- name: Run SunLint
|
|
160
|
+
run: |
|
|
161
|
+
npx @sun-asterisk/sunlint --all --eslint-integration --changed-files \
|
|
162
|
+
--diff-base=origin/main --fail-on-new-violations --format=summary
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### **GitHub Actions with Direct Tarball**
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
name: Code Quality
|
|
169
|
+
on: [pull_request]
|
|
170
|
+
|
|
171
|
+
jobs:
|
|
172
|
+
lint:
|
|
173
|
+
runs-on: ubuntu-latest
|
|
174
|
+
steps:
|
|
175
|
+
- uses: actions/checkout@v4
|
|
176
|
+
- uses: actions/setup-node@v4
|
|
177
|
+
|
|
178
|
+
- name: Install SunLint
|
|
179
|
+
run: |
|
|
180
|
+
npm install -g https://github.com/sun-asterisk/engineer-excellence/releases/download/sunlint-v1.0.5/sunlint-1.0.5.tgz
|
|
181
|
+
|
|
182
|
+
- name: Run SunLint
|
|
183
|
+
run: |
|
|
184
|
+
sunlint --all --eslint-integration --changed-files \
|
|
185
|
+
--diff-base=origin/main --fail-on-new-violations --format=summary
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 🔍 **Verification**
|
|
189
|
+
|
|
190
|
+
After installation, verify SunLint is working:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# Check version
|
|
194
|
+
sunlint --version
|
|
195
|
+
|
|
196
|
+
# Test basic functionality
|
|
197
|
+
sunlint --rule=C006 --input=src
|
|
198
|
+
|
|
199
|
+
# Test ESLint integration
|
|
200
|
+
sunlint --all --eslint-integration --input=src
|
|
201
|
+
|
|
202
|
+
# Test Git integration
|
|
203
|
+
sunlint --all --changed-files
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## 📊 **Release Metrics**
|
|
207
|
+
|
|
208
|
+
Track adoption through:
|
|
209
|
+
- GitHub Package downloads
|
|
210
|
+
- GitHub Release download statistics
|
|
211
|
+
- GitHub stars/forks
|
|
212
|
+
- Issue reports and feature requests
|
|
213
|
+
|
|
214
|
+
## 🎉 **Benefits Summary**
|
|
215
|
+
|
|
216
|
+
### **For Teams**
|
|
217
|
+
- ✅ **Zero-disruption**: Works with existing ESLint
|
|
218
|
+
- ✅ **Flexible deployment**: GitHub Package or direct download
|
|
219
|
+
- ✅ **Enterprise-ready**: Private package distribution
|
|
220
|
+
- ✅ **CI/CD optimized**: Git integration for performance
|
|
221
|
+
|
|
222
|
+
### **For Maintainers**
|
|
223
|
+
- ✅ **Cost-effective**: No NPM Registry fees
|
|
224
|
+
- ✅ **Control**: Private distribution via GitHub
|
|
225
|
+
- ✅ **Automation**: GitHub Actions release pipeline
|
|
226
|
+
- ✅ **Monitoring**: Built-in download analytics
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
**🚀 Ready to deploy SunLint v1.0.5 with dual release strategy!**
|