@openwebf/webf 0.23.2 → 0.23.10

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
@@ -3,7 +3,8 @@ 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
+ import { generateModuleArtifacts } from './module';
7
+ import { globSync } from 'glob';
7
8
  import _ from 'lodash';
8
9
  import inquirer from 'inquirer';
9
10
  import yaml from 'yaml';
@@ -15,6 +16,7 @@ interface GenerateOptions {
15
16
  publishToNpm?: boolean;
16
17
  npmRegistry?: string;
17
18
  exclude?: string[];
19
+ dartOnly?: boolean;
18
20
  }
19
21
 
20
22
  interface FlutterPackageMetadata {
@@ -39,12 +41,12 @@ interface FlutterPackageMetadata {
39
41
  function sanitizePackageName(name: string): string {
40
42
  // Remove any leading/trailing whitespace
41
43
  let sanitized = name.trim();
42
-
44
+
43
45
  // Check if it's a scoped package
44
46
  const isScoped = sanitized.startsWith('@');
45
47
  let scope = '';
46
48
  let packageName = sanitized;
47
-
49
+
48
50
  if (isScoped) {
49
51
  const parts = sanitized.split('/');
50
52
  if (parts.length >= 2) {
@@ -55,7 +57,7 @@ function sanitizePackageName(name: string): string {
55
57
  packageName = sanitized.substring(1);
56
58
  }
57
59
  }
58
-
60
+
59
61
  // Sanitize scope if present
60
62
  if (scope) {
61
63
  scope = scope.toLowerCase();
@@ -65,7 +67,7 @@ function sanitizePackageName(name: string): string {
65
67
  scope = '@pkg'; // Default scope if only @ remains
66
68
  }
67
69
  }
68
-
70
+
69
71
  // Sanitize package name part
70
72
  packageName = packageName.toLowerCase();
71
73
  packageName = packageName.replace(/\s+/g, '-');
@@ -74,20 +76,20 @@ function sanitizePackageName(name: string): string {
74
76
  packageName = packageName.replace(/[._]+$/, '');
75
77
  packageName = packageName.replace(/[-_.]{2,}/g, '-');
76
78
  packageName = packageName.replace(/^-+/, '').replace(/-+$/, '');
77
-
79
+
78
80
  // Ensure package name is not empty
79
81
  if (!packageName) {
80
82
  packageName = 'package';
81
83
  }
82
-
84
+
83
85
  // Ensure it starts with a letter or number
84
86
  if (!/^[a-z0-9]/.test(packageName)) {
85
87
  packageName = 'pkg-' + packageName;
86
88
  }
87
-
89
+
88
90
  // Combine scope and package name
89
91
  let result = scope ? `${scope}/${packageName}` : packageName;
90
-
92
+
91
93
  // Truncate to 214 characters (npm limit)
92
94
  if (result.length > 214) {
93
95
  if (scope) {
@@ -101,7 +103,7 @@ function sanitizePackageName(name: string): string {
101
103
  result = result.replace(/[._-]+$/, '');
102
104
  }
103
105
  }
104
-
106
+
105
107
  return result;
106
108
  }
107
109
 
@@ -112,36 +114,36 @@ function isValidNpmPackageName(name: string): boolean {
112
114
  // Check basic rules
113
115
  if (!name || name.length === 0 || name.length > 214) return false;
114
116
  if (name.trim() !== name) return false;
115
-
117
+
116
118
  // Check if it's a scoped package
117
119
  if (name.startsWith('@')) {
118
120
  const parts = name.split('/');
119
121
  if (parts.length !== 2) return false; // Scoped packages must have exactly one /
120
-
122
+
121
123
  const scope = parts[0];
122
124
  const packageName = parts[1];
123
-
125
+
124
126
  // Validate scope
125
127
  if (!/^@[a-z0-9][a-z0-9-]*$/.test(scope)) return false;
126
-
128
+
127
129
  // Validate package name part
128
130
  return isValidNpmPackageName(packageName);
129
131
  }
130
-
132
+
131
133
  // For non-scoped packages
132
134
  if (name !== name.toLowerCase()) return false;
133
135
  if (name.startsWith('.') || name.startsWith('_')) return false;
134
-
136
+
135
137
  // Check for valid characters (letters, numbers, hyphens, underscores, dots)
136
138
  if (!/^[a-z0-9][a-z0-9\-_.]*$/.test(name)) return false;
137
-
139
+
138
140
  // Check for URL-safe characters
139
141
  try {
140
142
  if (encodeURIComponent(name) !== name) return false;
141
143
  } catch {
142
144
  return false;
143
145
  }
144
-
146
+
145
147
  return true;
146
148
  }
147
149
 
@@ -163,6 +165,20 @@ const gitignore = fs.readFileSync(
163
165
  'utf-8'
164
166
  );
165
167
 
168
+ const modulePackageJson = fs.readFileSync(
169
+ path.resolve(__dirname, '../templates/module.package.json.tpl'),
170
+ 'utf-8'
171
+ );
172
+
173
+ const moduleTsConfig = fs.readFileSync(
174
+ path.resolve(__dirname, '../templates/module.tsconfig.json.tpl'),
175
+ 'utf-8'
176
+ );
177
+
178
+ const moduleTsUpConfig = fs.readFileSync(
179
+ path.resolve(__dirname, '../templates/module.tsup.config.ts.tpl'),
180
+ 'utf-8'
181
+ );
166
182
  const reactPackageJson = fs.readFileSync(
167
183
  path.resolve(__dirname, '../templates/react.package.json.tpl'),
168
184
  'utf-8'
@@ -200,15 +216,15 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
200
216
  console.warn(`Warning: pubspec.yaml not found at ${pubspecPath}. Using default metadata.`);
201
217
  return null;
202
218
  }
203
-
219
+
204
220
  const pubspecContent = fs.readFileSync(pubspecPath, 'utf-8');
205
221
  const pubspec = yaml.parse(pubspecContent);
206
-
222
+
207
223
  // Validate required fields
208
224
  if (!pubspec.name) {
209
225
  console.warn(`Warning: Flutter package name not found in ${pubspecPath}. Using default name.`);
210
226
  }
211
-
227
+
212
228
  return {
213
229
  name: pubspec.name || '',
214
230
  version: pubspec.version || '0.0.1',
@@ -221,7 +237,8 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
221
237
  }
222
238
  }
223
239
 
224
- // Copy markdown docs that match .d.ts basenames from source to the built dist folder
240
+ // Copy markdown docs that match .d.ts basenames from source to the built dist folder,
241
+ // and generate an aggregated README.md in the dist directory.
225
242
  async function copyMarkdownDocsToDist(params: {
226
243
  sourceRoot: string;
227
244
  distRoot: string;
@@ -239,9 +256,10 @@ async function copyMarkdownDocsToDist(params: {
239
256
  const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
240
257
 
241
258
  // Find all .d.ts files and check for sibling .md files
242
- const dtsFiles = glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
259
+ const dtsFiles = globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
243
260
  let copied = 0;
244
261
  let skipped = 0;
262
+ const readmeSections: { title: string; relPath: string; content: string }[] = [];
245
263
 
246
264
  for (const relDts of dtsFiles) {
247
265
  if (path.basename(relDts) === 'global.d.ts') {
@@ -255,6 +273,13 @@ async function copyMarkdownDocsToDist(params: {
255
273
  continue;
256
274
  }
257
275
 
276
+ let content = '';
277
+ try {
278
+ content = fs.readFileSync(absMd, 'utf-8');
279
+ } catch {
280
+ // If we cannot read the file, still attempt to copy it and skip README aggregation for this entry.
281
+ }
282
+
258
283
  // Copy into dist preserving relative path
259
284
  const destPath = path.join(distRoot, relMd);
260
285
  const destDir = path.dirname(destPath);
@@ -263,6 +288,64 @@ async function copyMarkdownDocsToDist(params: {
263
288
  }
264
289
  fs.copyFileSync(absMd, destPath);
265
290
  copied++;
291
+
292
+ if (content) {
293
+ const base = path.basename(relMd, '.md');
294
+ const title = base
295
+ .split(/[-_]+/)
296
+ .filter(Boolean)
297
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
298
+ .join(' ');
299
+ readmeSections.push({
300
+ title: title || base,
301
+ relPath: relMd,
302
+ content
303
+ });
304
+ }
305
+ }
306
+
307
+ // Generate an aggregated README.md inside distRoot so consumers can see component docs easily.
308
+ if (readmeSections.length > 0) {
309
+ const readmePath = path.join(distRoot, 'README.md');
310
+ let existing = '';
311
+ if (fs.existsSync(readmePath)) {
312
+ try {
313
+ existing = fs.readFileSync(readmePath, 'utf-8');
314
+ } catch {
315
+ existing = '';
316
+ }
317
+ }
318
+
319
+ const headerLines: string[] = [
320
+ '# WebF Component Documentation',
321
+ '',
322
+ '> This README is generated from markdown docs co-located with TypeScript definitions in the Flutter package.',
323
+ ''
324
+ ];
325
+
326
+ const sectionBlocks = readmeSections.map(section => {
327
+ const lines: string[] = [];
328
+ lines.push(`## ${section.title}`);
329
+ lines.push('');
330
+ lines.push(`_Source: \`./${section.relPath}\`_`);
331
+ lines.push('');
332
+ lines.push(section.content.trim());
333
+ lines.push('');
334
+ return lines.join('\n');
335
+ }).join('\n');
336
+
337
+ let finalContent: string;
338
+ if (existing && existing.trim().length > 0) {
339
+ finalContent = `${existing.trim()}\n\n---\n\n${headerLines.join('\n')}${sectionBlocks}\n`;
340
+ } else {
341
+ finalContent = `${headerLines.join('\n')}${sectionBlocks}\n`;
342
+ }
343
+
344
+ try {
345
+ fs.writeFileSync(readmePath, finalContent, 'utf-8');
346
+ } catch {
347
+ // If README generation fails, do not affect overall codegen.
348
+ }
266
349
  }
267
350
 
268
351
  return { copied, skipped };
@@ -270,40 +353,40 @@ async function copyMarkdownDocsToDist(params: {
270
353
 
271
354
  function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean; errors: string[] } {
272
355
  const errors: string[] = [];
273
-
356
+
274
357
  // Check for TypeScript configuration
275
358
  const tsConfigPath = path.join(projectPath, 'tsconfig.json');
276
359
  if (!fs.existsSync(tsConfigPath)) {
277
360
  errors.push('Missing tsconfig.json - TypeScript configuration is required for type definitions');
278
361
  }
279
-
362
+
280
363
  // Check for .d.ts files - this is critical
281
364
  const libPath = path.join(projectPath, 'lib');
282
365
  let hasDtsFiles = false;
283
-
366
+
284
367
  if (fs.existsSync(libPath)) {
285
368
  // Check in lib directory
286
- hasDtsFiles = fs.readdirSync(libPath).some(file =>
287
- file.endsWith('.d.ts') ||
288
- (fs.statSync(path.join(libPath, file)).isDirectory() &&
369
+ hasDtsFiles = fs.readdirSync(libPath).some(file =>
370
+ file.endsWith('.d.ts') ||
371
+ (fs.statSync(path.join(libPath, file)).isDirectory() &&
289
372
  fs.readdirSync(path.join(libPath, file)).some(f => f.endsWith('.d.ts')))
290
373
  );
291
374
  }
292
-
375
+
293
376
  // Also check in root directory
294
377
  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' &&
378
+ hasDtsFiles = fs.readdirSync(projectPath).some(file =>
379
+ file.endsWith('.d.ts') ||
380
+ (fs.statSync(path.join(projectPath, file)).isDirectory() &&
381
+ file !== 'node_modules' &&
299
382
  fs.existsSync(path.join(projectPath, file, 'index.d.ts')))
300
383
  );
301
384
  }
302
-
385
+
303
386
  if (!hasDtsFiles) {
304
387
  errors.push('No TypeScript definition files (.d.ts) found in the project - Please create .d.ts files for your components');
305
388
  }
306
-
389
+
307
390
  return {
308
391
  isValid: errors.length === 0,
309
392
  errors
@@ -313,8 +396,8 @@ function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean;
313
396
  function createCommand(target: string, options: { framework: string; packageName: string; metadata?: FlutterPackageMetadata }): void {
314
397
  const { framework, metadata } = options;
315
398
  // Ensure package name is always valid
316
- const packageName = isValidNpmPackageName(options.packageName)
317
- ? options.packageName
399
+ const packageName = isValidNpmPackageName(options.packageName)
400
+ ? options.packageName
318
401
  : sanitizePackageName(options.packageName);
319
402
 
320
403
  if (!fs.existsSync(target)) {
@@ -394,28 +477,74 @@ function createCommand(target: string, options: { framework: string; packageName
394
477
  console.log(`WebF ${framework} package created at: ${target}`);
395
478
  }
396
479
 
480
+ function createModuleProject(target: string, options: { packageName: string; metadata?: FlutterPackageMetadata; skipGitignore?: boolean }): void {
481
+ const { metadata, skipGitignore } = options;
482
+ const packageName = isValidNpmPackageName(options.packageName)
483
+ ? options.packageName
484
+ : sanitizePackageName(options.packageName);
485
+
486
+ if (!fs.existsSync(target)) {
487
+ fs.mkdirSync(target, { recursive: true });
488
+ }
489
+
490
+ const packageJsonPath = path.join(target, 'package.json');
491
+ const packageJsonContent = _.template(modulePackageJson)({
492
+ packageName,
493
+ version: metadata?.version || '0.0.1',
494
+ description: metadata?.description || '',
495
+ });
496
+ writeFileIfChanged(packageJsonPath, packageJsonContent);
497
+
498
+ const tsConfigPath = path.join(target, 'tsconfig.json');
499
+ const tsConfigContent = _.template(moduleTsConfig)({});
500
+ writeFileIfChanged(tsConfigPath, tsConfigContent);
501
+
502
+ const tsupConfigPath = path.join(target, 'tsup.config.ts');
503
+ const tsupConfigContent = _.template(moduleTsUpConfig)({});
504
+ writeFileIfChanged(tsupConfigPath, tsupConfigContent);
505
+
506
+ if (!skipGitignore) {
507
+ const gitignorePath = path.join(target, '.gitignore');
508
+ const gitignoreContent = _.template(gitignore)({});
509
+ writeFileIfChanged(gitignorePath, gitignoreContent);
510
+ }
511
+
512
+ const srcDir = path.join(target, 'src');
513
+ if (!fs.existsSync(srcDir)) {
514
+ fs.mkdirSync(srcDir, { recursive: true });
515
+ }
516
+
517
+ console.log(`WebF module package scaffold created at: ${target}`);
518
+ }
519
+
397
520
  async function generateCommand(distPath: string, options: GenerateOptions): Promise<void> {
398
521
  // If distPath is not provided or is '.', create a temporary directory
399
522
  let resolvedDistPath: string;
400
523
  let isTempDir = false;
401
-
524
+ const isDartOnly = options.dartOnly;
525
+
402
526
  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}`);
527
+ if (isDartOnly) {
528
+ // In Dart-only mode we don't need a temporary Node project directory
529
+ resolvedDistPath = path.resolve(distPath || '.');
530
+ } else {
531
+ // Create a temporary directory for the generated package
532
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webf-typings-'));
533
+ resolvedDistPath = tempDir;
534
+ isTempDir = true;
535
+ console.log(`\nUsing temporary directory: ${tempDir}`);
536
+ }
408
537
  } else {
409
538
  resolvedDistPath = path.resolve(distPath);
410
539
  }
411
-
540
+
412
541
  // First, check if we're in a Flutter package directory when flutter-package-src is not provided
413
542
  if (!options.flutterPackageSrc) {
414
543
  // Check if current directory or parent directories contain pubspec.yaml
415
544
  let currentDir = process.cwd();
416
545
  let foundPubspec = false;
417
546
  let pubspecDir = '';
418
-
547
+
419
548
  // Search up to 3 levels up for pubspec.yaml
420
549
  for (let i = 0; i < 3; i++) {
421
550
  const pubspecPath = path.join(currentDir, 'pubspec.yaml');
@@ -428,141 +557,151 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
428
557
  if (parentDir === currentDir) break; // Reached root
429
558
  currentDir = parentDir;
430
559
  }
431
-
560
+
432
561
  if (foundPubspec) {
433
562
  // Use the directory containing pubspec.yaml as the flutter package source
434
563
  options.flutterPackageSrc = pubspecDir;
435
564
  console.log(`\nDetected Flutter package at: ${pubspecDir}`);
436
565
  }
437
566
  }
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
-
567
+
454
568
  let framework = options.framework;
455
569
  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;
570
+ let isExistingProject = false;
571
+
572
+ if (!isDartOnly) {
573
+ // Check if the directory exists and has required files
574
+ const packageJsonPath = path.join(resolvedDistPath, 'package.json');
575
+ const globalDtsPath = path.join(resolvedDistPath, 'global.d.ts');
576
+ const tsConfigPath = path.join(resolvedDistPath, 'tsconfig.json');
577
+
578
+ const hasPackageJson = fs.existsSync(packageJsonPath);
579
+ const hasGlobalDts = fs.existsSync(globalDtsPath);
580
+ const hasTsConfig = fs.existsSync(tsConfigPath);
581
+
582
+ // Determine if we need to create a new project
583
+ const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
584
+
585
+ // Track if this is an existing project (has all required files)
586
+ isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
587
+
588
+ // Validate and sanitize package name if provided
589
+ if (packageName && !isValidNpmPackageName(packageName)) {
590
+ console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
591
+ const sanitized = sanitizePackageName(packageName);
592
+ console.log(`Using sanitized name: "${sanitized}"`);
593
+ packageName = sanitized;
594
+ }
595
+
596
+ if (needsProjectCreation) {
597
+ // If project needs creation but options are missing, prompt for them
598
+ if (!framework) {
599
+ const frameworkAnswer = await inquirer.prompt([{
600
+ type: 'list',
601
+ name: 'framework',
602
+ message: 'Which framework would you like to use?',
603
+ choices: ['react', 'vue']
604
+ }]);
605
+ framework = frameworkAnswer.framework;
606
+ }
607
+
608
+ // Try to read Flutter package metadata if flutterPackageSrc is provided
609
+ let metadata: FlutterPackageMetadata | null = null;
610
+ if (options.flutterPackageSrc) {
611
+ metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
612
+ }
613
+
614
+ if (!packageName) {
615
+ // Use Flutter package name as default if available, sanitized for npm
616
+ const rawDefaultName = metadata?.name || path.basename(resolvedDistPath);
617
+ const defaultPackageName = sanitizePackageName(rawDefaultName);
618
+
619
+ const packageNameAnswer = await inquirer.prompt([{
620
+ type: 'input',
621
+ name: 'packageName',
622
+ message: 'What is your package name?',
623
+ default: defaultPackageName,
624
+ validate: (input: string) => {
625
+ if (!input || input.trim() === '') {
626
+ return 'Package name is required';
627
+ }
628
+
629
+ // Check if it's valid as-is
630
+ if (isValidNpmPackageName(input)) {
631
+ return true;
632
+ }
633
+
634
+ // If not valid, show what it would be sanitized to
635
+ const sanitized = sanitizePackageName(input);
636
+ return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
501
637
  }
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;
638
+ }]);
639
+ packageName = packageNameAnswer.packageName;
640
+ }
641
+
642
+ console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
643
+ createCommand(resolvedDistPath, {
644
+ framework: framework!,
645
+ packageName: packageName!,
646
+ metadata: metadata || undefined
647
+ });
648
+ } else {
649
+ // Validate existing project structure
650
+ if (hasPackageJson) {
651
+ try {
652
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
653
+
654
+ // Detect framework from existing package.json
655
+ if (!framework) {
656
+ if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {
657
+ framework = 'react';
658
+ } else if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {
659
+ framework = 'vue';
660
+ } else {
661
+ // If can't detect, prompt for it
662
+ const frameworkAnswer = await inquirer.prompt([{
663
+ type: 'list',
664
+ name: 'framework',
665
+ message: 'Which framework are you using?',
666
+ choices: ['react', 'vue']
667
+ }]);
668
+ framework = frameworkAnswer.framework;
669
+ }
538
670
  }
671
+
672
+ console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
673
+ } catch (e) {
674
+ console.error('Error reading package.json:', e);
675
+ process.exit(1);
539
676
  }
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
677
  }
546
678
  }
679
+ } else {
680
+ // In Dart-only mode, framework/packageName are unused; ensure framework is not accidentally required later.
681
+ framework = options.framework;
547
682
  }
548
-
683
+
549
684
  // Now proceed with code generation if flutter package source is provided
550
685
  if (!options.flutterPackageSrc) {
551
686
  console.log('\nProject is ready for code generation.');
552
687
  console.log('To generate code, run:');
553
688
  const displayPath = isTempDir ? '<output-dir>' : distPath;
554
- console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
689
+ if (isDartOnly) {
690
+ console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --dart-only`);
691
+ } else {
692
+ console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
693
+ }
555
694
  if (isTempDir) {
556
695
  // Clean up temporary directory if we're not using it
557
696
  fs.rmSync(resolvedDistPath, { recursive: true, force: true });
558
697
  }
559
698
  return;
560
699
  }
561
-
700
+
562
701
  // Validate TypeScript environment in the Flutter package
563
702
  console.log(`\nValidating TypeScript environment in ${options.flutterPackageSrc}...`);
564
703
  const validation = validateTypeScriptEnvironment(options.flutterPackageSrc);
565
-
704
+
566
705
  if (!validation.isValid) {
567
706
  // Check specifically for missing tsconfig.json
568
707
  const tsConfigPath = path.join(options.flutterPackageSrc, 'tsconfig.json');
@@ -573,7 +712,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
573
712
  message: 'No tsconfig.json found. Would you like me to create one for you?',
574
713
  default: true
575
714
  }]);
576
-
715
+
577
716
  if (createTsConfigAnswer.createTsConfig) {
578
717
  // Create a default tsconfig.json
579
718
  const defaultTsConfig = {
@@ -592,10 +731,10 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
592
731
  include: ['lib/**/*.d.ts', '**/*.d.ts'],
593
732
  exclude: ['node_modules', 'dist', 'build']
594
733
  };
595
-
734
+
596
735
  fs.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
597
736
  console.log('āœ… Created tsconfig.json');
598
-
737
+
599
738
  // Re-validate after creating tsconfig
600
739
  const newValidation = validateTypeScriptEnvironment(options.flutterPackageSrc);
601
740
  if (!newValidation.isValid) {
@@ -617,21 +756,40 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
617
756
  process.exit(1);
618
757
  }
619
758
  }
620
-
621
- const command = `webf codegen --flutter-package-src=${options.flutterPackageSrc} --framework=${framework} <distPath>`;
622
-
759
+
760
+ const baseCommand = 'webf codegen';
761
+ const flutterPart = options.flutterPackageSrc ? ` --flutter-package-src=${options.flutterPackageSrc}` : '';
762
+ const modePart = isDartOnly
763
+ ? ' --dart-only'
764
+ : (framework ? ` --framework=${framework}` : '');
765
+ const command = `${baseCommand}${flutterPart}${modePart} <distPath>`;
766
+
767
+ if (isDartOnly) {
768
+ console.log(`\nGenerating Dart bindings from ${options.flutterPackageSrc}...`);
769
+
770
+ await dartGen({
771
+ source: options.flutterPackageSrc,
772
+ target: options.flutterPackageSrc,
773
+ command,
774
+ exclude: options.exclude,
775
+ });
776
+
777
+ console.log('\nDart code generation completed successfully!');
778
+ return;
779
+ }
780
+
623
781
  // Auto-initialize typings in the output directory if needed
624
782
  ensureInitialized(resolvedDistPath);
625
-
783
+
626
784
  console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
627
-
785
+
628
786
  await dartGen({
629
787
  source: options.flutterPackageSrc,
630
788
  target: options.flutterPackageSrc,
631
789
  command,
632
790
  exclude: options.exclude,
633
791
  });
634
-
792
+
635
793
  if (framework === 'react') {
636
794
  // Get the package name from package.json if it exists
637
795
  let reactPackageName: string | undefined;
@@ -644,7 +802,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
644
802
  } catch (e) {
645
803
  // Ignore errors
646
804
  }
647
-
805
+
648
806
  await reactGen({
649
807
  source: options.flutterPackageSrc,
650
808
  target: resolvedDistPath,
@@ -662,9 +820,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
662
820
  exclude: options.exclude,
663
821
  });
664
822
  }
665
-
823
+
666
824
  console.log('\nCode generation completed successfully!');
667
-
825
+
668
826
  // Automatically build the generated package
669
827
  if (framework) {
670
828
  try {
@@ -686,7 +844,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
686
844
  // Don't exit here since generation was successful
687
845
  }
688
846
  }
689
-
847
+
690
848
  // Handle npm publishing if requested via command line option
691
849
  if (options.publishToNpm && framework) {
692
850
  try {
@@ -703,7 +861,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
703
861
  message: 'Would you like to publish this package to npm?',
704
862
  default: false
705
863
  }]);
706
-
864
+
707
865
  if (publishAnswer.publish) {
708
866
  // Ask for registry
709
867
  const registryAnswer = await inquirer.prompt([{
@@ -721,10 +879,10 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
721
879
  }
722
880
  }
723
881
  }]);
724
-
882
+
725
883
  try {
726
884
  await buildAndPublishPackage(
727
- resolvedDistPath,
885
+ resolvedDistPath,
728
886
  registryAnswer.registry || undefined,
729
887
  isExistingProject
730
888
  );
@@ -734,7 +892,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
734
892
  }
735
893
  }
736
894
  }
737
-
895
+
738
896
  // If using a temporary directory, remind the user where the files are
739
897
  if (isTempDir) {
740
898
  console.log(`\nšŸ“ Generated files are in: ${resolvedDistPath}`);
@@ -742,6 +900,236 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
742
900
  }
743
901
  }
744
902
 
903
+ async function generateModuleCommand(distPath: string, options: GenerateOptions): Promise<void> {
904
+ let resolvedDistPath: string;
905
+ let isTempDir = false;
906
+ if (!distPath || distPath === '.') {
907
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webf-module-'));
908
+ resolvedDistPath = tempDir;
909
+ isTempDir = true;
910
+ console.log(`\nUsing temporary directory for module package: ${tempDir}`);
911
+ } else {
912
+ resolvedDistPath = path.resolve(distPath);
913
+ }
914
+
915
+ // Detect Flutter package root if not provided
916
+ if (!options.flutterPackageSrc) {
917
+ let currentDir = process.cwd();
918
+ let foundPubspec = false;
919
+ let pubspecDir = '';
920
+
921
+ for (let i = 0; i < 3; i++) {
922
+ const pubspecPath = path.join(currentDir, 'pubspec.yaml');
923
+ if (fs.existsSync(pubspecPath)) {
924
+ foundPubspec = true;
925
+ pubspecDir = currentDir;
926
+ break;
927
+ }
928
+ const parentDir = path.dirname(currentDir);
929
+ if (parentDir === currentDir) break;
930
+ currentDir = parentDir;
931
+ }
932
+
933
+ if (!foundPubspec) {
934
+ console.error('Could not find pubspec.yaml. Please provide --flutter-package-src.');
935
+ process.exit(1);
936
+ }
937
+
938
+ options.flutterPackageSrc = pubspecDir;
939
+ console.log(`Detected Flutter package at: ${pubspecDir}`);
940
+ }
941
+
942
+ const flutterPackageSrc = path.resolve(options.flutterPackageSrc);
943
+
944
+ // Validate TS environment in the Flutter package
945
+ console.log(`\nValidating TypeScript environment in ${flutterPackageSrc}...`);
946
+ let validation = validateTypeScriptEnvironment(flutterPackageSrc);
947
+ if (!validation.isValid) {
948
+ const tsConfigPath = path.join(flutterPackageSrc, 'tsconfig.json');
949
+ if (!fs.existsSync(tsConfigPath)) {
950
+ const defaultTsConfig = {
951
+ compilerOptions: {
952
+ target: 'ES2020',
953
+ module: 'commonjs',
954
+ lib: ['ES2020'],
955
+ declaration: true,
956
+ strict: true,
957
+ esModuleInterop: true,
958
+ skipLibCheck: true,
959
+ forceConsistentCasingInFileNames: true,
960
+ resolveJsonModule: true,
961
+ moduleResolution: 'node',
962
+ },
963
+ include: ['lib/**/*.d.ts', '**/*.d.ts'],
964
+ exclude: ['node_modules', 'dist', 'build'],
965
+ };
966
+
967
+ fs.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
968
+ console.log('āœ… Created tsconfig.json for module package');
969
+
970
+ validation = validateTypeScriptEnvironment(flutterPackageSrc);
971
+ }
972
+
973
+ if (!validation.isValid) {
974
+ console.error('\nāŒ TypeScript environment validation failed:');
975
+ validation.errors.forEach(err => console.error(` - ${err}`));
976
+ console.error('\nPlease fix the above issues before running `webf module-codegen` again.');
977
+ process.exit(1);
978
+ }
979
+ }
980
+
981
+ // Read Flutter metadata for package.json
982
+ const metadata = readFlutterPackageMetadata(flutterPackageSrc);
983
+
984
+ // Determine package name
985
+ let packageName = options.packageName;
986
+ if (packageName && !isValidNpmPackageName(packageName)) {
987
+ console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
988
+ const sanitized = sanitizePackageName(packageName);
989
+ console.log(`Using sanitized name: "${sanitized}"`);
990
+ packageName = sanitized;
991
+ }
992
+
993
+ if (!packageName) {
994
+ const rawDefaultName = metadata?.name
995
+ ? `@openwebf/${metadata.name.replace(/^webf_/, 'webf-')}`
996
+ : '@openwebf/webf-module';
997
+
998
+ const defaultPackageName = isValidNpmPackageName(rawDefaultName)
999
+ ? rawDefaultName
1000
+ : sanitizePackageName(rawDefaultName);
1001
+
1002
+ const packageNameAnswer = await inquirer.prompt([{
1003
+ type: 'input',
1004
+ name: 'packageName',
1005
+ message: 'What is your npm package name for this module?',
1006
+ default: defaultPackageName,
1007
+ validate: (input: string) => {
1008
+ if (!input || input.trim() === '') {
1009
+ return 'Package name is required';
1010
+ }
1011
+
1012
+ if (isValidNpmPackageName(input)) {
1013
+ return true;
1014
+ }
1015
+
1016
+ const sanitized = sanitizePackageName(input);
1017
+ return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
1018
+ }
1019
+ }]);
1020
+ packageName = packageNameAnswer.packageName;
1021
+ }
1022
+
1023
+ // Prevent npm scaffolding (package.json, tsup.config.ts, etc.) from being written into
1024
+ // the Flutter package itself. Force users to choose a separate output directory.
1025
+ if (resolvedDistPath === flutterPackageSrc) {
1026
+ console.error('\nāŒ Output directory must not be the Flutter package root.');
1027
+ console.error('Please choose a separate directory for the generated npm package, for example:');
1028
+ console.error(' webf module-codegen ../packages/webf-share --flutter-package-src=../webf_modules/share');
1029
+ process.exit(1);
1030
+ }
1031
+
1032
+ // Scaffold npm project for the module
1033
+ if (!packageName) {
1034
+ throw new Error('Package name could not be resolved for module package.');
1035
+ }
1036
+ createModuleProject(resolvedDistPath, {
1037
+ packageName,
1038
+ metadata: metadata || undefined,
1039
+ });
1040
+
1041
+ // Locate module interface file (*.module.d.ts)
1042
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
1043
+ const ignore = options.exclude && options.exclude.length
1044
+ ? [...defaultIgnore, ...options.exclude]
1045
+ : defaultIgnore;
1046
+
1047
+ const candidates = globSync('**/*.module.d.ts', {
1048
+ cwd: flutterPackageSrc,
1049
+ ignore,
1050
+ });
1051
+
1052
+ if (candidates.length === 0) {
1053
+ console.error(
1054
+ `\nāŒ No module interface files (*.module.d.ts) found under ${flutterPackageSrc}.`
1055
+ );
1056
+ console.error('Please add a TypeScript interface file describing your module API.');
1057
+ process.exit(1);
1058
+ }
1059
+
1060
+ const moduleInterfaceRel = candidates[0];
1061
+ const moduleInterfacePath = path.join(flutterPackageSrc, moduleInterfaceRel);
1062
+
1063
+ const command = `webf module-codegen --flutter-package-src=${flutterPackageSrc} <distPath>`;
1064
+
1065
+ console.log(`\nGenerating module npm package and Dart bindings from ${moduleInterfaceRel}...`);
1066
+
1067
+ generateModuleArtifacts({
1068
+ moduleInterfacePath,
1069
+ npmTargetDir: resolvedDistPath,
1070
+ flutterPackageDir: flutterPackageSrc,
1071
+ command,
1072
+ });
1073
+
1074
+ console.log('\nModule code generation completed successfully!');
1075
+
1076
+ try {
1077
+ await buildPackage(resolvedDistPath);
1078
+ } catch (error) {
1079
+ console.error('\nWarning: Build failed:', error);
1080
+ }
1081
+
1082
+ if (options.publishToNpm) {
1083
+ try {
1084
+ await buildAndPublishPackage(resolvedDistPath, options.npmRegistry, false);
1085
+ } catch (error) {
1086
+ console.error('\nError during npm publish:', error);
1087
+ process.exit(1);
1088
+ }
1089
+ } else {
1090
+ const publishAnswer = await inquirer.prompt([{
1091
+ type: 'confirm',
1092
+ name: 'publish',
1093
+ message: 'Would you like to publish this module package to npm?',
1094
+ default: false
1095
+ }]);
1096
+
1097
+ if (publishAnswer.publish) {
1098
+ const registryAnswer = await inquirer.prompt([{
1099
+ type: 'input',
1100
+ name: 'registry',
1101
+ message: 'NPM registry URL (leave empty for default npm registry):',
1102
+ default: '',
1103
+ validate: (input: string) => {
1104
+ if (!input) return true;
1105
+ try {
1106
+ new URL(input);
1107
+ return true;
1108
+ } catch {
1109
+ return 'Please enter a valid URL';
1110
+ }
1111
+ }
1112
+ }]);
1113
+
1114
+ try {
1115
+ await buildAndPublishPackage(
1116
+ resolvedDistPath,
1117
+ registryAnswer.registry || undefined,
1118
+ false
1119
+ );
1120
+ } catch (error) {
1121
+ console.error('\nError during npm publish:', error);
1122
+ // Don't exit here since generation was successful
1123
+ }
1124
+ }
1125
+ }
1126
+
1127
+ if (isTempDir) {
1128
+ console.log(`\nšŸ“ Generated module npm package is in: ${resolvedDistPath}`);
1129
+ console.log('šŸ’” To use it, copy this directory to your packages folder or publish it directly.');
1130
+ }
1131
+ }
1132
+
745
1133
  function writeFileIfChanged(filePath: string, content: string) {
746
1134
  if (fs.existsSync(filePath)) {
747
1135
  const oldContent = fs.readFileSync(filePath, 'utf-8')
@@ -756,19 +1144,19 @@ function writeFileIfChanged(filePath: string, content: string) {
756
1144
  function ensureInitialized(targetPath: string): void {
757
1145
  const globalDtsPath = path.join(targetPath, 'global.d.ts');
758
1146
  const tsConfigPath = path.join(targetPath, 'tsconfig.json');
759
-
1147
+
760
1148
  // Check if initialization files already exist
761
1149
  const needsInit = !fs.existsSync(globalDtsPath) || !fs.existsSync(tsConfigPath);
762
-
1150
+
763
1151
  if (needsInit) {
764
1152
  console.log('Initializing WebF typings...');
765
1153
  fs.mkdirSync(targetPath, { recursive: true });
766
-
1154
+
767
1155
  if (!fs.existsSync(globalDtsPath)) {
768
1156
  fs.writeFileSync(globalDtsPath, gloabalDts, 'utf-8');
769
1157
  console.log('Created global.d.ts');
770
1158
  }
771
-
1159
+
772
1160
  if (!fs.existsSync(tsConfigPath)) {
773
1161
  fs.writeFileSync(tsConfigPath, tsConfig, 'utf-8');
774
1162
  console.log('Created tsconfig.json');
@@ -778,7 +1166,7 @@ function ensureInitialized(targetPath: string): void {
778
1166
 
779
1167
  async function buildPackage(packagePath: string): Promise<void> {
780
1168
  const packageJsonPath = path.join(packagePath, 'package.json');
781
-
1169
+
782
1170
  if (!fs.existsSync(packageJsonPath)) {
783
1171
  // Skip the error in test environment to avoid console warnings
784
1172
  if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined) {
@@ -786,34 +1174,34 @@ async function buildPackage(packagePath: string): Promise<void> {
786
1174
  }
787
1175
  throw new Error(`No package.json found in ${packagePath}`);
788
1176
  }
789
-
1177
+
790
1178
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
791
1179
  const packageName = packageJson.name;
792
1180
  const packageVersion = packageJson.version;
793
-
1181
+
794
1182
  // Check if node_modules exists
795
1183
  const nodeModulesPath = path.join(packagePath, 'node_modules');
796
1184
  if (!fs.existsSync(nodeModulesPath)) {
797
1185
  console.log(`\nšŸ“¦ Installing dependencies for ${packageName}...`);
798
-
1186
+
799
1187
  // Check if yarn.lock exists to determine package manager
800
1188
  const yarnLockPath = path.join(packagePath, 'yarn.lock');
801
1189
  const useYarn = fs.existsSync(yarnLockPath);
802
-
1190
+
803
1191
  const installCommand = useYarn ? 'yarn' : NPM;
804
1192
  const installArgs = useYarn ? [] : ['install'];
805
-
1193
+
806
1194
  const installResult = spawnSync(installCommand, installArgs, {
807
1195
  cwd: packagePath,
808
1196
  stdio: 'inherit'
809
1197
  });
810
-
1198
+
811
1199
  if (installResult.status !== 0) {
812
1200
  throw new Error('Failed to install dependencies');
813
1201
  }
814
1202
  console.log('āœ… Dependencies installed successfully!');
815
1203
  }
816
-
1204
+
817
1205
  // Check if package has a build script
818
1206
  if (packageJson.scripts?.build) {
819
1207
  console.log(`\nBuilding ${packageName}@${packageVersion}...`);
@@ -821,7 +1209,7 @@ async function buildPackage(packagePath: string): Promise<void> {
821
1209
  cwd: packagePath,
822
1210
  stdio: 'inherit'
823
1211
  });
824
-
1212
+
825
1213
  if (buildResult.status !== 0) {
826
1214
  throw new Error('Build failed');
827
1215
  }
@@ -833,18 +1221,18 @@ async function buildPackage(packagePath: string): Promise<void> {
833
1221
 
834
1222
  async function buildAndPublishPackage(packagePath: string, registry?: string, isExistingProject: boolean = false): Promise<void> {
835
1223
  const packageJsonPath = path.join(packagePath, 'package.json');
836
-
1224
+
837
1225
  if (!fs.existsSync(packageJsonPath)) {
838
1226
  throw new Error(`No package.json found in ${packagePath}`);
839
1227
  }
840
-
1228
+
841
1229
  let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
842
1230
  const packageName = packageJson.name;
843
1231
  let packageVersion = packageJson.version;
844
-
1232
+
845
1233
  // First, ensure dependencies are installed and build the package
846
1234
  await buildPackage(packagePath);
847
-
1235
+
848
1236
  // If this is an existing project, increment the patch version before publishing
849
1237
  if (isExistingProject) {
850
1238
  console.log(`\nIncrementing version for existing project...`);
@@ -853,18 +1241,18 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
853
1241
  encoding: 'utf-8',
854
1242
  stdio: 'pipe'
855
1243
  });
856
-
1244
+
857
1245
  if (versionResult.status !== 0) {
858
1246
  console.error('Failed to increment version:', versionResult.stderr);
859
1247
  throw new Error('Failed to increment version');
860
1248
  }
861
-
1249
+
862
1250
  // Re-read package.json to get the new version
863
1251
  packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
864
1252
  packageVersion = packageJson.version;
865
1253
  console.log(`Version updated to ${packageVersion}`);
866
1254
  }
867
-
1255
+
868
1256
  // Set registry if provided
869
1257
  if (registry) {
870
1258
  console.log(`\nUsing npm registry: ${registry}`);
@@ -872,38 +1260,38 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
872
1260
  cwd: packagePath,
873
1261
  stdio: 'inherit'
874
1262
  });
875
-
1263
+
876
1264
  if (setRegistryResult.status !== 0) {
877
1265
  throw new Error('Failed to set npm registry');
878
1266
  }
879
1267
  }
880
-
1268
+
881
1269
  // Check if user is logged in to npm
882
1270
  const whoamiResult = spawnSync(NPM, ['whoami'], {
883
1271
  cwd: packagePath,
884
1272
  encoding: 'utf-8'
885
1273
  });
886
-
1274
+
887
1275
  if (whoamiResult.status !== 0) {
888
1276
  console.error('\nError: You must be logged in to npm to publish packages.');
889
1277
  console.error('Please run "npm login" first.');
890
1278
  throw new Error('Not logged in to npm');
891
1279
  }
892
-
1280
+
893
1281
  console.log(`\nPublishing ${packageName}@${packageVersion} to npm...`);
894
-
1282
+
895
1283
  // Publish the package
896
1284
  const publishResult = spawnSync(NPM, ['publish'], {
897
1285
  cwd: packagePath,
898
1286
  stdio: 'inherit'
899
1287
  });
900
-
1288
+
901
1289
  if (publishResult.status !== 0) {
902
1290
  throw new Error('Publish failed');
903
1291
  }
904
-
1292
+
905
1293
  console.log(`\nāœ… Successfully published ${packageName}@${packageVersion}`);
906
-
1294
+
907
1295
  // Reset registry to default if it was changed
908
1296
  if (registry) {
909
1297
  spawnSync(NPM, ['config', 'delete', 'registry'], {
@@ -912,4 +1300,4 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
912
1300
  }
913
1301
  }
914
1302
 
915
- export { generateCommand };
1303
+ export { generateCommand, generateModuleCommand };