@openwebf/webf 0.1.0

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.
Files changed (60) hide show
  1. package/CLAUDE.md +206 -0
  2. package/README-zhCN.md +256 -0
  3. package/README.md +232 -0
  4. package/bin/webf.js +25 -0
  5. package/coverage/clover.xml +1295 -0
  6. package/coverage/coverage-final.json +12 -0
  7. package/coverage/lcov-report/IDLBlob.ts.html +142 -0
  8. package/coverage/lcov-report/analyzer.ts.html +2158 -0
  9. package/coverage/lcov-report/analyzer_original.ts.html +1450 -0
  10. package/coverage/lcov-report/base.css +224 -0
  11. package/coverage/lcov-report/block-navigation.js +87 -0
  12. package/coverage/lcov-report/commands.ts.html +700 -0
  13. package/coverage/lcov-report/dart.ts.html +490 -0
  14. package/coverage/lcov-report/declaration.ts.html +337 -0
  15. package/coverage/lcov-report/favicon.png +0 -0
  16. package/coverage/lcov-report/generator.ts.html +1171 -0
  17. package/coverage/lcov-report/index.html +266 -0
  18. package/coverage/lcov-report/logger.ts.html +424 -0
  19. package/coverage/lcov-report/prettify.css +1 -0
  20. package/coverage/lcov-report/prettify.js +2 -0
  21. package/coverage/lcov-report/react.ts.html +619 -0
  22. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  23. package/coverage/lcov-report/sorter.js +196 -0
  24. package/coverage/lcov-report/utils.ts.html +466 -0
  25. package/coverage/lcov-report/vue.ts.html +613 -0
  26. package/coverage/lcov.info +2149 -0
  27. package/global.d.ts +2 -0
  28. package/jest.config.js +24 -0
  29. package/package.json +36 -0
  30. package/src/IDLBlob.ts +20 -0
  31. package/src/analyzer.ts +692 -0
  32. package/src/commands.ts +645 -0
  33. package/src/dart.ts +170 -0
  34. package/src/declaration.ts +84 -0
  35. package/src/generator.ts +454 -0
  36. package/src/logger.ts +114 -0
  37. package/src/react.ts +186 -0
  38. package/src/utils.ts +127 -0
  39. package/src/vue.ts +176 -0
  40. package/templates/class.dart.tpl +86 -0
  41. package/templates/gitignore.tpl +2 -0
  42. package/templates/react.component.tsx.tpl +53 -0
  43. package/templates/react.createComponent.tpl +286 -0
  44. package/templates/react.index.ts.tpl +8 -0
  45. package/templates/react.package.json.tpl +26 -0
  46. package/templates/react.tsconfig.json.tpl +16 -0
  47. package/templates/react.tsup.config.ts.tpl +10 -0
  48. package/templates/tsconfig.json.tpl +8 -0
  49. package/templates/vue.component.partial.tpl +31 -0
  50. package/templates/vue.components.d.ts.tpl +49 -0
  51. package/templates/vue.package.json.tpl +11 -0
  52. package/templates/vue.tsconfig.json.tpl +15 -0
  53. package/test/IDLBlob.test.ts +75 -0
  54. package/test/analyzer.test.ts +370 -0
  55. package/test/commands.test.ts +1253 -0
  56. package/test/generator.test.ts +460 -0
  57. package/test/logger.test.ts +215 -0
  58. package/test/react.test.ts +49 -0
  59. package/test/utils.test.ts +316 -0
  60. package/tsconfig.json +30 -0
