@webpieces/dev-config 0.0.0-dev

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 (31) hide show
  1. package/README.md +306 -0
  2. package/bin/set-version.sh +86 -0
  3. package/bin/setup-claude-patterns.sh +51 -0
  4. package/bin/start.sh +107 -0
  5. package/bin/stop.sh +65 -0
  6. package/bin/use-local-webpieces.sh +89 -0
  7. package/bin/use-published-webpieces.sh +33 -0
  8. package/config/eslint/base.mjs +91 -0
  9. package/config/typescript/tsconfig.base.json +25 -0
  10. package/eslint-plugin/__tests__/catch-error-pattern.test.ts +360 -0
  11. package/eslint-plugin/__tests__/max-file-lines.test.ts +195 -0
  12. package/eslint-plugin/__tests__/max-method-lines.test.ts +246 -0
  13. package/eslint-plugin/index.d.ts +14 -0
  14. package/eslint-plugin/index.js +19 -0
  15. package/eslint-plugin/index.js.map +1 -0
  16. package/eslint-plugin/index.ts +18 -0
  17. package/eslint-plugin/rules/catch-error-pattern.d.ts +11 -0
  18. package/eslint-plugin/rules/catch-error-pattern.js +196 -0
  19. package/eslint-plugin/rules/catch-error-pattern.js.map +1 -0
  20. package/eslint-plugin/rules/catch-error-pattern.ts +281 -0
  21. package/eslint-plugin/rules/max-file-lines.d.ts +12 -0
  22. package/eslint-plugin/rules/max-file-lines.js +257 -0
  23. package/eslint-plugin/rules/max-file-lines.js.map +1 -0
  24. package/eslint-plugin/rules/max-file-lines.ts +272 -0
  25. package/eslint-plugin/rules/max-method-lines.d.ts +12 -0
  26. package/eslint-plugin/rules/max-method-lines.js +257 -0
  27. package/eslint-plugin/rules/max-method-lines.js.map +1 -0
  28. package/eslint-plugin/rules/max-method-lines.ts +304 -0
  29. package/package.json +54 -0
  30. package/patterns/CLAUDE.md +293 -0
  31. package/patterns/claude.patterns.md +798 -0
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # Switch back to published webpieces packages from npm
5
+ # This script removes local symlinks and reinstalls from npm
6
+
7
+ # Detect project root
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+
10
+ if [[ "$SCRIPT_DIR" == *"node_modules/@webpieces/dev-config"* ]]; then
11
+ # Running in consumer project
12
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
13
+ else
14
+ # Running in webpieces-ts workspace
15
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
16
+ fi
17
+
18
+ cd "$PROJECT_ROOT" || exit 1
19
+
20
+ echo "📦 Switching to published @webpieces packages..."
21
+
22
+ # Remove all @webpieces symlinks
23
+ echo " Removing local symlinks..."
24
+ rm -rf node_modules/@webpieces
25
+
26
+ # Reinstall from npm
27
+ echo " Reinstalling from npm..."
28
+ npm install
29
+
30
+ echo ""
31
+ echo "✅ Successfully switched to published @webpieces packages from npm"
32
+ echo " To switch back to local development, run: wp-use-local"
33
+ echo " (Make sure to set WEBPIECES_ROOT environment variable first)"
@@ -0,0 +1,91 @@
1
+ // @webpieces/dev-config base ESLint configuration
2
+ // Consumer projects can extend this configuration
3
+
4
+ import webpiecesPlugin from '../../eslint-plugin/index.js';
5
+
6
+ /**
7
+ * WebPieces base ESLint configuration using flat config format.
8
+ *
9
+ * This provides sensible defaults for TypeScript projects following
10
+ * WebPieces patterns and conventions.
11
+ *
12
+ * Includes custom WebPieces rules:
13
+ * - catch-error-pattern: Enforces toError() usage in catch blocks
14
+ *
15
+ * Usage in consumer projects:
16
+ *
17
+ * ```javascript
18
+ * // eslint.config.mjs
19
+ * import webpiecesConfig from '@webpieces/dev-config/eslint';
20
+ * import nx from '@nx/eslint-plugin';
21
+ *
22
+ * export default [
23
+ * ...webpiecesConfig,
24
+ * ...nx.configs['flat/typescript'],
25
+ * {
26
+ * // Project-specific overrides
27
+ * rules: {}
28
+ * }
29
+ * ];
30
+ * ```
31
+ */
32
+ export default [
33
+ {
34
+ // Ignore common directories
35
+ ignores: [
36
+ '**/dist',
37
+ '**/out-tsc',
38
+ '**/tmp',
39
+ '**/coverage',
40
+ '**/node_modules',
41
+ '**/.nx',
42
+ '**/.vscode',
43
+ '**/.idea',
44
+ ],
45
+ },
46
+ {
47
+ files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
48
+ plugins: {
49
+ '@webpieces': webpiecesPlugin,
50
+ },
51
+ languageOptions: {
52
+ ecmaVersion: 2021,
53
+ sourceType: 'module',
54
+ },
55
+ rules: {
56
+ // WebPieces custom rules
57
+ '@webpieces/catch-error-pattern': 'error',
58
+ // General code quality
59
+ 'no-console': 'off', // Allow console for logging
60
+ 'no-debugger': 'warn',
61
+ 'no-alert': 'warn',
62
+ 'no-var': 'error',
63
+ 'prefer-const': 'warn',
64
+ 'prefer-arrow-callback': 'warn',
65
+
66
+ // TypeScript rules (when @typescript-eslint is available)
67
+ '@typescript-eslint/no-explicit-any': 'warn', // Prefer unknown over any
68
+ '@typescript-eslint/explicit-function-return-type': 'off', // Allow inference
69
+ '@typescript-eslint/no-unused-vars': [
70
+ 'warn',
71
+ {
72
+ argsIgnorePattern: '^_',
73
+ varsIgnorePattern: '^_',
74
+ },
75
+ ],
76
+ '@typescript-eslint/no-empty-interface': 'off', // WebPieces uses classes for data
77
+ '@typescript-eslint/no-empty-function': 'off',
78
+
79
+ // Import organization
80
+ 'sort-imports': 'off', // Handled by IDE
81
+ },
82
+ },
83
+ {
84
+ // Specific rules for test files
85
+ files: ['**/*.spec.ts', '**/*.test.ts'],
86
+ rules: {
87
+ '@typescript-eslint/no-explicit-any': 'off', // Allow any in tests
88
+ '@typescript-eslint/no-non-null-assertion': 'off',
89
+ },
90
+ },
91
+ ];
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/tsconfig",
3
+ "compileOnSave": false,
4
+ "compilerOptions": {
5
+ "sourceMap": true,
6
+ "inlineSources": true,
7
+ "declaration": true,
8
+ "moduleResolution": "node",
9
+ "emitDecoratorMetadata": true,
10
+ "experimentalDecorators": true,
11
+ "importHelpers": true,
12
+ "target": "ES2021",
13
+ "module": "commonjs",
14
+ "lib": ["ES2021"],
15
+ "skipLibCheck": true,
16
+ "skipDefaultLibCheck": true,
17
+ "strict": true,
18
+ "esModuleInterop": true,
19
+ "resolveJsonModule": true,
20
+ "forceConsistentCasingInFileNames": true,
21
+ "noImplicitReturns": true,
22
+ "noFallthroughCasesInSwitch": true
23
+ },
24
+ "exclude": ["node_modules", "tmp", "dist", "out-tsc", "**/*.spec.ts", "**/*.test.ts"]
25
+ }
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Tests for catch-error-pattern ESLint rule
3
+ */
4
+
5
+ /* eslint-disable @webpieces/catch-error-pattern */
6
+ import { RuleTester } from 'eslint';
7
+ import rule from '../rules/catch-error-pattern';
8
+
9
+ // Use require to load parser at runtime (avoids TypeScript import issues)
10
+ const tsParser = require('@typescript-eslint/parser');
11
+
12
+ const ruleTester = new RuleTester({
13
+ languageOptions: {
14
+ parser: tsParser,
15
+ parserOptions: {
16
+ ecmaVersion: 2020,
17
+ sourceType: 'module',
18
+ },
19
+ },
20
+ });
21
+
22
+ ruleTester.run('catch-error-pattern', rule, {
23
+ valid: [
24
+ // Pattern 1: Standard toError usage
25
+ {
26
+ code: `try {
27
+ doSomething();
28
+ } catch (err: any) {
29
+ const error = toError(err);
30
+ }`,
31
+ },
32
+ // Pattern 1 with additional statements after toError
33
+ {
34
+ code: `try {
35
+ doSomething();
36
+ } catch (err: any) {
37
+ const error = toError(err);
38
+ console.log('Error occurred:', error);
39
+ throw error;
40
+ }`,
41
+ },
42
+ // Pattern 2: Explicitly ignored error
43
+ {
44
+ code: `try {
45
+ doSomething();
46
+ } catch (err: any) {
47
+ //const error = toError(err);
48
+ }`,
49
+ },
50
+ // Pattern 2 with extra whitespace
51
+ {
52
+ code: `try {
53
+ doSomething();
54
+ } catch (err: any) {
55
+ // const error = toError(err);
56
+ }`,
57
+ },
58
+ // Pattern 3: Nested catch blocks
59
+ {
60
+ code: `try {
61
+ doSomething();
62
+ } catch (err: any) {
63
+ const error = toError(err);
64
+ try {
65
+ cleanup();
66
+ } catch (err2: any) {
67
+ const error2 = toError(err2);
68
+ }
69
+ }`,
70
+ },
71
+ // Triple nested
72
+ {
73
+ code: `try {
74
+ operation1();
75
+ } catch (err: any) {
76
+ const error = toError(err);
77
+ try {
78
+ operation2();
79
+ } catch (err2: any) {
80
+ const error2 = toError(err2);
81
+ try {
82
+ operation3();
83
+ } catch (err3: any) {
84
+ const error3 = toError(err3);
85
+ }
86
+ }
87
+ }`,
88
+ },
89
+ // With finally block
90
+ {
91
+ code: `try {
92
+ doSomething();
93
+ } catch (err: any) {
94
+ const error = toError(err);
95
+ } finally {
96
+ cleanup();
97
+ }`,
98
+ },
99
+ // Re-throwing after toError
100
+ {
101
+ code: `try {
102
+ doSomething();
103
+ } catch (err: any) {
104
+ const error = toError(err);
105
+ logger.error(error);
106
+ throw error;
107
+ }`,
108
+ },
109
+ ],
110
+
111
+ invalid: [
112
+ // Wrong parameter name (e instead of err)
113
+ {
114
+ code: `
115
+ try {
116
+ doSomething();
117
+ } catch (e: any) {
118
+ const error = toError(e);
119
+ }
120
+ `,
121
+ errors: [
122
+ {
123
+ messageId: 'wrongParameterName',
124
+ data: { actual: 'e' },
125
+ },
126
+ ],
127
+ },
128
+ // Wrong parameter name (error instead of err) AND wrong variable name
129
+ {
130
+ code: `
131
+ try {
132
+ doSomething();
133
+ } catch (error: any) {
134
+ const error2 = toError(error);
135
+ }
136
+ `,
137
+ errors: [
138
+ {
139
+ messageId: 'wrongParameterName',
140
+ data: { actual: 'error' },
141
+ },
142
+ {
143
+ messageId: 'wrongVariableName',
144
+ data: { expected: 'error', actual: 'error2' },
145
+ },
146
+ ],
147
+ },
148
+ // Missing type annotation
149
+ {
150
+ code: `
151
+ try {
152
+ doSomething();
153
+ } catch (err) {
154
+ const error = toError(err);
155
+ }
156
+ `,
157
+ errors: [
158
+ {
159
+ messageId: 'missingTypeAnnotation',
160
+ },
161
+ ],
162
+ },
163
+ // Wrong type annotation (Error instead of any)
164
+ {
165
+ code: `
166
+ try {
167
+ doSomething();
168
+ } catch (err: Error) {
169
+ const error = toError(err);
170
+ }
171
+ `,
172
+ errors: [
173
+ {
174
+ messageId: 'missingTypeAnnotation',
175
+ },
176
+ ],
177
+ },
178
+ // Empty catch block
179
+ {
180
+ code: `
181
+ try {
182
+ doSomething();
183
+ } catch (err: any) {
184
+ }
185
+ `,
186
+ errors: [
187
+ {
188
+ messageId: 'missingToError',
189
+ },
190
+ ],
191
+ },
192
+ // Missing toError call
193
+ {
194
+ code: `
195
+ try {
196
+ doSomething();
197
+ } catch (err: any) {
198
+ console.log(err);
199
+ }
200
+ `,
201
+ errors: [
202
+ {
203
+ messageId: 'missingToError',
204
+ },
205
+ ],
206
+ },
207
+ // Wrong variable name (e instead of error)
208
+ {
209
+ code: `
210
+ try {
211
+ doSomething();
212
+ } catch (err: any) {
213
+ const e = toError(err);
214
+ }
215
+ `,
216
+ errors: [
217
+ {
218
+ messageId: 'wrongVariableName',
219
+ data: { expected: 'error', actual: 'e' },
220
+ },
221
+ ],
222
+ },
223
+ // Wrong variable name (myError instead of error)
224
+ {
225
+ code: `
226
+ try {
227
+ doSomething();
228
+ } catch (err: any) {
229
+ const myError = toError(err);
230
+ }
231
+ `,
232
+ errors: [
233
+ {
234
+ messageId: 'wrongVariableName',
235
+ data: { expected: 'error', actual: 'myError' },
236
+ },
237
+ ],
238
+ },
239
+ // toError not first statement
240
+ {
241
+ code: `
242
+ try {
243
+ doSomething();
244
+ } catch (err: any) {
245
+ console.log('caught error');
246
+ const error = toError(err);
247
+ }
248
+ `,
249
+ errors: [
250
+ {
251
+ messageId: 'missingToError',
252
+ },
253
+ ],
254
+ },
255
+ // Using wrong function (not toError)
256
+ {
257
+ code: `
258
+ try {
259
+ doSomething();
260
+ } catch (err: any) {
261
+ const error = handleError(err);
262
+ }
263
+ `,
264
+ errors: [
265
+ {
266
+ messageId: 'missingToError',
267
+ },
268
+ ],
269
+ },
270
+ // Nested: wrong parameter name for err2
271
+ {
272
+ code: `
273
+ try {
274
+ operation1();
275
+ } catch (err: any) {
276
+ const error = toError(err);
277
+ try {
278
+ operation2();
279
+ } catch (e: any) {
280
+ const error2 = toError(e);
281
+ }
282
+ }
283
+ `,
284
+ errors: [
285
+ {
286
+ messageId: 'wrongParameterName',
287
+ data: { actual: 'e' },
288
+ },
289
+ ],
290
+ },
291
+ // Nested: wrong variable name for error2
292
+ {
293
+ code: `
294
+ try {
295
+ operation1();
296
+ } catch (err: any) {
297
+ const error = toError(err);
298
+ try {
299
+ operation2();
300
+ } catch (err2: any) {
301
+ const err = toError(err2);
302
+ }
303
+ }
304
+ `,
305
+ errors: [
306
+ {
307
+ messageId: 'wrongVariableName',
308
+ data: { expected: 'error2', actual: 'err' },
309
+ },
310
+ ],
311
+ },
312
+ // No parameter at all
313
+ {
314
+ code: `
315
+ try {
316
+ doSomething();
317
+ } catch {
318
+ console.log('error');
319
+ }
320
+ `,
321
+ errors: [
322
+ {
323
+ messageId: 'missingTypeAnnotation',
324
+ },
325
+ ],
326
+ },
327
+ // Variable declared but not initialized
328
+ {
329
+ code: `
330
+ try {
331
+ doSomething();
332
+ } catch (err: any) {
333
+ const error;
334
+ }
335
+ `,
336
+ errors: [
337
+ {
338
+ messageId: 'missingToError',
339
+ },
340
+ ],
341
+ },
342
+ // Using new Error instead of toError
343
+ {
344
+ code: `
345
+ try {
346
+ doSomething();
347
+ } catch (err: any) {
348
+ const error = new Error(err.message);
349
+ }
350
+ `,
351
+ errors: [
352
+ {
353
+ messageId: 'missingToError',
354
+ },
355
+ ],
356
+ },
357
+ ],
358
+ });
359
+
360
+ console.log('✅ All catch-error-pattern rule tests passed!');
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Tests for max-file-lines ESLint rule
3
+ */
4
+
5
+ import { RuleTester } from 'eslint';
6
+ import rule from '../rules/max-file-lines';
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+
10
+ // Use require to load parser at runtime (avoids TypeScript import issues)
11
+ const tsParser = require('@typescript-eslint/parser');
12
+
13
+ const ruleTester = new RuleTester({
14
+ languageOptions: {
15
+ parser: tsParser,
16
+ parserOptions: {
17
+ ecmaVersion: 2020,
18
+ sourceType: 'module',
19
+ },
20
+ },
21
+ });
22
+
23
+ ruleTester.run('max-file-lines', rule, {
24
+ valid: [
25
+ // Short file (well under limit)
26
+ {
27
+ code: `function shortFunc() {
28
+ return 42;
29
+ }`,
30
+ },
31
+ // File with exactly 700 lines (default limit)
32
+ {
33
+ code: Array(700)
34
+ .fill(0)
35
+ .map((_, i) => `const line${i} = ${i};`)
36
+ .join('\n'),
37
+ },
38
+ // File with 699 lines (just under default limit)
39
+ {
40
+ code: Array(699)
41
+ .fill(0)
42
+ .map((_, i) => `const line${i} = ${i};`)
43
+ .join('\n'),
44
+ },
45
+ // Custom limit: 10 lines
46
+ {
47
+ code: `function shortFunc() {
48
+ const a = 1;
49
+ const b = 2;
50
+ const c = 3;
51
+ const d = 4;
52
+ const e = 5;
53
+ return a + b + c + d + e;
54
+ }`,
55
+ options: [{ max: 10 }],
56
+ },
57
+ // Empty file
58
+ {
59
+ code: '',
60
+ },
61
+ // File with comments and blank lines (all count)
62
+ {
63
+ code: `// Comment line 1
64
+ // Comment line 2
65
+
66
+ function func() {
67
+ return 42;
68
+ }
69
+
70
+ // Another comment`,
71
+ options: [{ max: 10 }],
72
+ },
73
+ ],
74
+
75
+ invalid: [
76
+ // File with 701 lines (exceeds default limit)
77
+ {
78
+ code: Array(701)
79
+ .fill(0)
80
+ .map((_, i) => `const line${i} = ${i};`)
81
+ .join('\n'),
82
+ errors: [
83
+ {
84
+ messageId: 'tooLong',
85
+ data: { actual: '701', max: '700' },
86
+ },
87
+ ],
88
+ },
89
+ // File with 1000 lines (way over limit)
90
+ {
91
+ code: Array(1000)
92
+ .fill(0)
93
+ .map((_, i) => `const line${i} = ${i};`)
94
+ .join('\n'),
95
+ errors: [
96
+ {
97
+ messageId: 'tooLong',
98
+ data: { actual: '1000', max: '700' },
99
+ },
100
+ ],
101
+ },
102
+ // Custom limit: exceed 5 lines
103
+ {
104
+ code: `function func() {
105
+ const a = 1;
106
+ const b = 2;
107
+ const c = 3;
108
+ const d = 4;
109
+ return a + b + c + d;
110
+ }`,
111
+ options: [{ max: 5 }],
112
+ errors: [
113
+ {
114
+ messageId: 'tooLong',
115
+ data: { actual: '7', max: '5' },
116
+ },
117
+ ],
118
+ },
119
+ // Custom limit: exceed 100 lines
120
+ {
121
+ code: Array(101)
122
+ .fill(0)
123
+ .map((_, i) => `const line${i} = ${i};`)
124
+ .join('\n'),
125
+ options: [{ max: 100 }],
126
+ errors: [
127
+ {
128
+ messageId: 'tooLong',
129
+ data: { actual: '101', max: '100' },
130
+ },
131
+ ],
132
+ },
133
+ // File with blank lines and comments (all lines count)
134
+ {
135
+ code: `// Line 1
136
+ // Line 2
137
+ // Line 3
138
+
139
+ function func() {
140
+ return 42;
141
+ }
142
+
143
+ // Line 9
144
+ // Line 10
145
+ // Line 11`,
146
+ options: [{ max: 10 }],
147
+ errors: [
148
+ {
149
+ messageId: 'tooLong',
150
+ data: { actual: '11', max: '10' },
151
+ },
152
+ ],
153
+ },
154
+ ],
155
+ });
156
+
157
+ console.log('✅ All max-file-lines rule tests passed!');
158
+
159
+ // Test documentation file creation
160
+ const docPath = path.join(process.cwd(), 'tmp', 'webpieces', 'webpieces.filesize.md');
161
+
162
+ // Run a test that triggers violation
163
+ try {
164
+ ruleTester.run('max-file-lines-doc-test', rule, {
165
+ valid: [],
166
+ invalid: [
167
+ {
168
+ code: Array(800)
169
+ .fill(0)
170
+ .map((_, i) => `const line${i} = ${i};`)
171
+ .join('\n'),
172
+ errors: [{ messageId: 'tooLong' }],
173
+ },
174
+ ],
175
+ });
176
+ } catch (err: any) {
177
+ //const error = toError(err);
178
+ // Test may fail, but file should be created
179
+ }
180
+
181
+ // Verify file was created
182
+ if (!fs.existsSync(docPath)) {
183
+ throw new Error('Documentation file was not created at ' + docPath);
184
+ }
185
+
186
+ // Verify content has AI directive
187
+ const content = fs.readFileSync(docPath, 'utf-8');
188
+ if (!content.includes('READ THIS FILE to fix files that are too long')) {
189
+ throw new Error('Documentation file missing AI directive');
190
+ }
191
+ if (!content.includes('SINGLE COHESIVE UNIT')) {
192
+ throw new Error('Documentation file missing single cohesive unit principle');
193
+ }
194
+
195
+ console.log('✅ Documentation file creation test passed!');