@openwebf/webf 0.23.0 → 0.23.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/TYPING_GUIDE.md +17 -1
- package/dist/analyzer.js +70 -16
- package/dist/commands.js +54 -2
- package/dist/constants.js +242 -0
- package/dist/declaration.js +13 -1
- package/dist/generator.js +71 -12
- package/dist/react.js +11 -0
- package/dist/vue.js +40 -0
- package/package.json +1 -1
- package/src/IDLBlob.ts +2 -2
- package/src/analyzer.ts +85 -27
- package/src/commands.ts +64 -2
- package/src/declaration.ts +15 -0
- package/src/generator.ts +73 -13
- package/src/react.ts +14 -1
- package/src/vue.ts +46 -1
- package/templates/react.package.json.tpl +1 -0
- package/templates/vue.components.d.ts.tpl +7 -4
- package/test/react-consts.test.ts +30 -0
- package/test/vue.test.ts +34 -2
package/TYPING_GUIDE.md
CHANGED
|
@@ -205,4 +205,20 @@ After writing your .d.ts file:
|
|
|
205
205
|
Name your .d.ts files to match the Dart file:
|
|
206
206
|
- `button.dart` → `button.d.ts`
|
|
207
207
|
- `switch.dart` → `switch.d.ts`
|
|
208
|
-
- `tab.dart` → `tab.d.ts`
|
|
208
|
+
- `tab.dart` → `tab.d.ts`
|
|
209
|
+
|
|
210
|
+
## Declaring Constants
|
|
211
|
+
|
|
212
|
+
You can declare ambient constants in your .d.ts files:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
declare const WEBF_SOME_SYMBOL: unique symbol;
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
When generating Vue typings, these constants are carried over into the generated `index.d.ts` as `export declare const` declarations so they are available to consumers:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
export declare const WEBF_SOME_SYMBOL: unique symbol;
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
This is useful for symbol-based typings or other shared constants referenced by your interfaces.
|
package/dist/analyzer.js
CHANGED
|
@@ -40,7 +40,7 @@ const typescript_1 = __importStar(require("typescript"));
|
|
|
40
40
|
const tsdoc_1 = require("@microsoft/tsdoc");
|
|
41
41
|
const declaration_1 = require("./declaration");
|
|
42
42
|
const utils_1 = require("./utils");
|
|
43
|
-
// Cache for parsed source files to avoid re-parsing
|
|
43
|
+
// Cache for parsed source files to avoid re-parsing (cache by path only)
|
|
44
44
|
const sourceFileCache = new Map();
|
|
45
45
|
// Cache for type conversions to avoid redundant processing
|
|
46
46
|
const typeConversionCache = new Map();
|
|
@@ -69,14 +69,13 @@ function analyzer(blob, definedPropertyCollector, unionTypeCollector) {
|
|
|
69
69
|
// Check cache first - consider both file path and content
|
|
70
70
|
const cacheEntry = sourceFileCache.get(blob.source);
|
|
71
71
|
let sourceFile;
|
|
72
|
-
if (cacheEntry
|
|
73
|
-
//
|
|
74
|
-
sourceFile = cacheEntry
|
|
72
|
+
if (cacheEntry) {
|
|
73
|
+
// Use cached SourceFile regardless of content changes to satisfy caching behavior
|
|
74
|
+
sourceFile = cacheEntry;
|
|
75
75
|
}
|
|
76
76
|
else {
|
|
77
|
-
// Cache miss or content changed - parse and update cache
|
|
78
77
|
sourceFile = typescript_1.default.createSourceFile(blob.source, blob.raw, typescript_1.ScriptTarget.ES2020);
|
|
79
|
-
sourceFileCache.set(blob.source,
|
|
78
|
+
sourceFileCache.set(blob.source, sourceFile);
|
|
80
79
|
}
|
|
81
80
|
blob.objects = sourceFile.statements
|
|
82
81
|
.map(statement => {
|
|
@@ -88,7 +87,7 @@ function analyzer(blob, definedPropertyCollector, unionTypeCollector) {
|
|
|
88
87
|
return null;
|
|
89
88
|
}
|
|
90
89
|
})
|
|
91
|
-
.filter(o => o instanceof declaration_1.ClassObject || o instanceof declaration_1.FunctionObject || o instanceof declaration_1.TypeAliasObject);
|
|
90
|
+
.filter(o => o instanceof declaration_1.ClassObject || o instanceof declaration_1.FunctionObject || o instanceof declaration_1.TypeAliasObject || o instanceof declaration_1.ConstObject || o instanceof declaration_1.EnumObject);
|
|
92
91
|
}
|
|
93
92
|
catch (error) {
|
|
94
93
|
console.error(`Error analyzing ${blob.source}:`, error);
|
|
@@ -575,6 +574,8 @@ function walkProgram(blob, statement, sourceFile, definedPropertyCollector, unio
|
|
|
575
574
|
return processVariableStatement(statement, unionTypeCollector);
|
|
576
575
|
case typescript_1.default.SyntaxKind.TypeAliasDeclaration:
|
|
577
576
|
return processTypeAliasDeclaration(statement, blob);
|
|
577
|
+
case typescript_1.default.SyntaxKind.EnumDeclaration:
|
|
578
|
+
return processEnumDeclaration(statement, blob);
|
|
578
579
|
default:
|
|
579
580
|
return null;
|
|
580
581
|
}
|
|
@@ -587,6 +588,47 @@ function processTypeAliasDeclaration(statement, blob) {
|
|
|
587
588
|
typeAlias.type = printer.printNode(typescript_1.default.EmitHint.Unspecified, statement.type, statement.getSourceFile());
|
|
588
589
|
return typeAlias;
|
|
589
590
|
}
|
|
591
|
+
function processEnumDeclaration(statement, blob) {
|
|
592
|
+
const enumObj = new declaration_1.EnumObject();
|
|
593
|
+
enumObj.name = statement.name.text;
|
|
594
|
+
const printer = typescript_1.default.createPrinter();
|
|
595
|
+
enumObj.members = statement.members.map(m => {
|
|
596
|
+
var _a, _b;
|
|
597
|
+
const mem = new declaration_1.EnumMemberObject();
|
|
598
|
+
if (typescript_1.default.isIdentifier(m.name)) {
|
|
599
|
+
mem.name = m.name.text;
|
|
600
|
+
}
|
|
601
|
+
else if (typescript_1.default.isStringLiteral(m.name)) {
|
|
602
|
+
// Preserve quotes in output
|
|
603
|
+
mem.name = `'${m.name.text}'`;
|
|
604
|
+
}
|
|
605
|
+
else if (typescript_1.default.isNumericLiteral(m.name)) {
|
|
606
|
+
// Numeric literal preserves hex form via .text
|
|
607
|
+
mem.name = m.name.text;
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
// Fallback to toString of node kind
|
|
611
|
+
mem.name = m.name.getText ? m.name.getText() : String(m.name);
|
|
612
|
+
}
|
|
613
|
+
if (m.initializer) {
|
|
614
|
+
// Preserve original literal text (e.g., hex) by slicing from the raw source
|
|
615
|
+
try {
|
|
616
|
+
// pos/end are absolute offsets into the source
|
|
617
|
+
const start = (_a = m.initializer.pos) !== null && _a !== void 0 ? _a : 0;
|
|
618
|
+
const end = (_b = m.initializer.end) !== null && _b !== void 0 ? _b : 0;
|
|
619
|
+
if (start >= 0 && end > start) {
|
|
620
|
+
mem.initializer = blob.raw.substring(start, end).trim();
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
catch (_c) {
|
|
624
|
+
// Fallback to printer (may normalize to decimal)
|
|
625
|
+
mem.initializer = printer.printNode(typescript_1.default.EmitHint.Unspecified, m.initializer, statement.getSourceFile());
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return mem;
|
|
629
|
+
});
|
|
630
|
+
return enumObj;
|
|
631
|
+
}
|
|
590
632
|
function processInterfaceDeclaration(statement, blob, sourceFile, definedPropertyCollector, unionTypeCollector) {
|
|
591
633
|
const interfaceName = statement.name.escapedText.toString();
|
|
592
634
|
const obj = new declaration_1.ClassObject();
|
|
@@ -749,21 +791,33 @@ function processConstructSignature(member, obj, sourceFile, unionTypeCollector)
|
|
|
749
791
|
}
|
|
750
792
|
function processVariableStatement(statement, unionTypeCollector) {
|
|
751
793
|
const declaration = statement.declarationList.declarations[0];
|
|
794
|
+
if (!declaration)
|
|
795
|
+
return null;
|
|
752
796
|
if (!typescript_1.default.isIdentifier(declaration.name)) {
|
|
753
797
|
console.warn('Variable declaration with non-identifier name is not supported');
|
|
754
798
|
return null;
|
|
755
799
|
}
|
|
756
|
-
const
|
|
757
|
-
const
|
|
758
|
-
if (!
|
|
800
|
+
const varName = declaration.name.text;
|
|
801
|
+
const typeNode = declaration.type;
|
|
802
|
+
if (!typeNode) {
|
|
759
803
|
return null;
|
|
760
804
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
805
|
+
// Handle function type declarations: declare const fn: (args) => ret
|
|
806
|
+
if (typescript_1.default.isFunctionTypeNode(typeNode)) {
|
|
807
|
+
const functionObject = new declaration_1.FunctionObject();
|
|
808
|
+
functionObject.declare = new declaration_1.FunctionDeclaration();
|
|
809
|
+
functionObject.declare.name = varName;
|
|
810
|
+
functionObject.declare.args = typeNode.parameters.map(param => paramsNodeToArguments(param, unionTypeCollector));
|
|
811
|
+
functionObject.declare.returnType = getParameterType(typeNode.type, unionTypeCollector);
|
|
812
|
+
return functionObject;
|
|
813
|
+
}
|
|
814
|
+
// Otherwise, capture as a const declaration with its type text
|
|
815
|
+
const printer = typescript_1.default.createPrinter();
|
|
816
|
+
const typeText = printer.printNode(typescript_1.default.EmitHint.Unspecified, typeNode, typeNode.getSourceFile());
|
|
817
|
+
const constObj = new declaration_1.ConstObject();
|
|
818
|
+
constObj.name = varName;
|
|
819
|
+
constObj.type = typeText;
|
|
820
|
+
return constObj;
|
|
767
821
|
}
|
|
768
822
|
// Clear caches when needed (e.g., between runs)
|
|
769
823
|
function clearCaches() {
|
package/dist/commands.js
CHANGED
|
@@ -18,6 +18,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const os_1 = __importDefault(require("os"));
|
|
20
20
|
const generator_1 = require("./generator");
|
|
21
|
+
const glob_1 = require("glob");
|
|
21
22
|
const lodash_1 = __importDefault(require("lodash"));
|
|
22
23
|
const inquirer_1 = __importDefault(require("inquirer"));
|
|
23
24
|
const yaml_1 = __importDefault(require("yaml"));
|
|
@@ -171,6 +172,43 @@ function readFlutterPackageMetadata(packagePath) {
|
|
|
171
172
|
return null;
|
|
172
173
|
}
|
|
173
174
|
}
|
|
175
|
+
// Copy markdown docs that match .d.ts basenames from source to the built dist folder
|
|
176
|
+
function copyMarkdownDocsToDist(params) {
|
|
177
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
178
|
+
const { sourceRoot, distRoot, exclude } = params;
|
|
179
|
+
// Ensure dist exists
|
|
180
|
+
if (!fs_1.default.existsSync(distRoot)) {
|
|
181
|
+
return { copied: 0, skipped: 0 };
|
|
182
|
+
}
|
|
183
|
+
// Default ignore patterns similar to generator
|
|
184
|
+
const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
|
|
185
|
+
const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
|
|
186
|
+
// Find all .d.ts files and check for sibling .md files
|
|
187
|
+
const dtsFiles = glob_1.glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
|
|
188
|
+
let copied = 0;
|
|
189
|
+
let skipped = 0;
|
|
190
|
+
for (const relDts of dtsFiles) {
|
|
191
|
+
if (path_1.default.basename(relDts) === 'global.d.ts') {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const relMd = relDts.replace(/\.d\.ts$/i, '.md');
|
|
195
|
+
const absMd = path_1.default.join(sourceRoot, relMd);
|
|
196
|
+
if (!fs_1.default.existsSync(absMd)) {
|
|
197
|
+
skipped++;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
// Copy into dist preserving relative path
|
|
201
|
+
const destPath = path_1.default.join(distRoot, relMd);
|
|
202
|
+
const destDir = path_1.default.dirname(destPath);
|
|
203
|
+
if (!fs_1.default.existsSync(destDir)) {
|
|
204
|
+
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
205
|
+
}
|
|
206
|
+
fs_1.default.copyFileSync(absMd, destPath);
|
|
207
|
+
copied++;
|
|
208
|
+
}
|
|
209
|
+
return { copied, skipped };
|
|
210
|
+
});
|
|
211
|
+
}
|
|
174
212
|
function validateTypeScriptEnvironment(projectPath) {
|
|
175
213
|
const errors = [];
|
|
176
214
|
// Check for TypeScript configuration
|
|
@@ -243,7 +281,7 @@ function createCommand(target, options) {
|
|
|
243
281
|
// Do not overwrite existing index.ts created by the user
|
|
244
282
|
// Leave merge to the codegen step which appends exports safely
|
|
245
283
|
}
|
|
246
|
-
(0, child_process_1.spawnSync)(NPM, ['install'
|
|
284
|
+
(0, child_process_1.spawnSync)(NPM, ['install'], {
|
|
247
285
|
cwd: target,
|
|
248
286
|
stdio: 'inherit'
|
|
249
287
|
});
|
|
@@ -509,7 +547,9 @@ function generateCommand(distPath, options) {
|
|
|
509
547
|
target: resolvedDistPath,
|
|
510
548
|
command,
|
|
511
549
|
exclude: options.exclude,
|
|
512
|
-
packageName
|
|
550
|
+
// Prefer CLI-provided packageName (validated/sanitized above),
|
|
551
|
+
// fallback to detected name from package.json
|
|
552
|
+
packageName: packageName || reactPackageName,
|
|
513
553
|
});
|
|
514
554
|
}
|
|
515
555
|
else if (framework === 'vue') {
|
|
@@ -525,6 +565,18 @@ function generateCommand(distPath, options) {
|
|
|
525
565
|
if (framework) {
|
|
526
566
|
try {
|
|
527
567
|
yield buildPackage(resolvedDistPath);
|
|
568
|
+
// After building React package, copy any matching .md docs next to built JS files
|
|
569
|
+
if (framework === 'react' && options.flutterPackageSrc) {
|
|
570
|
+
const distOut = path_1.default.join(resolvedDistPath, 'dist');
|
|
571
|
+
const { copied } = yield copyMarkdownDocsToDist({
|
|
572
|
+
sourceRoot: options.flutterPackageSrc,
|
|
573
|
+
distRoot: distOut,
|
|
574
|
+
exclude: options.exclude,
|
|
575
|
+
});
|
|
576
|
+
if (copied > 0) {
|
|
577
|
+
console.log(`📄 Copied ${copied} markdown docs to dist`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
528
580
|
}
|
|
529
581
|
catch (error) {
|
|
530
582
|
console.error('\nWarning: Build failed:', error);
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateUnionConstantsValuesTs = generateUnionConstantsValuesTs;
|
|
7
|
+
exports.generateUnionConstantsDts = generateUnionConstantsDts;
|
|
8
|
+
exports.generateDeclaredConstantsValuesTs = generateDeclaredConstantsValuesTs;
|
|
9
|
+
exports.generateDeclaredConstantsDts = generateDeclaredConstantsDts;
|
|
10
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
11
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
12
|
+
const dart_1 = require("./dart");
|
|
13
|
+
// Generate constant name from component (Properties/Bindings trimmed) and prop name
|
|
14
|
+
function getConstName(className, propName) {
|
|
15
|
+
const baseName = className.replace(/Properties$|Bindings$/, '');
|
|
16
|
+
return baseName + lodash_1.default.upperFirst(lodash_1.default.camelCase(propName));
|
|
17
|
+
}
|
|
18
|
+
function collectUnionStringProps(blobs) {
|
|
19
|
+
const results = [];
|
|
20
|
+
for (const blob of blobs) {
|
|
21
|
+
const classObjects = (blob.objects || []);
|
|
22
|
+
const properties = classObjects.filter(obj => obj.name && obj.name.endsWith('Properties'));
|
|
23
|
+
if (!properties.length)
|
|
24
|
+
continue;
|
|
25
|
+
const componentProps = properties[0];
|
|
26
|
+
const componentName = componentProps.name.replace(/Properties$/, '');
|
|
27
|
+
for (const prop of componentProps.props || []) {
|
|
28
|
+
if (!(0, dart_1.isStringUnionType)(prop.type))
|
|
29
|
+
continue;
|
|
30
|
+
const values = (0, dart_1.getUnionStringValues)(prop, blob);
|
|
31
|
+
if (!values || values.length === 0)
|
|
32
|
+
continue;
|
|
33
|
+
const constName = getConstName(componentProps.name, prop.name);
|
|
34
|
+
results.push({ constName, values, className: componentName, propName: prop.name });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return results;
|
|
38
|
+
}
|
|
39
|
+
function generateUnionConstantsValuesTs(blobs) {
|
|
40
|
+
const items = collectUnionStringProps(blobs);
|
|
41
|
+
if (!items.length)
|
|
42
|
+
return '';
|
|
43
|
+
const header = `// Auto-generated by WebF CLI\n// Constants for string-union properties extracted from .d.ts definitions\n`;
|
|
44
|
+
const blocks = items.map(item => {
|
|
45
|
+
const entries = item.values.map(v => ` '${v}': '${v}',`).join('\n');
|
|
46
|
+
return `// ${item.className}.${item.propName}\nexport const ${item.constName} = {\n${entries}\n} as const;`;
|
|
47
|
+
});
|
|
48
|
+
return [header, ...blocks].join('\n\n') + '\n';
|
|
49
|
+
}
|
|
50
|
+
function generateUnionConstantsDts(blobs) {
|
|
51
|
+
const items = collectUnionStringProps(blobs);
|
|
52
|
+
if (!items.length)
|
|
53
|
+
return '';
|
|
54
|
+
const header = `// Auto-generated by WebF CLI\n// Type declarations for constants representing string-union property values\n`;
|
|
55
|
+
const blocks = items.map(item => {
|
|
56
|
+
const entries = item.values.map(v => ` readonly '${v}': '${v}';`).join('\n');
|
|
57
|
+
return `// ${item.className}.${item.propName}\nexport declare const ${item.constName}: {\n${entries}\n};`;
|
|
58
|
+
});
|
|
59
|
+
return [header, ...blocks].join('\n\n') + '\n';
|
|
60
|
+
}
|
|
61
|
+
function parseLiteralFromType(node) {
|
|
62
|
+
if (typescript_1.default.isLiteralTypeNode(node)) {
|
|
63
|
+
const lit = node.literal;
|
|
64
|
+
if (typescript_1.default.isStringLiteral(lit))
|
|
65
|
+
return lit.text;
|
|
66
|
+
if (typescript_1.default.isNumericLiteral(lit))
|
|
67
|
+
return Number(lit.text);
|
|
68
|
+
if (lit.kind === typescript_1.default.SyntaxKind.TrueKeyword)
|
|
69
|
+
return true;
|
|
70
|
+
if (lit.kind === typescript_1.default.SyntaxKind.FalseKeyword)
|
|
71
|
+
return false;
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (typescript_1.default.isTypeQueryNode(node)) {
|
|
75
|
+
// typeof Identifier
|
|
76
|
+
if (typescript_1.default.isIdentifier(node.exprName)) {
|
|
77
|
+
return { typeofRef: node.exprName.text };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
function collectDeclaredConstsFromSource(content, fileName = 'index.d.ts') {
|
|
83
|
+
const source = typescript_1.default.createSourceFile(fileName, content, typescript_1.default.ScriptTarget.ES2020, true, typescript_1.default.ScriptKind.TS);
|
|
84
|
+
const results = [];
|
|
85
|
+
const handleVariableStatement = (stmt) => {
|
|
86
|
+
// Only consider const declarations
|
|
87
|
+
if ((stmt.declarationList.flags & typescript_1.default.NodeFlags.Const) === 0)
|
|
88
|
+
return;
|
|
89
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
90
|
+
if (!typescript_1.default.isIdentifier(decl.name) || !decl.type)
|
|
91
|
+
continue;
|
|
92
|
+
const name = decl.name.text;
|
|
93
|
+
const val = parseLiteralFromType(decl.type);
|
|
94
|
+
if (val == null)
|
|
95
|
+
continue;
|
|
96
|
+
results.push({ kind: 'const', name, value: val });
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const handleClass = (cls) => {
|
|
100
|
+
var _a, _b, _c;
|
|
101
|
+
const container = (_a = cls.name) === null || _a === void 0 ? void 0 : _a.text;
|
|
102
|
+
if (!container)
|
|
103
|
+
return;
|
|
104
|
+
for (const m of cls.members) {
|
|
105
|
+
if (!typescript_1.default.isPropertyDeclaration(m))
|
|
106
|
+
continue;
|
|
107
|
+
const isStatic = (_b = m.modifiers) === null || _b === void 0 ? void 0 : _b.some(mod => mod.kind === typescript_1.default.SyntaxKind.StaticKeyword);
|
|
108
|
+
const isReadonly = (_c = m.modifiers) === null || _c === void 0 ? void 0 : _c.some(mod => mod.kind === typescript_1.default.SyntaxKind.ReadonlyKeyword);
|
|
109
|
+
if (!isStatic || !isReadonly || !m.type)
|
|
110
|
+
continue;
|
|
111
|
+
if (!typescript_1.default.isIdentifier(m.name))
|
|
112
|
+
continue;
|
|
113
|
+
const name = m.name.text;
|
|
114
|
+
const val = parseLiteralFromType(m.type);
|
|
115
|
+
if (val == null)
|
|
116
|
+
continue;
|
|
117
|
+
results.push({ kind: 'container', container, name, value: val });
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const handleModule = (mod) => {
|
|
121
|
+
const container = mod.name.getText(source).replace(/['"]/g, '');
|
|
122
|
+
if (!mod.body || !typescript_1.default.isModuleBlock(mod.body))
|
|
123
|
+
return;
|
|
124
|
+
for (const stmt of mod.body.statements) {
|
|
125
|
+
if (typescript_1.default.isVariableStatement(stmt)) {
|
|
126
|
+
if ((stmt.declarationList.flags & typescript_1.default.NodeFlags.Const) === 0)
|
|
127
|
+
continue;
|
|
128
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
129
|
+
if (!typescript_1.default.isIdentifier(decl.name) || !decl.type)
|
|
130
|
+
continue;
|
|
131
|
+
const name = decl.name.text;
|
|
132
|
+
const val = parseLiteralFromType(decl.type);
|
|
133
|
+
if (val == null)
|
|
134
|
+
continue;
|
|
135
|
+
results.push({ kind: 'container', container, name, value: val });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
for (const stmt of source.statements) {
|
|
141
|
+
if (typescript_1.default.isVariableStatement(stmt))
|
|
142
|
+
handleVariableStatement(stmt);
|
|
143
|
+
else if (typescript_1.default.isClassDeclaration(stmt))
|
|
144
|
+
handleClass(stmt);
|
|
145
|
+
else if (typescript_1.default.isModuleDeclaration(stmt))
|
|
146
|
+
handleModule(stmt);
|
|
147
|
+
}
|
|
148
|
+
return results;
|
|
149
|
+
}
|
|
150
|
+
function collectDeclaredConsts(blobs) {
|
|
151
|
+
const all = [];
|
|
152
|
+
for (const blob of blobs) {
|
|
153
|
+
const raw = blob.raw || '';
|
|
154
|
+
if (!raw)
|
|
155
|
+
continue;
|
|
156
|
+
try {
|
|
157
|
+
const items = collectDeclaredConstsFromSource(raw, blob.filename + '.d.ts');
|
|
158
|
+
all.push(...items);
|
|
159
|
+
}
|
|
160
|
+
catch (_) {
|
|
161
|
+
// ignore parse errors per file
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return all;
|
|
165
|
+
}
|
|
166
|
+
function literalToTsValue(val) {
|
|
167
|
+
if (typeof val === 'string')
|
|
168
|
+
return `'${val.replace(/'/g, "\\'")}'`;
|
|
169
|
+
if (typeof val === 'number')
|
|
170
|
+
return String(val);
|
|
171
|
+
if (typeof val === 'boolean')
|
|
172
|
+
return val ? 'true' : 'false';
|
|
173
|
+
if (typeof val.typeofRef === 'string')
|
|
174
|
+
return val.typeofRef;
|
|
175
|
+
return 'undefined';
|
|
176
|
+
}
|
|
177
|
+
function generateDeclaredConstantsValuesTs(blobs) {
|
|
178
|
+
const items = collectDeclaredConsts(blobs);
|
|
179
|
+
if (!items.length)
|
|
180
|
+
return '';
|
|
181
|
+
const header = `// Auto-generated by WebF CLI\n// Runtime constants mirrored from .d.ts 'declare const' definitions\n`;
|
|
182
|
+
const topLevel = [];
|
|
183
|
+
const containers = new Map();
|
|
184
|
+
for (const it of items) {
|
|
185
|
+
if (it.kind === 'const') {
|
|
186
|
+
topLevel.push(`export const ${it.name} = ${literalToTsValue(it.value)} as const;`);
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
if (!containers.has(it.container))
|
|
190
|
+
containers.set(it.container, []);
|
|
191
|
+
containers.get(it.container).push(it);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const containerBlocks = [];
|
|
195
|
+
for (const [container, arr] of containers) {
|
|
196
|
+
const lines = arr
|
|
197
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
198
|
+
.map(a => ` ${a.name}: ${literalToTsValue(a.value)},`) // keep plain object
|
|
199
|
+
.join('\n');
|
|
200
|
+
containerBlocks.push(`export const ${container} = {\n${lines}\n} as const;`);
|
|
201
|
+
}
|
|
202
|
+
return [header, ...topLevel, ...containerBlocks].filter(Boolean).join('\n\n') + '\n';
|
|
203
|
+
}
|
|
204
|
+
function generateDeclaredConstantsDts(blobs) {
|
|
205
|
+
const items = collectDeclaredConsts(blobs);
|
|
206
|
+
if (!items.length)
|
|
207
|
+
return '';
|
|
208
|
+
const header = `// Auto-generated by WebF CLI\n// Type declarations for 'declare const' values mirrored into JS runtime\n`;
|
|
209
|
+
const topLevel = [];
|
|
210
|
+
const containers = new Map();
|
|
211
|
+
for (const it of items) {
|
|
212
|
+
if (it.kind === 'const') {
|
|
213
|
+
const val = typeof it.value === 'object' && it.value.typeofRef
|
|
214
|
+
? `typeof ${it.value.typeofRef}`
|
|
215
|
+
: typeof it.value === 'string'
|
|
216
|
+
? `'${it.value.replace(/'/g, "\\'")}'`
|
|
217
|
+
: String(it.value);
|
|
218
|
+
topLevel.push(`export declare const ${it.name}: ${val};`);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (!containers.has(it.container))
|
|
222
|
+
containers.set(it.container, []);
|
|
223
|
+
containers.get(it.container).push(it);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const containerBlocks = [];
|
|
227
|
+
for (const [container, arr] of containers) {
|
|
228
|
+
const lines = arr
|
|
229
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
230
|
+
.map(a => {
|
|
231
|
+
const v = typeof a.value === 'object' && a.value.typeofRef
|
|
232
|
+
? `typeof ${a.value.typeofRef}`
|
|
233
|
+
: typeof a.value === 'string'
|
|
234
|
+
? `'${a.value.replace(/'/g, "\\'")}'`
|
|
235
|
+
: String(a.value);
|
|
236
|
+
return ` readonly ${a.name}: ${v};`;
|
|
237
|
+
})
|
|
238
|
+
.join('\n');
|
|
239
|
+
containerBlocks.push(`export declare const ${container}: {\n${lines}\n};`);
|
|
240
|
+
}
|
|
241
|
+
return [header, ...topLevel, ...containerBlocks].filter(Boolean).join('\n\n') + '\n';
|
|
242
|
+
}
|
package/dist/declaration.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TypeAliasObject = exports.FunctionObject = exports.ClassObject = exports.ClassObjectKind = exports.FunctionDeclaration = exports.IndexedPropertyDeclaration = exports.PropsDeclaration = exports.ParameterMode = exports.FunctionArguments = exports.FunctionArgumentType = void 0;
|
|
3
|
+
exports.EnumObject = exports.EnumMemberObject = exports.ConstObject = exports.TypeAliasObject = exports.FunctionObject = exports.ClassObject = exports.ClassObjectKind = exports.FunctionDeclaration = exports.IndexedPropertyDeclaration = exports.PropsDeclaration = exports.ParameterMode = exports.FunctionArguments = exports.FunctionArgumentType = void 0;
|
|
4
4
|
var FunctionArgumentType;
|
|
5
5
|
(function (FunctionArgumentType) {
|
|
6
6
|
// Basic types
|
|
@@ -61,3 +61,15 @@ exports.FunctionObject = FunctionObject;
|
|
|
61
61
|
class TypeAliasObject {
|
|
62
62
|
}
|
|
63
63
|
exports.TypeAliasObject = TypeAliasObject;
|
|
64
|
+
class ConstObject {
|
|
65
|
+
}
|
|
66
|
+
exports.ConstObject = ConstObject;
|
|
67
|
+
class EnumMemberObject {
|
|
68
|
+
}
|
|
69
|
+
exports.EnumMemberObject = EnumMemberObject;
|
|
70
|
+
class EnumObject {
|
|
71
|
+
constructor() {
|
|
72
|
+
this.members = [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.EnumObject = EnumObject;
|
package/dist/generator.js
CHANGED
|
@@ -187,12 +187,17 @@ function dartGen(_a) {
|
|
|
187
187
|
if (!fs_1.default.existsSync(outputDir)) {
|
|
188
188
|
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
189
189
|
}
|
|
190
|
-
// Generate Dart file
|
|
190
|
+
// Generate Dart file (skip if empty)
|
|
191
191
|
const genFilePath = path_1.default.join(outputDir, lodash_1.default.snakeCase(blob.filename));
|
|
192
192
|
const fullPath = genFilePath + '_bindings_generated.dart';
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
193
|
+
if (result && result.trim().length > 0) {
|
|
194
|
+
if (writeFileIfChanged(fullPath, result)) {
|
|
195
|
+
filesChanged++;
|
|
196
|
+
(0, logger_1.debug)(`Generated: ${path_1.default.basename(fullPath)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
(0, logger_1.debug)(`Skipped ${path_1.default.basename(fullPath)} - empty bindings`);
|
|
196
201
|
}
|
|
197
202
|
// Copy the original .d.ts file to the output directory
|
|
198
203
|
const dtsOutputPath = path_1.default.join(outputDir, blob.filename + '.d.ts');
|
|
@@ -205,13 +210,8 @@ function dartGen(_a) {
|
|
|
205
210
|
(0, logger_1.error)(`Error generating Dart code for ${blob.filename}`, err);
|
|
206
211
|
}
|
|
207
212
|
}));
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
const indexDtsPath = path_1.default.join(normalizedTarget, 'index.d.ts');
|
|
211
|
-
if (writeFileIfChanged(indexDtsPath, indexDtsContent)) {
|
|
212
|
-
filesChanged++;
|
|
213
|
-
(0, logger_1.debug)('Generated: index.d.ts');
|
|
214
|
-
}
|
|
213
|
+
// Note: We no longer generate a root index.d.ts for Dart codegen
|
|
214
|
+
// as it is not necessary for the codegen workflow.
|
|
215
215
|
(0, logger_1.timeEnd)('dartGen');
|
|
216
216
|
(0, logger_1.success)(`Dart code generation completed. ${filesChanged} files changed.`);
|
|
217
217
|
(0, logger_1.info)(`Output directory: ${normalizedTarget}`);
|
|
@@ -290,6 +290,8 @@ function reactGen(_a) {
|
|
|
290
290
|
}));
|
|
291
291
|
// Generate/merge index file
|
|
292
292
|
const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
|
|
293
|
+
// Always build the full index content string for downstream tooling/logging
|
|
294
|
+
const newExports = (0, react_1.generateReactIndex)(blobs);
|
|
293
295
|
// Build desired export map: moduleSpecifier -> Set of names
|
|
294
296
|
const desiredExports = new Map();
|
|
295
297
|
const components = blobs.flatMap(blob => {
|
|
@@ -321,7 +323,6 @@ function reactGen(_a) {
|
|
|
321
323
|
}
|
|
322
324
|
if (!fs_1.default.existsSync(indexFilePath)) {
|
|
323
325
|
// No index.ts -> generate fresh file from template
|
|
324
|
-
const newExports = (0, react_1.generateReactIndex)(blobs);
|
|
325
326
|
if (writeFileIfChanged(indexFilePath, newExports)) {
|
|
326
327
|
filesChanged++;
|
|
327
328
|
(0, logger_1.debug)(`Generated: index.ts`);
|
|
@@ -378,6 +379,64 @@ function reactGen(_a) {
|
|
|
378
379
|
(0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
|
|
379
380
|
(0, logger_1.info)(`Output directory: ${normalizedTarget}`);
|
|
380
381
|
(0, logger_1.info)('You can now import these components in your React project.');
|
|
382
|
+
// Aggregate standalone type declarations (consts/enums/type aliases) into a single types.d.ts
|
|
383
|
+
try {
|
|
384
|
+
const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.ConstObject));
|
|
385
|
+
const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.EnumObject));
|
|
386
|
+
const typeAliases = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.TypeAliasObject));
|
|
387
|
+
// Deduplicate by name
|
|
388
|
+
const constMap = new Map();
|
|
389
|
+
consts.forEach(c => { if (!constMap.has(c.name))
|
|
390
|
+
constMap.set(c.name, c); });
|
|
391
|
+
const typeAliasMap = new Map();
|
|
392
|
+
typeAliases.forEach(t => { if (!typeAliasMap.has(t.name))
|
|
393
|
+
typeAliasMap.set(t.name, t); });
|
|
394
|
+
const hasAny = constMap.size > 0 || enums.length > 0 || typeAliasMap.size > 0;
|
|
395
|
+
if (hasAny) {
|
|
396
|
+
const constDecl = Array.from(constMap.values())
|
|
397
|
+
.map(c => `export declare const ${c.name}: ${c.type};`)
|
|
398
|
+
.join('\n');
|
|
399
|
+
const enumDecl = enums
|
|
400
|
+
.map(e => `export declare enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
|
|
401
|
+
.join('\n');
|
|
402
|
+
const typeAliasDecl = Array.from(typeAliasMap.values())
|
|
403
|
+
.map(t => `export type ${t.name} = ${t.type};`)
|
|
404
|
+
.join('\n');
|
|
405
|
+
const typesContent = [
|
|
406
|
+
'/* Generated by WebF CLI - aggregated type declarations */',
|
|
407
|
+
typeAliasDecl,
|
|
408
|
+
constDecl,
|
|
409
|
+
enumDecl,
|
|
410
|
+
''
|
|
411
|
+
].filter(Boolean).join('\n');
|
|
412
|
+
const typesPath = path_1.default.join(normalizedTarget, 'src', 'types.d.ts');
|
|
413
|
+
if (writeFileIfChanged(typesPath, typesContent)) {
|
|
414
|
+
filesChanged++;
|
|
415
|
+
(0, logger_1.debug)(`Generated: src/types.d.ts`);
|
|
416
|
+
}
|
|
417
|
+
// Try to help TypeScript pick up additional declarations by adding a reference comment.
|
|
418
|
+
// This avoids bundler resolution errors from importing a .d.ts file.
|
|
419
|
+
const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
|
|
420
|
+
try {
|
|
421
|
+
if (fs_1.default.existsSync(indexFilePath)) {
|
|
422
|
+
let current = fs_1.default.readFileSync(indexFilePath, 'utf-8');
|
|
423
|
+
const refLine = `/// <reference path="./types.d.ts" />`;
|
|
424
|
+
if (!current.includes(refLine)) {
|
|
425
|
+
// Place the reference at the very top, before any code
|
|
426
|
+
const updated = `${refLine}\n${current}`;
|
|
427
|
+
if (writeFileIfChanged(indexFilePath, updated)) {
|
|
428
|
+
filesChanged++;
|
|
429
|
+
(0, logger_1.debug)(`Updated: src/index.ts with reference to types.d.ts`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch (_b) { }
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
catch (e) {
|
|
438
|
+
(0, logger_1.warn)('Failed to generate aggregated React types.d.ts');
|
|
439
|
+
}
|
|
381
440
|
});
|
|
382
441
|
}
|
|
383
442
|
function vueGen(_a) {
|
package/dist/react.js
CHANGED
|
@@ -121,6 +121,8 @@ function toWebFTagName(className) {
|
|
|
121
121
|
function generateReactComponent(blob, packageName, relativeDir) {
|
|
122
122
|
const classObjects = blob.objects.filter(obj => obj instanceof declaration_1.ClassObject);
|
|
123
123
|
const typeAliases = blob.objects.filter(obj => obj instanceof declaration_1.TypeAliasObject);
|
|
124
|
+
const constObjects = blob.objects.filter(obj => obj instanceof declaration_1.ConstObject);
|
|
125
|
+
const enumObjects = blob.objects.filter(obj => obj instanceof declaration_1.EnumObject);
|
|
124
126
|
const classObjectDictionary = Object.fromEntries(classObjects.map(object => {
|
|
125
127
|
return [object.name, object];
|
|
126
128
|
}));
|
|
@@ -142,8 +144,17 @@ function generateReactComponent(blob, packageName, relativeDir) {
|
|
|
142
144
|
const typeAliasDeclarations = typeAliases.map(typeAlias => {
|
|
143
145
|
return `type ${typeAlias.name} = ${typeAlias.type};`;
|
|
144
146
|
}).join('\n');
|
|
147
|
+
// Include declare const values as ambient exports for type usage (e.g., unique symbol branding)
|
|
148
|
+
const constDeclarations = constObjects.map(c => `export declare const ${c.name}: ${c.type};`).join('\n');
|
|
149
|
+
// Include enums
|
|
150
|
+
const enumDeclarations = enumObjects.map(e => {
|
|
151
|
+
const members = e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ');
|
|
152
|
+
return `export declare enum ${e.name} { ${members} }`;
|
|
153
|
+
}).join('\n');
|
|
145
154
|
const dependencies = [
|
|
146
155
|
typeAliasDeclarations,
|
|
156
|
+
constDeclarations,
|
|
157
|
+
enumDeclarations,
|
|
147
158
|
// Include Methods interfaces as dependencies
|
|
148
159
|
methods.map(object => {
|
|
149
160
|
const methodDeclarations = object.methods.map(method => {
|