@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.
Files changed (85) hide show
  1. package/README.md +273 -0
  2. package/bin/uxp-lint.js +2 -0
  3. package/dist/cli.d.ts +1 -0
  4. package/dist/cli.js +100 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config.d.ts +4 -0
  7. package/dist/config.js +42 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/eslint/config.d.ts +2 -0
  10. package/dist/eslint/config.js +225 -0
  11. package/dist/eslint/config.js.map +1 -0
  12. package/dist/eslint/rules/event-name-format.d.ts +5 -0
  13. package/dist/eslint/rules/event-name-format.js +59 -0
  14. package/dist/eslint/rules/event-name-format.js.map +1 -0
  15. package/dist/eslint/rules/no-bad-hook-deps.d.ts +5 -0
  16. package/dist/eslint/rules/no-bad-hook-deps.js +57 -0
  17. package/dist/eslint/rules/no-bad-hook-deps.js.map +1 -0
  18. package/dist/eslint/rules/no-fa-prefix.d.ts +5 -0
  19. package/dist/eslint/rules/no-fa-prefix.js +59 -0
  20. package/dist/eslint/rules/no-fa-prefix.js.map +1 -0
  21. package/dist/eslint/rules/no-hardcoded-jsx-text.d.ts +5 -0
  22. package/dist/eslint/rules/no-hardcoded-jsx-text.js +55 -0
  23. package/dist/eslint/rules/no-hardcoded-jsx-text.js.map +1 -0
  24. package/dist/eslint/rules/no-inline-styles.d.ts +8 -0
  25. package/dist/eslint/rules/no-inline-styles.js +41 -0
  26. package/dist/eslint/rules/no-inline-styles.js.map +1 -0
  27. package/dist/eslint/rules/no-native-html-interactive.d.ts +5 -0
  28. package/dist/eslint/rules/no-native-html-interactive.js +81 -0
  29. package/dist/eslint/rules/no-native-html-interactive.js.map +1 -0
  30. package/dist/eslint/rules/require-memo.d.ts +9 -0
  31. package/dist/eslint/rules/require-memo.js +70 -0
  32. package/dist/eslint/rules/require-memo.js.map +1 -0
  33. package/dist/eslint/rules/service-config-shape.d.ts +5 -0
  34. package/dist/eslint/rules/service-config-shape.js +80 -0
  35. package/dist/eslint/rules/service-config-shape.js.map +1 -0
  36. package/dist/eslint/rules/url-params-form-state.d.ts +5 -0
  37. package/dist/eslint/rules/url-params-form-state.js +75 -0
  38. package/dist/eslint/rules/url-params-form-state.js.map +1 -0
  39. package/dist/reporter.d.ts +22 -0
  40. package/dist/reporter.js +260 -0
  41. package/dist/reporter.js.map +1 -0
  42. package/dist/rule-registry.d.ts +2 -0
  43. package/dist/rule-registry.js +46 -0
  44. package/dist/rule-registry.js.map +1 -0
  45. package/dist/rules-reader.d.ts +4 -0
  46. package/dist/rules-reader.js +16 -0
  47. package/dist/rules-reader.js.map +1 -0
  48. package/dist/runner.d.ts +7 -0
  49. package/dist/runner.js +62 -0
  50. package/dist/runner.js.map +1 -0
  51. package/dist/setup.d.ts +2 -0
  52. package/dist/setup.js +80 -0
  53. package/dist/setup.js.map +1 -0
  54. package/dist/types.d.ts +55 -0
  55. package/dist/types.js +39 -0
  56. package/dist/types.js.map +1 -0
  57. package/dist/validators/ai.d.ts +2 -0
  58. package/dist/validators/ai.js +115 -0
  59. package/dist/validators/ai.js.map +1 -0
  60. package/dist/validators/bulk-imports.d.ts +3 -0
  61. package/dist/validators/bulk-imports.js +94 -0
  62. package/dist/validators/bulk-imports.js.map +1 -0
  63. package/dist/validators/bundle-json.d.ts +3 -0
  64. package/dist/validators/bundle-json.js +130 -0
  65. package/dist/validators/bundle-json.js.map +1 -0
  66. package/dist/validators/bundle-size.d.ts +3 -0
  67. package/dist/validators/bundle-size.js +51 -0
  68. package/dist/validators/bundle-size.js.map +1 -0
  69. package/dist/validators/config-yml.d.ts +3 -0
  70. package/dist/validators/config-yml.js +148 -0
  71. package/dist/validators/config-yml.js.map +1 -0
  72. package/dist/validators/duplication.d.ts +3 -0
  73. package/dist/validators/duplication.js +65 -0
  74. package/dist/validators/duplication.js.map +1 -0
  75. package/dist/validators/folder-structure.d.ts +3 -0
  76. package/dist/validators/folder-structure.js +123 -0
  77. package/dist/validators/folder-structure.js.map +1 -0
  78. package/dist/validators/formatting.d.ts +3 -0
  79. package/dist/validators/formatting.js +97 -0
  80. package/dist/validators/formatting.js.map +1 -0
  81. package/dist/validators/scss-rules.d.ts +3 -0
  82. package/dist/validators/scss-rules.js +156 -0
  83. package/dist/validators/scss-rules.js.map +1 -0
  84. package/package.json +58 -0
  85. package/templates/prettier.config.js +11 -0
