@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/src/commands.ts CHANGED
@@ -15,6 +15,7 @@ interface GenerateOptions {
15
15
  publishToNpm?: boolean;
16
16
  npmRegistry?: string;
17
17
  exclude?: string[];
18
+ dartOnly?: boolean;
18
19
  }
19
20
 
20
21
  interface FlutterPackageMetadata {
@@ -39,12 +40,12 @@ interface FlutterPackageMetadata {
39
40
  function sanitizePackageName(name: string): string {
40
41
  // Remove any leading/trailing whitespace
41
42
  let sanitized = name.trim();
42
-
43
+
43
44
  // Check if it's a scoped package
44
45
  const isScoped = sanitized.startsWith('@');
45
46
  let scope = '';
46
47
  let packageName = sanitized;
47
-
48
+
48
49
  if (isScoped) {
49
50
  const parts = sanitized.split('/');
50
51
  if (parts.length >= 2) {
@@ -55,7 +56,7 @@ function sanitizePackageName(name: string): string {
55
56
  packageName = sanitized.substring(1);
56
57
  }
57
58
  }
58
-
59
+
59
60
  // Sanitize scope if present
60
61
  if (scope) {
61
62
  scope = scope.toLowerCase();
@@ -65,7 +66,7 @@ function sanitizePackageName(name: string): string {
65
66
  scope = '@pkg'; // Default scope if only @ remains
66
67
  }
67
68
  }
68
-
69
+
69
70
  // Sanitize package name part
70
71
  packageName = packageName.toLowerCase();
71
72
  packageName = packageName.replace(/\s+/g, '-');
@@ -74,20 +75,20 @@ function sanitizePackageName(name: string): string {
74
75
  packageName = packageName.replace(/[._]+$/, '');
75
76
  packageName = packageName.replace(/[-_.]{2,}/g, '-');
76
77
  packageName = packageName.replace(/^-+/, '').replace(/-+$/, '');
77
-
78
+
78
79
  // Ensure package name is not empty
79
80
  if (!packageName) {
80
81
  packageName = 'package';
81
82
  }
82
-
83
+
83
84
  // Ensure it starts with a letter or number
84
85
  if (!/^[a-z0-9]/.test(packageName)) {
85
86
  packageName = 'pkg-' + packageName;
86
87
  }
87
-
88
+
88
89
  // Combine scope and package name
89
90
  let result = scope ? `${scope}/${packageName}` : packageName;
90
-
91
+
91
92
  // Truncate to 214 characters (npm limit)
92
93
  if (result.length > 214) {
93
94
  if (scope) {
@@ -101,7 +102,7 @@ function sanitizePackageName(name: string): string {
101
102
  result = result.replace(/[._-]+$/, '');
102
103
  }
103
104
  }
104
-
105
+
105
106
  return result;
106
107
  }
107
108
 
@@ -112,36 +113,36 @@ function isValidNpmPackageName(name: string): boolean {
112
113
  // Check basic rules
113
114
  if (!name || name.length === 0 || name.length > 214) return false;
114
115
  if (name.trim() !== name) return false;
115
-
116
+
116
117
  // Check if it's a scoped package
117
118
  if (name.startsWith('@')) {
118
119
  const parts = name.split('/');
119
120
  if (parts.length !== 2) return false; // Scoped packages must have exactly one /
120
-
121
+
121
122
  const scope = parts[0];
122
123
  const packageName = parts[1];
123
-
124
+
124
125
  // Validate scope
125
126
  if (!/^@[a-z0-9][a-z0-9-]*$/.test(scope)) return false;
126
-
127
+
127
128
  // Validate package name part
128
129
  return isValidNpmPackageName(packageName);
129
130
  }
130
-
131
+
131
132
  // For non-scoped packages
132
133
  if (name !== name.toLowerCase()) return false;
133
134
  if (name.startsWith('.') || name.startsWith('_')) return false;
134
-
135
+
135
136
  // Check for valid characters (letters, numbers, hyphens, underscores, dots)
136
137
  if (!/^[a-z0-9][a-z0-9\-_.]*$/.test(name)) return false;
137
-
138
+
138
139
  // Check for URL-safe characters
139
140
  try {
140
141
  if (encodeURIComponent(name) !== name) return false;
141
142
  } catch {
142
143
  return false;
143
144
  }
144
-
145
+
145
146
  return true;
146
147
  }
147
148
 
@@ -200,15 +201,15 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
200
201
  console.warn(`Warning: pubspec.yaml not found at ${pubspecPath}. Using default metadata.`);
201
202
  return null;
202
203
  }
203
-
204
+
204
205
  const pubspecContent = fs.readFileSync(pubspecPath, 'utf-8');
205
206
  const pubspec = yaml.parse(pubspecContent);
206
-
207
+
207
208
  // Validate required fields
208
209
  if (!pubspec.name) {
209
210
  console.warn(`Warning: Flutter package name not found in ${pubspecPath}. Using default name.`);
210
211
  }
211
-
212
+
212
213
  return {
213
214
  name: pubspec.name || '',
214
215
  version: pubspec.version || '0.0.1',
@@ -221,7 +222,8 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
221
222
  }
222
223
  }
223
224
 
224
- // Copy markdown docs that match .d.ts basenames from source to the built dist folder
225
+ // Copy markdown docs that match .d.ts basenames from source to the built dist folder,
226
+ // and generate an aggregated README.md in the dist directory.
225
227
  async function copyMarkdownDocsToDist(params: {
226
228
  sourceRoot: string;
227
229
  distRoot: string;
@@ -242,6 +244,7 @@ async function copyMarkdownDocsToDist(params: {
242
244
  const dtsFiles = glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
243
245
  let copied = 0;
244
246
  let skipped = 0;
247
+ const readmeSections: { title: string; relPath: string; content: string }[] = [];
245
248
 
246
249
  for (const relDts of dtsFiles) {
247
250
  if (path.basename(relDts) === 'global.d.ts') {
@@ -255,6 +258,13 @@ async function copyMarkdownDocsToDist(params: {
255
258
  continue;
256
259
  }
257
260
 
261
+ let content = '';
262
+ try {
263
+ content = fs.readFileSync(absMd, 'utf-8');
264
+ } catch {
265
+ // If we cannot read the file, still attempt to copy it and skip README aggregation for this entry.
266
+ }
267
+
258
268
  // Copy into dist preserving relative path
259
269
  const destPath = path.join(distRoot, relMd);
260
270
  const destDir = path.dirname(destPath);
@@ -263,6 +273,64 @@ async function copyMarkdownDocsToDist(params: {
263
273
  }
264
274
  fs.copyFileSync(absMd, destPath);
265
275
  copied++;
276
+
277
+ if (content) {
278
+ const base = path.basename(relMd, '.md');
279
+ const title = base
280
+ .split(/[-_]+/)
281
+ .filter(Boolean)
282
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
283
+ .join(' ');
284
+ readmeSections.push({
285
+ title: title || base,
286
+ relPath: relMd,
287
+ content
288
+ });
289
+ }
290
+ }
291
+
292
+ // Generate an aggregated README.md inside distRoot so consumers can see component docs easily.
293
+ if (readmeSections.length > 0) {
294
+ const readmePath = path.join(distRoot, 'README.md');
295
+ let existing = '';
296
+ if (fs.existsSync(readmePath)) {
297
+ try {
298
+ existing = fs.readFileSync(readmePath, 'utf-8');
299
+ } catch {
300
+ existing = '';
301
+ }
302
+ }
303
+
304
+ const headerLines: string[] = [
305
+ '# WebF Component Documentation',
306
+ '',
307
+ '> This README is generated from markdown docs co-located with TypeScript definitions in the Flutter package.',
308
+ ''
309
+ ];
310
+
311
+ const sectionBlocks = readmeSections.map(section => {
312
+ const lines: string[] = [];
313
+ lines.push(`## ${section.title}`);
314
+ lines.push('');
315
+ lines.push(`_Source: \`./${section.relPath}\`_`);
316
+ lines.push('');
317
+ lines.push(section.content.trim());
318
+ lines.push('');
319
+ return lines.join('\n');
320
+ }).join('\n');
321
+
322
+ let finalContent: string;
323
+ if (existing && existing.trim().length > 0) {
324
+ finalContent = `${existing.trim()}\n\n---\n\n${headerLines.join('\n')}${sectionBlocks}\n`;
325
+ } else {
326
+ finalContent = `${headerLines.join('\n')}${sectionBlocks}\n`;
327
+ }
328
+
329
+ try {
330
+ fs.writeFileSync(readmePath, finalContent, 'utf-8');
331
+ } catch {
332
+ // If README generation fails, do not affect overall codegen.
333
+ }
266
334
  }
267
335
 
268
336
  return { copied, skipped };
@@ -270,40 +338,40 @@ async function copyMarkdownDocsToDist(params: {
270
338
 
271
339
  function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean; errors: string[] } {
272
340
  const errors: string[] = [];
273
-
341
+
274
342
  // Check for TypeScript configuration
275
343
  const tsConfigPath = path.join(projectPath, 'tsconfig.json');
276
344
  if (!fs.existsSync(tsConfigPath)) {
277
345
  errors.push('Missing tsconfig.json - TypeScript configuration is required for type definitions');
278
346
  }
279
-
347
+
280
348
  // Check for .d.ts files - this is critical
281
349
  const libPath = path.join(projectPath, 'lib');
282
350
  let hasDtsFiles = false;
283
-
351
+
284
352
  if (fs.existsSync(libPath)) {
285
353
  // Check in lib directory
286
- hasDtsFiles = fs.readdirSync(libPath).some(file =>
287
- file.endsWith('.d.ts') ||
288
- (fs.statSync(path.join(libPath, file)).isDirectory() &&
354
+ hasDtsFiles = fs.readdirSync(libPath).some(file =>
355
+ file.endsWith('.d.ts') ||
356
+ (fs.statSync(path.join(libPath, file)).isDirectory() &&
289
357
  fs.readdirSync(path.join(libPath, file)).some(f => f.endsWith('.d.ts')))
290
358
  );
291
359
  }
292
-
360
+
293
361
  // Also check in root directory
294
362
  if (!hasDtsFiles) {
295
- hasDtsFiles = fs.readdirSync(projectPath).some(file =>
296
- file.endsWith('.d.ts') ||
297
- (fs.statSync(path.join(projectPath, file)).isDirectory() &&
298
- file !== 'node_modules' &&
363
+ hasDtsFiles = fs.readdirSync(projectPath).some(file =>
364
+ file.endsWith('.d.ts') ||
365
+ (fs.statSync(path.join(projectPath, file)).isDirectory() &&
366
+ file !== 'node_modules' &&
299
367
  fs.existsSync(path.join(projectPath, file, 'index.d.ts')))
300
368
  );
301
369
  }
302
-
370
+
303
371
  if (!hasDtsFiles) {
304
372
  errors.push('No TypeScript definition files (.d.ts) found in the project - Please create .d.ts files for your components');
305
373
  }
306
-
374
+
307
375
  return {
308
376
  isValid: errors.length === 0,
309
377
  errors
@@ -313,8 +381,8 @@ function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean;
313
381
  function createCommand(target: string, options: { framework: string; packageName: string; metadata?: FlutterPackageMetadata }): void {
314
382
  const { framework, metadata } = options;
315
383
  // Ensure package name is always valid
316
- const packageName = isValidNpmPackageName(options.packageName)
317
- ? options.packageName
384
+ const packageName = isValidNpmPackageName(options.packageName)
385
+ ? options.packageName
318
386
  : sanitizePackageName(options.packageName);
319
387
 
320
388
  if (!fs.existsSync(target)) {
@@ -358,6 +426,7 @@ function createCommand(target: string, options: { framework: string; packageName
358
426
  // Leave merge to the codegen step which appends exports safely
359
427
  }
360
428
 
429
+ // !no '--omit=peer' here.
361
430
  spawnSync(NPM, ['install'], {
362
431
  cwd: target,
363
432
  stdio: 'inherit'
@@ -398,24 +467,30 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
398
467
  // If distPath is not provided or is '.', create a temporary directory
399
468
  let resolvedDistPath: string;
400
469
  let isTempDir = false;
401
-
470
+ const isDartOnly = options.dartOnly;
471
+
402
472
  if (!distPath || distPath === '.') {
403
- // Create a temporary directory for the generated package
404
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webf-typings-'));
405
- resolvedDistPath = tempDir;
406
- isTempDir = true;
407
- console.log(`\nUsing temporary directory: ${tempDir}`);
473
+ if (isDartOnly) {
474
+ // In Dart-only mode we don't need a temporary Node project directory
475
+ resolvedDistPath = path.resolve(distPath || '.');
476
+ } else {
477
+ // Create a temporary directory for the generated package
478
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webf-typings-'));
479
+ resolvedDistPath = tempDir;
480
+ isTempDir = true;
481
+ console.log(`\nUsing temporary directory: ${tempDir}`);
482
+ }
408
483
  } else {
409
484
  resolvedDistPath = path.resolve(distPath);
410
485
  }
411
-
486
+
412
487
  // First, check if we're in a Flutter package directory when flutter-package-src is not provided
413
488
  if (!options.flutterPackageSrc) {
414
489
  // Check if current directory or parent directories contain pubspec.yaml
415
490
  let currentDir = process.cwd();
416
491
  let foundPubspec = false;
417
492
  let pubspecDir = '';
418
-
493
+
419
494
  // Search up to 3 levels up for pubspec.yaml
420
495
  for (let i = 0; i < 3; i++) {
421
496
  const pubspecPath = path.join(currentDir, 'pubspec.yaml');
@@ -428,141 +503,151 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
428
503
  if (parentDir === currentDir) break; // Reached root
429
504
  currentDir = parentDir;
430
505
  }
431
-
506
+
432
507
  if (foundPubspec) {
433
508
  // Use the directory containing pubspec.yaml as the flutter package source
434
509
  options.flutterPackageSrc = pubspecDir;
435
510
  console.log(`\nDetected Flutter package at: ${pubspecDir}`);
436
511
  }
437
512
  }
438
-
439
- // Check if the directory exists and has required files
440
- const packageJsonPath = path.join(resolvedDistPath, 'package.json');
441
- const globalDtsPath = path.join(resolvedDistPath, 'global.d.ts');
442
- const tsConfigPath = path.join(resolvedDistPath, 'tsconfig.json');
443
-
444
- const hasPackageJson = fs.existsSync(packageJsonPath);
445
- const hasGlobalDts = fs.existsSync(globalDtsPath);
446
- const hasTsConfig = fs.existsSync(tsConfigPath);
447
-
448
- // Determine if we need to create a new project
449
- const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
450
-
451
- // Track if this is an existing project (has all required files)
452
- const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
453
-
513
+
454
514
  let framework = options.framework;
455
515
  let packageName = options.packageName;
456
-
457
- // Validate and sanitize package name if provided
458
- if (packageName && !isValidNpmPackageName(packageName)) {
459
- console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
460
- const sanitized = sanitizePackageName(packageName);
461
- console.log(`Using sanitized name: "${sanitized}"`);
462
- packageName = sanitized;
463
- }
464
-
465
- if (needsProjectCreation) {
466
- // If project needs creation but options are missing, prompt for them
467
- if (!framework) {
468
- const frameworkAnswer = await inquirer.prompt([{
469
- type: 'list',
470
- name: 'framework',
471
- message: 'Which framework would you like to use?',
472
- choices: ['react', 'vue']
473
- }]);
474
- framework = frameworkAnswer.framework;
475
- }
476
-
477
- // Try to read Flutter package metadata if flutterPackageSrc is provided
478
- let metadata: FlutterPackageMetadata | null = null;
479
- if (options.flutterPackageSrc) {
480
- metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
481
- }
482
-
483
- if (!packageName) {
484
- // Use Flutter package name as default if available, sanitized for npm
485
- const rawDefaultName = metadata?.name || path.basename(resolvedDistPath);
486
- const defaultPackageName = sanitizePackageName(rawDefaultName);
487
-
488
- const packageNameAnswer = await inquirer.prompt([{
489
- type: 'input',
490
- name: 'packageName',
491
- message: 'What is your package name?',
492
- default: defaultPackageName,
493
- validate: (input: string) => {
494
- if (!input || input.trim() === '') {
495
- return 'Package name is required';
496
- }
497
-
498
- // Check if it's valid as-is
499
- if (isValidNpmPackageName(input)) {
500
- return true;
516
+ let isExistingProject = false;
517
+
518
+ if (!isDartOnly) {
519
+ // Check if the directory exists and has required files
520
+ const packageJsonPath = path.join(resolvedDistPath, 'package.json');
521
+ const globalDtsPath = path.join(resolvedDistPath, 'global.d.ts');
522
+ const tsConfigPath = path.join(resolvedDistPath, 'tsconfig.json');
523
+
524
+ const hasPackageJson = fs.existsSync(packageJsonPath);
525
+ const hasGlobalDts = fs.existsSync(globalDtsPath);
526
+ const hasTsConfig = fs.existsSync(tsConfigPath);
527
+
528
+ // Determine if we need to create a new project
529
+ const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
530
+
531
+ // Track if this is an existing project (has all required files)
532
+ isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
533
+
534
+ // Validate and sanitize package name if provided
535
+ if (packageName && !isValidNpmPackageName(packageName)) {
536
+ console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
537
+ const sanitized = sanitizePackageName(packageName);
538
+ console.log(`Using sanitized name: "${sanitized}"`);
539
+ packageName = sanitized;
540
+ }
541
+
542
+ if (needsProjectCreation) {
543
+ // If project needs creation but options are missing, prompt for them
544
+ if (!framework) {
545
+ const frameworkAnswer = await inquirer.prompt([{
546
+ type: 'list',
547
+ name: 'framework',
548
+ message: 'Which framework would you like to use?',
549
+ choices: ['react', 'vue']
550
+ }]);
551
+ framework = frameworkAnswer.framework;
552
+ }
553
+
554
+ // Try to read Flutter package metadata if flutterPackageSrc is provided
555
+ let metadata: FlutterPackageMetadata | null = null;
556
+ if (options.flutterPackageSrc) {
557
+ metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
558
+ }
559
+
560
+ if (!packageName) {
561
+ // Use Flutter package name as default if available, sanitized for npm
562
+ const rawDefaultName = metadata?.name || path.basename(resolvedDistPath);
563
+ const defaultPackageName = sanitizePackageName(rawDefaultName);
564
+
565
+ const packageNameAnswer = await inquirer.prompt([{
566
+ type: 'input',
567
+ name: 'packageName',
568
+ message: 'What is your package name?',
569
+ default: defaultPackageName,
570
+ validate: (input: string) => {
571
+ if (!input || input.trim() === '') {
572
+ return 'Package name is required';
573
+ }
574
+
575
+ // Check if it's valid as-is
576
+ if (isValidNpmPackageName(input)) {
577
+ return true;
578
+ }
579
+
580
+ // If not valid, show what it would be sanitized to
581
+ const sanitized = sanitizePackageName(input);
582
+ return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
501
583
  }
502
-
503
- // If not valid, show what it would be sanitized to
504
- const sanitized = sanitizePackageName(input);
505
- return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
506
- }
507
- }]);
508
- packageName = packageNameAnswer.packageName;
509
- }
510
-
511
- console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
512
- createCommand(resolvedDistPath, {
513
- framework: framework!,
514
- packageName: packageName!,
515
- metadata: metadata || undefined
516
- });
517
- } else {
518
- // Validate existing project structure
519
- if (hasPackageJson) {
520
- try {
521
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
522
-
523
- // Detect framework from existing package.json
524
- if (!framework) {
525
- if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {
526
- framework = 'react';
527
- } else if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {
528
- framework = 'vue';
529
- } else {
530
- // If can't detect, prompt for it
531
- const frameworkAnswer = await inquirer.prompt([{
532
- type: 'list',
533
- name: 'framework',
534
- message: 'Which framework are you using?',
535
- choices: ['react', 'vue']
536
- }]);
537
- framework = frameworkAnswer.framework;
584
+ }]);
585
+ packageName = packageNameAnswer.packageName;
586
+ }
587
+
588
+ console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
589
+ createCommand(resolvedDistPath, {
590
+ framework: framework!,
591
+ packageName: packageName!,
592
+ metadata: metadata || undefined
593
+ });
594
+ } else {
595
+ // Validate existing project structure
596
+ if (hasPackageJson) {
597
+ try {
598
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
599
+
600
+ // Detect framework from existing package.json
601
+ if (!framework) {
602
+ if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {
603
+ framework = 'react';
604
+ } else if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {
605
+ framework = 'vue';
606
+ } else {
607
+ // If can't detect, prompt for it
608
+ const frameworkAnswer = await inquirer.prompt([{
609
+ type: 'list',
610
+ name: 'framework',
611
+ message: 'Which framework are you using?',
612
+ choices: ['react', 'vue']
613
+ }]);
614
+ framework = frameworkAnswer.framework;
615
+ }
538
616
  }
617
+
618
+ console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
619
+ } catch (e) {
620
+ console.error('Error reading package.json:', e);
621
+ process.exit(1);
539
622
  }
540
-
541
- console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
542
- } catch (e) {
543
- console.error('Error reading package.json:', e);
544
- process.exit(1);
545
623
  }
546
624
  }
625
+ } else {
626
+ // In Dart-only mode, framework/packageName are unused; ensure framework is not accidentally required later.
627
+ framework = options.framework;
547
628
  }
548
-
629
+
549
630
  // Now proceed with code generation if flutter package source is provided
550
631
  if (!options.flutterPackageSrc) {
551
632
  console.log('\nProject is ready for code generation.');
552
633
  console.log('To generate code, run:');
553
634
  const displayPath = isTempDir ? '<output-dir>' : distPath;
554
- console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
635
+ if (isDartOnly) {
636
+ console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --dart-only`);
637
+ } else {
638
+ console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
639
+ }
555
640
  if (isTempDir) {
556
641
  // Clean up temporary directory if we're not using it
557
642
  fs.rmSync(resolvedDistPath, { recursive: true, force: true });
558
643
  }
559
644
  return;
560
645
  }
561
-
646
+
562
647
  // Validate TypeScript environment in the Flutter package
563
648
  console.log(`\nValidating TypeScript environment in ${options.flutterPackageSrc}...`);
564
649
  const validation = validateTypeScriptEnvironment(options.flutterPackageSrc);
565
-
650
+
566
651
  if (!validation.isValid) {
567
652
  // Check specifically for missing tsconfig.json
568
653
  const tsConfigPath = path.join(options.flutterPackageSrc, 'tsconfig.json');
@@ -573,7 +658,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
573
658
  message: 'No tsconfig.json found. Would you like me to create one for you?',
574
659
  default: true
575
660
  }]);
576
-
661
+
577
662
  if (createTsConfigAnswer.createTsConfig) {
578
663
  // Create a default tsconfig.json
579
664
  const defaultTsConfig = {
@@ -592,10 +677,10 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
592
677
  include: ['lib/**/*.d.ts', '**/*.d.ts'],
593
678
  exclude: ['node_modules', 'dist', 'build']
594
679
  };
595
-
680
+
596
681
  fs.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
597
682
  console.log('āœ… Created tsconfig.json');
598
-
683
+
599
684
  // Re-validate after creating tsconfig
600
685
  const newValidation = validateTypeScriptEnvironment(options.flutterPackageSrc);
601
686
  if (!newValidation.isValid) {
@@ -617,21 +702,40 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
617
702
  process.exit(1);
618
703
  }
619
704
  }
620
-
621
- const command = `webf codegen --flutter-package-src=${options.flutterPackageSrc} --framework=${framework} <distPath>`;
622
-
705
+
706
+ const baseCommand = 'webf codegen';
707
+ const flutterPart = options.flutterPackageSrc ? ` --flutter-package-src=${options.flutterPackageSrc}` : '';
708
+ const modePart = isDartOnly
709
+ ? ' --dart-only'
710
+ : (framework ? ` --framework=${framework}` : '');
711
+ const command = `${baseCommand}${flutterPart}${modePart} <distPath>`;
712
+
713
+ if (isDartOnly) {
714
+ console.log(`\nGenerating Dart bindings from ${options.flutterPackageSrc}...`);
715
+
716
+ await dartGen({
717
+ source: options.flutterPackageSrc,
718
+ target: options.flutterPackageSrc,
719
+ command,
720
+ exclude: options.exclude,
721
+ });
722
+
723
+ console.log('\nDart code generation completed successfully!');
724
+ return;
725
+ }
726
+
623
727
  // Auto-initialize typings in the output directory if needed
624
728
  ensureInitialized(resolvedDistPath);
625
-
729
+
626
730
  console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
627
-
731
+
628
732
  await dartGen({
629
733
  source: options.flutterPackageSrc,
630
734
  target: options.flutterPackageSrc,
631
735
  command,
632
736
  exclude: options.exclude,
633
737
  });
634
-
738
+
635
739
  if (framework === 'react') {
636
740
  // Get the package name from package.json if it exists
637
741
  let reactPackageName: string | undefined;
@@ -644,7 +748,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
644
748
  } catch (e) {
645
749
  // Ignore errors
646
750
  }
647
-
751
+
648
752
  await reactGen({
649
753
  source: options.flutterPackageSrc,
650
754
  target: resolvedDistPath,
@@ -662,9 +766,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
662
766
  exclude: options.exclude,
663
767
  });
664
768
  }
665
-
769
+
666
770
  console.log('\nCode generation completed successfully!');
667
-
771
+
668
772
  // Automatically build the generated package
669
773
  if (framework) {
670
774
  try {
@@ -686,7 +790,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
686
790
  // Don't exit here since generation was successful
687
791
  }
688
792
  }
689
-
793
+
690
794
  // Handle npm publishing if requested via command line option
691
795
  if (options.publishToNpm && framework) {
692
796
  try {
@@ -703,7 +807,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
703
807
  message: 'Would you like to publish this package to npm?',
704
808
  default: false
705
809
  }]);
706
-
810
+
707
811
  if (publishAnswer.publish) {
708
812
  // Ask for registry
709
813
  const registryAnswer = await inquirer.prompt([{
@@ -721,10 +825,10 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
721
825
  }
722
826
  }
723
827
  }]);
724
-
828
+
725
829
  try {
726
830
  await buildAndPublishPackage(
727
- resolvedDistPath,
831
+ resolvedDistPath,
728
832
  registryAnswer.registry || undefined,
729
833
  isExistingProject
730
834
  );
@@ -734,7 +838,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
734
838
  }
735
839
  }
736
840
  }
737
-
841
+
738
842
  // If using a temporary directory, remind the user where the files are
739
843
  if (isTempDir) {
740
844
  console.log(`\nšŸ“ Generated files are in: ${resolvedDistPath}`);
@@ -756,19 +860,19 @@ function writeFileIfChanged(filePath: string, content: string) {
756
860
  function ensureInitialized(targetPath: string): void {
757
861
  const globalDtsPath = path.join(targetPath, 'global.d.ts');
758
862
  const tsConfigPath = path.join(targetPath, 'tsconfig.json');
759
-
863
+
760
864
  // Check if initialization files already exist
761
865
  const needsInit = !fs.existsSync(globalDtsPath) || !fs.existsSync(tsConfigPath);
762
-
866
+
763
867
  if (needsInit) {
764
868
  console.log('Initializing WebF typings...');
765
869
  fs.mkdirSync(targetPath, { recursive: true });
766
-
870
+
767
871
  if (!fs.existsSync(globalDtsPath)) {
768
872
  fs.writeFileSync(globalDtsPath, gloabalDts, 'utf-8');
769
873
  console.log('Created global.d.ts');
770
874
  }
771
-
875
+
772
876
  if (!fs.existsSync(tsConfigPath)) {
773
877
  fs.writeFileSync(tsConfigPath, tsConfig, 'utf-8');
774
878
  console.log('Created tsconfig.json');
@@ -778,7 +882,7 @@ function ensureInitialized(targetPath: string): void {
778
882
 
779
883
  async function buildPackage(packagePath: string): Promise<void> {
780
884
  const packageJsonPath = path.join(packagePath, 'package.json');
781
-
885
+
782
886
  if (!fs.existsSync(packageJsonPath)) {
783
887
  // Skip the error in test environment to avoid console warnings
784
888
  if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined) {
@@ -786,34 +890,34 @@ async function buildPackage(packagePath: string): Promise<void> {
786
890
  }
787
891
  throw new Error(`No package.json found in ${packagePath}`);
788
892
  }
789
-
893
+
790
894
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
791
895
  const packageName = packageJson.name;
792
896
  const packageVersion = packageJson.version;
793
-
897
+
794
898
  // Check if node_modules exists
795
899
  const nodeModulesPath = path.join(packagePath, 'node_modules');
796
900
  if (!fs.existsSync(nodeModulesPath)) {
797
901
  console.log(`\nšŸ“¦ Installing dependencies for ${packageName}...`);
798
-
902
+
799
903
  // Check if yarn.lock exists to determine package manager
800
904
  const yarnLockPath = path.join(packagePath, 'yarn.lock');
801
905
  const useYarn = fs.existsSync(yarnLockPath);
802
-
906
+
803
907
  const installCommand = useYarn ? 'yarn' : NPM;
804
908
  const installArgs = useYarn ? [] : ['install'];
805
-
909
+
806
910
  const installResult = spawnSync(installCommand, installArgs, {
807
911
  cwd: packagePath,
808
912
  stdio: 'inherit'
809
913
  });
810
-
914
+
811
915
  if (installResult.status !== 0) {
812
916
  throw new Error('Failed to install dependencies');
813
917
  }
814
918
  console.log('āœ… Dependencies installed successfully!');
815
919
  }
816
-
920
+
817
921
  // Check if package has a build script
818
922
  if (packageJson.scripts?.build) {
819
923
  console.log(`\nBuilding ${packageName}@${packageVersion}...`);
@@ -821,7 +925,7 @@ async function buildPackage(packagePath: string): Promise<void> {
821
925
  cwd: packagePath,
822
926
  stdio: 'inherit'
823
927
  });
824
-
928
+
825
929
  if (buildResult.status !== 0) {
826
930
  throw new Error('Build failed');
827
931
  }
@@ -833,18 +937,18 @@ async function buildPackage(packagePath: string): Promise<void> {
833
937
 
834
938
  async function buildAndPublishPackage(packagePath: string, registry?: string, isExistingProject: boolean = false): Promise<void> {
835
939
  const packageJsonPath = path.join(packagePath, 'package.json');
836
-
940
+
837
941
  if (!fs.existsSync(packageJsonPath)) {
838
942
  throw new Error(`No package.json found in ${packagePath}`);
839
943
  }
840
-
944
+
841
945
  let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
842
946
  const packageName = packageJson.name;
843
947
  let packageVersion = packageJson.version;
844
-
948
+
845
949
  // First, ensure dependencies are installed and build the package
846
950
  await buildPackage(packagePath);
847
-
951
+
848
952
  // If this is an existing project, increment the patch version before publishing
849
953
  if (isExistingProject) {
850
954
  console.log(`\nIncrementing version for existing project...`);
@@ -853,18 +957,18 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
853
957
  encoding: 'utf-8',
854
958
  stdio: 'pipe'
855
959
  });
856
-
960
+
857
961
  if (versionResult.status !== 0) {
858
962
  console.error('Failed to increment version:', versionResult.stderr);
859
963
  throw new Error('Failed to increment version');
860
964
  }
861
-
965
+
862
966
  // Re-read package.json to get the new version
863
967
  packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
864
968
  packageVersion = packageJson.version;
865
969
  console.log(`Version updated to ${packageVersion}`);
866
970
  }
867
-
971
+
868
972
  // Set registry if provided
869
973
  if (registry) {
870
974
  console.log(`\nUsing npm registry: ${registry}`);
@@ -872,38 +976,38 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
872
976
  cwd: packagePath,
873
977
  stdio: 'inherit'
874
978
  });
875
-
979
+
876
980
  if (setRegistryResult.status !== 0) {
877
981
  throw new Error('Failed to set npm registry');
878
982
  }
879
983
  }
880
-
984
+
881
985
  // Check if user is logged in to npm
882
986
  const whoamiResult = spawnSync(NPM, ['whoami'], {
883
987
  cwd: packagePath,
884
988
  encoding: 'utf-8'
885
989
  });
886
-
990
+
887
991
  if (whoamiResult.status !== 0) {
888
992
  console.error('\nError: You must be logged in to npm to publish packages.');
889
993
  console.error('Please run "npm login" first.');
890
994
  throw new Error('Not logged in to npm');
891
995
  }
892
-
996
+
893
997
  console.log(`\nPublishing ${packageName}@${packageVersion} to npm...`);
894
-
998
+
895
999
  // Publish the package
896
1000
  const publishResult = spawnSync(NPM, ['publish'], {
897
1001
  cwd: packagePath,
898
1002
  stdio: 'inherit'
899
1003
  });
900
-
1004
+
901
1005
  if (publishResult.status !== 0) {
902
1006
  throw new Error('Publish failed');
903
1007
  }
904
-
1008
+
905
1009
  console.log(`\nāœ… Successfully published ${packageName}@${packageVersion}`);
906
-
1010
+
907
1011
  // Reset registry to default if it was changed
908
1012
  if (registry) {
909
1013
  spawnSync(NPM, ['config', 'delete', 'registry'], {