@openwebf/webf 0.23.2 → 0.23.7

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/README.md CHANGED
@@ -25,6 +25,7 @@ webf codegen [output-dir] [options]
25
25
  - `--flutter-package-src <path>`: Flutter package source path containing TypeScript definitions
26
26
  - `--framework <framework>`: Target framework - 'react' or 'vue'
27
27
  - `--package-name <name>`: Package name for the webf typings
28
+ - `--dart-only`: Only generate Dart bindings in the Flutter package (skip React/Vue code and npm package generation)
28
29
  - `--publish-to-npm`: Automatically publish the generated package to npm
29
30
  - `--npm-registry <url>`: Custom npm registry URL (defaults to https://registry.npmjs.org/)
30
31
 
@@ -40,6 +41,9 @@ webf codegen my-vue-app --flutter-package-src=./flutter_pkg --framework=vue --pa
40
41
 
41
42
  # Use temporary directory (auto-created)
42
43
  webf codegen --flutter-package-src=../webf_cupertino_ui
44
+
45
+ # Generate only Dart bindings inside the Flutter package
46
+ webf codegen --flutter-package-src=../webf_cupertino_ui --dart-only
43
47
  ```
44
48
 
45
49
  **Create a new project without code generation:**
@@ -229,4 +233,4 @@ npm link # Link for local testing
229
233
 
230
234
  ## License
231
235
 
232
- ISC
236
+ ISC
package/bin/webf.js CHANGED
@@ -16,6 +16,7 @@ program
16
16
  .option('--flutter-package-src <src>', 'Flutter package source path (for code generation)')
17
17
  .option('--framework <framework>', 'Target framework (react or vue)')
18
18
  .option('--package-name <name>', 'Package name for the webf typings')
19
+ .option('--dart-only', 'Only generate Dart bindings in the Flutter package (skip React/Vue code and npm package generation)')
19
20
  .option('--publish-to-npm', 'Automatically publish the generated package to npm')
20
21
  .option('--npm-registry <url>', 'Custom npm registry URL (defaults to https://registry.npmjs.org/)')
21
22
  .option('--exclude <patterns...>', 'Additional glob patterns to exclude from code generation')
package/dist/analyzer.js CHANGED
@@ -256,6 +256,20 @@ function getParameterBaseType(type, mode) {
256
256
  if (basicType !== undefined) {
257
257
  return basicType;
258
258
  }
259
+ // Handle `typeof SomeIdentifier` (TypeQuery) by preserving the textual form
260
+ // so React/Vue can keep strong typing (e.g., `typeof CupertinoIcons`).
261
+ // Dart mapping will convert this to `dynamic` later.
262
+ if (type.kind === typescript_1.default.SyntaxKind.TypeQuery) {
263
+ const tq = type;
264
+ const getEntityNameText = (name) => {
265
+ if (typescript_1.default.isIdentifier(name))
266
+ return name.text;
267
+ // Qualified name: A.B.C
268
+ return `${getEntityNameText(name.left)}.${name.right.text}`;
269
+ };
270
+ const nameText = getEntityNameText(tq.exprName);
271
+ return `typeof ${nameText}`;
272
+ }
259
273
  if (type.kind === typescript_1.default.SyntaxKind.TypeReference) {
260
274
  const typeReference = type;
261
275
  const typeName = typeReference.typeName;
@@ -352,13 +366,58 @@ function handleGenericWrapper(typeReference, mode) {
352
366
  return getParameterBaseType(argument, mode);
353
367
  }
354
368
  function handleCustomEventType(typeReference) {
369
+ var _a;
355
370
  // Handle CustomEvent<T> by returning the full type with generic parameter
356
371
  if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
357
372
  return 'CustomEvent';
358
373
  }
359
374
  const argument = typeReference.typeArguments[0];
360
375
  let genericType;
361
- if (typescript_1.default.isTypeReferenceNode(argument) && typescript_1.default.isIdentifier(argument.typeName)) {
376
+ // Preserve simple union/compound generic types (e.g., boolean | null)
377
+ if (typescript_1.default.isUnionTypeNode(argument) || typescript_1.default.isIntersectionTypeNode(argument)) {
378
+ const unionTypes = (_a = argument.types) !== null && _a !== void 0 ? _a : [];
379
+ const parts = unionTypes.map(t => {
380
+ // Literal union members: handle null/undefined explicitly
381
+ if (typescript_1.default.isLiteralTypeNode(t)) {
382
+ const lit = t.literal;
383
+ if (lit.kind === typescript_1.default.SyntaxKind.NullKeyword)
384
+ return 'null';
385
+ if (lit.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
386
+ return 'undefined';
387
+ if (typescript_1.default.isStringLiteral(lit))
388
+ return JSON.stringify(lit.text);
389
+ return 'any';
390
+ }
391
+ // Basic keywords: boolean, string, number, null, undefined
392
+ const basic = BASIC_TYPE_MAP[t.kind];
393
+ if (basic !== undefined) {
394
+ switch (basic) {
395
+ case declaration_1.FunctionArgumentType.boolean:
396
+ return 'boolean';
397
+ case declaration_1.FunctionArgumentType.dom_string:
398
+ return 'string';
399
+ case declaration_1.FunctionArgumentType.double:
400
+ case declaration_1.FunctionArgumentType.int:
401
+ return 'number';
402
+ case declaration_1.FunctionArgumentType.null:
403
+ return 'null';
404
+ case declaration_1.FunctionArgumentType.undefined:
405
+ return 'undefined';
406
+ default:
407
+ return 'any';
408
+ }
409
+ }
410
+ // Literal null/undefined keywords that BASIC_TYPE_MAP may not cover
411
+ if (t.kind === typescript_1.default.SyntaxKind.NullKeyword)
412
+ return 'null';
413
+ if (t.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
414
+ return 'undefined';
415
+ // Fallback: rely on toString of node kind
416
+ return 'any';
417
+ });
418
+ genericType = parts.join(' | ');
419
+ }
420
+ else if (typescript_1.default.isTypeReferenceNode(argument) && typescript_1.default.isIdentifier(argument.typeName)) {
362
421
  const typeName = argument.typeName.text;
363
422
  // Check if it's a mapped type reference like 'int' or 'double'
364
423
  const mappedType = TYPE_REFERENCE_MAP[typeName];
@@ -627,6 +686,11 @@ function processEnumDeclaration(statement, blob) {
627
686
  }
628
687
  return mem;
629
688
  });
689
+ // Register globally for cross-file lookups (e.g., Dart mapping decisions)
690
+ try {
691
+ declaration_1.EnumObject.globalEnumSet.add(enumObj.name);
692
+ }
693
+ catch (_a) { }
630
694
  return enumObj;
631
695
  }
632
696
  function processInterfaceDeclaration(statement, blob, sourceFile, definedPropertyCollector, unionTypeCollector) {
package/dist/commands.js CHANGED
@@ -172,7 +172,8 @@ function readFlutterPackageMetadata(packagePath) {
172
172
  return null;
173
173
  }
174
174
  }
175
- // Copy markdown docs that match .d.ts basenames from source to the built dist folder
175
+ // Copy markdown docs that match .d.ts basenames from source to the built dist folder,
176
+ // and generate an aggregated README.md in the dist directory.
176
177
  function copyMarkdownDocsToDist(params) {
177
178
  return __awaiter(this, void 0, void 0, function* () {
178
179
  const { sourceRoot, distRoot, exclude } = params;
@@ -187,6 +188,7 @@ function copyMarkdownDocsToDist(params) {
187
188
  const dtsFiles = glob_1.glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
188
189
  let copied = 0;
189
190
  let skipped = 0;
191
+ const readmeSections = [];
190
192
  for (const relDts of dtsFiles) {
191
193
  if (path_1.default.basename(relDts) === 'global.d.ts') {
192
194
  continue;
@@ -197,6 +199,13 @@ function copyMarkdownDocsToDist(params) {
197
199
  skipped++;
198
200
  continue;
199
201
  }
202
+ let content = '';
203
+ try {
204
+ content = fs_1.default.readFileSync(absMd, 'utf-8');
205
+ }
206
+ catch (_a) {
207
+ // If we cannot read the file, still attempt to copy it and skip README aggregation for this entry.
208
+ }
200
209
  // Copy into dist preserving relative path
201
210
  const destPath = path_1.default.join(distRoot, relMd);
202
211
  const destDir = path_1.default.dirname(destPath);
@@ -205,6 +214,61 @@ function copyMarkdownDocsToDist(params) {
205
214
  }
206
215
  fs_1.default.copyFileSync(absMd, destPath);
207
216
  copied++;
217
+ if (content) {
218
+ const base = path_1.default.basename(relMd, '.md');
219
+ const title = base
220
+ .split(/[-_]+/)
221
+ .filter(Boolean)
222
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
223
+ .join(' ');
224
+ readmeSections.push({
225
+ title: title || base,
226
+ relPath: relMd,
227
+ content
228
+ });
229
+ }
230
+ }
231
+ // Generate an aggregated README.md inside distRoot so consumers can see component docs easily.
232
+ if (readmeSections.length > 0) {
233
+ const readmePath = path_1.default.join(distRoot, 'README.md');
234
+ let existing = '';
235
+ if (fs_1.default.existsSync(readmePath)) {
236
+ try {
237
+ existing = fs_1.default.readFileSync(readmePath, 'utf-8');
238
+ }
239
+ catch (_b) {
240
+ existing = '';
241
+ }
242
+ }
243
+ const headerLines = [
244
+ '# WebF Component Documentation',
245
+ '',
246
+ '> This README is generated from markdown docs co-located with TypeScript definitions in the Flutter package.',
247
+ ''
248
+ ];
249
+ const sectionBlocks = readmeSections.map(section => {
250
+ const lines = [];
251
+ lines.push(`## ${section.title}`);
252
+ lines.push('');
253
+ lines.push(`_Source: \`./${section.relPath}\`_`);
254
+ lines.push('');
255
+ lines.push(section.content.trim());
256
+ lines.push('');
257
+ return lines.join('\n');
258
+ }).join('\n');
259
+ let finalContent;
260
+ if (existing && existing.trim().length > 0) {
261
+ finalContent = `${existing.trim()}\n\n---\n\n${headerLines.join('\n')}${sectionBlocks}\n`;
262
+ }
263
+ else {
264
+ finalContent = `${headerLines.join('\n')}${sectionBlocks}\n`;
265
+ }
266
+ try {
267
+ fs_1.default.writeFileSync(readmePath, finalContent, 'utf-8');
268
+ }
269
+ catch (_c) {
270
+ // If README generation fails, do not affect overall codegen.
271
+ }
208
272
  }
