@openwebf/webf 0.22.1 → 0.22.4

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 (57) hide show
  1. package/bin/webf.js +1 -0
  2. package/dist/IDLBlob.js +17 -0
  3. package/dist/analyzer.js +578 -0
  4. package/dist/analyzer_original.js +467 -0
  5. package/dist/commands.js +704 -0
  6. package/dist/dart.js +300 -0
  7. package/dist/declaration.js +63 -0
  8. package/dist/generator.js +466 -0
  9. package/dist/logger.js +103 -0
  10. package/dist/react.js +283 -0
  11. package/dist/utils.js +127 -0
  12. package/dist/vue.js +159 -0
  13. package/package.json +8 -1
  14. package/src/IDLBlob.ts +2 -2
  15. package/src/analyzer.ts +19 -1
  16. package/src/commands.ts +201 -22
  17. package/src/dart.ts +172 -11
  18. package/src/declaration.ts +5 -0
  19. package/src/generator.ts +82 -14
  20. package/src/react.ts +197 -62
  21. package/templates/class.dart.tpl +10 -4
  22. package/templates/gitignore.tpl +8 -1
  23. package/templates/react.component.tsx.tpl +78 -26
  24. package/templates/react.index.ts.tpl +0 -1
  25. package/templates/react.package.json.tpl +3 -0
  26. package/test/commands.test.ts +0 -5
  27. package/test/generator.test.ts +29 -8
  28. package/test/packageName.test.ts +231 -0
  29. package/test/react.test.ts +94 -9
  30. package/CLAUDE.md +0 -206
  31. package/README-zhCN.md +0 -256
  32. package/coverage/clover.xml +0 -1295
  33. package/coverage/coverage-final.json +0 -12
  34. package/coverage/lcov-report/IDLBlob.ts.html +0 -142
  35. package/coverage/lcov-report/analyzer.ts.html +0 -2158
  36. package/coverage/lcov-report/analyzer_original.ts.html +0 -1450
  37. package/coverage/lcov-report/base.css +0 -224
  38. package/coverage/lcov-report/block-navigation.js +0 -87
  39. package/coverage/lcov-report/commands.ts.html +0 -700
  40. package/coverage/lcov-report/dart.ts.html +0 -490
  41. package/coverage/lcov-report/declaration.ts.html +0 -337
  42. package/coverage/lcov-report/favicon.png +0 -0
  43. package/coverage/lcov-report/generator.ts.html +0 -1171
  44. package/coverage/lcov-report/index.html +0 -266
  45. package/coverage/lcov-report/logger.ts.html +0 -424
  46. package/coverage/lcov-report/prettify.css +0 -1
  47. package/coverage/lcov-report/prettify.js +0 -2
  48. package/coverage/lcov-report/react.ts.html +0 -619
  49. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  50. package/coverage/lcov-report/sorter.js +0 -196
  51. package/coverage/lcov-report/utils.ts.html +0 -466
  52. package/coverage/lcov-report/vue.ts.html +0 -613
  53. package/coverage/lcov.info +0 -2149
  54. package/global.d.ts +0 -2
  55. package/jest.config.js +0 -24
  56. package/templates/react.createComponent.tpl +0 -286
  57. package/tsconfig.json +0 -30
