@openwebf/webf 0.23.2 ā 0.23.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/bin/webf.js +1 -0
- package/dist/analyzer.js +65 -1
- package/dist/commands.js +198 -98
- package/dist/dart.js +91 -25
- package/dist/declaration.js +1 -0
- package/dist/generator.js +26 -17
- package/dist/react.js +272 -25
- package/dist/vue.js +74 -11
- package/package.json +1 -1
- package/src/analyzer.ts +58 -2
- package/src/commands.ts +300 -196
- package/src/dart.ts +95 -20
- package/src/declaration.ts +1 -0
- package/src/generator.ts +24 -16
- package/src/react.ts +288 -29
- package/src/vue.ts +85 -13
- package/templates/class.dart.tpl +1 -1
- package/templates/vue.components.d.ts.tpl +2 -0
- package/test/commands.test.ts +82 -2
- package/test/dart-nullable-props.test.ts +58 -0
- package/test/react-consts.test.ts +1 -1
- package/test/react-vue-nullable-props.test.ts +66 -0
- package/test/react.test.ts +46 -4
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
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
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
|
|
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'], {
|