209
273
  return { copied, skipped };
210
274
  });
@@ -281,6 +345,7 @@ function createCommand(target, options) {
281
345
  // Do not overwrite existing index.ts created by the user
282
346
  // Leave merge to the codegen step which appends exports safely
283
347
  }
348
+ // !no '--omit=peer' here.
284
349
  (0, child_process_1.spawnSync)(NPM, ['install'], {
285
350
  cwd: target,
286
351
  stdio: 'inherit'
@@ -317,12 +382,19 @@ function generateCommand(distPath, options) {
317
382
  // If distPath is not provided or is '.', create a temporary directory
318
383
  let resolvedDistPath;
319
384
  let isTempDir = false;
385
+ const isDartOnly = options.dartOnly;
320
386
  if (!distPath || distPath === '.') {
321
- // Create a temporary directory for the generated package
322
- const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'webf-typings-'));
323
- resolvedDistPath = tempDir;
324
- isTempDir = true;
325
- console.log(`\nUsing temporary directory: ${tempDir}`);
387
+ if (isDartOnly) {
388
+ // In Dart-only mode we don't need a temporary Node project directory
389
+ resolvedDistPath = path_1.default.resolve(distPath || '.');
390
+ }
391
+ else {
392
+ // Create a temporary directory for the generated package
393
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'webf-typings-'));
394
+ resolvedDistPath = tempDir;
395
+ isTempDir = true;
396
+ console.log(`\nUsing temporary directory: ${tempDir}`);
397
+ }
326
398
  }
