@openwebf/webf 0.22.4 → 0.22.6

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.
@@ -0,0 +1,208 @@
1
+ # WebF TypeScript Definition Files (.d.ts) Writing Guide
2
+
3
+ This guide explains how to write TypeScript definition files (.d.ts) for WebF custom elements that will be parsed by the WebF CLI to generate Dart bindings and React/Vue components.
4
+
5
+ ## Basic Structure
6
+
7
+ Each WebF custom element should have two interfaces:
8
+ 1. `<ComponentName>Properties` - Defines the element's properties/attributes
9
+ 2. `<ComponentName>Events` - Defines the element's events
10
+
11
+ The component name is derived by removing the "Properties" or "Events" suffix.
12
+
13
+ ## Interface Naming Convention
14
+
15
+ ```typescript
16
+ // For a component named "FlutterCupertinoButton"
17
+ interface FlutterCupertinoButtonProperties {
18
+ // properties...
19
+ }
20
+
21
+ interface FlutterCupertinoButtonEvents {
22
+ // events...
23
+ }
24
+ ```
25
+
26
+ ## Properties Interface
27
+
28
+ ### Basic Property Types
29
+
30
+ The CLI supports the following TypeScript types that map to Dart types:
31
+
32
+ - `string` → `String` (Dart)
33
+ - `number` → `double` (Dart)
34
+ - `int` → `int` (Dart) - Use type alias `type int = number`
35
+ - `boolean` → `bool` (Dart)
36
+ - `any` → `dynamic` (Dart)
37
+ - `void` → `void` (Dart)
38
+
39
+ ### Property Syntax
40
+
41
+ ```typescript
42
+ interface FlutterCupertinoButtonProperties {
43
+ // Required property
44
+ variant: string;
45
+
46
+ // Optional property (use ? modifier)
47
+ size?: string;
48
+
49
+ // Boolean property (always non-nullable in Dart)
50
+ disabled?: boolean;
51
+
52
+ // Kebab-case properties (for HTML attributes)
53
+ 'pressed-opacity'?: string;
54
+ }
55
+ ```
56
+
57
+ ### Important Rules for Properties
58
+
59
+ 1. **Boolean properties are always non-nullable** in the generated Dart code, even if marked as optional with `?`
60
+ 2. **Kebab-case properties** should be quoted (e.g., `'pressed-opacity'`)
61
+ 3. **Optional properties** use the `?` modifier and will be nullable in Dart (except booleans)
62
+ 4. **Methods in Properties interface** become instance methods on the element
63
+
64
+ ### Methods in Properties
65
+
66
+ Methods defined in the Properties interface become callable methods on the element:
67
+
68
+ ```typescript
69
+ interface FlutterCupertinoInputProperties {
70
+ // Properties
71
+ val?: string;
72
+ placeholder?: string;
73
+
74
+ // Methods
75
+ getValue(): string;
76
+ setValue(value: string): void;
77
+ focus(): void;
78
+ blur(): void;
79
+ }
80
+ ```
81
+
82
+ ## Events Interface
83
+
84
+ Events are defined as properties of the Events interface, where:
85
+ - Property name = event name
86
+ - Property type = event type (usually `Event` or `CustomEvent<T>`)
87
+
88
+ ```typescript
89
+ interface FlutterCupertinoButtonEvents {
90
+ // Standard DOM event
91
+ click: Event;
92
+ }
93
+
94
+ interface FlutterCupertinoInputEvents {
95
+ // CustomEvent with string detail
96
+ input: CustomEvent<string>;
97
+ submit: CustomEvent<string>;
98
+ }
99
+
100
+ interface FlutterCupertinoSwitchEvents {
101
+ // CustomEvent with boolean detail
102
+ change: CustomEvent<boolean>;
103
+ }
104
+ ```
105
+
106
+ ### Event Type Guidelines
107
+
108
+ - Use `Event` for standard DOM events
109
+ - Use `CustomEvent<T>` for custom events with data:
110
+ - `CustomEvent<string>` for string data
111
+ - `CustomEvent<number>` for numeric data
112
+ - `CustomEvent<boolean>` for boolean data
113
+
114
+ ## Attribute Handling
115
+
116
+ The CLI automatically handles type conversion for HTML attributes:
117
+
118
+ 1. **Boolean attributes**:
119
+ - HTML: `disabled` or `disabled="true"` → Dart: `true`
120
+ - HTML: `disabled="false"` or absent → Dart: `false`
121
+
122
+ 2. **Numeric attributes**:
123
+ - HTML: `maxlength="100"` → Dart: `100` (int)
124
+ - HTML: `opacity="0.5"` → Dart: `0.5` (double)
125
+
126
+ 3. **String attributes**: Passed through as-is
127
+
128
+ ## Complete Example
129
+
130
+ Here's a complete example for a switch component:
131
+
132
+ ```typescript
133
+ // Type alias for int (optional, for clarity)
134
+ type int = number;
135
+
136
+ interface FlutterCupertinoSwitchProperties {
137
+ // Boolean property (non-nullable in Dart)
138
+ checked?: boolean;
139
+
140
+ // Boolean property
141
+ disabled?: boolean;
142
+
143
+ // Kebab-case string properties
144
+ 'active-color'?: string;
145
+ 'inactive-color'?: string;
146
+ }
147
+
148
+ interface FlutterCupertinoSwitchEvents {
149
+ // CustomEvent with boolean detail
150
+ change: CustomEvent<boolean>;
151
+ }
152
+ ```
153
+
154
+ ## Special Types and Features
155
+
156
+ ### Arrays
157
+ Arrays are supported but rarely used in element properties:
158
+ ```typescript
159
+ interface ExampleProperties {
160
+ items?: string[]; // Generates String[]? in Dart
161
+ }
162
+ ```
163
+
164
+ ### Object Types
165
+ Reference other interfaces for complex types:
166
+ ```typescript
167
+ interface ItemData {
168
+ id: string;
169
+ label: string;
170
+ }
171
+
172
+ interface ListProperties {
173
+ selectedItem?: ItemData;
174
+ }
175
+ ```
176
+
177
+ ## Best Practices
178
+
179
+ 1. **Keep interfaces simple** - Properties should represent HTML attributes or simple methods
180
+ 2. **Use optional properties** for all attributes that have default values
181
+ 3. **Document complex properties** with JSDoc comments (these are preserved in generated code)
182
+ 4. **Follow naming conventions**:
183
+ - PascalCase for interface names
184
+ - camelCase for property names (except kebab-case HTML attributes)
185
+ - Properties interface name must end with "Properties"
186
+ - Events interface name must end with "Events"
187
+
188
+ ## What to Avoid
189
+
190
+ 1. **Don't use complex union types** - Keep types simple
191
+ 2. **Don't use generics** in property types (except CustomEvent<T>)
192
+ 3. **Don't use function types** as properties - Define them as methods instead
193
+ 4. **Don't forget the Events interface** - Even if there are no events, include an empty interface
194
+
195
+ ## Testing Your Definitions
196
+
197
+ After writing your .d.ts file:
198
+
199
+ 1. Run the CLI generator to ensure it parses correctly
200
+ 2. Check the generated Dart bindings match your expectations
201
+ 3. Verify the React/Vue components have the correct prop types
202
+
203
+ ## File Naming
204
+
205
+ Name your .d.ts files to match the Dart file:
206
+ - `button.dart` → `button.d.ts`
207
+ - `switch.dart` → `switch.d.ts`
208
+ - `tab.dart` → `tab.d.ts`
package/dist/commands.js CHANGED
@@ -317,6 +317,8 @@ function generateCommand(distPath, options) {
317
317
  const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
318
318
  // Determine if we need to create a new project
319
319
  const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
320
+ // Track if this is an existing project (has all required files)
321
+ const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
320
322
  let framework = options.framework;
321
323
  let packageName = options.packageName;
322
324
  // Validate and sanitize package name if provided
@@ -526,7 +528,7 @@ function generateCommand(distPath, options) {
526
528
  // Handle npm publishing if requested via command line option
527
529
  if (options.publishToNpm && framework) {
528
530
  try {
529
- yield buildAndPublishPackage(resolvedDistPath, options.npmRegistry);
531
+ yield buildAndPublishPackage(resolvedDistPath, options.npmRegistry, isExistingProject);
530
532
  }
531
533
  catch (error) {
532
534
  console.error('\nError during npm publish:', error);
@@ -561,7 +563,7 @@ function generateCommand(distPath, options) {
561
563
  }
562
564
  }]);
563
565
  try {
564
- yield buildAndPublishPackage(resolvedDistPath, registryAnswer.registry || undefined);
566
+ yield buildAndPublishPackage(resolvedDistPath, registryAnswer.registry || undefined, isExistingProject);
565
567
  }
566
568
  catch (error) {
567
569
  console.error('\nError during npm publish:', error);
@@ -652,17 +654,34 @@ function buildPackage(packagePath) {
652
654
  }
653
655
  });