@@ -0,0 +1,645 @@
1
+ import { spawnSync } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { dartGen, reactGen, vueGen } from './generator';
6
+ import _ from 'lodash';
7
+ import inquirer from 'inquirer';
8
+ import yaml from 'yaml';
9
+
10
+ interface GenerateOptions {
11
+ flutterPackageSrc?: string;
12
+ framework?: string;
13
+ packageName?: string;
14
+ publishToNpm?: boolean;
15
+ npmRegistry?: string;
16
+ }
17
+
18
+ interface FlutterPackageMetadata {
19
+ name: string;
20
+ version: string;
21
+ description: string;
22
+ }
23
+
24
+ const platform = process.platform;
25
+ const NPM = platform == 'win32' ? 'npm.cmd' : 'npm';
26
+
27
+ const gloabalDts = fs.readFileSync(
28
+ path.resolve(__dirname, '../global.d.ts'),
29
+ 'utf-8'
30
+ );
31
+
32
+ const tsConfig = fs.readFileSync(
33
+ path.resolve(__dirname, '../templates/tsconfig.json.tpl'),
34
+ 'utf-8'
35
+ );
36
+
37
+ const gitignore = fs.readFileSync(
38
+ path.resolve(__dirname, '../templates/gitignore.tpl'),
39
+ 'utf-8'
40
+ );
41
+
42
+ const reactPackageJson = fs.readFileSync(
43
+ path.resolve(__dirname, '../templates/react.package.json.tpl'),
44
+ 'utf-8'
45
+ );
46
+
47
+ const reactTsConfig = fs.readFileSync(
48
+ path.resolve(__dirname, '../templates/react.tsconfig.json.tpl'),
49
+ 'utf-8'
50
+ );
51
+
52
+ const reactTsUpConfig = fs.readFileSync(
53
+ path.resolve(__dirname, '../templates/react.tsup.config.ts.tpl'),
54
+ 'utf-8'
55
+ );
56
+
57
+ const createComponentTpl = fs.readFileSync(
58
+ path.resolve(__dirname, '../templates/react.createComponent.tpl'),
59
+ 'utf-8'
60
+ );
61
+
62
+ const reactIndexTpl = fs.readFileSync(
63
+ path.resolve(__dirname, '../templates/react.index.ts.tpl'),
64
+ 'utf-8'
65
+ );
66
+
67
+ const vuePackageJson = fs.readFileSync(
68
+ path.resolve(__dirname, '../templates/vue.package.json.tpl'),
69
+ 'utf-8'
70
+ );
71
+
72
+ const vueTsConfig = fs.readFileSync(
73
+ path.resolve(__dirname, '../templates/vue.tsconfig.json.tpl'),
74
+ 'utf-8'
75
+ );
76
+
77
+ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata | null {
78
+ try {
79
+ const pubspecPath = path.join(packagePath, 'pubspec.yaml');
80
+ if (!fs.existsSync(pubspecPath)) {
81
+ return null;
82
+ }
83
+
84
+ const pubspecContent = fs.readFileSync(pubspecPath, 'utf-8');
85
+ const pubspec = yaml.parse(pubspecContent);
86
+
87
+ return {
88
+ name: pubspec.name || '',
89
+ version: pubspec.version || '0.0.1',
90
+ description: pubspec.description || ''
91
+ };
92
+ } catch (error) {
93
+ console.warn('Warning: Could not read Flutter package metadata:', error);
94
+ return null;
95
+ }
96
+ }
97
+
98
+ function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean; errors: string[] } {
99
+ const errors: string[] = [];
100
+
101
+ // Check for TypeScript configuration
102
+ const tsConfigPath = path.join(projectPath, 'tsconfig.json');
103
+ if (!fs.existsSync(tsConfigPath)) {
104
+ errors.push('Missing tsconfig.json - TypeScript configuration is required for type definitions');
105
+ }
106
+
107
+ // Check for .d.ts files - this is critical
108
+ const libPath = path.join(projectPath, 'lib');
109
+ let hasDtsFiles = false;
110
+
111
+ if (fs.existsSync(libPath)) {
112
+ // Check in lib directory
113
+ hasDtsFiles = fs.readdirSync(libPath).some(file =>
114
+ file.endsWith('.d.ts') ||
115
+ (fs.statSync(path.join(libPath, file)).isDirectory() &&
116
+ fs.readdirSync(path.join(libPath, file)).some(f => f.endsWith('.d.ts')))
117
+ );
118
+ }
119
+
120
+ // Also check in root directory
121
+ if (!hasDtsFiles) {
122
+ hasDtsFiles = fs.readdirSync(projectPath).some(file =>
123
+ file.endsWith('.d.ts') ||
124
+ (fs.statSync(path.join(projectPath, file)).isDirectory() &&
125
+ file !== 'node_modules' &&
126
+ fs.existsSync(path.join(projectPath, file, 'index.d.ts')))
127
+ );
128
+ }
129
+
130
+ if (!hasDtsFiles) {
131
+ errors.push('No TypeScript definition files (.d.ts) found in the project - Please create .d.ts files for your components');
132
+ }
133
+
134
+ return {
135
+ isValid: errors.length === 0,
136
+ errors
137
+ };
138
+ }
139
+
140
+ function createCommand(target: string, options: { framework: string; packageName: string; metadata?: FlutterPackageMetadata }): void {
141
+ const { framework, packageName, metadata } = options;
142
+
143
+ if (!fs.existsSync(target)) {
144
+ fs.mkdirSync(target, { recursive: true });
145
+ }
146
+
147
+ if (framework === 'react') {
148
+ const packageJsonPath = path.join(target, 'package.json');
149
+ const packageJsonContent = _.template(reactPackageJson)({
150
+ packageName,
151
+ version: metadata?.version || '0.0.1',
152
+ description: metadata?.description || ''
153
+ });
154
+ writeFileIfChanged(packageJsonPath, packageJsonContent);
155
+
156
+ const tsConfigPath = path.join(target, 'tsconfig.json');
157
+ const tsConfigContent = _.template(reactTsConfig)({});
158
+ writeFileIfChanged(tsConfigPath, tsConfigContent);
159
+
160
+ const tsupConfigPath = path.join(target, 'tsup.config.ts');
161
+ const tsupConfigContent = _.template(reactTsUpConfig)({});
162
+ writeFileIfChanged(tsupConfigPath, tsupConfigContent);
163
+
164
+ const gitignorePath = path.join(target, '.gitignore');
165
+ const gitignoreContent = _.template(gitignore)({});
166
+ writeFileIfChanged(gitignorePath, gitignoreContent);
167
+
168
+ const srcDir = path.join(target, 'src');
169
+ if (!fs.existsSync(srcDir)) {
170
+ fs.mkdirSync(srcDir, { recursive: true });
171
+ }
172
+
173
+ const indexFilePath = path.join(srcDir, 'index.ts');
174
+ const indexContent = _.template(reactIndexTpl)({
175
+ components: [],
176
+ });
177
+ writeFileIfChanged(indexFilePath, indexContent);
178
+
179
+ const utilsDir = path.join(srcDir, 'utils');
180
+ if (!fs.existsSync(utilsDir)) {
181
+ fs.mkdirSync(utilsDir, { recursive: true });
182
+ }
183
+
184
+ const createComponentPath = path.join(utilsDir, 'createComponent.ts');
185
+ const createComponentContent = _.template(createComponentTpl)({});
186
+ writeFileIfChanged(createComponentPath, createComponentContent);
187
+
188
+ spawnSync(NPM, ['install', '--omit=peer'], {
189
+ cwd: target,
190
+ stdio: 'inherit'
191
+ });
192
+
193
+ } else if (framework === 'vue') {
194
+ const packageJsonPath = path.join(target, 'package.json');
195
+ const packageJsonContent = _.template(vuePackageJson)({
196
+ packageName,
197
+ version: metadata?.version || '0.0.1',
198
+ description: metadata?.description || ''
199
+ });
200
+ writeFileIfChanged(packageJsonPath, packageJsonContent);
201
+
202
+ const tsConfigPath = path.join(target, 'tsconfig.json');
203
+ const tsConfigContent = _.template(vueTsConfig)({});
204
+ writeFileIfChanged(tsConfigPath, tsConfigContent);
205
+
206
+ const gitignorePath = path.join(target, '.gitignore');
207
+ const gitignoreContent = _.template(gitignore)({});
208
+ writeFileIfChanged(gitignorePath, gitignoreContent);
209
+
210
+ spawnSync(NPM, ['install', '@openwebf/webf-enterprise-typings'], {
211
+ cwd: target,
212
+ stdio: 'inherit'
213
+ });
214
+
215
+ spawnSync(NPM, ['install', '@types/vue', '-D'], {
216
+ cwd: target,
217
+ stdio: 'inherit'
218
+ });
219
+ }
220
+
221
+ console.log(`WebF ${framework} package created at: ${target}`);
222
+ }
223
+
224
+ async function generateCommand(distPath: string, options: GenerateOptions): Promise<void> {
225
+ // If distPath is not provided or is '.', create a temporary directory
226
+ let resolvedDistPath: string;
227
+ let isTempDir = false;
228
+
229
+ if (!distPath || distPath === '.') {
230
+ // Create a temporary directory for the generated package
231
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webf-typings-'));
232
+ resolvedDistPath = tempDir;
233
+ isTempDir = true;
234
+ console.log(`\nUsing temporary directory: ${tempDir}`);
235
+ } else {
236
+ resolvedDistPath = path.resolve(distPath);
237
+ }
238
+
239
+ // First, check if we're in a Flutter package directory when flutter-package-src is not provided
240
+ if (!options.flutterPackageSrc) {
241
+ // Check if current directory or parent directories contain pubspec.yaml
242
+ let currentDir = process.cwd();
243
+ let foundPubspec = false;
244
+ let pubspecDir = '';
245
+
246
+ // Search up to 3 levels up for pubspec.yaml
247
+ for (let i = 0; i < 3; i++) {
248
+ const pubspecPath = path.join(currentDir, 'pubspec.yaml');
249
+ if (fs.existsSync(pubspecPath)) {
250
+ foundPubspec = true;
251
+ pubspecDir = currentDir;
252
+ break;
253
+ }
254
+ const parentDir = path.dirname(currentDir);
255
+ if (parentDir === currentDir) break; // Reached root
256
+ currentDir = parentDir;
257
+ }
258
+
259
+ if (foundPubspec) {
260
+ // Use the directory containing pubspec.yaml as the flutter package source
261
+ options.flutterPackageSrc = pubspecDir;
262
+ console.log(`\nDetected Flutter package at: ${pubspecDir}`);
263
+ }
264
+ }
265
+
266
+ // Check if the directory exists and has required files
267
+ const packageJsonPath = path.join(resolvedDistPath, 'package.json');
268
+ const globalDtsPath = path.join(resolvedDistPath, 'global.d.ts');
269
+ const tsConfigPath = path.join(resolvedDistPath, 'tsconfig.json');
270
+
271
+ const hasPackageJson = fs.existsSync(packageJsonPath);
272
+ const hasGlobalDts = fs.existsSync(globalDtsPath);
273
+ const hasTsConfig = fs.existsSync(tsConfigPath);
274
+
275
+ // Determine if we need to create a new project
276
+ const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
277
+
278
+ let framework = options.framework;
279
+ let packageName = options.packageName;
280
+
281
+ if (needsProjectCreation) {
282
+ // If project needs creation but options are missing, prompt for them
283
+ if (!framework) {
284
+ const frameworkAnswer = await inquirer.prompt([{
285
+ type: 'list',
286
+ name: 'framework',
287
+ message: 'Which framework would you like to use?',
288
+ choices: ['react', 'vue']
289
+ }]);
290
+ framework = frameworkAnswer.framework;
291
+ }
292
+
293
+ // Try to read Flutter package metadata if flutterPackageSrc is provided
294
+ let metadata: FlutterPackageMetadata | null = null;
295
+ if (options.flutterPackageSrc) {
296
+ metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
297
+ }
298
+
299
+ if (!packageName) {
300
+ // Use Flutter package name as default if available
301
+ const defaultPackageName = metadata?.name || path.basename(resolvedDistPath);
302
+
303
+ const packageNameAnswer = await inquirer.prompt([{
304
+ type: 'input',
305
+ name: 'packageName',
306
+ message: 'What is your package name?',
307
+ default: defaultPackageName,
308
+ validate: (input: string) => {
309
+ if (!input || input.trim() === '') {
310
+ return 'Package name is required';
311
+ }
312
+ // Basic npm package name validation
313
+ if (!/^[a-z0-9]([a-z0-9-._])*$/.test(input)) {
314
+ return 'Package name must be lowercase and may contain hyphens, dots, and underscores';
315
+ }
316
+ return true;
317
+ }
318
+ }]);
319
+ packageName = packageNameAnswer.packageName;
320
+ }
321
+
322
+ console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
323
+ createCommand(resolvedDistPath, {
324
+ framework: framework!,
325
+ packageName: packageName!,
326
+ metadata: metadata || undefined
327
+ });
328
+ } else {
329
+ // Validate existing project structure
330
+ if (hasPackageJson) {
331
+ try {
332
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
333
+
334
+ // Detect framework from existing package.json
335
+ if (!framework) {
336
+ if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {
337
+ framework = 'react';
338
+ } else if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {
339
+ framework = 'vue';
340
+ } else {
341
+ // If can't detect, prompt for it
342
+ const frameworkAnswer = await inquirer.prompt([{
343
+ type: 'list',
344
+ name: 'framework',
345
+ message: 'Which framework are you using?',
346
+ choices: ['react', 'vue']
347
+ }]);
348
+ framework = frameworkAnswer.framework;
349
+ }
350
+ }
351
+
352
+ console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
353
+ } catch (e) {
354
+ console.error('Error reading package.json:', e);
355
+ process.exit(1);
356
+ }
357
+ }
358
+ }
359
+
360
+ // Now proceed with code generation if flutter package source is provided
361
+ if (!options.flutterPackageSrc) {
362
+ console.log('\nProject is ready for code generation.');
363
+ console.log('To generate code, run:');
364
+ const displayPath = isTempDir ? '<output-dir>' : distPath;
365
+ console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
366
+ if (isTempDir) {
367
+ // Clean up temporary directory if we're not using it
368
+ fs.rmSync(resolvedDistPath, { recursive: true, force: true });
369
+ }
370
+ return;
371
+ }
372
+
373
+ // Validate TypeScript environment in the Flutter package
374
+ console.log(`\nValidating TypeScript environment in ${options.flutterPackageSrc}...`);
375
+ const validation = validateTypeScriptEnvironment(options.flutterPackageSrc);
376
+
377
+ if (!validation.isValid) {
378
+ // Check specifically for missing tsconfig.json
379
+ const tsConfigPath = path.join(options.flutterPackageSrc, 'tsconfig.json');
380
+ if (!fs.existsSync(tsConfigPath)) {
381
+ const createTsConfigAnswer = await inquirer.prompt([{
382
+ type: 'confirm',
383
+ name: 'createTsConfig',
384
+ message: 'No tsconfig.json found. Would you like me to create one for you?',
385
+ default: true
386
+ }]);
387
+
388
+ if (createTsConfigAnswer.createTsConfig) {
389
+ // Create a default tsconfig.json
390
+ const defaultTsConfig = {
391
+ compilerOptions: {
392
+ target: 'ES2020',
393
+ module: 'commonjs',
394
+ lib: ['ES2020'],
395
+ declaration: true,
396
+ strict: true,
397
+ esModuleInterop: true,
398
+ skipLibCheck: true,
399
+ forceConsistentCasingInFileNames: true,
400
+ resolveJsonModule: true,
401
+ moduleResolution: 'node'
402
+ },
403
+ include: ['lib/**/*.d.ts', '**/*.d.ts'],
404
+ exclude: ['node_modules', 'dist', 'build']
405
+ };
406
+
407
+ fs.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
408
+ console.log('āœ… Created tsconfig.json');
409
+
410
+ // Re-validate after creating tsconfig
411
+ const newValidation = validateTypeScriptEnvironment(options.flutterPackageSrc);
412
+ if (!newValidation.isValid) {
413
+ console.error('\nāš ļø Additional setup required:');
414
+ newValidation.errors.forEach(error => console.error(` - ${error}`));
415
+ console.error('\nPlease fix the above issues and run the command again.');
416
+ process.exit(1);
417
+ }
418
+ } else {
419
+ console.error('\nāŒ TypeScript configuration is required for code generation.');
420
+ console.error('Please create a tsconfig.json file manually and run the command again.');
421
+ process.exit(1);
422
+ }
423
+ } else {
424
+ // Show all validation errors
425
+ console.error('\nāŒ TypeScript environment validation failed:');
426
+ validation.errors.forEach(error => console.error(` - ${error}`));
427
+ console.error('\nPlease fix the above issues before generating code.');
428
+ process.exit(1);
429
+ }
430
+ }
431
+
432
+ const command = `webf codegen --flutter-package-src=${options.flutterPackageSrc} --framework=${framework} <distPath>`;
433
+
434
+ // Auto-initialize typings in the output directory if needed
435
+ ensureInitialized(resolvedDistPath);
436
+
437
+ console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
438
+
439
+ await dartGen({
440
+ source: options.flutterPackageSrc,
441
+ target: options.flutterPackageSrc,
442
+ command,
443
+ });
444
+
445
+ if (framework === 'react') {
446
+ await reactGen({
447
+ source: options.flutterPackageSrc,
448
+ target: resolvedDistPath,
449
+ command,
450
+ });
451
+ } else if (framework === 'vue') {
452
+ await vueGen({
453
+ source: options.flutterPackageSrc,
454
+ target: resolvedDistPath,
455
+ command,
456
+ });
457
+ }
458
+
459
+ console.log('\nCode generation completed successfully!');
460
+
461
+ // Automatically build the generated package
462
+ if (framework) {
463
+ try {
464
+ await buildPackage(resolvedDistPath);
465
+ } catch (error) {
466
+ console.error('\nWarning: Build failed:', error);
467
+ // Don't exit here since generation was successful
468
+ }
469
+ }
470
+
471
+ // Handle npm publishing if requested via command line option
472
+ if (options.publishToNpm && framework) {
473
+ try {
474
+ await buildAndPublishPackage(resolvedDistPath, options.npmRegistry);
475
+ } catch (error) {
476
+ console.error('\nError during npm publish:', error);
477
+ process.exit(1);
478
+ }
479
+ } else if (framework && !options.publishToNpm) {
480
+ // If not publishing via command line option, ask the user
481
+ const publishAnswer = await inquirer.prompt([{
482
+ type: 'confirm',
483
+ name: 'publish',
484
+ message: 'Would you like to publish this package to npm?',
485
+ default: false
486
+ }]);
487
+
488
+ if (publishAnswer.publish) {
489
+ // Ask for registry
490
+ const registryAnswer = await inquirer.prompt([{
491
+ type: 'input',
492
+ name: 'registry',
493
+ message: 'NPM registry URL (leave empty for default npm registry):',
494
+ default: '',
495
+ validate: (input: string) => {
496
+ if (!input) return true; // Empty is valid (use default)
497
+ try {
498
+ new URL(input); // Validate URL format
499
+ return true;
500
+ } catch {
501
+ return 'Please enter a valid URL';
502
+ }
503
+ }
504
+ }]);
505
+
506
+ try {
507
+ await buildAndPublishPackage(
508
+ resolvedDistPath,
509
+ registryAnswer.registry || undefined
510
+ );
511
+ } catch (error) {
512
+ console.error('\nError during npm publish:', error);
513
+ // Don't exit here since generation was successful
514
+ }
515
+ }
516
+ }
517
+
518
+ // If using a temporary directory, remind the user where the files are
519
+ if (isTempDir) {
520
+ console.log(`\nšŸ“ Generated files are in: ${resolvedDistPath}`);
521
+ console.log('šŸ’” To use these files, copy them to your desired location or publish to npm.');
522
+ }
523
+ }
524
+
525
+ function writeFileIfChanged(filePath: string, content: string) {
526
+ if (fs.existsSync(filePath)) {
527
+ const oldContent = fs.readFileSync(filePath, 'utf-8')
528
+ if (oldContent === content) {
529
+ return;
530
+ }
531
+ }
532
+
533
+ fs.writeFileSync(filePath, content, 'utf-8');
534
+ }
535
+
536
+ function ensureInitialized(targetPath: string): void {
537
+ const globalDtsPath = path.join(targetPath, 'global.d.ts');
538
+ const tsConfigPath = path.join(targetPath, 'tsconfig.json');
539
+
540
+ // Check if initialization files already exist
541
+ const needsInit = !fs.existsSync(globalDtsPath) || !fs.existsSync(tsConfigPath);
542
+
543
+ if (needsInit) {
544
+ console.log('Initializing WebF typings...');
545
+ fs.mkdirSync(targetPath, { recursive: true });
546
+
547
+ if (!fs.existsSync(globalDtsPath)) {
548
+ fs.writeFileSync(globalDtsPath, gloabalDts, 'utf-8');
549
+ console.log('Created global.d.ts');
550
+ }
551
+
552
+ if (!fs.existsSync(tsConfigPath)) {
553
+ fs.writeFileSync(tsConfigPath, tsConfig, 'utf-8');
554
+ console.log('Created tsconfig.json');
555
+ }
556
+ }
557
+ }
558
+
559
+ async function buildPackage(packagePath: string): Promise<void> {
560
+ const packageJsonPath = path.join(packagePath, 'package.json');
561
+
562
+ if (!fs.existsSync(packageJsonPath)) {
563
+ throw new Error(`No package.json found in ${packagePath}`);
564
+ }
565
+
566
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
567
+ const packageName = packageJson.name;
568
+ const packageVersion = packageJson.version;
569
+
570
+ // Check if package has a build script
571
+ if (packageJson.scripts?.build) {
572
+ console.log(`\nBuilding ${packageName}@${packageVersion}...`);
573
+ const buildResult = spawnSync(NPM, ['run', 'build'], {
574
+ cwd: packagePath,
575
+ stdio: 'inherit'
576
+ });
577
+
578
+ if (buildResult.status !== 0) {
579
+ throw new Error('Build failed');
580
+ }
581
+ console.log('āœ… Build completed successfully!');
582
+ } else {
583
+ console.log(`\nNo build script found for ${packageName}@${packageVersion}`);
584
+ }
585
+ }
586
+
587
+ async function buildAndPublishPackage(packagePath: string, registry?: string): Promise<void> {
588
+ const packageJsonPath = path.join(packagePath, 'package.json');
589
+
590
+ if (!fs.existsSync(packageJsonPath)) {
591
+ throw new Error(`No package.json found in ${packagePath}`);
592
+ }
593
+
594
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
595
+ const packageName = packageJson.name;
596
+ const packageVersion = packageJson.version;
597
+
598
+ // Set registry if provided
599
+ if (registry) {
600
+ console.log(`\nUsing npm registry: ${registry}`);
601
+ const setRegistryResult = spawnSync(NPM, ['config', 'set', 'registry', registry], {
602
+ cwd: packagePath,
603
+ stdio: 'inherit'
604
+ });
605
+
606
+ if (setRegistryResult.status !== 0) {
607
+ throw new Error('Failed to set npm registry');
608
+ }
609
+ }
610
+
611
+ // Check if user is logged in to npm
612
+ const whoamiResult = spawnSync(NPM, ['whoami'], {
613
+ cwd: packagePath,
614
+ encoding: 'utf-8'
615
+ });
616
+
617
+ if (whoamiResult.status !== 0) {
618
+ console.error('\nError: You must be logged in to npm to publish packages.');
619
+ console.error('Please run "npm login" first.');
620
+ throw new Error('Not logged in to npm');
621
+ }
622
+
623
+ console.log(`\nPublishing ${packageName}@${packageVersion} to npm...`);
624
+
625
+ // Publish the package
626
+ const publishResult = spawnSync(NPM, ['publish'], {
627
+ cwd: packagePath,
628
+ stdio: 'inherit'
629
+ });
630
+
631
+ if (publishResult.status !== 0) {
632
+ throw new Error('Publish failed');
633
+ }
634
+
635
+ console.log(`\nāœ… Successfully published ${packageName}@${packageVersion}`);
636
+
637
+ // Reset registry to default if it was changed
638
+ if (registry) {
639
+ spawnSync(NPM, ['config', 'delete', 'registry'], {
640
+ cwd: packagePath
641
+ });
642
+ }
643
+ }
644
+
645
+ export { generateCommand };