@@ -20,7 +20,6 @@ mockFs.readFileSync = jest.fn().mockImplementation((filePath: any) => {
20
20
  if (pathStr.includes('react.package.json.tpl')) return '<%= packageName %> <%= version %> <%= description %>';
21
21
  if (pathStr.includes('react.tsconfig.json.tpl')) return 'react tsconfig';
22
22
  if (pathStr.includes('react.tsup.config.ts.tpl')) return 'tsup config';
23
- if (pathStr.includes('react.createComponent.tpl')) return 'create component';
24
23
  if (pathStr.includes('react.index.ts.tpl')) return 'index template';
25
24
  if (pathStr.includes('vue.package.json.tpl')) return '<%= packageName %> <%= version %> <%= description %>';
26
25
  if (pathStr.includes('vue.tsconfig.json.tpl')) return 'vue tsconfig';
@@ -127,10 +126,6 @@ describe('Commands', () => {
127
126
  path.join(path.resolve(target), 'src'),
128
127
  { recursive: true }
129
128
  );
130
- expect(mockFs.mkdirSync).toHaveBeenCalledWith(
131
- path.join(path.resolve(target), 'src', 'utils'),
132
- { recursive: true }
133
- );
134
129
  });
135
130
 
136
131
  it('should prompt for framework and package name when missing', async () => {
@@ -72,7 +72,7 @@ describe('Generator', () => {
72
72
 
73
73
  mockDartGenerator.generateDartClass.mockReturnValue('dart code');
74
74
  mockReactGenerator.generateReactComponent.mockReturnValue('react component');
75
- mockReactGenerator.generateReactIndex.mockReturnValue('export * from "./test"');
75
+ mockReactGenerator.generateReactIndex.mockReturnValue('export { Component, ComponentElement } from "./lib/src/html/component";');
76
76
  mockVueGenerator.generateVueTypings.mockReturnValue('vue typings');
77
77
  });
78
78
 
@@ -86,7 +86,7 @@ describe('Generator', () => {
86
86
 
87
87
  expect(mockGlob.globSync).toHaveBeenCalledWith('**/*.d.ts', {
88
88
  cwd: '/test/source',
89
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
89
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**']
90
90
  });
91
91
 
92
92
  expect(mockAnalyzer.analyzer).toHaveBeenCalledTimes(2); // For each file
@@ -103,7 +103,7 @@ describe('Generator', () => {
103
103
 
104
104
  expect(mockGlob.globSync).toHaveBeenCalledWith('**/*.d.ts', {
105
105
  cwd: expect.stringContaining('relative/source'),
106
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**']
106
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**']
107
107
  });
108
108
  });
109
109
 
@@ -270,6 +270,20 @@ describe('Generator', () => {
270
270
  'utf-8'
271
271
  );
272
272
  });
273
+
274
+ it('should handle custom exclude patterns', async () => {
275
+ await dartGen({
276
+ source: '/test/source',
277
+ target: '/test/target',
278
+ command: 'test command',
279
+ exclude: ['**/test/**', '**/docs/**']
280
+ });
281
+
282
+ expect(mockGlob.globSync).toHaveBeenCalledWith('**/*.d.ts', {
283
+ cwd: '/test/source',
284
+ ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**', '**/test/**', '**/docs/**']
285
+ });
286
+ });
273
287
  });
274
288
 
275
289
  describe('reactGen', () => {
@@ -317,6 +331,12 @@ describe('Generator', () => {
317
331
  });
318
332
 
319
333
  it('should generate index file', async () => {
334
+ // Update the mock to return proper format with export statements
335
+ mockReactGenerator.generateReactIndex.mockReturnValue(
336
+ 'export { Test, TestElement } from "./lib/src/html/test";\n' +
337
+ 'export { Component, ComponentElement } from "./lib/src/html/component";'
338
+ );
339
+
320
340
  await reactGen({
321
341
  source: '/test/source',
322
342
  target: '/test/target',
@@ -330,11 +350,12 @@ describe('Generator', () => {
330
350
  ])
331
351
  );
332
352
 
333
- expect(mockFs.writeFileSync).toHaveBeenCalledWith(
334
- expect.stringContaining('index.ts'),
335
- 'export * from "./test"',
336
- 'utf-8'
337
- );
353
+ // Check that index.ts was written (the content check is done by looking at the mock return value)
354
+ const writeCalls = mockFs.writeFileSync.mock.calls;
355
+ const indexCall = writeCalls.find(call => call[0].toString().includes('index.ts'));
356
+ expect(indexCall).toBeDefined();
357
+ expect(indexCall![1]).toContain('export { Test, TestElement }');
358
+ expect(indexCall![1]).toContain('export { Component, ComponentElement }');
338
359
  });
339
360
  });
340
361
 
@@ -0,0 +1,231 @@
1
+ // Mock fs module before importing commands
2
+ jest.mock('fs');
3
+
4
+ // Import the functions we want to test (we'll need to export them first)
5
+ // For now, let's just define them here for testing
6
+ function sanitizePackageName(name: string): string {
7
+ // Remove any leading/trailing whitespace
8
+ let sanitized = name.trim();
9
+
10
+ // Check if it's a scoped package
11
+ const isScoped = sanitized.startsWith('@');
12
+ let scope = '';
13
+ let packageName = sanitized;
14
+
15
+ if (isScoped) {
16
+ const parts = sanitized.split('/');
17
+ if (parts.length >= 2) {
18
+ scope = parts[0];
19
+ packageName = parts.slice(1).join('/');
20
+ } else {
21
+ // Invalid scoped package, treat as regular
22
+ packageName = sanitized.substring(1);
23
+ }
24
+ }
25
+
26
+ // Sanitize scope if present
27
+ if (scope) {
28
+ scope = scope.toLowerCase();
29
+ // Remove invalid characters from scope (keep only @ and alphanumeric/hyphen)
30
+ scope = scope.replace(/[^@a-z0-9-]/g, '');
31
+ if (scope === '@') {
32
+ scope = '@pkg'; // Default scope if only @ remains
33
+ }
34
+ }
35
+
36
+ // Sanitize package name part
37
+ packageName = packageName.toLowerCase();
38
+ packageName = packageName.replace(/\s+/g, '-');
39
+ packageName = packageName.replace(/[^a-z0-9\-_.]/g, '');
40
+ packageName = packageName.replace(/^[._]+/, '');
41
+ packageName = packageName.replace(/[._]+$/, '');
42
+ packageName = packageName.replace(/[-_.]{2,}/g, '-');
43
+ packageName = packageName.replace(/^-+/, '').replace(/-+$/, '');
44
+
45
+ // Ensure package name is not empty
46
+ if (!packageName) {
47
+ packageName = 'package';
48
+ }
49
+
50
+ // Ensure it starts with a letter or number
51
+ if (!/^[a-z0-9]/.test(packageName)) {
52
+ packageName = 'pkg-' + packageName;
53
+ }
54
+
55
+ // Combine scope and package name
56
+ let result = scope ? `${scope}/${packageName}` : packageName;
57
+
58
+ // Truncate to 214 characters (npm limit)
59
+ if (result.length > 214) {
60
+ if (scope) {
61
+ // Try to preserve scope
62
+ const maxPackageLength = 214 - scope.length - 1; // -1 for the /
63
+ packageName = packageName.substring(0, maxPackageLength);
64
+ packageName = packageName.replace(/[._-]+$/, '');
65
+ result = `${scope}/${packageName}`;
66
+ } else {
67
+ result = result.substring(0, 214);
68
+ result = result.replace(/[._-]+$/, '');
69
+ }
70
+ }
71
+
72
+ return result;
73
+ }
74
+
75
+ function isValidNpmPackageName(name: string): boolean {
76
+ // Check basic rules
77
+ if (!name || name.length === 0 || name.length > 214) return false;
78
+ if (name.trim() !== name) return false;
79
+
80
+ // Check if it's a scoped package
81
+ if (name.startsWith('@')) {
82
+ const parts = name.split('/');
83
+ if (parts.length !== 2) return false; // Scoped packages must have exactly one /
84
+
85
+ const scope = parts[0];
86
+ const packageName = parts[1];
87
+
88
+ // Validate scope
89
+ if (!/^@[a-z0-9][a-z0-9-]*$/.test(scope)) return false;
90
+
91
+ // Validate package name part
92
+ return isValidNpmPackageName(packageName);
93
+ }
94
+
95
+ // For non-scoped packages
96
+ if (name !== name.toLowerCase()) return false;
97
+ if (name.startsWith('.') || name.startsWith('_')) return false;
98
+
99
+ // Check for valid characters (letters, numbers, hyphens, underscores, dots)
100
+ if (!/^[a-z0-9][a-z0-9\-_.]*$/.test(name)) return false;
101
+
102
+ // Check for URL-safe characters
103
+ try {
104
+ if (encodeURIComponent(name) !== name) return false;
105
+ } catch {
106
+ return false;
107
+ }
108
+
109
+ return true;
110
+ }
111
+
112
+ describe('npm package name sanitization', () => {
113
+ describe('sanitizePackageName', () => {
114
+ it('should convert uppercase to lowercase', () => {
115
+ expect(sanitizePackageName('MyPackage')).toBe('mypackage');
116
+ expect(sanitizePackageName('HELLO-WORLD')).toBe('hello-world');
117
+ });
118
+
119
+ it('should replace spaces with hyphens', () => {
120
+ expect(sanitizePackageName('my package')).toBe('my-package');
121
+ expect(sanitizePackageName('hello world app')).toBe('hello-world-app');
122
+ });
123
+
124
+ it('should remove invalid characters', () => {
125
+ expect(sanitizePackageName('my@package!')).toBe('mypackage');
126
+ expect(sanitizePackageName('hello#world$')).toBe('helloworld');
127
+ expect(sanitizePackageName('test(1)_package')).toBe('test1_package');
128
+ });
129
+
130
+ it('should remove leading dots and underscores', () => {
131
+ expect(sanitizePackageName('.package')).toBe('package');
132
+ expect(sanitizePackageName('_package')).toBe('package');
133
+ expect(sanitizePackageName('...package')).toBe('package');
134
+ });
135
+
136
+ it('should remove trailing dots and underscores', () => {
137
+ expect(sanitizePackageName('package.')).toBe('package');
138
+ expect(sanitizePackageName('package_')).toBe('package');
139
+ expect(sanitizePackageName('package...')).toBe('package');
140
+ });
141
+
142
+ it('should handle consecutive special characters', () => {
143
+ expect(sanitizePackageName('my--package')).toBe('my-package');
144
+ expect(sanitizePackageName('hello___world')).toBe('hello-world');
145
+ expect(sanitizePackageName('test...app')).toBe('test-app');
146
+ });
147
+
148
+ it('should ensure package starts with letter or number', () => {
149
+ expect(sanitizePackageName('-package')).toBe('package');
150
+ expect(sanitizePackageName('--package')).toBe('package');
151
+ expect(sanitizePackageName('_test')).toBe('test');
152
+ expect(sanitizePackageName('.app')).toBe('app');
153
+ expect(sanitizePackageName('---')).toBe('package'); // all special chars removed, falls back to default
154
+ expect(sanitizePackageName('-')).toBe('package'); // single hyphen removed, falls back to default
155
+ });
156
+
157
+ it('should handle empty or invalid input', () => {
158
+ expect(sanitizePackageName('')).toBe('package');
159
+ expect(sanitizePackageName(' ')).toBe('package');
160
+ expect(sanitizePackageName('___')).toBe('package');
161
+ });
162
+
163
+ it('should truncate long names', () => {
164
+ const longName = 'a'.repeat(220);
165
+ const result = sanitizePackageName(longName);
166
+ expect(result.length).toBeLessThanOrEqual(214);
167
+ expect(result).toBe('a'.repeat(214));
168
+ });
169
+
170
+ it('should handle Flutter package names', () => {
171
+ expect(sanitizePackageName('webf_cupertino_ui')).toBe('webf_cupertino_ui');
172
+ expect(sanitizePackageName('WebF Cupertino UI')).toBe('webf-cupertino-ui');
173
+ expect(sanitizePackageName('flutter_package_name')).toBe('flutter_package_name');
174
+ });
175
+
176
+ it('should handle scoped packages', () => {
177
+ expect(sanitizePackageName('@openwebf/react-cupertino-ui')).toBe('@openwebf/react-cupertino-ui');
178
+ expect(sanitizePackageName('@OpenWebF/React-Cupertino-UI')).toBe('@openwebf/react-cupertino-ui');
179
+ expect(sanitizePackageName('@my-org/my-package')).toBe('@my-org/my-package');
180
+ expect(sanitizePackageName(' @scope/package ')).toBe('@scope/package');
181
+ expect(sanitizePackageName('@SCOPE/PACKAGE')).toBe('@scope/package');
182
+ expect(sanitizePackageName('@/package')).toBe('@pkg/package'); // Invalid scope
183
+ expect(sanitizePackageName('@123/package')).toBe('@123/package'); // Numeric scope is valid
184
+ expect(sanitizePackageName('@scope/my package')).toBe('@scope/my-package');
185
+ expect(sanitizePackageName('@scope/.package')).toBe('@scope/package');
186
+ expect(sanitizePackageName('@scope/_package')).toBe('@scope/package');
187
+ });
188
+ });
189
+
190
+ describe('isValidNpmPackageName', () => {
191
+ it('should accept valid package names', () => {
192
+ expect(isValidNpmPackageName('mypackage')).toBe(true);
193
+ expect(isValidNpmPackageName('my-package')).toBe(true);
194
+ expect(isValidNpmPackageName('my_package')).toBe(true);
195
+ expect(isValidNpmPackageName('my.package')).toBe(true);
196
+ expect(isValidNpmPackageName('package123')).toBe(true);
197
+ expect(isValidNpmPackageName('@openwebf/react-cupertino-ui')).toBe(true);
198
+ expect(isValidNpmPackageName('@scope/package')).toBe(true);
199
+ expect(isValidNpmPackageName('@my-org/my-package')).toBe(true);
200
+ expect(isValidNpmPackageName('@123/package')).toBe(true);
201
+ });
202
+
203
+ it('should reject invalid package names', () => {
204
+ expect(isValidNpmPackageName('MyPackage')).toBe(false); // uppercase
205
+ expect(isValidNpmPackageName('my package')).toBe(false); // space
206
+ expect(isValidNpmPackageName('.package')).toBe(false); // starts with dot
207
+ expect(isValidNpmPackageName('_package')).toBe(false); // starts with underscore
208
+ expect(isValidNpmPackageName('my@package')).toBe(false); // special char in wrong position
209
+ expect(isValidNpmPackageName('')).toBe(false); // empty
210
+ expect(isValidNpmPackageName(' package ')).toBe(false); // leading/trailing space
211
+ expect(isValidNpmPackageName('@')).toBe(false); // just @
212
+ expect(isValidNpmPackageName('@/')).toBe(false); // missing both parts
213
+ expect(isValidNpmPackageName('@scope')).toBe(false); // missing package name
214
+ expect(isValidNpmPackageName('@/package')).toBe(false); // missing scope name
215
+ expect(isValidNpmPackageName('@Scope/package')).toBe(false); // uppercase in scope
216
+ expect(isValidNpmPackageName('@scope/Package')).toBe(false); // uppercase in package
217
+ expect(isValidNpmPackageName('@scope/.package')).toBe(false); // package starts with dot
218
+ expect(isValidNpmPackageName('@scope/_package')).toBe(false); // package starts with underscore
219
+ });
220
+
221
+ it('should reject names that are too long', () => {
222
+ const longName = 'a'.repeat(215);
223
+ expect(isValidNpmPackageName(longName)).toBe(false);
224
+ });
225
+
226
+ it('should reject non-URL-safe names', () => {
227
+ expect(isValidNpmPackageName('my package')).toBe(false);
228
+ expect(isValidNpmPackageName('package?name')).toBe(false);
229
+ });
230
+ });
231
+ });
@@ -2,9 +2,32 @@ import { generateReactComponent } from '../src/react';
2
2
  import { IDLBlob } from '../src/IDLBlob';
3
3
  import { ClassObject, ClassObjectKind } from '../src/declaration';
4
4
 
5
+ // Import the toWebFTagName function for testing
6
+ import { toWebFTagName } from '../src/react';
7
+
5
8
  describe('React Generator', () => {
9
+ describe('toWebFTagName', () => {
10
+ it('should convert WebF prefixed components correctly', () => {
11
+ expect(toWebFTagName('WebFTable')).toBe('webf-table');
12
+ expect(toWebFTagName('WebFTableCell')).toBe('webf-table-cell');
13
+ expect(toWebFTagName('WebFListView')).toBe('webf-list-view');
14
+ expect(toWebFTagName('WebFTouchArea')).toBe('webf-touch-area');
15
+ });
16
+
17
+ it('should convert Flutter prefixed components correctly', () => {
18
+ expect(toWebFTagName('FlutterShimmer')).toBe('flutter-shimmer');
19
+ expect(toWebFTagName('FlutterShimmerText')).toBe('flutter-shimmer-text');
20
+ expect(toWebFTagName('FlutterShimmerAvatar')).toBe('flutter-shimmer-avatar');
21
+ });
22
+
23
+ it('should handle components without special prefixes', () => {
24
+ expect(toWebFTagName('CustomComponent')).toBe('custom-component');
25
+ expect(toWebFTagName('MyElement')).toBe('my-element');
26
+ });
27
+ });
28
+
6
29
  describe('generateReactComponent', () => {
7
- it('should use correct import path for createComponent in subdirectories', () => {
30
+ it('should import createWebFComponent from @openwebf/react-core-ui', () => {
8
31
  const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'components/ui');
9
32
 
10
33
  const properties = new ClassObject();
@@ -14,11 +37,11 @@ describe('React Generator', () => {
14
37
 
15
38
  const result = generateReactComponent(blob);
16
39
 
17
- // Component in components/ui/ needs to go up 2 levels
18
- expect(result).toContain('import { createComponent } from "../../utils/createComponent"');
40
+ // Should import from npm package by default
41
+ expect(result).toContain('import { createWebFComponent, WebFElementWithMethods } from "@openwebf/react-core-ui"');
19
42
  });
20
43
 
21
- it('should use correct import path for createComponent in root directory', () => {
44
+ it('should generate component using createWebFComponent', () => {
22
45
  const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', '');
23
46
 
24
47
  const properties = new ClassObject();
@@ -28,11 +51,13 @@ describe('React Generator', () => {
28
51
 
29
52
  const result = generateReactComponent(blob);
30
53
 
31
- // Component in root needs simple relative path
32
- expect(result).toContain('import { createComponent } from "./utils/createComponent"');
54
+ // Should use createWebFComponent
55
+ expect(result).toContain('export const TestComponent = createWebFComponent<TestComponentElement, TestComponentProps>({');
56
+ expect(result).toContain('tagName: \'test-component\'');
57
+ expect(result).toContain('displayName: \'TestComponent\'');
33
58
  });
34
59
 
35
- it('should use correct import path for single level subdirectory', () => {
60
+ it('should generate proper TypeScript interfaces', () => {
36
61
  const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'widgets');
37
62
 
38
63
  const properties = new ClassObject();
@@ -42,8 +67,68 @@ describe('React Generator', () => {
42
67
 
43
68
  const result = generateReactComponent(blob);
44
69
 
45
- // Component in widgets/ needs to go up 1 level
46
- expect(result).toContain('import { createComponent } from "../utils/createComponent"');
70
+ // Should have proper interfaces
71
+ expect(result).toContain('export interface TestComponentProps {');
72
+ expect(result).toContain('export interface TestComponentElement extends WebFElementWithMethods<{');
73
+ });
74
+
75
+ it('should generate correct tagName for WebF components', () => {
76
+ const blob = new IDLBlob('/test/source', '/test/target', 'WebFTableCell', 'test', '');
77
+
78
+ const properties = new ClassObject();
79
+ properties.name = 'WebFTableCellProperties';
80
+ properties.kind = ClassObjectKind.interface;
81
+ blob.objects = [properties];
82
+
83
+ const result = generateReactComponent(blob);
84
+
85
+ // Should generate correct tagName
86
+ expect(result).toContain('tagName: \'webf-table-cell\'');
87
+ expect(result).not.toContain('tagName: \'web-f-table-cell\'');
88
+ });
89
+
90
+ it('should generate correct tagName for Flutter components', () => {
91
+ const blob = new IDLBlob('/test/source', '/test/target', 'FlutterShimmerText', 'test', '');
92
+
93
+ const properties = new ClassObject();
94
+ properties.name = 'FlutterShimmerTextProperties';
95
+ properties.kind = ClassObjectKind.interface;
96
+ blob.objects = [properties];
97
+
98
+ const result = generateReactComponent(blob);
99
+
100
+ // Should generate correct tagName
101
+ expect(result).toContain('tagName: \'flutter-shimmer-text\'');
102
+ });
103
+
104
+ it('should use relative import for @openwebf/react-core-ui package', () => {
105
+ const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'lib/src/html');
106
+
107
+ const properties = new ClassObject();
108
+ properties.name = 'TestComponentProperties';
109
+ properties.kind = ClassObjectKind.interface;
110
+ blob.objects = [properties];
111
+
112
+ const result = generateReactComponent(blob, '@openwebf/react-core-ui', 'lib/src/html');
113
+
114
+ // Should use relative import for react-core-ui package itself
115
+ // From src/lib/src/html to src/utils: ../../../utils
116
+ expect(result).toContain('import { createWebFComponent, WebFElementWithMethods } from "../../../utils/createWebFComponent"');
117
+ });
118
+
119
+ it('should use relative import for nested directories in @openwebf/react-core-ui', () => {
120
+ const blob = new IDLBlob('/test/source', '/test/target', 'TestComponent', 'test', 'lib/src/html/shimmer');
121
+
122
+ const properties = new ClassObject();
123
+ properties.name = 'TestComponentProperties';
124
+ properties.kind = ClassObjectKind.interface;
125
+ blob.objects = [properties];
126
+
127
+ const result = generateReactComponent(blob, '@openwebf/react-core-ui', 'lib/src/html/shimmer');
128
+
129
+ // Should use relative import with correct depth
130
+ // From src/lib/src/html/shimmer to src/utils: ../../../../utils
131
+ expect(result).toContain('import { createWebFComponent, WebFElementWithMethods } from "../../../../utils/createWebFComponent"');
47
132
  });
48
133
  });
49
134
  });
package/CLAUDE.md DELETED
@@ -1,206 +0,0 @@
1
- # WebF CLI Development Guide
2
-
3
- ## Overview
4
- The WebF CLI is a code generation tool that creates type-safe bindings between Flutter/Dart and JavaScript frameworks (React, Vue). It analyzes TypeScript definition files and generates corresponding Dart classes and JavaScript/TypeScript components.
5
-
6
- ## Architecture
7
-
8
- ### Core Components
9
- - `analyzer.ts` - TypeScript AST analysis with multi-level caching
10
- - `generator.ts` - Orchestrates code generation for Dart, React, and Vue
11
- - `dart.ts` - Dart code generation from TypeScript definitions
12
- - `react.ts` - React component generation
13
- - `vue.ts` - Vue component generation
14
- - `commands.ts` - CLI command implementations
15
- - `logger.ts` - Logging utility without external dependencies
16
-
17
- ### Key Features
18
- - Multi-level caching for performance optimization
19
- - Parallel file processing
20
- - Type-safe attribute handling with automatic conversions
21
- - Comprehensive error handling and recovery
22
-
23
- ## Code Generation Patterns
24
-
25
- ### TypeScript to Dart Type Mapping
26
- ```typescript
27
- // Boolean attributes are always non-nullable in Dart
28
- interface Props {
29
- open?: boolean; // Generates: bool get open;
30
- title?: string; // Generates: String? get title;
31
- }
32
- ```
33
-
34
- ### Attribute Type Conversion
35
- HTML attributes are always strings, so the generator includes automatic type conversion:
36
- - Boolean: `value == 'true' || value == ''`
37
- - Integer: `int.tryParse(value) ?? 0`
38
- - Double: `double.tryParse(value) ?? 0.0`
39
- - String: Direct assignment
40
-
41
- ## Performance Optimizations
42
-
43
- ### Caching Strategy
44
- 1. **Source File Cache**: Parsed TypeScript AST files are cached
45
- 2. **Type Conversion Cache**: Frequently used type conversions are cached
46
- 3. **File Content Cache**: File contents are cached to detect changes
47
-
48
- ### Batch Processing
49
- Files are processed in batches for optimal parallelism:
50
- ```typescript
51
- await processFilesInBatch(items, batchSize, processor);
52
- ```
53
-
54
- ## Testing Guidelines
55
-
56
- ### Test Structure
57
- - Unit tests for all core modules
58
- - Mock file system operations before module imports
59
- - Test coverage threshold: 70%
60
-
61
- ### Running Tests
62
- ```bash
63
- npm test # Run all tests
64
- npm test -- test/analyzer.test.ts # Run specific test
65
- npm run test:coverage # Run with coverage report
66
- ```
67
-
68
- ### Mock Patterns
69
- For modules that read files at load time:
70
- ```typescript
71
- jest.mock('fs');
72
- import fs from 'fs';
73
- const mockFs = fs as jest.Mocked<typeof fs>;
74
- mockFs.readFileSync = jest.fn().mockImplementation((path) => {
75
- // Return appropriate content
76
- });
77
- // Now import the module
78
- import { moduleUnderTest } from './module';
79
- ```
80
-
81
- ## CLI Usage
82
-
83
- ### Commands
84
- ```bash
85
- # Generate code from TypeScript definitions (auto-creates project if needed)
86
- webf codegen <output-dir> --flutter-package-src=<path> [--framework=react|vue] [--package-name=<name>] [--publish-to-npm] [--npm-registry=<url>]
87
-
88
- # Create a new project without code generation
89
- webf codegen <output-dir> [--framework=react|vue] [--package-name=<name>]
90
-
91
- # Generate and publish to npm
92
- webf codegen <output-dir> --flutter-package-src=<path> --publish-to-npm
93
-
94
- # Generate and publish to custom registry
95
- webf codegen <output-dir> --flutter-package-src=<path> --publish-to-npm --npm-registry=https://custom.registry.com/
96
- ```
97
-
98
- ### Auto-creation Behavior
99
- The `generate` command now automatically detects if a project needs to be created:
100
- - If required files (package.json, global.d.ts, tsconfig.json) are missing, it will create a new project
101
- - If framework or package name are not provided, it will prompt interactively
102
- - If an existing project is detected, it will use the existing configuration
103
- - Framework can be auto-detected from existing package.json dependencies
104
-
105
- ### Metadata Synchronization
106
- When creating typing projects, the CLI automatically synchronizes metadata from the Flutter package:
107
- - Reads `pubspec.yaml` from the Flutter package source directory
108
- - Extracts version and description information
109
- - Applies this metadata to the generated `package.json` files
110
- - Ensures typing packages match the same version as the Flutter package
111
-
112
- ### Automatic Build
113
- After code generation, the CLI automatically runs `npm run build` if a build script is present in the package.json. This ensures the generated package is immediately ready for use or publishing. The build process:
114
- - Checks for the presence of a `build` script in package.json
115
- - Runs the build command if available
116
- - Continues successfully even if the build fails (with a warning)
117
- - Provides clear console output about the build status
118
-
119
- ### NPM Publishing
120
- The CLI supports automatic npm publishing with the following features:
121
- - **--publish-to-npm**: Automatically publishes the generated package to npm (build is run automatically)
122
- - **--npm-registry**: Specify a custom npm registry URL (defaults to https://registry.npmjs.org/)
123
- - **Interactive publishing**: If not using the --publish-to-npm flag, the CLI will ask if you want to publish after generation
124
- - **Registry configuration**: When choosing to publish interactively, you can specify a custom registry URL
125
- - Checks if user is logged in before attempting to publish
126
- - Temporarily sets and resets registry configuration when custom registry is used
127
-
128
- Requirements for publishing:
129
- - Must be logged in to npm (`npm login`)
130
- - Package must have a valid package.json
131
- - Package will be built automatically before publishing (if build script exists)
132
-
133
- Publishing workflow:
134
- 1. If `--publish-to-npm` is not specified, CLI prompts after successful generation
135
- 2. If user chooses to publish, CLI asks for registry URL (optional)
136
- 3. Validates npm login status
137
- 4. Publishes to specified registry (no need to build separately)
138
-
139
- ### Output Directory Behavior
140
- - Dart files are generated in the Flutter package source directory
141
- - React/Vue files are generated in the specified output directory
142
- - If no output directory is specified, a temporary directory is created
143
- - Temporary directories are created in the system temp folder with prefix `webf-typings-`
144
- - When using temporary directories, the path is displayed at the end of generation
145
- - Paths can be absolute or relative to current working directory
146
-
147
- ### Generated Files Structure
148
- When running `dartGen`:
149
- - Dart binding files are generated with `_bindings_generated.dart` suffix
150
- - Original `.d.ts` files are copied to the output directory maintaining their structure
151
- - An `index.d.ts` file is generated with:
152
- - TypeScript triple-slash references to all `.d.ts` files
153
- - ES module exports for all type definitions
154
- - Package metadata from `pubspec.yaml` in documentation comments
155
- - Directory structure from source is preserved in the output
156
-
157
- ## Development Workflow
158
-
159
- ### Adding New Features
160
- 1. Update TypeScript interfaces/types
161
- 2. Implement feature with tests
162
- 3. Update templates if needed
163
- 4. Ensure all tests pass
164
- 5. Update this documentation
165
-
166
- ### Debugging
167
- Use the logger for debugging:
168
- ```typescript
169
- import { debug, info, warn, error } from './logger';
170
- debug('Processing file:', filename);
171
- ```
172
-
173
- ### Template Modification
174
- Templates are in `/templates/*.tpl`. When modifying:
175
- 1. Update the template file
176
- 2. Update the corresponding generator function
177
- 3. Ensure generated code follows style guidelines
178
-
179
- ## Common Issues and Solutions
180
-
181
- ### Issue: Boolean attributes treated as strings
182
- **Solution**: Use `generateAttributeSetter` which handles type conversion
183
-
184
- ### Issue: Null type handling
185
- **Solution**: Check for literal types containing null:
186
- ```typescript
187
- if (type.kind === ts.SyntaxKind.LiteralType) {
188
- const literalType = type as ts.LiteralTypeNode;
189
- if (literalType.literal.kind === ts.SyntaxKind.NullKeyword) {
190
- return FunctionArgumentType.null;
191
- }
192
- }
193
- ```
194
-
195
- ### Issue: File changes not detected
196
- **Solution**: Clear caches before generation:
197
- ```typescript
198
- clearCaches();
199
- ```
200
-
201
- ## Code Style
202
- - Use async/await for asynchronous operations
203
- - Implement proper error handling with try-catch
204
- - Add descriptive error messages
205
- - Use TypeScript strict mode
206
- - Follow existing patterns in the codebase