@@ -0,0 +1,55 @@
1
+ export type Severity = 'error' | 'warn' | 'off';
2
+ export interface RuleDoc {
3
+ id: string;
4
+ severity: Severity;
5
+ category: 'react' | 'configuration' | 'patterns' | 'standards';
6
+ description: string;
7
+ rationale: string;
8
+ fix: string;
9
+ }
10
+ export interface LintMessage {
11
+ file?: string;
12
+ line?: number;
13
+ col?: number;
14
+ rule?: string;
15
+ message: string;
16
+ }
17
+ export interface GroupResult {
18
+ group: string;
19
+ passed: boolean;
20
+ errors: LintMessage[];
21
+ warnings: LintMessage[];
22
+ skipped?: boolean;
23
+ skipReason?: string;
24
+ }
25
+ export interface LintConfig {
26
+ v5: boolean;
27
+ viewsSrc: string;
28
+ configYml: string;
29
+ bundleJson: string;
30
+ bulkImportsDir: string;
31
+ appPrefix?: string;
32
+ rules: Record<string, Severity>;
33
+ bundleSize: {
34
+ maxKb: number;
35
+ severity: Severity;
36
+ };
37
+ duplication: {
38
+ minLines: number;
39
+ severity: Severity;
40
+ };
41
+ ai: {
42
+ enabled: boolean;
43
+ model: string;
44
+ apiKey?: string;
45
+ };
46
+ }
47
+ export interface RunOptions {
48
+ fix: boolean;
49
+ ci: boolean;
50
+ ai: boolean;
51
+ explain?: string;
52
+ listRules?: boolean;
53
+ report?: string;
54
+ }
55
+ export declare const DEFAULT_CONFIG: LintConfig;
package/dist/types.js ADDED
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = void 0;
4
+ exports.DEFAULT_CONFIG = {
5
+ v5: true,
6
+ viewsSrc: './Resources/views/src',
7
+ configYml: './Configuration.yml',
8
+ bundleJson: './Resources/views/bundle.json',
9
+ bulkImportsDir: './BulkImports',
10
+ rules: {
11
+ // ── UXP-specific (custom rules) ────────────────────────────────────────
12
+ 'require-memo': 'error',
13
+ 'no-inline-styles': 'error',
14
+ 'url-params-form-state': 'error',
15
+ 'service-config-shape': 'error',
16
+ 'no-fa-prefix': 'error',
17
+ 'no-bad-hook-deps': 'warn',
18
+ // ── Pattern rules (warn by default, upgrade to error when ready) ────────
19
+ 'no-hardcoded-jsx-text': 'warn',
20
+ 'no-native-html-interactive': 'warn',
21
+ 'event-name-format': 'warn',
22
+ 'folder-structure': 'warn',
23
+ 'scss-app-prefix': 'warn',
24
+ 'no-direct-uxp-override': 'error',
25
+ },
26
+ bundleSize: {
27
+ maxKb: 1024,
28
+ severity: 'warn',
29
+ },
30
+ duplication: {
31
+ minLines: 10,
32
+ severity: 'warn',
33
+ },
34
+ ai: {
35
+ enabled: false,
36
+ model: 'claude-sonnet-4-6',
37
+ },
38
+ };
39
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":";;;AA4Da,QAAA,cAAc,GAAe;IACxC,EAAE,EAAE,IAAI;IACR,QAAQ,EAAE,uBAAuB;IACjC,SAAS,EAAE,qBAAqB;IAChC,UAAU,EAAE,+BAA+B;IAC3C,cAAc,EAAE,eAAe;IAC/B,KAAK,EAAE;QACL,0EAA0E;QAC1E,cAAc,EAAE,OAAO;QACvB,kBAAkB,EAAE,OAAO;QAC3B,uBAAuB,EAAE,OAAO;QAChC,sBAAsB,EAAE,OAAO;QAC/B,cAAc,EAAE,OAAO;QACvB,kBAAkB,EAAE,MAAM;QAC1B,2EAA2E;QAC3E,uBAAuB,EAAE,MAAM;QAC/B,4BAA4B,EAAE,MAAM;QACpC,mBAAmB,EAAE,MAAM;QAC3B,kBAAkB,EAAE,MAAM;QAC1B,iBAAiB,EAAE,MAAM;QACzB,wBAAwB,EAAE,OAAO;KAClC;IACD,UAAU,EAAE;QACV,KAAK,EAAE,IAAI;QACX,QAAQ,EAAE,MAAM;KACjB;IACD,WAAW,EAAE;QACX,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,MAAM;KACjB;IACD,EAAE,EAAE;QACF,OAAO,EAAE,KAAK;QACd,KAAK,EAAE,mBAAmB;KAC3B;CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { GroupResult, LintConfig } from '../types';
2
+ export declare function validateWithAI(config: LintConfig, cwd: string, rulesContext: string): Promise<GroupResult>;
@@ -0,0 +1,115 @@
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.validateWithAI = validateWithAI;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ function collectSourceFiles(dir, ext = ['.ts', '.tsx']) {
10
+ if (!fs_1.default.existsSync(dir))
11
+ return [];
12
+ const results = [];
13
+ for (const entry of fs_1.default.readdirSync(dir, { withFileTypes: true })) {
14
+ const full = path_1.default.join(dir, entry.name);
15
+ if (entry.isDirectory() && entry.name !== 'node_modules') {
16
+ results.push(...collectSourceFiles(full, ext));
17
+ }
18
+ else if (entry.isFile() && ext.some((e) => entry.name.endsWith(e))) {
19
+ results.push(full);
20
+ }
21
+ }
22
+ return results;
23
+ }
24
+ async function validateWithAI(config, cwd, rulesContext) {
25
+ var _a;
26
+ const apiKey = (_a = config.ai.apiKey) !== null && _a !== void 0 ? _a : process.env['UXP_CLAUDE_API_KEY'];
27
+ if (!apiKey) {
28
+ return {
29
+ group: 'AI Analysis',
30
+ passed: true,
31
+ errors: [],
32
+ warnings: [],
33
+ skipped: true,
34
+ skipReason: 'No API key — set UXP_CLAUDE_API_KEY or run uxp-lint setup',
35
+ };
36
+ }
37
+ const srcDir = path_1.default.resolve(cwd, config.viewsSrc);
38
+ const files = collectSourceFiles(srcDir).slice(0, 20); // limit to avoid token overflow
39
+ if (files.length === 0) {
40
+ return {
41
+ group: 'AI Analysis',
42
+ passed: true,
43
+ errors: [],
44
+ warnings: [],
45
+ skipped: true,
46
+ skipReason: 'No source files found',
47
+ };
48
+ }
49
+ let filesSummary = '';
50
+ for (const f of files) {
51
+ const rel = path_1.default.relative(cwd, f);
52
+ const content = fs_1.default.readFileSync(f, 'utf8');
53
+ // Truncate large files
54
+ const truncated = content.length > 3000 ? content.slice(0, 3000) + '\n// ... (truncated)' : content;
55
+ filesSummary += `\n\n--- ${rel} ---\n${truncated}`;
56
+ }
57
+ let Anthropic;
58
+ try {
59
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
60
+ Anthropic = require('@anthropic-ai/sdk');
61
+ }
62
+ catch {
63
+ return {
64
+ group: 'AI Analysis',
65
+ passed: true,
66
+ errors: [],
67
+ warnings: [],
68
+ skipped: true,
69
+ skipReason: '@anthropic-ai/sdk not installed',
70
+ };
71
+ }
72
+ const client = new Anthropic({ apiKey });
73
+ const prompt = `You are a code reviewer for iviva v5 UXP apps (React + TypeScript).
74
+
75
+ Rules to enforce:
76
+ ${rulesContext}
77
+
78
+ Review the following source files and report ONLY real violations. Be concise.
79
+ Format each issue as: FILE:LINE severity message [rule-id]
80
+ If no issues found, say "No issues found."
81
+
82
+ ${filesSummary}`;
83
+ try {
84
+ const response = await client.messages.create({
85
+ model: config.ai.model,
86
+ max_tokens: 1024,
87
+ messages: [{ role: 'user', content: prompt }],
88
+ });
89
+ const text = response.content.map((b) => (b.type === 'text' ? b.text : '')).join('');
90
+ const warnings = [];
91
+ if (!text.includes('No issues found')) {
92
+ for (const line of text.split('\n')) {
93
+ const trimmed = line.trim();
94
+ if (!trimmed || trimmed.startsWith('#'))
95
+ continue;
96
+ warnings.push({ message: trimmed, rule: 'ai-review' });
97
+ }
98
+ }
99
+ return {
100
+ group: 'AI Analysis',
101
+ passed: true, // AI findings are always warnings
102
+ errors: [],
103
+ warnings,
104
+ };
105
+ }
106
+ catch (e) {
107
+ return {
108
+ group: 'AI Analysis',
109
+ passed: true,
110
+ errors: [],
111
+ warnings: [{ message: `AI analysis failed: ${e}`, rule: 'ai-review' }],
112
+ };
113
+ }
114
+ }
115
+ //# sourceMappingURL=ai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/validators/ai.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { GroupResult, LintConfig, RuleDoc } from '../types';
2
+ export declare const doc: RuleDoc;
3
+ export declare function validateBulkImports(config: LintConfig, cwd: string): Promise<GroupResult>;
@@ -0,0 +1,94 @@
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.doc = void 0;
7
+ exports.validateBulkImports = validateBulkImports;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const fast_xml_parser_1 = require("fast-xml-parser");
11
+ exports.doc = {
12
+ id: 'bulk-import-name',
13
+ severity: 'error',
14
+ category: 'configuration',
15
+ description: 'BulkImports XML files must have a "name" attribute on the root element.',
16
+ rationale: 'The name attribute is used to identify the import in the UI — without it the import is anonymous and cannot be managed.',
17
+ fix: 'Add name="My Import Name" to the root XML element.',
18
+ };
19
+ const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_' });
20
+ async function validateBulkImports(config, cwd) {
21
+ const errors = [];
22
+ const warnings = [];
23
+ const dir = path_1.default.resolve(cwd, config.bulkImportsDir);
24
+ if (!fs_1.default.existsSync(dir)) {
25
+ return {
26
+ group: 'Bulk Imports',
27
+ passed: true,
28
+ errors: [],
29
+ warnings: [],
30
+ skipped: true,
31
+ skipReason: `No BulkImports/ folder found`,
32
+ };
33
+ }
34
+ const xmlFiles = fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.xml'));
35
+ if (xmlFiles.length === 0) {
36
+ return {
37
+ group: 'Bulk Imports',
38
+ passed: true,
39
+ errors: [],
40
+ warnings: [],
41
+ skipped: true,
42
+ skipReason: 'No XML files in BulkImports/',
43
+ };
44
+ }
45
+ for (const filename of xmlFiles) {
46
+ const filePath = path_1.default.join(dir, filename);
47
+ const relPath = path_1.default.join(config.bulkImportsDir, filename);
48
+ let raw;
49
+ try {
50
+ raw = fs_1.default.readFileSync(filePath, 'utf8');
51
+ }
52
+ catch (e) {
53
+ errors.push({ file: relPath, message: `Cannot read file: ${e}` });
54
+ continue;
55
+ }
56
+ let parsed;
57
+ try {
58
+ parsed = parser.parse(raw);
59
+ }
60
+ catch (e) {
61
+ errors.push({ file: relPath, message: `Invalid XML: ${e}` });
62
+ continue;
63
+ }
64
+ // Find the root element (skip ?xml declaration)
65
+ const rootKey = Object.keys(parsed).find((k) => k !== '?xml');
66
+ if (!rootKey) {
67
+ errors.push({ file: relPath, message: 'Cannot find root XML element' });
68
+ continue;
69
+ }
70
+ const root = parsed[rootKey];
71
+ const attrs = root;
72
+ // Check `name` attribute (required)
73
+ if (!attrs['@_name']) {
74
+ errors.push({
75
+ file: relPath,
76
+ message: `Missing required "name" attribute on <${rootKey}>. Add: name='...' to the root element.`,
77
+ rule: 'bulk-import-name',
78
+ });
79
+ }
80
+ // Check `schemaservice` — only warn if there's a service= attribute referencing a schema,
81
+ // but no schemaservice (informational only, hard to auto-detect)
82
+ if (attrs['@_service'] && !attrs['@_schemaservice']) {
83
+ // It's possible (but not certain) this import needs schemaservice
84
+ // Only the Location app uses this — we warn rather than error
85
+ }
86
+ }
87
+ return {
88
+ group: 'Bulk Imports',
89
+ passed: errors.length === 0,
90
+ errors,
91
+ warnings,
92
+ };
93
+ }
94
+ //# sourceMappingURL=bulk-imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulk-imports.js","sourceRoot":"","sources":["../../src/validators/bulk-imports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { GroupResult, LintConfig, RuleDoc } from '../types';
2
+ export declare const docs: RuleDoc[];
3
+ export declare function validateBundleJson(config: LintConfig, cwd: string): Promise<GroupResult>;
@@ -0,0 +1,130 @@
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.docs = void 0;
7
+ exports.validateBundleJson = validateBundleJson;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ exports.docs = [
11
+ {
12
+ id: 'bundle-label-not-component',
13
+ severity: 'error',
14
+ category: 'configuration',
15
+ description: 'bundle.json entries must use the "label" key, not "component".',
16
+ rationale: 'The "component" key is a v4 artifact — v5 uses "label" to display the UI name.',
17
+ fix: 'Rename "component" to "label" in each bundle.json entry.',
18
+ },
19
+ {
20
+ id: 'bundle-ids-match',
21
+ severity: 'error',
22
+ category: 'configuration',
23
+ description: 'All IDs registered via registerUI() in index.tsx must be present in bundle.json.',
24
+ rationale: 'Missing IDs cause the UXP shell to silently skip loading the component.',
25
+ fix: 'Add the missing ID to bundle.json uis array.',
26
+ },
27
+ {
28
+ id: 'localization-order',
29
+ severity: 'error',
30
+ category: 'configuration',
31
+ description: 'enableLocalization() must be called after all registerUI() calls in index.tsx.',
32
+ rationale: 'Calling enableLocalization() before registerUI() means some strings are never localized.',
33
+ fix: 'Move enableLocalization() to after the last registerUI() call.',
34
+ },
35
+ ];
36
+ function extractRegisterUIIds(source) {
37
+ const ids = [];
38
+ // Match: registerUI({ id: 'some-id', ... }) or registerUI({ id: "some-id", ... })
39
+ const re = /registerUI\s*\(\s*\{[^}]*id\s*:\s*['"]([^'"]+)['"]/g;
40
+ let m;
41
+ while ((m = re.exec(source)) !== null) {
42
+ ids.push(m[1]);
43
+ }
44
+ return ids;
45
+ }
46
+ async function validateBundleJson(config, cwd) {
47
+ var _a;
48
+ const errors = [];
49
+ const warnings = [];
50
+ const bundleJsonPath = path_1.default.resolve(cwd, config.bundleJson);
51
+ const indexTsxPath = path_1.default.resolve(cwd, config.viewsSrc, 'index.tsx');
52
+ // Check bundle.json exists
53
+ if (!fs_1.default.existsSync(bundleJsonPath)) {
54
+ return {
55
+ group: 'bundle.json',
56
+ passed: false,
57
+ errors: [{ message: `File not found: ${config.bundleJson}` }],
58
+ warnings: [],
59
+ };
60
+ }
61
+ let bundle;
62
+ try {
63
+ bundle = JSON.parse(fs_1.default.readFileSync(bundleJsonPath, 'utf8'));
64
+ }
65
+ catch (e) {
66
+ return {
67
+ group: 'bundle.json',
68
+ passed: false,
69
+ errors: [{ message: `Failed to parse bundle.json: ${e}` }],
70
+ warnings: [],
71
+ };
72
+ }
73
+ const uis = (_a = bundle.uis) !== null && _a !== void 0 ? _a : [];
74
+ // Check `label` not `component`
75
+ for (const ui of uis) {
76
+ if ('component' in ui && ui.component) {
77
+ errors.push({
78
+ file: config.bundleJson,
79
+ message: `Entry "${ui.id}" uses "component" key — should be "label"`,
80
+ rule: 'bundle-label-not-component',
81
+ });
82
+ }
83
+ }
84
+ const bundleIds = new Set(uis.map((u) => u.id).filter(Boolean));
85
+ // Cross-check with index.tsx registerUI calls
86
+ if (!fs_1.default.existsSync(indexTsxPath)) {
87
+ warnings.push({ message: `Cannot find ${config.viewsSrc}/index.tsx — skipping registerUI cross-check` });
88
+ return { group: 'bundle.json', passed: errors.length === 0, errors, warnings };
89
+ }
90
+ const indexSource = fs_1.default.readFileSync(indexTsxPath, 'utf8');
91
+ const registeredIds = extractRegisterUIIds(indexSource);
92
+ // IDs in index.tsx but missing from bundle.json
93
+ for (const id of registeredIds) {
94
+ if (!bundleIds.has(id)) {
95
+ errors.push({
96
+ file: config.bundleJson,
97
+ message: `ID "${id}" is registered in index.tsx but missing from bundle.json`,
98
+ rule: 'bundle-ids-match',
99
+ });
100
+ }
101
+ }
102
+ // IDs in bundle.json but not in index.tsx
103
+ const registeredSet = new Set(registeredIds);
104
+ for (const id of bundleIds) {
105
+ if (!registeredSet.has(id)) {
106
+ warnings.push({
107
+ file: config.bundleJson,
108
+ message: `ID "${id}" is in bundle.json but not found in index.tsx registerUI calls`,
109
+ rule: 'bundle-ids-match',
110
+ });
111
+ }
112
+ }
113
+ // Check enableLocalization() comes after all registerUI() in index.tsx
114
+ const lastRegisterUI = indexSource.lastIndexOf('registerUI(');
115
+ const enableLocalization = indexSource.indexOf('enableLocalization(');
116
+ if (enableLocalization !== -1 && lastRegisterUI !== -1 && enableLocalization < lastRegisterUI) {
117
+ errors.push({
118
+ file: `${config.viewsSrc}/index.tsx`,
119
+ message: 'enableLocalization() must be called after all registerUI() calls',
120
+ rule: 'localization-order',
121
+ });
122
+ }
123
+ return {
124
+ group: 'bundle.json',
125
+ passed: errors.length === 0,
126
+ errors,
127
+ warnings,
128
+ };
129
+ }
130
+ //# sourceMappingURL=bundle-json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-json.js","sourceRoot":"","sources":["../../src/validators/bundle-json.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { GroupResult, LintConfig, RuleDoc } from '../types';
2
+ export declare const doc: RuleDoc;
3
+ export declare function validateBundleSize(config: LintConfig, cwd: string): Promise<GroupResult>;
@@ -0,0 +1,51 @@
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.doc = void 0;
7
+ exports.validateBundleSize = validateBundleSize;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ exports.doc = {
11
+ id: 'bundle-size',
12
+ severity: 'warn',
13
+ category: 'standards',
14
+ description: 'The compiled dist/main.js should stay under the configured size limit (default 1MB).',
15
+ rationale: 'Large bundles slow initial load and hurt UXP shell startup time.',
16
+ fix: 'Analyze with `npx webpack-bundle-analyzer`. Lazy-load heavy components, remove unused imports.',
17
+ };
18
+ async function validateBundleSize(config, cwd) {
19
+ // Derive dist path from bundleJson location
20
+ const bundleDir = path_1.default.dirname(path_1.default.resolve(cwd, config.bundleJson));
21
+ const distFile = path_1.default.join(bundleDir, 'dist', 'main.js');
22
+ if (!fs_1.default.existsSync(distFile)) {
23
+ return {
24
+ group: 'Bundle Size',
25
+ passed: true,
26
+ errors: [],
27
+ warnings: [],
28
+ skipped: true,
29
+ skipReason: 'dist/main.js not found — run build first',
30
+ };
31
+ }
32
+ const bytes = fs_1.default.statSync(distFile).size;
33
+ const kb = Math.round(bytes / 1024);
34
+ const limitKb = config.bundleSize.maxKb;
35
+ const severity = config.bundleSize.severity;
36
+ if (kb > limitKb) {
37
+ const msg = {
38
+ file: 'dist/main.js',
39
+ message: `Bundle is ${kb}KB (limit: ${limitKb}KB)`,
40
+ rule: 'bundle-size',
41
+ };
42
+ return {
43
+ group: 'Bundle Size',
44
+ passed: severity !== 'error',
45
+ errors: severity === 'error' ? [msg] : [],
46
+ warnings: severity === 'warn' ? [msg] : [],
47
+ };
48
+ }
49
+ return { group: 'Bundle Size', passed: true, errors: [], warnings: [] };
50
+ }
51
+ //# sourceMappingURL=bundle-size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-size.js","sourceRoot":"","sources":["../../src/validators/bundle-size.ts"],"names":[],"mappings":";;;;;;AAaA,gDAoCC;AAjDD,4CAAoB;AACpB,gDAAwB;AAGX,QAAA,GAAG,GAAY;IAC1B,EAAE,EAAE,aAAa;IACjB,QAAQ,EAAE,MAAM;IAChB,QAAQ,EAAE,WAAW;IACrB,WAAW,EAAE,sFAAsF;IACnG,SAAS,EAAE,kEAAkE;IAC7E,GAAG,EAAE,gGAAgG;CACtG,CAAC;AAEK,KAAK,UAAU,kBAAkB,CAAC,MAAkB,EAAE,GAAW;IACtE,4CAA4C;IAC5C,MAAM,SAAS,GAAG,cAAI,CAAC,OAAO,CAAC,cAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;IAEzD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,KAAK,EAAE,aAAa;YACpB,MAAM,EAAE,IAAI;YACZ,MAAM,EAAE,EAAE;YACV,QAAQ,EAAE,EAAE;YACZ,OAAO,EAAE,IAAI;YACb,UAAU,EAAE,0CAA0C;SACvD,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;IACzC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;IACpC,MAAM,OAAO,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC;IACxC,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC;IAE5C,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;QACjB,MAAM,GAAG,GAAG;YACV,IAAI,EAAE,cAAc;YACpB,OAAO,EAAE,aAAa,EAAE,cAAc,OAAO,KAAK;YAClD,IAAI,EAAE,aAAa;SACpB,CAAC;QACF,OAAO;YACL,KAAK,EAAE,aAAa;YACpB,MAAM,EAAE,QAAQ,KAAK,OAAO;YAC5B,MAAM,EAAE,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE;YACzC,QAAQ,EAAE,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE;SAC3C,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { GroupResult, LintConfig, RuleDoc } from '../types';
2
+ export declare const docs: RuleDoc[];
3
+ export declare function validateConfigYml(config: LintConfig, cwd: string): Promise<GroupResult>;