327
399
  else {
328
400
  resolvedDistPath = path_1.default.resolve(distPath);
@@ -352,111 +424,123 @@ function generateCommand(distPath, options) {
352
424
  console.log(`\nDetected Flutter package at: ${pubspecDir}`);
353
425
  }
354
426
  }
355
- // Check if the directory exists and has required files
356
- const packageJsonPath = path_1.default.join(resolvedDistPath, 'package.json');
357
- const globalDtsPath = path_1.default.join(resolvedDistPath, 'global.d.ts');
358
- const tsConfigPath = path_1.default.join(resolvedDistPath, 'tsconfig.json');
359
- const hasPackageJson = fs_1.default.existsSync(packageJsonPath);
360
- const hasGlobalDts = fs_1.default.existsSync(globalDtsPath);
361
- const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
362
- // Determine if we need to create a new project
363
- const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
364
- // Track if this is an existing project (has all required files)
365
- const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
366
427
  let framework = options.framework;
367
428
  let packageName = options.packageName;
368
- // Validate and sanitize package name if provided
369
- if (packageName && !isValidNpmPackageName(packageName)) {
370
- console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
371
- const sanitized = sanitizePackageName(packageName);
372
- console.log(`Using sanitized name: "${sanitized}"`);
373
- packageName = sanitized;
374
- }
375
- if (needsProjectCreation) {
376
- // If project needs creation but options are missing, prompt for them
377
- if (!framework) {
378
- const frameworkAnswer = yield inquirer_1.default.prompt([{
379
- type: 'list',
380
- name: 'framework',
381
- message: 'Which framework would you like to use?',
382
- choices: ['react', 'vue']
383
- }]);
384
- framework = frameworkAnswer.framework;
385
- }
386
- // Try to read Flutter package metadata if flutterPackageSrc is provided
387
- let metadata = null;
388
- if (options.flutterPackageSrc) {
389
- metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
390
- }
391
- if (!packageName) {
392
- // Use Flutter package name as default if available, sanitized for npm
393
- const rawDefaultName = (metadata === null || metadata === void 0 ? void 0 : metadata.name) || path_1.default.basename(resolvedDistPath);
394
- const defaultPackageName = sanitizePackageName(rawDefaultName);
395
- const packageNameAnswer = yield inquirer_1.default.prompt([{
396
- type: 'input',
397
- name: 'packageName',
398
- message: 'What is your package name?',
399
- default: defaultPackageName,
400
- validate: (input) => {
401
- if (!input || input.trim() === '') {
402
- return 'Package name is required';
403
- }
404
- // Check if it's valid as-is
405
- if (isValidNpmPackageName(input)) {
406
- return true;
429
+ let isExistingProject = false;
430
+ if (!isDartOnly) {
431
+ // Check if the directory exists and has required files
432
+ const packageJsonPath = path_1.default.join(resolvedDistPath, 'package.json');
433
+ const globalDtsPath = path_1.default.join(resolvedDistPath, 'global.d.ts');
434
+ const tsConfigPath = path_1.default.join(resolvedDistPath, 'tsconfig.json');
435
+ const hasPackageJson = fs_1.default.existsSync(packageJsonPath);
436
+ const hasGlobalDts = fs_1.default.existsSync(globalDtsPath);
437
+ const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
438
+ // Determine if we need to create a new project
439
+ const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
440
+ // Track if this is an existing project (has all required files)
441
+ isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
442
+ // Validate and sanitize package name if provided
443
+ if (packageName && !isValidNpmPackageName(packageName)) {
444
+ console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
445
+ const sanitized = sanitizePackageName(packageName);
446
+ console.log(`Using sanitized name: "${sanitized}"`);
447
+ packageName = sanitized;
448
+ }
449
+ if (needsProjectCreation) {
450
+ // If project needs creation but options are missing, prompt for them
451
+ if (!framework) {
452
+ const frameworkAnswer = yield inquirer_1.default.prompt([{
453
+ type: 'list',
454
+ name: 'framework',
455
+ message: 'Which framework would you like to use?',
456
+ choices: ['react', 'vue']
457
+ }]);
458
+ framework = frameworkAnswer.framework;
459
+ }
460
+ // Try to read Flutter package metadata if flutterPackageSrc is provided
461
+ let metadata = null;
462
+ if (options.flutterPackageSrc) {
463
+ metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
464
+ }
465
+ if (!packageName) {
466
+ // Use Flutter package name as default if available, sanitized for npm
467
+ const rawDefaultName = (metadata === null || metadata === void 0 ? void 0 : metadata.name) || path_1.default.basename(resolvedDistPath);
468
+ const defaultPackageName = sanitizePackageName(rawDefaultName);
469
+ const packageNameAnswer = yield inquirer_1.default.prompt([{
470
+ type: 'input',
471
+ name: 'packageName',
472
+ message: 'What is your package name?',
473
+ default: defaultPackageName,
474
+ validate: (input) => {
475
+ if (!input || input.trim() === '') {
476
+ return 'Package name is required';
477
+ }
478
+ // Check if it's valid as-is
479
+ if (isValidNpmPackageName(input)) {
480
+ return true;
481
+ }
482
+ // If not valid, show what it would be sanitized to
483
+ const sanitized = sanitizePackageName(input);
484
+ return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
407
485
  }
408
- // If not valid, show what it would be sanitized to
409
- const sanitized = sanitizePackageName(input);
410
- return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
411
- }
412
- }]);
413
- packageName = packageNameAnswer.packageName;
486
+ }]);
487
+ packageName = packageNameAnswer.packageName;
488
+ }
489
+ console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
490
+ createCommand(resolvedDistPath, {
491
+ framework: framework,
492
+ packageName: packageName,
493
+ metadata: metadata || undefined
494
+ });
414
495
  }
415
- console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
416
- createCommand(resolvedDistPath, {
417
- framework: framework,
418
- packageName: packageName,
419
- metadata: metadata || undefined
420
- });
421
- }
422
- else {
423
- // Validate existing project structure
424
- if (hasPackageJson) {
425
- try {
426
- const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
427
- // Detect framework from existing package.json
428
- if (!framework) {
429
- if (((_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a.react) || ((_b = packageJson.devDependencies) === null || _b === void 0 ? void 0 : _b.react)) {
430
- framework = 'react';
431
- }
432
- else if (((_c = packageJson.dependencies) === null || _c === void 0 ? void 0 : _c.vue) || ((_d = packageJson.devDependencies) === null || _d === void 0 ? void 0 : _d.vue)) {
433
- framework = 'vue';
434
- }
435
- else {
436
- // If can't detect, prompt for it
437
- const frameworkAnswer = yield inquirer_1.default.prompt([{
438
- type: 'list',
439
- name: 'framework',
440
- message: 'Which framework are you using?',
441
- choices: ['react', 'vue']
442
- }]);
443
- framework = frameworkAnswer.framework;
496
+ else {
497
+ // Validate existing project structure
498
+ if (hasPackageJson) {
499
+ try {
500
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
501
+ // Detect framework from existing package.json
502
+ if (!framework) {
503
+ if (((_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a.react) || ((_b = packageJson.devDependencies) === null || _b === void 0 ? void 0 : _b.react)) {
504
+ framework = 'react';
505
+ }
506
+ else if (((_c = packageJson.dependencies) === null || _c === void 0 ? void 0 : _c.vue) || ((_d = packageJson.devDependencies) === null || _d === void 0 ? void 0 : _d.vue)) {
507
+ framework = 'vue';
508
+ }
509
+ else {
510
+ // If can't detect, prompt for it
511
+ const frameworkAnswer = yield inquirer_1.default.prompt([{
512
+ type: 'list',
513
+ name: 'framework',
514
+ message: 'Which framework are you using?',
515
+ choices: ['react', 'vue']
516
+ }]);
517
+ framework = frameworkAnswer.framework;
518
+ }
444
519
  }
520
+ console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
521
+ }
522
+ catch (e) {
523
+ console.error('Error reading package.json:', e);
524
+ process.exit(1);
445
525
  }
446
- console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
447
- }
448
- catch (e) {
449
- console.error('Error reading package.json:', e);
450
- process.exit(1);
451
526
  }
452
527
  }
453
528
  }
529
+ else {
530
+ // In Dart-only mode, framework/packageName are unused; ensure framework is not accidentally required later.
531
+ framework = options.framework;
532
+ }
454
533
  // Now proceed with code generation if flutter package source is provided
455
534
  if (!options.flutterPackageSrc) {
456
535
  console.log('\nProject is ready for code generation.');
457
536
  console.log('To generate code, run:');
458
537
  const displayPath = isTempDir ? '<output-dir>' : distPath;
459
- console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
538
+ if (isDartOnly) {
539
+ console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --dart-only`);
540
+ }
541
+ else {
542
+ console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
543
+ }
460
544
  if (isTempDir) {
461
545
  // Clean up temporary directory if we're not using it
462
546
  fs_1.default.rmSync(resolvedDistPath, { recursive: true, force: true });
@@ -519,7 +603,23 @@ function generateCommand(distPath, options) {
519
603
  process.exit(1);
520
604
  }
521
605
  }
522
- const command = `webf codegen --flutter-package-src=${options.flutterPackageSrc} --framework=${framework} <distPath>`;
606
+ const baseCommand = 'webf codegen';
607
+ const flutterPart = options.flutterPackageSrc ? ` --flutter-package-src=${options.flutterPackageSrc}` : '';
608
+ const modePart = isDartOnly
609
+ ? ' --dart-only'
610
+ : (framework ? ` --framework=${framework}` : '');
611
+ const command = `${baseCommand}${flutterPart}${modePart} <distPath>`;
612
+ if (isDartOnly) {
613
+ console.log(`\nGenerating Dart bindings from ${options.flutterPackageSrc}...`);
614
+ yield (0, generator_1.dartGen)({
615
+ source: options.flutterPackageSrc,
616
+ target: options.flutterPackageSrc,
617
+ command,
618
+ exclude: options.exclude,
619
+ });
620
+ console.log('\nDart code generation completed successfully!');
621
+ return;
622
+ }
523
623
  // Auto-initialize typings in the output directory if needed
524
624
  ensureInitialized(resolvedDistPath);
525
625
  console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);