@iviva/uxp-lint 0.0.1
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/README.md +273 -0
- package/bin/uxp-lint.js +2 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +100 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +4 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/eslint/config.d.ts +2 -0
- package/dist/eslint/config.js +225 -0
- package/dist/eslint/config.js.map +1 -0
- package/dist/eslint/rules/event-name-format.d.ts +5 -0
- package/dist/eslint/rules/event-name-format.js +59 -0
- package/dist/eslint/rules/event-name-format.js.map +1 -0
- package/dist/eslint/rules/no-bad-hook-deps.d.ts +5 -0
- package/dist/eslint/rules/no-bad-hook-deps.js +57 -0
- package/dist/eslint/rules/no-bad-hook-deps.js.map +1 -0
- package/dist/eslint/rules/no-fa-prefix.d.ts +5 -0
- package/dist/eslint/rules/no-fa-prefix.js +59 -0
- package/dist/eslint/rules/no-fa-prefix.js.map +1 -0
- package/dist/eslint/rules/no-hardcoded-jsx-text.d.ts +5 -0
- package/dist/eslint/rules/no-hardcoded-jsx-text.js +55 -0
- package/dist/eslint/rules/no-hardcoded-jsx-text.js.map +1 -0
- package/dist/eslint/rules/no-inline-styles.d.ts +8 -0
- package/dist/eslint/rules/no-inline-styles.js +41 -0
- package/dist/eslint/rules/no-inline-styles.js.map +1 -0
- package/dist/eslint/rules/no-native-html-interactive.d.ts +5 -0
- package/dist/eslint/rules/no-native-html-interactive.js +81 -0
- package/dist/eslint/rules/no-native-html-interactive.js.map +1 -0
- package/dist/eslint/rules/require-memo.d.ts +9 -0
- package/dist/eslint/rules/require-memo.js +70 -0
- package/dist/eslint/rules/require-memo.js.map +1 -0
- package/dist/eslint/rules/service-config-shape.d.ts +5 -0
- package/dist/eslint/rules/service-config-shape.js +80 -0
- package/dist/eslint/rules/service-config-shape.js.map +1 -0
- package/dist/eslint/rules/url-params-form-state.d.ts +5 -0
- package/dist/eslint/rules/url-params-form-state.js +75 -0
- package/dist/eslint/rules/url-params-form-state.js.map +1 -0
- package/dist/reporter.d.ts +22 -0
- package/dist/reporter.js +260 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rule-registry.d.ts +2 -0
- package/dist/rule-registry.js +46 -0
- package/dist/rule-registry.js.map +1 -0
- package/dist/rules-reader.d.ts +4 -0
- package/dist/rules-reader.js +16 -0
- package/dist/rules-reader.js.map +1 -0
- package/dist/runner.d.ts +7 -0
- package/dist/runner.js +62 -0
- package/dist/runner.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.js +80 -0
- package/dist/setup.js.map +1 -0
- package/dist/types.d.ts +55 -0
- package/dist/types.js +39 -0
- package/dist/types.js.map +1 -0
- package/dist/validators/ai.d.ts +2 -0
- package/dist/validators/ai.js +115 -0
- package/dist/validators/ai.js.map +1 -0
- package/dist/validators/bulk-imports.d.ts +3 -0
- package/dist/validators/bulk-imports.js +94 -0
- package/dist/validators/bulk-imports.js.map +1 -0
- package/dist/validators/bundle-json.d.ts +3 -0
- package/dist/validators/bundle-json.js +130 -0
- package/dist/validators/bundle-json.js.map +1 -0
- package/dist/validators/bundle-size.d.ts +3 -0
- package/dist/validators/bundle-size.js +51 -0
- package/dist/validators/bundle-size.js.map +1 -0
- package/dist/validators/config-yml.d.ts +3 -0
- package/dist/validators/config-yml.js +148 -0
- package/dist/validators/config-yml.js.map +1 -0
- package/dist/validators/duplication.d.ts +3 -0
- package/dist/validators/duplication.js +65 -0
- package/dist/validators/duplication.js.map +1 -0
- package/dist/validators/folder-structure.d.ts +3 -0
- package/dist/validators/folder-structure.js +123 -0
- package/dist/validators/folder-structure.js.map +1 -0
- package/dist/validators/formatting.d.ts +3 -0
- package/dist/validators/formatting.js +97 -0
- package/dist/validators/formatting.js.map +1 -0
- package/dist/validators/scss-rules.d.ts +3 -0
- package/dist/validators/scss-rules.js +156 -0
- package/dist/validators/scss-rules.js.map +1 -0
- package/package.json +58 -0
- package/templates/prettier.config.js +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
# @iviva/uxp-lint
|
|
2
|
+
|
|
3
|
+
Linting and validation tool for iviva v5 UXP apps. Enforces the patterns established across the System, Location, and User reference apps.
|
|
4
|
+
|
|
5
|
+
## What it checks
|
|
6
|
+
|
|
7
|
+
| Group | Rules |
|
|
8
|
+
|-------|-------|
|
|
9
|
+
| **Configuration.yml** | pageId format, nav group structure, duplicate links, otherRoutes redirects |
|
|
10
|
+
| **bundle.json** | label vs component key, registerUI/bundle.json ID sync, localization order |
|
|
11
|
+
| **Bulk Imports** | `name` attribute on root XML element |
|
|
12
|
+
| **Folder Structure** | one .tsx per views/ subfolder, forms in forms/, services.ts required |
|
|
13
|
+
| **Code Quality (ESLint)** | TypeScript recommended + React recommended + 9 custom UXP rules |
|
|
14
|
+
| **Formatting (Prettier)** | All .ts/.tsx/.scss files match project Prettier config |
|
|
15
|
+
| **SCSS Rules** | App-prefix class naming, no top-level .uxp-* overrides |
|
|
16
|
+
| **Bundle Size** | dist/main.js under configured limit (default 1MB) |
|
|
17
|
+
| **Duplicate Code** | Code clones ≥10 lines (via jscpd) |
|
|
18
|
+
|
|
19
|
+
Run `uxp-lint --list-rules` to see all 23 rules with severity. Run `uxp-lint --explain <rule>` for details on any rule.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
Install in the app you want to lint:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -D @iviva/uxp-lint
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Add a script to the app's `package.json`:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
"scripts": {
|
|
35
|
+
"lint": "uxp-lint",
|
|
36
|
+
"lint:fix": "uxp-lint --fix",
|
|
37
|
+
"lint:ci": "uxp-lint --ci"
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Setup
|
|
44
|
+
|
|
45
|
+
On first run with no config file, an interactive setup wizard creates `.uxplintrc.json`:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx uxp-lint
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or create `.uxplintrc.json` manually at the app root:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"v5": true,
|
|
56
|
+
"viewsSrc": "./Resources/views/src",
|
|
57
|
+
"configYml": "./Configuration.yml",
|
|
58
|
+
"bundleJson": "./Resources/views/bundle.json",
|
|
59
|
+
"bulkImportsDir": "./BulkImports",
|
|
60
|
+
"rules": {},
|
|
61
|
+
"bundleSize": { "maxKb": 1024, "severity": "warn" },
|
|
62
|
+
"duplication": { "minLines": 10, "severity": "warn" },
|
|
63
|
+
"ai": { "enabled": false, "model": "claude-sonnet-4-6" }
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### App prefix (optional)
|
|
68
|
+
|
|
69
|
+
The SCSS validator auto-detects your app prefix from `global.scss`. To set it explicitly:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{ "appPrefix": "ilocapp_" }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Override rule severity
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
{
|
|
79
|
+
"rules": {
|
|
80
|
+
"no-hardcoded-jsx-text": "error",
|
|
81
|
+
"no-native-html-interactive": "off"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Usage
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Interactive run (default)
|
|
92
|
+
uxp-lint
|
|
93
|
+
|
|
94
|
+
# Auto-fix formatting and safe ESLint issues
|
|
95
|
+
uxp-lint --fix
|
|
96
|
+
|
|
97
|
+
# CI mode — exits with code 1 on any error
|
|
98
|
+
uxp-lint --ci
|
|
99
|
+
|
|
100
|
+
# Write full report to a file (default: uxp-lint-report.txt)
|
|
101
|
+
uxp-lint --report
|
|
102
|
+
uxp-lint --report my-report.txt
|
|
103
|
+
|
|
104
|
+
# List all rules
|
|
105
|
+
uxp-lint --list-rules
|
|
106
|
+
|
|
107
|
+
# Explain a specific rule
|
|
108
|
+
uxp-lint --explain no-direct-uxp-override
|
|
109
|
+
|
|
110
|
+
# Include AI code review (Claude API)
|
|
111
|
+
UXP_CLAUDE_API_KEY=sk-ant-... uxp-lint --ai
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Terminal output
|
|
115
|
+
|
|
116
|
+
Messages are grouped by file within each check group. Each file header shows the error and warning count at a glance:
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
── Code Quality (ESLint)
|
|
120
|
+
|
|
121
|
+
src/components/AuthManager.tsx 1 error 4 warnings
|
|
122
|
+
14:14 error Component "AuthManager" should be wrapped with memo(). [uxp/require-memo]
|
|
123
|
+
28:8 warn React Hook useEffect has a missing dependency: 'loadConfigs'. [react-hooks/exhaustive-deps]
|
|
124
|
+
...
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
A summary table is always printed at the end:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
┌────────────────────────────────┬────────┬──────────┬────────────┐
|
|
131
|
+
│ Group │ Status │ Errors │ Warnings │
|
|
132
|
+
├────────────────────────────────┼────────┼──────────┼────────────┤
|
|
133
|
+
│ Configuration.yml │ ✔ │ – │ – │
|
|
134
|
+
│ bundle.json │ ✔ │ – │ 1 │
|
|
135
|
+
│ Code Quality (ESLint) │ ✗ │ 418 │ 270 │
|
|
136
|
+
│ Formatting (Prettier) │ ✔ │ – │ 115 │
|
|
137
|
+
│ SCSS Rules │ ✗ │ 2 │ – │
|
|
138
|
+
├────────────────────────────────┼────────┼──────────┼────────────┤
|
|
139
|
+
│ Total │ │ 420 │ 398 │
|
|
140
|
+
└────────────────────────────────┴────────┴──────────┴────────────┘
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
With `--report`, per-group details are written to the file only; the terminal shows the summary table and the report path.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
### Prerequisites
|
|
150
|
+
|
|
151
|
+
- Node ≥ 18
|
|
152
|
+
- npm ≥ 9
|
|
153
|
+
|
|
154
|
+
### Building
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
cd /path/to/uxp-lint
|
|
158
|
+
|
|
159
|
+
# Install dependencies
|
|
160
|
+
npm install
|
|
161
|
+
|
|
162
|
+
# Build (compiles TypeScript to dist/)
|
|
163
|
+
npm run build
|
|
164
|
+
|
|
165
|
+
# Watch mode
|
|
166
|
+
npm run watch
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Testing against a real app
|
|
170
|
+
|
|
171
|
+
The fastest way to test is to point the CLI at a local v5 app without installing:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# From inside the app directory (e.g. Location/5.0)
|
|
175
|
+
node /path/to/uxp-lint/bin/uxp-lint.js
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Or use a temporary config file to avoid touching the app's own `.uxplintrc.json`:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Write a temp config
|
|
182
|
+
cat > /tmp/lint-test.json << 'EOF'
|
|
183
|
+
{
|
|
184
|
+
"v5": true,
|
|
185
|
+
"viewsSrc": "./Resources/views/src",
|
|
186
|
+
"configYml": "./Configuration.yml",
|
|
187
|
+
"bulkImportsDir": "./BulkImports",
|
|
188
|
+
"rules": {},
|
|
189
|
+
"ai": { "enabled": false }
|
|
190
|
+
}
|
|
191
|
+
EOF
|
|
192
|
+
|
|
193
|
+
# Copy to app dir, run, then clean up
|
|
194
|
+
cp /tmp/lint-test.json /path/to/app/.uxplintrc.json
|
|
195
|
+
node /path/to/uxp-lint/bin/uxp-lint.js --ci
|
|
196
|
+
rm /path/to/app/.uxplintrc.json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Testing specific features
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# Verify all 23 rules are registered
|
|
203
|
+
node bin/uxp-lint.js --list-rules
|
|
204
|
+
|
|
205
|
+
# Test --explain for a rule (picks any rule id from --list-rules)
|
|
206
|
+
node bin/uxp-lint.js --explain folder-structure
|
|
207
|
+
node bin/uxp-lint.js --explain no-direct-uxp-override
|
|
208
|
+
node bin/uxp-lint.js --explain require-memo
|
|
209
|
+
|
|
210
|
+
# Test --fix (run inside an app directory with .uxplintrc.json)
|
|
211
|
+
node /path/to/uxp-lint/bin/uxp-lint.js --fix
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Adding a new ESLint rule
|
|
215
|
+
|
|
216
|
+
1. Create `src/eslint/rules/<rule-name>.ts`
|
|
217
|
+
2. Export `doc: RuleDoc` and a default `Rule.RuleModule`
|
|
218
|
+
3. Register in `src/eslint/config.ts` — add to `UXP_RULES` and `rules[...]`
|
|
219
|
+
4. Import `doc` in `src/rule-registry.ts` and add to `ALL_RULES`
|
|
220
|
+
5. Add the rule ID + default severity to `DEFAULT_CONFIG.rules` in `src/types.ts`
|
|
221
|
+
6. `npm run build` — zero errors
|
|
222
|
+
|
|
223
|
+
### Adding a new validator
|
|
224
|
+
|
|
225
|
+
1. Create `src/validators/<name>.ts`
|
|
226
|
+
2. Export `doc: RuleDoc` (or `docs: RuleDoc[]` for multiple rule IDs)
|
|
227
|
+
3. Export an `async function validate<Name>(config, cwd): Promise<GroupResult>`
|
|
228
|
+
4. Import and call it in `src/runner.ts`
|
|
229
|
+
5. Import `doc`/`docs` in `src/rule-registry.ts` and add to `ALL_RULES`
|
|
230
|
+
6. `npm run build` — zero errors
|
|
231
|
+
|
|
232
|
+
### Project structure
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
src/
|
|
236
|
+
cli.ts Entry point — parses CLI args, calls runner
|
|
237
|
+
runner.ts Orchestrates all validators in order
|
|
238
|
+
types.ts Shared interfaces (LintConfig, GroupResult, RuleDoc, …)
|
|
239
|
+
rule-registry.ts Collects all rule docs into ALL_RULES
|
|
240
|
+
rules-reader.ts Public API: loadAllRules(), findRule(), getRulesAsContext()
|
|
241
|
+
reporter.ts Console output formatting
|
|
242
|
+
setup.ts Interactive first-run config wizard
|
|
243
|
+
eslint/
|
|
244
|
+
config.ts Loads standard recommended configs + registers custom rules
|
|
245
|
+
rules/ 9 custom UXP/iviva ESLint rules (each exports doc + Rule.RuleModule)
|
|
246
|
+
validators/ File-system and static validators (each exports doc + validate fn)
|
|
247
|
+
|
|
248
|
+
bin/
|
|
249
|
+
uxp-lint.js Shell wrapper that calls dist/cli.js
|
|
250
|
+
|
|
251
|
+
templates/
|
|
252
|
+
prettier.config.js Prettier config template (copied to project on first run)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## Publishing
|
|
258
|
+
|
|
259
|
+
Publishing is automated via GitHub Actions (`.github/workflows/npm-publish.yml`). The workflow runs on every push to `master` and publishes to npm only when the version in `package.json` has changed.
|
|
260
|
+
|
|
261
|
+
**To release a new version:**
|
|
262
|
+
|
|
263
|
+
1. Bump `version` in `package.json`
|
|
264
|
+
2. Commit with a message starting with `Release`:
|
|
265
|
+
```bash
|
|
266
|
+
git commit -m "Release 1.0.1"
|
|
267
|
+
git push
|
|
268
|
+
```
|
|
269
|
+
3. The workflow builds, publishes the package, and tags the commit `v1.0.1` automatically.
|
|
270
|
+
|
|
271
|
+
**Required GitHub secrets:**
|
|
272
|
+
- `NPM_AUTH_TOKEN` — npm access token with publish rights to `@iviva/uxp-lint`
|
|
273
|
+
- `ACTION_MONITORING_SLACK` *(optional)* — Slack webhook for build status notifications
|
package/bin/uxp-lint.js
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function main(): Promise<void>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.main = main;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const runner_1 = require("./runner");
|
|
12
|
+
const setup_1 = require("./setup");
|
|
13
|
+
const rules_reader_1 = require("./rules-reader");
|
|
14
|
+
const reporter_1 = require("./reporter");
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
+
const { version } = require('../package.json');
|
|
17
|
+
async function main() {
|
|
18
|
+
var _a, _b, _c;
|
|
19
|
+
const program = new commander_1.Command();
|
|
20
|
+
program
|
|
21
|
+
.name('uxp-lint')
|
|
22
|
+
.description('Linting and validation tool for iviva v5 UXP apps')
|
|
23
|
+
.version(version)
|
|
24
|
+
.option('--fix', 'auto-fix formatting and safe ESLint issues')
|
|
25
|
+
.option('--ci', 'non-interactive mode — exit code 1 on any error')
|
|
26
|
+
.option('--ai', 'include AI-powered code review (requires Claude API key)')
|
|
27
|
+
.option('--report [file]', 'write full report to a file (default: uxp-lint-report.txt)')
|
|
28
|
+
.option('--explain <rule>', 'show documentation for a specific rule')
|
|
29
|
+
.option('--list-rules', 'list all rules with their severity');
|
|
30
|
+
program.parse(process.argv);
|
|
31
|
+
const opts = program.opts();
|
|
32
|
+
(0, reporter_1.printHeader)(version);
|
|
33
|
+
// --explain <rule>
|
|
34
|
+
if (opts.explain) {
|
|
35
|
+
const doc = (0, rules_reader_1.findRule)(opts.explain);
|
|
36
|
+
if (!doc) {
|
|
37
|
+
(0, reporter_1.printError)(`Rule "${opts.explain}" not found. Run --list-rules to see available rules.`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
console.log(chalk_1.default.bold(` Rule: ${doc.id}`));
|
|
41
|
+
console.log(chalk_1.default.gray(` Category: ${doc.category} | Severity: ${doc.severity}`));
|
|
42
|
+
console.log('');
|
|
43
|
+
console.log(` ${doc.description}`);
|
|
44
|
+
if (doc.rationale)
|
|
45
|
+
console.log(chalk_1.default.gray(`\n Why: ${doc.rationale}`));
|
|
46
|
+
if (doc.fix)
|
|
47
|
+
console.log(chalk_1.default.cyan(`\n Fix: ${doc.fix}`));
|
|
48
|
+
console.log('');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// --list-rules
|
|
52
|
+
if (opts.listRules) {
|
|
53
|
+
const rules = (0, rules_reader_1.loadAllRules)();
|
|
54
|
+
if (rules.length === 0) {
|
|
55
|
+
(0, reporter_1.printInfo)('No rule documentation found.');
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(chalk_1.default.bold(' Available rules:'));
|
|
59
|
+
console.log('');
|
|
60
|
+
for (const rule of rules) {
|
|
61
|
+
const sev = rule.severity === 'error' ? chalk_1.default.red(rule.severity) : chalk_1.default.yellow(rule.severity);
|
|
62
|
+
console.log(` ${sev.padEnd(14)} ${chalk_1.default.cyan(rule.id.padEnd(32))} ${chalk_1.default.gray(rule.description)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
console.log('');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const cwd = process.cwd();
|
|
69
|
+
// Interactive setup if no config exists (and not in CI mode)
|
|
70
|
+
let config = (0, config_1.configExists)(cwd) ? (0, config_1.loadConfig)(cwd) : null;
|
|
71
|
+
if (!config) {
|
|
72
|
+
if (opts.ci) {
|
|
73
|
+
(0, reporter_1.printError)('No .uxplintrc.json found. Run uxp-lint (without --ci) to set up.');
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
config = await (0, setup_1.runSetup)();
|
|
77
|
+
(0, config_1.saveConfig)(config, cwd);
|
|
78
|
+
(0, reporter_1.printInfo)(`Saved .uxplintrc.json`);
|
|
79
|
+
console.log('');
|
|
80
|
+
}
|
|
81
|
+
// Resolve --report path: true (flag with no value) → default filename
|
|
82
|
+
const reportPath = opts.report
|
|
83
|
+
? (typeof opts.report === 'string' ? opts.report : 'uxp-lint-report.txt')
|
|
84
|
+
: undefined;
|
|
85
|
+
try {
|
|
86
|
+
const { errors, reportPath: writtenPath } = await (0, runner_1.runAllValidators)(config, { fix: (_a = opts.fix) !== null && _a !== void 0 ? _a : false, ci: (_b = opts.ci) !== null && _b !== void 0 ? _b : false, ai: (_c = opts.ai) !== null && _c !== void 0 ? _c : false, report: reportPath }, cwd);
|
|
87
|
+
if (writtenPath) {
|
|
88
|
+
console.log(chalk_1.default.bold(' Report: ') + chalk_1.default.cyan(path_1.default.resolve(writtenPath)));
|
|
89
|
+
console.log('');
|
|
90
|
+
}
|
|
91
|
+
if (errors > 0) {
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
(0, reporter_1.printError)(`Unexpected error: ${e}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.configExists = configExists;
|
|
7
|
+
exports.loadConfig = loadConfig;
|
|
8
|
+
exports.saveConfig = saveConfig;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const types_1 = require("./types");
|
|
12
|
+
const CONFIG_FILE = '.uxplintrc.json';
|
|
13
|
+
function configExists(cwd = process.cwd()) {
|
|
14
|
+
return fs_1.default.existsSync(path_1.default.join(cwd, CONFIG_FILE));
|
|
15
|
+
}
|
|
16
|
+
function loadConfig(cwd = process.cwd()) {
|
|
17
|
+
var _a, _b, _c, _d;
|
|
18
|
+
const configPath = path_1.default.join(cwd, CONFIG_FILE);
|
|
19
|
+
if (!fs_1.default.existsSync(configPath)) {
|
|
20
|
+
return { ...types_1.DEFAULT_CONFIG };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const raw = fs_1.default.readFileSync(configPath, 'utf8');
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
return {
|
|
26
|
+
...types_1.DEFAULT_CONFIG,
|
|
27
|
+
...parsed,
|
|
28
|
+
rules: { ...types_1.DEFAULT_CONFIG.rules, ...((_a = parsed.rules) !== null && _a !== void 0 ? _a : {}) },
|
|
29
|
+
bundleSize: { ...types_1.DEFAULT_CONFIG.bundleSize, ...((_b = parsed.bundleSize) !== null && _b !== void 0 ? _b : {}) },
|
|
30
|
+
duplication: { ...types_1.DEFAULT_CONFIG.duplication, ...((_c = parsed.duplication) !== null && _c !== void 0 ? _c : {}) },
|
|
31
|
+
ai: { ...types_1.DEFAULT_CONFIG.ai, ...((_d = parsed.ai) !== null && _d !== void 0 ? _d : {}) },
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
throw new Error(`Failed to parse ${CONFIG_FILE}: ${e}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function saveConfig(config, cwd = process.cwd()) {
|
|
39
|
+
const configPath = path_1.default.join(cwd, CONFIG_FILE);
|
|
40
|
+
fs_1.default.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;;;AAMA,oCAEC;AAED,gCAmBC;AAED,gCAGC;AAlCD,4CAAoB;AACpB,gDAAwB;AACxB,mCAAqD;AAErD,MAAM,WAAW,GAAG,iBAAiB,CAAC;AAEtC,SAAgB,YAAY,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAC9C,OAAO,YAAE,CAAC,UAAU,CAAC,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,SAAgB,UAAU,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;;IAC5C,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC/C,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,GAAG,sBAAc,EAAE,CAAC;IAC/B,CAAC;IACD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAwB,CAAC;QACtD,OAAO;YACL,GAAG,sBAAc;YACjB,GAAG,MAAM;YACT,KAAK,EAAE,EAAE,GAAG,sBAAc,CAAC,KAAK,EAAE,GAAG,CAAC,MAAA,MAAM,CAAC,KAAK,mCAAI,EAAE,CAAC,EAAE;YAC3D,UAAU,EAAE,EAAE,GAAG,sBAAc,CAAC,UAAU,EAAE,GAAG,CAAC,MAAA,MAAM,CAAC,UAAU,mCAAI,EAAE,CAAC,EAAE;YAC1E,WAAW,EAAE,EAAE,GAAG,sBAAc,CAAC,WAAW,EAAE,GAAG,CAAC,MAAA,MAAM,CAAC,WAAW,mCAAI,EAAE,CAAC,EAAE;YAC7E,EAAE,EAAE,EAAE,GAAG,sBAAc,CAAC,EAAE,EAAE,GAAG,CAAC,MAAA,MAAM,CAAC,EAAE,mCAAI,EAAE,CAAC,EAAE;SACnD,CAAC;IACJ,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,mBAAmB,WAAW,KAAK,CAAC,EAAE,CAAC,CAAC;IAC1D,CAAC;AACH,CAAC;AAED,SAAgB,UAAU,CAAC,MAAkB,EAAE,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE;IAChE,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC/C,YAAE,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAC/E,CAAC"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runEslint = runEslint;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const eslint_1 = require("eslint");
|
|
10
|
+
// Suppress @typescript-eslint version mismatch warning — the app being linted
|
|
11
|
+
// may have a newer TypeScript than @typescript-eslint/parser v7 officially supports.
|
|
12
|
+
process.env['TYPESCRIPT_ESLINT_NO_VERSION_WARNING'] = '1';
|
|
13
|
+
const require_memo_1 = __importDefault(require("./rules/require-memo"));
|
|
14
|
+
const no_inline_styles_1 = __importDefault(require("./rules/no-inline-styles"));
|
|
15
|
+
const url_params_form_state_1 = __importDefault(require("./rules/url-params-form-state"));
|
|
16
|
+
const service_config_shape_1 = __importDefault(require("./rules/service-config-shape"));
|
|
17
|
+
const no_fa_prefix_1 = __importDefault(require("./rules/no-fa-prefix"));
|
|
18
|
+
const no_bad_hook_deps_1 = __importDefault(require("./rules/no-bad-hook-deps"));
|
|
19
|
+
const no_hardcoded_jsx_text_1 = __importDefault(require("./rules/no-hardcoded-jsx-text"));
|
|
20
|
+
const no_native_html_interactive_1 = __importDefault(require("./rules/no-native-html-interactive"));
|
|
21
|
+
const event_name_format_1 = __importDefault(require("./rules/event-name-format"));
|
|
22
|
+
// ── Custom uxp/ rules ──────────────────────────────────────────────────────
|
|
23
|
+
const UXP_RULES = {
|
|
24
|
+
'require-memo': require_memo_1.default,
|
|
25
|
+
'no-inline-styles': no_inline_styles_1.default,
|
|
26
|
+
'url-params-form-state': url_params_form_state_1.default,
|
|
27
|
+
'service-config-shape': service_config_shape_1.default,
|
|
28
|
+
'no-fa-prefix': no_fa_prefix_1.default,
|
|
29
|
+
'no-bad-hook-deps': no_bad_hook_deps_1.default,
|
|
30
|
+
'no-hardcoded-jsx-text': no_hardcoded_jsx_text_1.default,
|
|
31
|
+
'no-native-html-interactive': no_native_html_interactive_1.default,
|
|
32
|
+
'event-name-format': event_name_format_1.default,
|
|
33
|
+
};
|
|
34
|
+
// Files excluded from linting — generated/vendor stubs
|
|
35
|
+
const EXCLUDED_FILES = new Set(['uxp.ts', 'uxp.d.ts', 'designer.d.ts']);
|
|
36
|
+
function collectSourceFiles(dir) {
|
|
37
|
+
if (!fs_1.default.existsSync(dir))
|
|
38
|
+
return [];
|
|
39
|
+
const results = [];
|
|
40
|
+
for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
|
|
41
|
+
const full = path_1.default.join(dir, entry.name);
|
|
42
|
+
if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== 'dist') {
|
|
43
|
+
results.push(...collectSourceFiles(full));
|
|
44
|
+
}
|
|
45
|
+
else if (entry.isFile() &&
|
|
46
|
+
(entry.name.endsWith('.ts') || entry.name.endsWith('.tsx')) &&
|
|
47
|
+
!entry.name.endsWith('.d.ts') &&
|
|
48
|
+
!EXCLUDED_FILES.has(entry.name)) {
|
|
49
|
+
results.push(full);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return results;
|
|
53
|
+
}
|
|
54
|
+
function sev(config, rule, fallback) {
|
|
55
|
+
var _a;
|
|
56
|
+
return (_a = config.rules[rule]) !== null && _a !== void 0 ? _a : fallback;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Load the recommended rule sets from each installed plugin and merge them.
|
|
60
|
+
* Using the Linter class (CJS-compatible) — no file-system plugin resolution needed.
|
|
61
|
+
*/
|
|
62
|
+
function buildRulesConfig(config, linter) {
|
|
63
|
+
const rules = {};
|
|
64
|
+
// ── eslint:recommended ──────────────────────────────────────────────────
|
|
65
|
+
// The Linter class has built-in access to eslint:recommended rules via getRules().
|
|
66
|
+
// We apply the ones flagged as recommended.
|
|
67
|
+
try {
|
|
68
|
+
const builtinRules = linter.getRules();
|
|
69
|
+
builtinRules.forEach((rule, name) => {
|
|
70
|
+
var _a, _b;
|
|
71
|
+
if ((_b = (_a = rule.meta) === null || _a === void 0 ? void 0 : _a.docs) === null || _b === void 0 ? void 0 : _b.recommended) {
|
|
72
|
+
rules[name] = 'error';
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// fallback: manual subset of eslint:recommended essentials
|
|
78
|
+
Object.assign(rules, {
|
|
79
|
+
'no-debugger': 'error',
|
|
80
|
+
'no-dupe-keys': 'error',
|
|
81
|
+
'no-duplicate-case': 'error',
|
|
82
|
+
'no-undef': 'off', // TypeScript handles this
|
|
83
|
+
'no-unreachable': 'error',
|
|
84
|
+
'no-unused-vars': 'off', // @typescript-eslint/no-unused-vars is used instead
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
// ── @typescript-eslint/recommended ─────────────────────────────────────
|
|
88
|
+
try {
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
90
|
+
const tsPlugin = require('@typescript-eslint/eslint-plugin');
|
|
91
|
+
// Register all plugin rules
|
|
92
|
+
for (const [name, rule] of Object.entries(tsPlugin.rules)) {
|
|
93
|
+
linter.defineRule(`@typescript-eslint/${name}`, rule);
|
|
94
|
+
}
|
|
95
|
+
// Apply recommended config rules
|
|
96
|
+
for (const [name, entry] of Object.entries(tsPlugin.configs.recommended.rules)) {
|
|
97
|
+
if (entry !== 'off' && entry !== 0) {
|
|
98
|
+
rules[name] = entry;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// @typescript-eslint/recommended disables base no-unused-vars — ensure our version is on
|
|
102
|
+
rules['no-unused-vars'] = 'off'; // disabled in favour of @typescript-eslint version
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// plugin not installed
|
|
106
|
+
}
|
|
107
|
+
// ── eslint-plugin-react/recommended ────────────────────────────────────
|
|
108
|
+
try {
|
|
109
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
110
|
+
const reactPlugin = require('eslint-plugin-react');
|
|
111
|
+
for (const [name, rule] of Object.entries(reactPlugin.rules)) {
|
|
112
|
+
linter.defineRule(`react/${name}`, rule);
|
|
113
|
+
}
|
|
114
|
+
for (const [name, entry] of Object.entries(reactPlugin.configs.recommended.rules)) {
|
|
115
|
+
if (entry !== 'off' && entry !== 0) {
|
|
116
|
+
rules[name] = entry;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// We use React 18 with the new JSX transform — suppress prop-types and react-in-scope
|
|
120
|
+
rules['react/prop-types'] = 'off';
|
|
121
|
+
rules['react/react-in-jsx-scope'] = 'off';
|
|
122
|
+
rules['react/display-name'] = 'warn';
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// plugin not installed
|
|
126
|
+
}
|
|
127
|
+
// ── eslint-plugin-react-hooks/recommended ──────────────────────────────
|
|
128
|
+
try {
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
130
|
+
const hooksPlugin = require('eslint-plugin-react-hooks');
|
|
131
|
+
for (const [name, rule] of Object.entries(hooksPlugin.rules)) {
|
|
132
|
+
linter.defineRule(`react-hooks/${name}`, rule);
|
|
133
|
+
}
|
|
134
|
+
for (const [name, entry] of Object.entries(hooksPlugin.configs.recommended.rules)) {
|
|
135
|
+
if (entry !== 'off' && entry !== 0) {
|
|
136
|
+
rules[name] = entry;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// plugin not installed
|
|
142
|
+
}
|
|
143
|
+
// ── iviva / UXP custom rules (uxp/ prefix) ─────────────────────────────
|
|
144
|
+
rules['uxp/require-memo'] = sev(config, 'require-memo', 'error');
|
|
145
|
+
rules['uxp/no-inline-styles'] = sev(config, 'no-inline-styles', 'error');
|
|
146
|
+
rules['uxp/url-params-form-state'] = sev(config, 'url-params-form-state', 'error');
|
|
147
|
+
rules['uxp/service-config-shape'] = sev(config, 'service-config-shape', 'error');
|
|
148
|
+
rules['uxp/no-fa-prefix'] = sev(config, 'no-fa-prefix', 'error');
|
|
149
|
+
rules['uxp/no-bad-hook-deps'] = sev(config, 'no-bad-hook-deps', 'warn');
|
|
150
|
+
rules['uxp/no-hardcoded-jsx-text'] = sev(config, 'no-hardcoded-jsx-text', 'warn');
|
|
151
|
+
rules['uxp/no-native-html-interactive'] = sev(config, 'no-native-html-interactive', 'warn');
|
|
152
|
+
rules['uxp/event-name-format'] = sev(config, 'event-name-format', 'warn');
|
|
153
|
+
return rules;
|
|
154
|
+
}
|
|
155
|
+
async function runEslint(config, cwd, fix) {
|
|
156
|
+
var _a;
|
|
157
|
+
const errors = [];
|
|
158
|
+
const warnings = [];
|
|
159
|
+
const srcDir = path_1.default.resolve(cwd, config.viewsSrc);
|
|
160
|
+
const files = collectSourceFiles(srcDir);
|
|
161
|
+
if (files.length === 0) {
|
|
162
|
+
return {
|
|
163
|
+
group: 'Code Quality (ESLint)',
|
|
164
|
+
passed: true, errors: [], warnings: [],
|
|
165
|
+
skipped: true,
|
|
166
|
+
skipReason: `No source files found in ${config.viewsSrc}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
const linter = new eslint_1.Linter();
|
|
170
|
+
// Register TypeScript parser
|
|
171
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
172
|
+
linter.defineParser('@typescript-eslint/parser', require('@typescript-eslint/parser'));
|
|
173
|
+
// Register custom rules
|
|
174
|
+
for (const [name, rule] of Object.entries(UXP_RULES)) {
|
|
175
|
+
linter.defineRule(`uxp/${name}`, rule);
|
|
176
|
+
}
|
|
177
|
+
const rules = buildRulesConfig(config, linter);
|
|
178
|
+
const lintConfig = {
|
|
179
|
+
parser: '@typescript-eslint/parser',
|
|
180
|
+
parserOptions: {
|
|
181
|
+
ecmaVersion: 2020,
|
|
182
|
+
sourceType: 'module',
|
|
183
|
+
ecmaFeatures: { jsx: true },
|
|
184
|
+
},
|
|
185
|
+
settings: {
|
|
186
|
+
react: { version: '18.3' },
|
|
187
|
+
},
|
|
188
|
+
env: { browser: true, es2020: true, node: true },
|
|
189
|
+
rules,
|
|
190
|
+
};
|
|
191
|
+
for (const filePath of files) {
|
|
192
|
+
const source = fs_1.default.readFileSync(filePath, 'utf8');
|
|
193
|
+
const relPath = path_1.default.relative(cwd, filePath);
|
|
194
|
+
let messages;
|
|
195
|
+
if (fix) {
|
|
196
|
+
const result = linter.verifyAndFix(source, lintConfig, { filename: filePath });
|
|
197
|
+
if (result.fixed)
|
|
198
|
+
fs_1.default.writeFileSync(filePath, result.output, 'utf8');
|
|
199
|
+
messages = linter.verify(result.output, lintConfig, { filename: filePath });
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
messages = linter.verify(source, lintConfig, { filename: filePath });
|
|
203
|
+
}
|
|
204
|
+
for (const msg of messages) {
|
|
205
|
+
const item = {
|
|
206
|
+
file: relPath,
|
|
207
|
+
line: msg.line,
|
|
208
|
+
col: msg.column,
|
|
209
|
+
rule: (_a = msg.ruleId) !== null && _a !== void 0 ? _a : undefined,
|
|
210
|
+
message: msg.message,
|
|
211
|
+
};
|
|
212
|
+
if (msg.severity === 2)
|
|
213
|
+
errors.push(item);
|
|
214
|
+
else if (msg.severity === 1)
|
|
215
|
+
warnings.push(item);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
group: 'Code Quality (ESLint)',
|
|
220
|
+
passed: errors.length === 0,
|
|
221
|
+
errors,
|
|
222
|
+
warnings,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=config.js.map
|