@openwebf/webf 0.24.1 → 0.24.2
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 +1 -1
- package/dist/analyzer.js +183 -121
- package/dist/commands.js +54 -9
- package/dist/generator.js +39 -16
- package/package.json +1 -1
- package/src/analyzer.ts +186 -114
- package/src/commands.ts +65 -11
- package/src/generator.ts +32 -12
- package/templates/module.package.json.tpl +17 -6
- package/templates/{module.tsup.config.ts.tpl → module.tsdown.config.ts.tpl} +2 -7
- package/templates/react.component.tsx.tpl +18 -2
- package/templates/react.package.json.tpl +17 -5
- package/templates/{react.tsup.config.ts.tpl → react.tsdown.config.ts.tpl} +2 -4
- package/templates/vue.package.json.tpl +1 -1
- package/test/analyzer.test.ts +45 -1
- package/test/commands.test.ts +76 -4
- package/test/generator.test.ts +37 -0
- package/test/react.test.ts +5 -0
package/src/analyzer.ts
CHANGED
|
@@ -410,6 +410,188 @@ function handleGenericWrapper(typeReference: ts.TypeReferenceNode, mode?: Parame
|
|
|
410
410
|
return getParameterBaseType(argument, mode);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
const customEventTypePrinter = ts.createPrinter({ removeComments: true });
|
|
414
|
+
|
|
415
|
+
function mapTypeReferenceIdentifierToTsType(identifier: string): string | null {
|
|
416
|
+
const mappedType = TYPE_REFERENCE_MAP[identifier];
|
|
417
|
+
if (mappedType === undefined) return null;
|
|
418
|
+
switch (mappedType) {
|
|
419
|
+
case FunctionArgumentType.boolean:
|
|
420
|
+
return 'boolean';
|
|
421
|
+
case FunctionArgumentType.dom_string:
|
|
422
|
+
return 'string';
|
|
423
|
+
case FunctionArgumentType.double:
|
|
424
|
+
case FunctionArgumentType.int:
|
|
425
|
+
return 'number';
|
|
426
|
+
case FunctionArgumentType.any:
|
|
427
|
+
return 'any';
|
|
428
|
+
case FunctionArgumentType.void:
|
|
429
|
+
return 'void';
|
|
430
|
+
case FunctionArgumentType.function:
|
|
431
|
+
return 'Function';
|
|
432
|
+
case FunctionArgumentType.promise:
|
|
433
|
+
return 'Promise<any>';
|
|
434
|
+
default:
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function getBasicTypeKindAsTsType(kind: ts.SyntaxKind): string | null {
|
|
440
|
+
const basicType = BASIC_TYPE_MAP[kind];
|
|
441
|
+
if (basicType === undefined) return null;
|
|
442
|
+
switch (basicType) {
|
|
443
|
+
case FunctionArgumentType.boolean:
|
|
444
|
+
return 'boolean';
|
|
445
|
+
case FunctionArgumentType.dom_string:
|
|
446
|
+
return 'string';
|
|
447
|
+
case FunctionArgumentType.double:
|
|
448
|
+
case FunctionArgumentType.int:
|
|
449
|
+
return 'number';
|
|
450
|
+
case FunctionArgumentType.any:
|
|
451
|
+
return 'any';
|
|
452
|
+
case FunctionArgumentType.void:
|
|
453
|
+
return 'void';
|
|
454
|
+
case FunctionArgumentType.null:
|
|
455
|
+
return 'null';
|
|
456
|
+
case FunctionArgumentType.undefined:
|
|
457
|
+
return 'undefined';
|
|
458
|
+
default:
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function stringifyEntityName(name: ts.EntityName): string {
|
|
464
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
465
|
+
return `${stringifyEntityName(name.left)}.${name.right.text}`;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function safePrintCustomEventNode(node: ts.Node): string {
|
|
469
|
+
const sourceFile = node.getSourceFile();
|
|
470
|
+
const printed = customEventTypePrinter.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
|
471
|
+
// Ensure WebF IDL-like aliases used in type definitions do not leak into generated TypeScript packages.
|
|
472
|
+
return printed.replace(/\bint\b/g, 'number').replace(/\bdouble\b/g, 'number');
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function stringifyCustomEventGenericTypeNode(typeNode: ts.TypeNode): string | null {
|
|
476
|
+
if (ts.isParenthesizedTypeNode(typeNode)) {
|
|
477
|
+
const inner = stringifyCustomEventGenericTypeNode(typeNode.type);
|
|
478
|
+
return inner ? `(${inner})` : null;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
if (ts.isUnionTypeNode(typeNode)) {
|
|
482
|
+
const parts = typeNode.types.map(t => stringifyCustomEventGenericTypeNode(t)).filter((t): t is string => Boolean(t));
|
|
483
|
+
return parts.length === typeNode.types.length ? parts.join(' | ') : null;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (ts.isIntersectionTypeNode(typeNode)) {
|
|
487
|
+
const parts = typeNode.types.map(t => stringifyCustomEventGenericTypeNode(t)).filter((t): t is string => Boolean(t));
|
|
488
|
+
return parts.length === typeNode.types.length ? parts.join(' & ') : null;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
492
|
+
const element = stringifyCustomEventGenericTypeNode(typeNode.elementType);
|
|
493
|
+
return element ? `${element}[]` : null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (ts.isTupleTypeNode(typeNode)) {
|
|
497
|
+
const elements = typeNode.elements.map(e => stringifyCustomEventGenericTypeNode(e)).filter((t): t is string => Boolean(t));
|
|
498
|
+
return elements.length === typeNode.elements.length ? `[${elements.join(', ')}]` : null;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (ts.isLiteralTypeNode(typeNode)) {
|
|
502
|
+
const literal = typeNode.literal;
|
|
503
|
+
if (literal.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
504
|
+
if (literal.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
505
|
+
if (literal.kind === ts.SyntaxKind.TrueKeyword) return 'true';
|
|
506
|
+
if (literal.kind === ts.SyntaxKind.FalseKeyword) return 'false';
|
|
507
|
+
if (ts.isStringLiteral(literal)) return JSON.stringify(literal.text);
|
|
508
|
+
if (ts.isNumericLiteral(literal)) return literal.text;
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const basic = getBasicTypeKindAsTsType(typeNode.kind);
|
|
513
|
+
if (basic) return basic;
|
|
514
|
+
|
|
515
|
+
if (ts.isTypeReferenceNode(typeNode)) {
|
|
516
|
+
const typeName = stringifyEntityName(typeNode.typeName);
|
|
517
|
+
|
|
518
|
+
// Unwrap internal helpers used by WebF typings.
|
|
519
|
+
if (typeName === 'DartImpl' && typeNode.typeArguments && typeNode.typeArguments[0]) {
|
|
520
|
+
return stringifyCustomEventGenericTypeNode(typeNode.typeArguments[0]);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (typeName === 'Promise') {
|
|
524
|
+
if (!typeNode.typeArguments || !typeNode.typeArguments[0]) return 'Promise<any>';
|
|
525
|
+
const inner = stringifyCustomEventGenericTypeNode(typeNode.typeArguments[0]);
|
|
526
|
+
return inner ? `Promise<${inner}>` : null;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
const mapped = mapTypeReferenceIdentifierToTsType(typeName);
|
|
530
|
+
if (mapped) return mapped;
|
|
531
|
+
|
|
532
|
+
if (!typeNode.typeArguments || typeNode.typeArguments.length === 0) {
|
|
533
|
+
return typeName;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const args = typeNode.typeArguments
|
|
537
|
+
.map(arg => stringifyCustomEventGenericTypeNode(arg))
|
|
538
|
+
.filter((t): t is string => Boolean(t));
|
|
539
|
+
if (args.length !== typeNode.typeArguments.length) return null;
|
|
540
|
+
return `${typeName}<${args.join(', ')}>`;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
if (ts.isTypeLiteralNode(typeNode)) {
|
|
544
|
+
const members: string[] = [];
|
|
545
|
+
for (const member of typeNode.members) {
|
|
546
|
+
if (ts.isPropertySignature(member) && member.type) {
|
|
547
|
+
const typeString = stringifyCustomEventGenericTypeNode(member.type);
|
|
548
|
+
if (!typeString) return null;
|
|
549
|
+
let nameText: string;
|
|
550
|
+
if (ts.isIdentifier(member.name)) nameText = member.name.text;
|
|
551
|
+
else if (ts.isStringLiteral(member.name)) nameText = JSON.stringify(member.name.text);
|
|
552
|
+
else if (ts.isNumericLiteral(member.name)) nameText = member.name.text;
|
|
553
|
+
else nameText = member.name.getText();
|
|
554
|
+
const optional = member.questionToken ? '?' : '';
|
|
555
|
+
members.push(`${nameText}${optional}: ${typeString}`);
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (ts.isIndexSignatureDeclaration(member) && member.type && member.parameters.length === 1) {
|
|
559
|
+
const param = member.parameters[0];
|
|
560
|
+
const paramName = ts.isIdentifier(param.name) ? param.name.text : param.name.getText();
|
|
561
|
+
const paramType = param.type ? stringifyCustomEventGenericTypeNode(param.type) : 'string';
|
|
562
|
+
const valueType = stringifyCustomEventGenericTypeNode(member.type);
|
|
563
|
+
if (!paramType || !valueType) return null;
|
|
564
|
+
members.push(`[${paramName}: ${paramType}]: ${valueType}`);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
// Fallback for uncommon members (call signatures, method signatures, etc.).
|
|
568
|
+
members.push(safePrintCustomEventNode(member));
|
|
569
|
+
}
|
|
570
|
+
return `{ ${members.join('; ')} }`;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (ts.isTypeOperatorNode(typeNode)) {
|
|
574
|
+
const inner = stringifyCustomEventGenericTypeNode(typeNode.type);
|
|
575
|
+
if (!inner) return null;
|
|
576
|
+
const operator =
|
|
577
|
+
typeNode.operator === ts.SyntaxKind.KeyOfKeyword ? 'keyof' :
|
|
578
|
+
typeNode.operator === ts.SyntaxKind.ReadonlyKeyword ? 'readonly' :
|
|
579
|
+
typeNode.operator === ts.SyntaxKind.UniqueKeyword ? 'unique' :
|
|
580
|
+
null;
|
|
581
|
+
return operator ? `${operator} ${inner}` : null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (ts.isIndexedAccessTypeNode(typeNode)) {
|
|
585
|
+
const objectType = stringifyCustomEventGenericTypeNode(typeNode.objectType);
|
|
586
|
+
const indexType = stringifyCustomEventGenericTypeNode(typeNode.indexType);
|
|
587
|
+
if (!objectType || !indexType) return null;
|
|
588
|
+
return `${objectType}[${indexType}]`;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// As a last resort, keep the original syntax but normalize known WebF aliases.
|
|
592
|
+
return safePrintCustomEventNode(typeNode);
|
|
593
|
+
}
|
|
594
|
+
|
|
413
595
|
function handleCustomEventType(typeReference: ts.TypeReferenceNode): ParameterBaseType {
|
|
414
596
|
// Handle CustomEvent<T> by returning the full type with generic parameter
|
|
415
597
|
if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
|
|
@@ -417,121 +599,11 @@ function handleCustomEventType(typeReference: ts.TypeReferenceNode): ParameterBa
|
|
|
417
599
|
}
|
|
418
600
|
|
|
419
601
|
const argument = typeReference.typeArguments[0];
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const unionTypes = (argument as ts.UnionTypeNode | ts.IntersectionTypeNode).types ?? [];
|
|
425
|
-
const parts = unionTypes.map(t => {
|
|
426
|
-
// Literal union members: handle null/undefined explicitly
|
|
427
|
-
if (ts.isLiteralTypeNode(t)) {
|
|
428
|
-
const lit = t.literal;
|
|
429
|
-
if (lit.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
430
|
-
if (lit.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
431
|
-
if (ts.isStringLiteral(lit)) return JSON.stringify(lit.text);
|
|
432
|
-
return 'any';
|
|
433
|
-
}
|
|
434
|
-
// Basic keywords: boolean, string, number, null, undefined
|
|
435
|
-
const basic = BASIC_TYPE_MAP[t.kind];
|
|
436
|
-
if (basic !== undefined) {
|
|
437
|
-
switch (basic) {
|
|
438
|
-
case FunctionArgumentType.boolean:
|
|
439
|
-
return 'boolean';
|
|
440
|
-
case FunctionArgumentType.dom_string:
|
|
441
|
-
return 'string';
|
|
442
|
-
case FunctionArgumentType.double:
|
|
443
|
-
case FunctionArgumentType.int:
|
|
444
|
-
return 'number';
|
|
445
|
-
case FunctionArgumentType.null:
|
|
446
|
-
return 'null';
|
|
447
|
-
case FunctionArgumentType.undefined:
|
|
448
|
-
return 'undefined';
|
|
449
|
-
default:
|
|
450
|
-
return 'any';
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
// Literal null/undefined keywords that BASIC_TYPE_MAP may not cover
|
|
454
|
-
if (t.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
455
|
-
if (t.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
456
|
-
// Fallback: rely on toString of node kind
|
|
457
|
-
return 'any';
|
|
458
|
-
});
|
|
459
|
-
genericType = parts.join(' | ');
|
|
460
|
-
} else if (ts.isTypeReferenceNode(argument) && ts.isIdentifier(argument.typeName)) {
|
|
461
|
-
const typeName = argument.typeName.text;
|
|
462
|
-
|
|
463
|
-
// Check if it's a mapped type reference like 'int' or 'double'
|
|
464
|
-
const mappedType = TYPE_REFERENCE_MAP[typeName];
|
|
465
|
-
if (mappedType !== undefined) {
|
|
466
|
-
switch (mappedType) {
|
|
467
|
-
case FunctionArgumentType.boolean:
|
|
468
|
-
genericType = 'boolean';
|
|
469
|
-
break;
|
|
470
|
-
case FunctionArgumentType.dom_string:
|
|
471
|
-
genericType = 'string';
|
|
472
|
-
break;
|
|
473
|
-
case FunctionArgumentType.double:
|
|
474
|
-
case FunctionArgumentType.int:
|
|
475
|
-
genericType = 'number';
|
|
476
|
-
break;
|
|
477
|
-
case FunctionArgumentType.any:
|
|
478
|
-
genericType = 'any';
|
|
479
|
-
break;
|
|
480
|
-
case FunctionArgumentType.void:
|
|
481
|
-
genericType = 'void';
|
|
482
|
-
break;
|
|
483
|
-
case FunctionArgumentType.function:
|
|
484
|
-
genericType = 'Function';
|
|
485
|
-
break;
|
|
486
|
-
case FunctionArgumentType.promise:
|
|
487
|
-
genericType = 'Promise<any>';
|
|
488
|
-
break;
|
|
489
|
-
default:
|
|
490
|
-
genericType = typeName;
|
|
491
|
-
}
|
|
492
|
-
} else {
|
|
493
|
-
// For other type references, use the type name directly
|
|
494
|
-
genericType = typeName;
|
|
495
|
-
}
|
|
496
|
-
} else if (ts.isLiteralTypeNode(argument) && ts.isStringLiteral(argument.literal)) {
|
|
497
|
-
genericType = argument.literal.text;
|
|
498
|
-
} else {
|
|
499
|
-
// Handle basic types (boolean, string, number, etc.)
|
|
500
|
-
const basicType = BASIC_TYPE_MAP[argument.kind];
|
|
501
|
-
if (basicType !== undefined) {
|
|
502
|
-
switch (basicType) {
|
|
503
|
-
case FunctionArgumentType.boolean:
|
|
504
|
-
genericType = 'boolean';
|
|
505
|
-
break;
|
|
506
|
-
case FunctionArgumentType.dom_string:
|
|
507
|
-
genericType = 'string';
|
|
508
|
-
break;
|
|
509
|
-
case FunctionArgumentType.double:
|
|
510
|
-
case FunctionArgumentType.int:
|
|
511
|
-
genericType = 'number';
|
|
512
|
-
break;
|
|
513
|
-
case FunctionArgumentType.any:
|
|
514
|
-
genericType = 'any';
|
|
515
|
-
break;
|
|
516
|
-
case FunctionArgumentType.void:
|
|
517
|
-
genericType = 'void';
|
|
518
|
-
break;
|
|
519
|
-
case FunctionArgumentType.null:
|
|
520
|
-
genericType = 'null';
|
|
521
|
-
break;
|
|
522
|
-
case FunctionArgumentType.undefined:
|
|
523
|
-
genericType = 'undefined';
|
|
524
|
-
break;
|
|
525
|
-
default:
|
|
526
|
-
genericType = 'any';
|
|
527
|
-
}
|
|
528
|
-
} else {
|
|
529
|
-
// For truly complex types, fallback to 'any' to avoid errors
|
|
530
|
-
console.warn('Complex generic type in CustomEvent, using any');
|
|
531
|
-
genericType = 'any';
|
|
532
|
-
}
|
|
602
|
+
const genericType = stringifyCustomEventGenericTypeNode(argument);
|
|
603
|
+
if (!genericType) {
|
|
604
|
+
console.warn('Complex generic type in CustomEvent, using any');
|
|
605
|
+
return 'CustomEvent<any>';
|
|
533
606
|
}
|
|
534
|
-
|
|
535
607
|
return `CustomEvent<${genericType}>`;
|
|
536
608
|
}
|
|
537
609
|
|
package/src/commands.ts
CHANGED
|
@@ -177,8 +177,8 @@ const moduleTsConfig = fs.readFileSync(
|
|
|
177
177
|
'utf-8'
|
|
178
178
|
);
|
|
179
179
|
|
|
180
|
-
const
|
|
181
|
-
path.resolve(__dirname, '../templates/module.
|
|
180
|
+
const moduleTsDownConfig = fs.readFileSync(
|
|
181
|
+
path.resolve(__dirname, '../templates/module.tsdown.config.ts.tpl'),
|
|
182
182
|
'utf-8'
|
|
183
183
|
);
|
|
184
184
|
const reactPackageJson = fs.readFileSync(
|
|
@@ -191,8 +191,8 @@ const reactTsConfig = fs.readFileSync(
|
|
|
191
191
|
'utf-8'
|
|
192
192
|
);
|
|
193
193
|
|
|
194
|
-
const
|
|
195
|
-
path.resolve(__dirname, '../templates/react.
|
|
194
|
+
const reactTsDownConfig = fs.readFileSync(
|
|
195
|
+
path.resolve(__dirname, '../templates/react.tsdown.config.ts.tpl'),
|
|
196
196
|
'utf-8'
|
|
197
197
|
);
|
|
198
198
|
|
|
@@ -239,6 +239,40 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
function copyReadmeToPackageRoot(params: {
|
|
243
|
+
sourceRoot: string;
|
|
244
|
+
targetRoot: string;
|
|
245
|
+
}): { copied: boolean; sourcePath?: string; targetPath: string } {
|
|
246
|
+
const { sourceRoot, targetRoot } = params;
|
|
247
|
+
const targetPath = path.join(targetRoot, 'README.md');
|
|
248
|
+
|
|
249
|
+
if (fs.existsSync(targetPath)) {
|
|
250
|
+
return { copied: false, targetPath };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const candidateNames = ['README.md', 'Readme.md', 'readme.md'];
|
|
254
|
+
let sourcePath: string | null = null;
|
|
255
|
+
for (const candidate of candidateNames) {
|
|
256
|
+
const abs = path.join(sourceRoot, candidate);
|
|
257
|
+
if (fs.existsSync(abs)) {
|
|
258
|
+
sourcePath = abs;
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!sourcePath) {
|
|
264
|
+
return { copied: false, targetPath };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const content = fs.readFileSync(sourcePath, 'utf-8');
|
|
269
|
+
writeFileIfChanged(targetPath, content);
|
|
270
|
+
return { copied: true, sourcePath, targetPath };
|
|
271
|
+
} catch {
|
|
272
|
+
return { copied: false, targetPath };
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
242
276
|
// Copy markdown docs that match .d.ts basenames from source to the built dist folder,
|
|
243
277
|
// and generate an aggregated README.md in the dist directory.
|
|
244
278
|
async function copyMarkdownDocsToDist(params: {
|
|
@@ -419,9 +453,9 @@ function createCommand(target: string, options: { framework: string; packageName
|
|
|
419
453
|
const tsConfigContent = _.template(reactTsConfig)({});
|
|
420
454
|
writeFileIfChanged(tsConfigPath, tsConfigContent);
|
|
421
455
|
|
|
422
|
-
const
|
|
423
|
-
const
|
|
424
|
-
writeFileIfChanged(
|
|
456
|
+
const tsdownConfigPath = path.join(target, 'tsdown.config.ts');
|
|
457
|
+
const tsdownConfigContent = _.template(reactTsDownConfig)({});
|
|
458
|
+
writeFileIfChanged(tsdownConfigPath, tsdownConfigContent);
|
|
425
459
|
|
|
426
460
|
const gitignorePath = path.join(target, '.gitignore');
|
|
427
461
|
const gitignoreContent = _.template(gitignore)({});
|
|
@@ -498,9 +532,9 @@ function createModuleProject(target: string, options: { packageName: string; met
|
|
|
498
532
|
const tsConfigContent = _.template(moduleTsConfig)({});
|
|
499
533
|
writeFileIfChanged(tsConfigPath, tsConfigContent);
|
|
500
534
|
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
writeFileIfChanged(
|
|
535
|
+
const tsdownConfigPath = path.join(target, 'tsdown.config.ts');
|
|
536
|
+
const tsdownConfigContent = _.template(moduleTsDownConfig)({});
|
|
537
|
+
writeFileIfChanged(tsdownConfigPath, tsdownConfigContent);
|
|
504
538
|
|
|
505
539
|
if (!skipGitignore) {
|
|
506
540
|
const gitignorePath = path.join(target, '.gitignore');
|
|
@@ -780,6 +814,17 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
780
814
|
// Auto-initialize typings in the output directory if needed
|
|
781
815
|
ensureInitialized(resolvedDistPath);
|
|
782
816
|
|
|
817
|
+
// Copy README.md from the source Flutter package into the npm package root (so `npm publish` includes it).
|
|
818
|
+
if (options.flutterPackageSrc) {
|
|
819
|
+
const { copied } = copyReadmeToPackageRoot({
|
|
820
|
+
sourceRoot: options.flutterPackageSrc,
|
|
821
|
+
targetRoot: resolvedDistPath,
|
|
822
|
+
});
|
|
823
|
+
if (copied) {
|
|
824
|
+
console.log('📄 Copied README.md to package root');
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
783
828
|
console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
|
|
784
829
|
|
|
785
830
|
await dartGen({
|
|
@@ -1019,7 +1064,7 @@ async function generateModuleCommand(distPath: string, options: GenerateOptions)
|
|
|
1019
1064
|
packageName = packageNameAnswer.packageName;
|
|
1020
1065
|
}
|
|
1021
1066
|
|
|
1022
|
-
// Prevent npm scaffolding (package.json,
|
|
1067
|
+
// Prevent npm scaffolding (package.json, tsdown.config.ts, etc.) from being written into
|
|
1023
1068
|
// the Flutter package itself. Force users to choose a separate output directory.
|
|
1024
1069
|
if (resolvedDistPath === flutterPackageSrc) {
|
|
1025
1070
|
console.error('\n❌ Output directory must not be the Flutter package root.');
|
|
@@ -1070,6 +1115,15 @@ async function generateModuleCommand(distPath: string, options: GenerateOptions)
|
|
|
1070
1115
|
command,
|
|
1071
1116
|
});
|
|
1072
1117
|
|
|
1118
|
+
// Copy README.md from the source Flutter package into the npm package root
|
|
1119
|
+
const { copied } = copyReadmeToPackageRoot({
|
|
1120
|
+
sourceRoot: flutterPackageSrc,
|
|
1121
|
+
targetRoot: resolvedDistPath,
|
|
1122
|
+
});
|
|
1123
|
+
if (copied) {
|
|
1124
|
+
console.log('📄 Copied README.md to package root');
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1073
1127
|
console.log('\nModule code generation completed successfully!');
|
|
1074
1128
|
|
|
1075
1129
|
try {
|
package/src/generator.ts
CHANGED
|
@@ -329,7 +329,8 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
|
|
|
329
329
|
const newExports = generateReactIndex(blobs);
|
|
330
330
|
|
|
331
331
|
// Build desired export map: moduleSpecifier -> Set of names
|
|
332
|
-
const
|
|
332
|
+
const desiredValueExports = new Map<string, Set<string>>();
|
|
333
|
+
const desiredTypeExports = new Map<string, Set<string>>();
|
|
333
334
|
const components = blobs.flatMap(blob => {
|
|
334
335
|
const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
|
|
335
336
|
const properties = classObjects.filter(object => object.name.endsWith('Properties'));
|
|
@@ -351,10 +352,10 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
|
|
|
351
352
|
}
|
|
352
353
|
for (const { className, fileName, relativeDir } of unique.values()) {
|
|
353
354
|
const spec = `./${relativeDir ? `${relativeDir}/` : ''}${fileName}`;
|
|
354
|
-
if (!
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
355
|
+
if (!desiredValueExports.has(spec)) desiredValueExports.set(spec, new Set());
|
|
356
|
+
if (!desiredTypeExports.has(spec)) desiredTypeExports.set(spec, new Set());
|
|
357
|
+
desiredValueExports.get(spec)!.add(className);
|
|
358
|
+
desiredTypeExports.get(spec)!.add(`${className}Element`);
|
|
358
359
|
}
|
|
359
360
|
|
|
360
361
|
if (!fs.existsSync(indexFilePath)) {
|
|
@@ -376,22 +377,41 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
|
|
|
376
377
|
? stmt.moduleSpecifier.text
|
|
377
378
|
: undefined;
|
|
378
379
|
if (!moduleSpecifier) continue;
|
|
379
|
-
const
|
|
380
|
-
|
|
380
|
+
const desiredValues = desiredValueExports.get(moduleSpecifier);
|
|
381
|
+
const desiredTypes = desiredTypeExports.get(moduleSpecifier);
|
|
382
|
+
if (!desiredValues && !desiredTypes) continue;
|
|
383
|
+
const declIsTypeOnly = Boolean((stmt as unknown as { isTypeOnly?: boolean }).isTypeOnly);
|
|
381
384
|
for (const el of stmt.exportClause.elements) {
|
|
382
385
|
const name = el.name.getText(sourceFile);
|
|
383
|
-
|
|
386
|
+
const specIsTypeOnly = Boolean((el as unknown as { isTypeOnly?: boolean }).isTypeOnly);
|
|
387
|
+
const isTypeOnly = declIsTypeOnly || specIsTypeOnly;
|
|
388
|
+
if (isTypeOnly) {
|
|
389
|
+
if (desiredTypes?.has(name)) desiredTypes.delete(name);
|
|
390
|
+
} else {
|
|
391
|
+
if (desiredValues?.has(name)) desiredValues.delete(name);
|
|
392
|
+
}
|
|
384
393
|
}
|
|
385
394
|
}
|
|
386
395
|
}
|
|
387
396
|
|
|
388
397
|
// Prepare new export lines for any remaining names
|
|
389
398
|
const lines: string[] = [];
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
399
|
+
const specs = new Set<string>([
|
|
400
|
+
...desiredValueExports.keys(),
|
|
401
|
+
...desiredTypeExports.keys()
|
|
402
|
+
]);
|
|
403
|
+
|
|
404
|
+
for (const spec of specs) {
|
|
405
|
+
const missingValues = Array.from(desiredValueExports.get(spec) ?? []);
|
|
406
|
+
const missingTypes = Array.from(desiredTypeExports.get(spec) ?? []);
|
|
407
|
+
if (missingValues.length === 0 && missingTypes.length === 0) continue;
|
|
393
408
|
const specEscaped = spec.replace(/\\/g, '/');
|
|
394
|
-
|
|
409
|
+
if (missingValues.length > 0) {
|
|
410
|
+
lines.push(`export { ${missingValues.join(', ')} } from "${specEscaped}";`);
|
|
411
|
+
}
|
|
412
|
+
if (missingTypes.length > 0) {
|
|
413
|
+
lines.push(`export type { ${missingTypes.join(', ')} } from "${specEscaped}";`);
|
|
414
|
+
}
|
|
395
415
|
}
|
|
396
416
|
|
|
397
417
|
if (lines.length > 0) {
|
|
@@ -2,13 +2,25 @@
|
|
|
2
2
|
"name": "<%= packageName %>",
|
|
3
3
|
"version": "<%= version %>",
|
|
4
4
|
"description": "<%= description %>",
|
|
5
|
-
"main": "dist/index.
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.
|
|
7
|
+
"types": "dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
8
20
|
"files": ["dist"],
|
|
9
21
|
"scripts": {
|
|
10
|
-
"build": "
|
|
11
|
-
"dev": "
|
|
22
|
+
"build": "tsdown",
|
|
23
|
+
"dev": "tsdown --watch",
|
|
12
24
|
"clean": "rimraf dist",
|
|
13
25
|
"prepublishOnly": "npm run build"
|
|
14
26
|
},
|
|
@@ -26,11 +38,10 @@
|
|
|
26
38
|
},
|
|
27
39
|
"devDependencies": {
|
|
28
40
|
"rimraf": "^5.0.0",
|
|
29
|
-
"
|
|
41
|
+
"tsdown": "^0.19.0",
|
|
30
42
|
"typescript": "^5.8.3"
|
|
31
43
|
},
|
|
32
44
|
"publishConfig": {
|
|
33
45
|
"access": "public"
|
|
34
46
|
}
|
|
35
47
|
}
|
|
36
|
-
|
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
1
|
+
module.exports = {
|
|
4
2
|
entry: ['src/index.ts'],
|
|
5
3
|
format: ['cjs', 'esm'],
|
|
6
4
|
dts: true,
|
|
7
5
|
clean: true,
|
|
8
|
-
splitting: false,
|
|
9
6
|
sourcemap: true,
|
|
10
|
-
minify: false,
|
|
11
7
|
external: [],
|
|
12
|
-
}
|
|
13
|
-
|
|
8
|
+
};
|
|
@@ -63,12 +63,16 @@ export interface <%= className %>Props {
|
|
|
63
63
|
|
|
64
64
|
<% if (methods && methods.methods.length > 0) { %>
|
|
65
65
|
/**
|
|
66
|
-
* Element interface with methods accessible via ref
|
|
66
|
+
* Element interface with methods/properties accessible via ref
|
|
67
67
|
* @example
|
|
68
68
|
* ```tsx
|
|
69
69
|
* const ref = useRef<<%= className %>Element>(null);
|
|
70
70
|
* // Call methods on the element
|
|
71
71
|
* ref.current?.finishRefresh('success');
|
|
72
|
+
<% if (properties && properties.props && properties.props.length > 0) { %>
|
|
73
|
+
* // Access properties
|
|
74
|
+
* console.log(ref.current?.<%= _.camelCase(properties.props[0].name || 'someProp') %>);
|
|
75
|
+
<% } %>
|
|
72
76
|
* ```
|
|
73
77
|
*/
|
|
74
78
|
<% } %>
|
|
@@ -76,7 +80,19 @@ export interface <%= className %>Element extends WebFElementWithMethods<{
|
|
|
76
80
|
<% _.forEach(methods?.methods, function(method, index) { %>
|
|
77
81
|
<%= generateMethodDeclarationWithDocs(method, ' ') %>
|
|
78
82
|
<% }); %>
|
|
79
|
-
}> {
|
|
83
|
+
}> {
|
|
84
|
+
<% _.forEach(properties?.props, function(prop, index) { %>
|
|
85
|
+
<% var propName = _.camelCase(prop.name); %>
|
|
86
|
+
<% if (prop.documentation) { %>
|
|
87
|
+
/** <%= prop.documentation.split('\n')[0] %> */
|
|
88
|
+
<% } %>
|
|
89
|
+
<% if (prop.readonly) { %>
|
|
90
|
+
readonly <%= propName %><% if (prop.optional) { %>?<% } %>: <%= generateReturnType(prop.type) %>;
|
|
91
|
+
<% } else { %>
|
|
92
|
+
<%= propName %><% if (prop.optional) { %>?<% } %>: <%= generateReturnType(prop.type) %>;
|
|
93
|
+
<% } %>
|
|
94
|
+
<% }); %>
|
|
95
|
+
}
|
|
80
96
|
|
|
81
97
|
<% if (properties?.documentation || methods?.documentation || events?.documentation) { %>
|
|
82
98
|
<% const docs = properties?.documentation || methods?.documentation || events?.documentation; %>
|
|
@@ -2,12 +2,24 @@
|
|
|
2
2
|
"name": "<%= packageName %>",
|
|
3
3
|
"version": "<%= version %>",
|
|
4
4
|
"description": "<%= description %>",
|
|
5
|
-
"main": "dist/index.
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
|
-
"types": "dist/index.d.
|
|
8
|
-
"
|
|
7
|
+
"types": "dist/index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"require": {
|
|
15
|
+
"types": "./dist/index.d.cts",
|
|
16
|
+
"default": "./dist/index.cjs"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist", "README.md"],
|
|
9
21
|
"scripts": {
|
|
10
|
-
"build": "
|
|
22
|
+
"build": "tsdown"
|
|
11
23
|
},
|
|
12
24
|
"keywords": [],
|
|
13
25
|
"author": "",
|
|
@@ -23,7 +35,7 @@
|
|
|
23
35
|
"@types/react": "^19.1.0",
|
|
24
36
|
"@types/react-dom": "^19.1.2",
|
|
25
37
|
"picomatch": "^4.0.2",
|
|
26
|
-
"
|
|
38
|
+
"tsdown": "^0.19.0",
|
|
27
39
|
"typescript": "^5.8.3"
|
|
28
40
|
}
|
|
29
41
|
}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export default defineConfig({
|
|
1
|
+
module.exports = {
|
|
4
2
|
entry: ['src/index.ts'],
|
|
5
3
|
format: ['esm', 'cjs'],
|
|
6
4
|
dts: true,
|
|
7
5
|
sourcemap: true,
|
|
8
6
|
clean: true,
|
|
9
7
|
external: ['react', 'react-dom', '@openwebf/react-core-ui'],
|
|
10
|
-
}
|
|
8
|
+
};
|