654
656
  }
655
- function buildAndPublishPackage(packagePath, registry) {
656
- return __awaiter(this, void 0, void 0, function* () {
657
+ function buildAndPublishPackage(packagePath_1, registry_1) {
658
+ return __awaiter(this, arguments, void 0, function* (packagePath, registry, isExistingProject = false) {
657
659
  const packageJsonPath = path_1.default.join(packagePath, 'package.json');
658
660
  if (!fs_1.default.existsSync(packageJsonPath)) {
659
661
  throw new Error(`No package.json found in ${packagePath}`);
660
662
  }
661
- const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
663
+ let packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
662
664
  const packageName = packageJson.name;
663
- const packageVersion = packageJson.version;
665
+ let packageVersion = packageJson.version;
664
666
  // First, ensure dependencies are installed and build the package
665
667
  yield buildPackage(packagePath);
668
+ // If this is an existing project, increment the patch version before publishing
669
+ if (isExistingProject) {
670
+ console.log(`\nIncrementing version for existing project...`);
671
+ const versionResult = (0, child_process_1.spawnSync)(NPM, ['version', 'patch', '--no-git-tag-version'], {
672
+ cwd: packagePath,
673
+ encoding: 'utf-8',
674
+ stdio: 'pipe'
675
+ });
676
+ if (versionResult.status !== 0) {
677
+ console.error('Failed to increment version:', versionResult.stderr);
678
+ throw new Error('Failed to increment version');
679
+ }
680
+ // Re-read package.json to get the new version
681
+ packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
682
+ packageVersion = packageJson.version;
683
+ console.log(`Version updated to ${packageVersion}`);
684
+ }
666
685
  // Set registry if provided
667
686
  if (registry) {
668
687
  console.log(`\nUsing npm registry: ${registry}`);
package/global.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ type double = number;
2
+ type int = number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwebf/webf",
3
- "version": "0.22.4",
3
+ "version": "0.22.6",
4
4
  "description": "Command line tools for WebF",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -11,7 +11,10 @@
11
11
  "dist",
12
12
  "src",
13
13
  "templates",
14
- "test"
14
+ "test",
15
+ "global.d.ts",
16
+ "tsconfig.json",
17
+ "TYPING_GUIDE.md"
15
18
  ],
16
19
  "scripts": {
17
20
  "build": "tsc",
package/src/commands.ts CHANGED
@@ -395,6 +395,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
395
395
  // Determine if we need to create a new project
396
396
  const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
397
397
 
398
+ // Track if this is an existing project (has all required files)
399
+ const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
400
+
398
401
  let framework = options.framework;
399
402
  let packageName = options.packageName;
400
403
 
@@ -620,7 +623,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
620
623
  // Handle npm publishing if requested via command line option
621
624
  if (options.publishToNpm && framework) {
622
625
  try {
623
- await buildAndPublishPackage(resolvedDistPath, options.npmRegistry);
626
+ await buildAndPublishPackage(resolvedDistPath, options.npmRegistry, isExistingProject);
624
627
  } catch (error) {
625
628
  console.error('\nError during npm publish:', error);
626
629
  process.exit(1);
@@ -655,7 +658,8 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
655
658
  try {
656
659
  await buildAndPublishPackage(
657
660
  resolvedDistPath,
658
- registryAnswer.registry || undefined
661
+ registryAnswer.registry || undefined,
662
+ isExistingProject
659
663
  );
660
664
  } catch (error) {
661
665
  console.error('\nError during npm publish:', error);
@@ -760,20 +764,40 @@ async function buildPackage(packagePath: string): Promise<void> {
760
764
  }
761
765
  }
762
766
 
763
- async function buildAndPublishPackage(packagePath: string, registry?: string): Promise<void> {
767
+ async function buildAndPublishPackage(packagePath: string, registry?: string, isExistingProject: boolean = false): Promise<void> {
764
768
  const packageJsonPath = path.join(packagePath, 'package.json');
765
769
 
766
770
  if (!fs.existsSync(packageJsonPath)) {
767
771
  throw new Error(`No package.json found in ${packagePath}`);
768
772
  }
769
773
 
770
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
774
+ let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
771
775
  const packageName = packageJson.name;
772
- const packageVersion = packageJson.version;
776
+ let packageVersion = packageJson.version;
773
777
 
774
778
  // First, ensure dependencies are installed and build the package
775
779
  await buildPackage(packagePath);
776
780
 
781
+ // If this is an existing project, increment the patch version before publishing
782
+ if (isExistingProject) {
783
+ console.log(`\nIncrementing version for existing project...`);
784
+ const versionResult = spawnSync(NPM, ['version', 'patch', '--no-git-tag-version'], {
785
+ cwd: packagePath,
786
+ encoding: 'utf-8',
787
+ stdio: 'pipe'
788
+ });
789
+
790
+ if (versionResult.status !== 0) {
791
+ console.error('Failed to increment version:', versionResult.stderr);
792
+ throw new Error('Failed to increment version');
793
+ }
794
+
795
+ // Re-read package.json to get the new version
796
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
797
+ packageVersion = packageJson.version;
798
+ console.log(`Version updated to ${packageVersion}`);
799
+ }
800
+
777
801
  // Set registry if provided
778
802
  if (registry) {
779
803
  console.log(`\nUsing npm registry: ${registry}`);
package/tsconfig.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "es6",
5
+ "lib": [
6
+ "es6",
7
+ "es7",
8
+ "DOM"
9
+ ],
10
+ "allowJs": false,
11
+ "moduleResolution": "node",
12
+ "forceConsistentCasingInFileNames": false,
13
+ "noImplicitReturns": true,
14
+ "noImplicitThis": true,
15
+ "noImplicitAny": true,
16
+ "strictNullChecks": true,
17
+ "noUnusedLocals": false,
18
+ "experimentalDecorators": true,
19
+ "emitDecoratorMetadata": true,
20
+ "esModuleInterop": true,
21
+ "allowSyntheticDefaultImports": true,
22
+ "declaration": false,
23
+ "outDir": "./dist"
24
+ },
25
+ "include": [
26
+ "src/**/*.ts"
27
+ ],
28
+ "exclude": [
29
+ ]
30
+ }