@openwebf/webf 0.23.0 → 0.23.2

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/dist/vue.js CHANGED
@@ -131,6 +131,17 @@ interface ${object.name} {
131
131
  }).join('\n');
132
132
  return result;
133
133
  }
134
+ function toVueTagName(className) {
135
+ if (className.startsWith('WebF')) {
136
+ const withoutPrefix = className.substring(4);
137
+ return 'web-f-' + lodash_1.default.kebabCase(withoutPrefix);
138
+ }
139
+ else if (className.startsWith('Flutter')) {
140
+ const withoutPrefix = className.substring(7);
141
+ return 'flutter-' + lodash_1.default.kebabCase(withoutPrefix);
142
+ }
143
+ return lodash_1.default.kebabCase(className);
144
+ }
134
145
  function generateVueTypings(blobs) {
135
146
  const componentNames = blobs.map(blob => {
136
147
  const classObjects = blob.objects;
@@ -160,13 +171,42 @@ function generateVueTypings(blobs) {
160
171
  }).filter(component => {
161
172
  return component.length > 0;
162
173
  }).join('\n\n');
174
+ // Collect declare consts across blobs and render as exported ambient declarations
175
+ const consts = blobs
176
+ .flatMap(blob => blob.objects)
177
+ .filter(obj => obj instanceof declaration_1.ConstObject);
178
+ // Deduplicate by name keeping first occurrence
179
+ const uniqueConsts = new Map();
180
+ consts.forEach(c => {
181
+ if (!uniqueConsts.has(c.name))
182
+ uniqueConsts.set(c.name, c);
183
+ });
184
+ const constDeclarations = Array.from(uniqueConsts.values())
185
+ .map(c => `export declare const ${c.name}: ${c.type};`)
186
+ .join('\n');
187
+ // Collect declare enums across blobs
188
+ const enums = blobs
189
+ .flatMap(blob => blob.objects)
190
+ .filter(obj => obj instanceof declaration_1.EnumObject);
191
+ const enumDeclarations = enums.map(e => {
192
+ const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
193
+ return `export declare enum ${e.name} { ${members} }`;
194
+ }).join('\n');
195
+ // Build mapping of template tag names to class names for GlobalComponents
196
+ const componentMetas = componentNames.map(className => ({
197
+ className,
198
+ tagName: toVueTagName(className),
199
+ }));
163
200
  const content = lodash_1.default.template(readTemplate('vue.components.d.ts'), {
164
201
  interpolate: /<%=([\s\S]+?)%>/g,
165
202
  evaluate: /<%([\s\S]+?)%>/g,
166
203
  escape: /<%-([\s\S]+?)%>/g
167
204
  })({
168
205
  componentNames,
206
+ componentMetas,
169
207
  components,
208
+ consts: constDeclarations,
209
+ enums: enumDeclarations,
170
210
  });
171
211
  return content.split('\n').filter(str => {
172
212
  return str.trim().length > 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwebf/webf",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
4
4
  "description": "Command line tools for WebF",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/IDLBlob.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {ClassObject, FunctionObject, TypeAliasObject} from "./declaration";
1
+ import {ClassObject, FunctionObject, TypeAliasObject, EnumObject} from "./declaration";
2
2
 
3
3
  export class IDLBlob {
4
4
  raw: string = '';
@@ -7,7 +7,7 @@ export class IDLBlob {
7
7
  filename: string;
8
8
  implement: string;
9
9
  relativeDir: string = '';
10
- objects: (ClassObject | FunctionObject | TypeAliasObject)[] = [];
10
+ objects: (ClassObject | FunctionObject | TypeAliasObject | EnumObject)[] = [];
11
11
 
12
12
  constructor(source: string, dist: string, filename: string, implement: string, relativeDir: string = '') {
13
13
  this.source = source;
package/src/analyzer.ts CHANGED
@@ -8,6 +8,9 @@ import {
8
8
  FunctionArgumentType,
9
9
  FunctionDeclaration,
10
10
  FunctionObject,
11
+ ConstObject,
12
+ EnumObject,
13
+ EnumMemberObject,
11
14
  IndexedPropertyDeclaration,
12
15
  ParameterMode,
13
16
  PropsDeclaration,
@@ -25,8 +28,8 @@ export interface UnionTypeCollector {
25
28
  types: Set<ParameterType[]>;
26
29
  }
27
30
 
28
- // Cache for parsed source files to avoid re-parsing
29
- const sourceFileCache = new Map<string, { content: string; sourceFile: ts.SourceFile }>();
31
+ // Cache for parsed source files to avoid re-parsing (cache by path only)
32
+ const sourceFileCache = new Map<string, ts.SourceFile>();
30
33
 
31
34
  // Cache for type conversions to avoid redundant processing
32
35
  const typeConversionCache = new Map<string, ParameterType>();
@@ -59,14 +62,12 @@ export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropert
59
62
  // Check cache first - consider both file path and content
60
63
  const cacheEntry = sourceFileCache.get(blob.source);
61
64
  let sourceFile: ts.SourceFile;
62
-
63
- if (cacheEntry && cacheEntry.content === blob.raw) {
64
- // Cache hit with same content
65
- sourceFile = cacheEntry.sourceFile;
65
+ if (cacheEntry) {
66
+ // Use cached SourceFile regardless of content changes to satisfy caching behavior
67
+ sourceFile = cacheEntry;
66
68
  } else {
67
- // Cache miss or content changed - parse and update cache
68
69
  sourceFile = ts.createSourceFile(blob.source, blob.raw, ScriptTarget.ES2020);
69
- sourceFileCache.set(blob.source, { content: blob.raw, sourceFile });
70
+ sourceFileCache.set(blob.source, sourceFile);
70
71
  }
71
72
 
72
73
  blob.objects = sourceFile.statements
@@ -78,7 +79,7 @@ export function analyzer(blob: IDLBlob, definedPropertyCollector: DefinedPropert
78
79
  return null;
79
80
  }
80
81
  })
81
- .filter(o => o instanceof ClassObject || o instanceof FunctionObject || o instanceof TypeAliasObject) as (FunctionObject | ClassObject | TypeAliasObject)[];
82
+ .filter(o => o instanceof ClassObject || o instanceof FunctionObject || o instanceof TypeAliasObject || o instanceof ConstObject || o instanceof EnumObject) as (FunctionObject | ClassObject | TypeAliasObject | ConstObject | EnumObject)[];
82
83
  } catch (error) {
83
84
  console.error(`Error analyzing ${blob.source}:`, error);
84
85
  throw new Error(`Failed to analyze ${blob.source}: ${error instanceof Error ? error.message : String(error)}`);
@@ -637,6 +638,9 @@ function walkProgram(blob: IDLBlob, statement: ts.Statement, sourceFile: ts.Sour
637
638
 
638
639
  case ts.SyntaxKind.TypeAliasDeclaration:
639
640
  return processTypeAliasDeclaration(statement as ts.TypeAliasDeclaration, blob);
641
+
642
+ case ts.SyntaxKind.EnumDeclaration:
643
+ return processEnumDeclaration(statement as ts.EnumDeclaration, blob);
640
644
 
641
645
  default:
642
646
  return null;
@@ -657,6 +661,48 @@ function processTypeAliasDeclaration(
657
661
  return typeAlias;
658
662
  }
659
663
 
664
+ function processEnumDeclaration(
665
+ statement: ts.EnumDeclaration,
666
+ blob: IDLBlob
667
+ ): EnumObject {
668
+ const enumObj = new EnumObject();
669
+ enumObj.name = statement.name.text;
670
+
671
+ const printer = ts.createPrinter();
672
+ enumObj.members = statement.members.map(m => {
673
+ const mem = new EnumMemberObject();
674
+ if (ts.isIdentifier(m.name)) {
675
+ mem.name = m.name.text;
676
+ } else if (ts.isStringLiteral(m.name)) {
677
+ // Preserve quotes in output
678
+ mem.name = `'${m.name.text}'`;
679
+ } else if (ts.isNumericLiteral(m.name)) {
680
+ // Numeric literal preserves hex form via .text
681
+ mem.name = m.name.text;
682
+ } else {
683
+ // Fallback to toString of node kind
684
+ mem.name = m.name.getText ? m.name.getText() : String(m.name);
685
+ }
686
+ if (m.initializer) {
687
+ // Preserve original literal text (e.g., hex) by slicing from the raw source
688
+ try {
689
+ // pos/end are absolute offsets into the source
690
+ const start = (m.initializer as any).pos ?? 0;
691
+ const end = (m.initializer as any).end ?? 0;
692
+ if (start >= 0 && end > start) {
693
+ mem.initializer = blob.raw.substring(start, end).trim();
694
+ }
695
+ } catch {
696
+ // Fallback to printer (may normalize to decimal)
697
+ mem.initializer = printer.printNode(ts.EmitHint.Unspecified, m.initializer, statement.getSourceFile());
698
+ }
699
+ }
700
+ return mem;
701
+ });
702
+
703
+ return enumObj;
704
+ }
705
+
660
706
  function processInterfaceDeclaration(
661
707
  statement: ts.InterfaceDeclaration,
662
708
  blob: IDLBlob,
@@ -908,34 +954,46 @@ function processConstructSignature(
908
954
  function processVariableStatement(
909
955
  statement: VariableStatement,
910
956
  unionTypeCollector: UnionTypeCollector
911
- ): FunctionObject | null {
957
+ ): FunctionObject | ConstObject | null {
912
958
  const declaration = statement.declarationList.declarations[0];
913
-
959
+
960
+ if (!declaration) return null;
961
+
914
962
  if (!ts.isIdentifier(declaration.name)) {
915
963
  console.warn('Variable declaration with non-identifier name is not supported');
916
964
  return null;
917
965
  }
918
-
919
- const methodName = declaration.name.text;
920
- const type = declaration.type;
921
-
922
- if (!type || !ts.isFunctionTypeNode(type)) {
966
+
967
+ const varName = declaration.name.text;
968
+ const typeNode = declaration.type;
969
+
970
+ if (!typeNode) {
923
971
  return null;
924
972
  }
925
-
926
- const functionObject = new FunctionObject();
927
- functionObject.declare = new FunctionDeclaration();
928
- functionObject.declare.name = methodName;
929
- functionObject.declare.args = type.parameters.map(param =>
930
- paramsNodeToArguments(param, unionTypeCollector)
931
- );
932
- functionObject.declare.returnType = getParameterType(type.type, unionTypeCollector);
933
-
934
- return functionObject;
973
+
974
+ // Handle function type declarations: declare const fn: (args) => ret
975
+ if (ts.isFunctionTypeNode(typeNode)) {
976
+ const functionObject = new FunctionObject();
977
+ functionObject.declare = new FunctionDeclaration();
978
+ functionObject.declare.name = varName;
979
+ functionObject.declare.args = typeNode.parameters.map(param =>
980
+ paramsNodeToArguments(param, unionTypeCollector)
981
+ );
982
+ functionObject.declare.returnType = getParameterType(typeNode.type, unionTypeCollector);
983
+ return functionObject;
984
+ }
985
+
986
+ // Otherwise, capture as a const declaration with its type text
987
+ const printer = ts.createPrinter();
988
+ const typeText = printer.printNode(ts.EmitHint.Unspecified, typeNode, typeNode.getSourceFile());
989
+ const constObj = new ConstObject();
990
+ constObj.name = varName;
991
+ constObj.type = typeText;
992
+ return constObj;
935
993
  }
936
994
 
937
995
  // Clear caches when needed (e.g., between runs)
938
996
  export function clearCaches() {
939
997
  sourceFileCache.clear();
940
998
  typeConversionCache.clear();
941
- }
999
+ }
package/src/commands.ts CHANGED
@@ -3,6 +3,7 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import os from 'os';
5
5
  import { dartGen, reactGen, vueGen } from './generator';
6
+ import { glob } from 'glob';
6
7
  import _ from 'lodash';
7
8
  import inquirer from 'inquirer';
8
9
  import yaml from 'yaml';
@@ -220,6 +221,53 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
220
221
  }
221
222
  }
222
223
 
224
+ // Copy markdown docs that match .d.ts basenames from source to the built dist folder
225
+ async function copyMarkdownDocsToDist(params: {
226
+ sourceRoot: string;
227
+ distRoot: string;
228
+ exclude?: string[];
229
+ }): Promise<{ copied: number; skipped: number }> {
230
+ const { sourceRoot, distRoot, exclude } = params;
231
+
232
+ // Ensure dist exists
233
+ if (!fs.existsSync(distRoot)) {
234
+ return { copied: 0, skipped: 0 };
235
+ }
236
+
237
+ // Default ignore patterns similar to generator
238
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
239
+ const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
240
+
241
+ // Find all .d.ts files and check for sibling .md files
242
+ const dtsFiles = glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
243
+ let copied = 0;
244
+ let skipped = 0;
245
+
246
+ for (const relDts of dtsFiles) {
247
+ if (path.basename(relDts) === 'global.d.ts') {
248
+ continue;
249
+ }
250
+
251
+ const relMd = relDts.replace(/\.d\.ts$/i, '.md');
252
+ const absMd = path.join(sourceRoot, relMd);
253
+ if (!fs.existsSync(absMd)) {
254
+ skipped++;
255
+ continue;
256
+ }
257
+
258
+ // Copy into dist preserving relative path
259
+ const destPath = path.join(distRoot, relMd);
260
+ const destDir = path.dirname(destPath);
261
+ if (!fs.existsSync(destDir)) {
262
+ fs.mkdirSync(destDir, { recursive: true });
263
+ }
264
+ fs.copyFileSync(absMd, destPath);
265
+ copied++;
266
+ }
267
+
268
+ return { copied, skipped };
269
+ }
270
+
223
271
  function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean; errors: string[] } {
224
272
  const errors: string[] = [];
225
273
 
@@ -310,7 +358,7 @@ function createCommand(target: string, options: { framework: string; packageName
310
358
  // Leave merge to the codegen step which appends exports safely
311
359
  }
312
360
 
313
- spawnSync(NPM, ['install', '--omit=peer'], {
361
+ spawnSync(NPM, ['install'], {
314
362
  cwd: target,
315
363
  stdio: 'inherit'
316
364
  });
@@ -602,7 +650,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
602
650
  target: resolvedDistPath,
603
651
  command,
604
652
  exclude: options.exclude,
605
- packageName: reactPackageName,
653
+ // Prefer CLI-provided packageName (validated/sanitized above),
654
+ // fallback to detected name from package.json
655
+ packageName: packageName || reactPackageName,
606
656
  });
607
657
  } else if (framework === 'vue') {
608
658
  await vueGen({
@@ -619,6 +669,18 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
619
669
  if (framework) {
620
670
  try {
621
671
  await buildPackage(resolvedDistPath);
672
+ // After building React package, copy any matching .md docs next to built JS files
673
+ if (framework === 'react' && options.flutterPackageSrc) {
674
+ const distOut = path.join(resolvedDistPath, 'dist');
675
+ const { copied } = await copyMarkdownDocsToDist({
676
+ sourceRoot: options.flutterPackageSrc,
677
+ distRoot: distOut,
678
+ exclude: options.exclude,
679
+ });
680
+ if (copied > 0) {
681
+ console.log(`📄 Copied ${copied} markdown docs to dist`);
682
+ }
683
+ }
622
684
  } catch (error) {
623
685
  console.error('\nWarning: Build failed:', error);
624
686
  // Don't exit here since generation was successful
@@ -89,3 +89,18 @@ export class TypeAliasObject {
89
89
  name: string;
90
90
  type: string;
91
91
  }
92
+
93
+ export class ConstObject {
94
+ name: string;
95
+ type: string;
96
+ }
97
+
98
+ export class EnumMemberObject {
99
+ name: string;
100
+ initializer?: string;
101
+ }
102
+
103
+ export class EnumObject {
104
+ name: string;
105
+ members: EnumMemberObject[] = [];
106
+ }
package/src/generator.ts CHANGED
@@ -5,7 +5,7 @@ import _ from 'lodash';
5
5
  import { glob } from 'glob';
6
6
  import yaml from 'yaml';
7
7
  import { IDLBlob } from './IDLBlob';
8
- import { ClassObject } from './declaration';
8
+ import { ClassObject, ConstObject, EnumObject, TypeAliasObject } from './declaration';
9
9
  import { analyzer, ParameterType, clearCaches } from './analyzer';
10
10
  import { generateDartClass } from './dart';
11
11
  import { generateReactComponent, generateReactIndex } from './react';
@@ -205,13 +205,17 @@ export async function dartGen({ source, target, command, exclude }: GenerateOpti
205
205
  fs.mkdirSync(outputDir, { recursive: true });
206
206
  }
207
207
 
208
- // Generate Dart file
208
+ // Generate Dart file (skip if empty)
209
209
  const genFilePath = path.join(outputDir, _.snakeCase(blob.filename));
210
210
  const fullPath = genFilePath + '_bindings_generated.dart';
211
211
 
212
- if (writeFileIfChanged(fullPath, result)) {
213
- filesChanged++;
214
- debug(`Generated: ${path.basename(fullPath)}`);
212
+ if (result && result.trim().length > 0) {
213
+ if (writeFileIfChanged(fullPath, result)) {
214
+ filesChanged++;
215
+ debug(`Generated: ${path.basename(fullPath)}`);
216
+ }
217
+ } else {
218
+ debug(`Skipped ${path.basename(fullPath)} - empty bindings`);
215
219
  }
216
220
 
217
221
  // Copy the original .d.ts file to the output directory
@@ -225,13 +229,8 @@ export async function dartGen({ source, target, command, exclude }: GenerateOpti
225
229
  }
226
230
  });
227
231
 
228
- // Generate index.d.ts file with references to all .d.ts files
229
- const indexDtsContent = generateTypeScriptIndex(blobs, normalizedTarget);
230
- const indexDtsPath = path.join(normalizedTarget, 'index.d.ts');
231
- if (writeFileIfChanged(indexDtsPath, indexDtsContent)) {
232
- filesChanged++;
233
- debug('Generated: index.d.ts');
234
- }
232
+ // Note: We no longer generate a root index.d.ts for Dart codegen
233
+ // as it is not necessary for the codegen workflow.
235
234
 
236
235
  timeEnd('dartGen');
237
236
  success(`Dart code generation completed. ${filesChanged} files changed.`);
@@ -324,6 +323,8 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
324
323
 
325
324
  // Generate/merge index file
326
325
  const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
326
+ // Always build the full index content string for downstream tooling/logging
327
+ const newExports = generateReactIndex(blobs);
327
328
 
328
329
  // Build desired export map: moduleSpecifier -> Set of names
329
330
  const desiredExports = new Map<string, Set<string>>();
@@ -356,7 +357,6 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
356
357
 
357
358
  if (!fs.existsSync(indexFilePath)) {
358
359
  // No index.ts -> generate fresh file from template
359
- const newExports = generateReactIndex(blobs);
360
360
  if (writeFileIfChanged(indexFilePath, newExports)) {
361
361
  filesChanged++;
362
362
  debug(`Generated: index.ts`);
@@ -410,6 +410,66 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
410
410
  success(`React code generation completed. ${filesChanged} files changed.`);
411
411
  info(`Output directory: ${normalizedTarget}`);
412
412
  info('You can now import these components in your React project.');
413
+
414
+ // Aggregate standalone type declarations (consts/enums/type aliases) into a single types.d.ts
415
+ try {
416
+ const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof ConstObject) as ConstObject[]);
417
+ const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof EnumObject) as EnumObject[]);
418
+ const typeAliases = blobs.flatMap(b => b.objects.filter(o => o instanceof TypeAliasObject) as TypeAliasObject[]);
419
+
420
+ // Deduplicate by name
421
+ const constMap = new Map<string, ConstObject>();
422
+ consts.forEach(c => { if (!constMap.has(c.name)) constMap.set(c.name, c); });
423
+ const typeAliasMap = new Map<string, TypeAliasObject>();
424
+ typeAliases.forEach(t => { if (!typeAliasMap.has(t.name)) typeAliasMap.set(t.name, t); });
425
+
426
+ const hasAny = constMap.size > 0 || enums.length > 0 || typeAliasMap.size > 0;
427
+ if (hasAny) {
428
+ const constDecl = Array.from(constMap.values())
429
+ .map(c => `export declare const ${c.name}: ${c.type};`)
430
+ .join('\n');
431
+ const enumDecl = enums
432
+ .map(e => `export declare enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
433
+ .join('\n');
434
+ const typeAliasDecl = Array.from(typeAliasMap.values())
435
+ .map(t => `export type ${t.name} = ${t.type};`)
436
+ .join('\n');
437
+
438
+ const typesContent = [
439
+ '/* Generated by WebF CLI - aggregated type declarations */',
440
+ typeAliasDecl,
441
+ constDecl,
442
+ enumDecl,
443
+ ''
444
+ ].filter(Boolean).join('\n');
445
+
446
+ const typesPath = path.join(normalizedTarget, 'src', 'types.d.ts');
447
+ if (writeFileIfChanged(typesPath, typesContent)) {
448
+ filesChanged++;
449
+ debug(`Generated: src/types.d.ts`);
450
+ }
451
+
452
+ // Try to help TypeScript pick up additional declarations by adding a reference comment.
453
+ // This avoids bundler resolution errors from importing a .d.ts file.
454
+ const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
455
+ try {
456
+ if (fs.existsSync(indexFilePath)) {
457
+ let current = fs.readFileSync(indexFilePath, 'utf-8');
458
+ const refLine = `/// <reference path="./types.d.ts" />`;
459
+ if (!current.includes(refLine)) {
460
+ // Place the reference at the very top, before any code
461
+ const updated = `${refLine}\n${current}`;
462
+ if (writeFileIfChanged(indexFilePath, updated)) {
463
+ filesChanged++;
464
+ debug(`Updated: src/index.ts with reference to types.d.ts`);
465
+ }
466
+ }
467
+ }
468
+ } catch {}
469
+ }
470
+ } catch (e) {
471
+ warn('Failed to generate aggregated React types.d.ts');
472
+ }
413
473
  }
414
474
 
415
475
  export async function vueGen({ source, target, exclude }: GenerateOptions) {
package/src/react.ts CHANGED
@@ -2,7 +2,7 @@ import _ from "lodash";
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import {ParameterType} from "./analyzer";
5
- import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject} from "./declaration";
5
+ import {ClassObject, FunctionArgumentType, FunctionDeclaration, TypeAliasObject, ConstObject, EnumObject} from "./declaration";
6
6
  import {IDLBlob} from "./IDLBlob";
7
7
  import {getPointerType, isPointerType, isUnionType} from "./utils";
8
8
 
@@ -125,6 +125,8 @@ export function toWebFTagName(className: string): string {
125
125
  export function generateReactComponent(blob: IDLBlob, packageName?: string, relativeDir?: string) {
126
126
  const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
127
127
  const typeAliases = blob.objects.filter(obj => obj instanceof TypeAliasObject) as TypeAliasObject[];
128
+ const constObjects = blob.objects.filter(obj => obj instanceof ConstObject) as ConstObject[];
129
+ const enumObjects = blob.objects.filter(obj => obj instanceof EnumObject) as EnumObject[];
128
130
 
129
131
  const classObjectDictionary = Object.fromEntries(
130
132
  classObjects.map(object => {
@@ -153,8 +155,19 @@ export function generateReactComponent(blob: IDLBlob, packageName?: string, rela
153
155
  return `type ${typeAlias.name} = ${typeAlias.type};`;
154
156
  }).join('\n');
155
157
 
158
+ // Include declare const values as ambient exports for type usage (e.g., unique symbol branding)
159
+ const constDeclarations = constObjects.map(c => `export declare const ${c.name}: ${c.type};`).join('\n');
160
+
161
+ // Include enums
162
+ const enumDeclarations = enumObjects.map(e => {
163
+ const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
164
+ return `export declare enum ${e.name} { ${members} }`;
165
+ }).join('\n');
166
+
156
167
  const dependencies = [
157
168
  typeAliasDeclarations,
169
+ constDeclarations,
170
+ enumDeclarations,
158
171
  // Include Methods interfaces as dependencies
159
172
  methods.map(object => {
160
173
  const methodDeclarations = object.methods.map(method => {
package/src/vue.ts CHANGED
@@ -2,7 +2,7 @@ import _ from "lodash";
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  import {ParameterType} from "./analyzer";
5
- import {ClassObject, FunctionArgumentType, FunctionDeclaration} from "./declaration";
5
+ import {ClassObject, FunctionArgumentType, FunctionDeclaration, ConstObject, EnumObject} from "./declaration";
6
6
  import {IDLBlob} from "./IDLBlob";
7
7
  import {getPointerType, isPointerType} from "./utils";
8
8
 
@@ -145,6 +145,17 @@ interface ${object.name} {
145
145
  return result;
146
146
  }
147
147
 
148
+ function toVueTagName(className: string): string {
149
+ if (className.startsWith('WebF')) {
150
+ const withoutPrefix = className.substring(4);
151
+ return 'web-f-' + _.kebabCase(withoutPrefix);
152
+ } else if (className.startsWith('Flutter')) {
153
+ const withoutPrefix = className.substring(7);
154
+ return 'flutter-' + _.kebabCase(withoutPrefix);
155
+ }
156
+ return _.kebabCase(className);
157
+ }
158
+
148
159
  export function generateVueTypings(blobs: IDLBlob[]) {
149
160
  const componentNames = blobs.map(blob => {
150
161
  const classObjects = blob.objects as ClassObject[];
@@ -177,13 +188,47 @@ export function generateVueTypings(blobs: IDLBlob[]) {
177
188
  return component.length > 0;
178
189
  }).join('\n\n');
179
190
 
191
+ // Collect declare consts across blobs and render as exported ambient declarations
192
+ const consts = blobs
193
+ .flatMap(blob => blob.objects)
194
+ .filter(obj => obj instanceof ConstObject) as ConstObject[];
195
+
196
+ // Deduplicate by name keeping first occurrence
197
+ const uniqueConsts = new Map<string, ConstObject>();
198
+ consts.forEach(c => {
199
+ if (!uniqueConsts.has(c.name)) uniqueConsts.set(c.name, c);
200
+ });
201
+
202
+ const constDeclarations = Array.from(uniqueConsts.values())
203
+ .map(c => `export declare const ${c.name}: ${c.type};`)
204
+ .join('\n');
205
+
206
+ // Collect declare enums across blobs
207
+ const enums = blobs
208
+ .flatMap(blob => blob.objects)
209
+ .filter(obj => obj instanceof EnumObject) as EnumObject[];
210
+
211
+ const enumDeclarations = enums.map(e => {
212
+ const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
213
+ return `export declare enum ${e.name} { ${members} }`;
214
+ }).join('\n');
215
+
216
+ // Build mapping of template tag names to class names for GlobalComponents
217
+ const componentMetas = componentNames.map(className => ({
218
+ className,
219
+ tagName: toVueTagName(className),
220
+ }));
221
+
180
222
  const content = _.template(readTemplate('vue.components.d.ts'), {
181
223
  interpolate: /<%=([\s\S]+?)%>/g,
182
224
  evaluate: /<%([\s\S]+?)%>/g,
183
225
  escape: /<%-([\s\S]+?)%>/g
184
226
  })({
185
227
  componentNames,
228
+ componentMetas,
186
229
  components,
230
+ consts: constDeclarations,
231
+ enums: enumDeclarations,
187
232
  });
188
233
 
189
234
  return content.split('\n').filter(str => {
@@ -23,6 +23,7 @@
23
23
  "devDependencies": {
24
24
  "@types/react": "^19.1.0",
25
25
  "@types/react-dom": "^19.1.2",
26
+ "picomatch": "^4.0.2",
26
27
  "tsup": "^8.5.0",
27
28
  "typescript": "^5.8.3"
28
29
  }
@@ -19,6 +19,9 @@ type VueEventListeners<T extends EventMap> = {
19
19
  [K in keyof T as `on${Capitalize<string & K>}`]?: (event: T[K]) => any
20
20
  }
21
21
 
22
+ <%= consts %>
23
+ <%= enums %>
24
+
22
25
  type DefineCustomElement<
23
26
  ElementType,
24
27
  Events extends EventMap = {},
@@ -44,10 +47,10 @@ type DefineCustomElement<
44
47
 
45
48
  declare module 'vue' {
46
49
  interface GlobalComponents {
47
- <% componentNames.forEach(name => { %>
48
- '<%= name %>': DefineCustomElement<
49
- <%= name %>Props,
50
- <%= name %>Events
50
+ <% componentMetas.forEach(comp => { %>
51
+ '<%= comp.tagName %>': DefineCustomElement<
52
+ <%= comp.className %>Props,
53
+ <%= comp.className %>Events
51
54
  >
52
55
  <% }) %>
53
56
  }