@openwebf/webf 0.23.0 → 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/TYPING_GUIDE.md +17 -1
- package/bin/webf.js +1 -0
- package/dist/analyzer.js +135 -17
- package/dist/commands.js +251 -99
- package/dist/constants.js +242 -0
- package/dist/dart.js +91 -25
- package/dist/declaration.js +14 -1
- package/dist/generator.js +80 -12
- package/dist/react.js +281 -23
- package/dist/vue.js +114 -11
- package/package.json +1 -1
- package/src/IDLBlob.ts +2 -2
- package/src/analyzer.ts +142 -28
- package/src/commands.ts +363 -197
- package/src/dart.ts +95 -20
- package/src/declaration.ts +16 -0
- package/src/generator.ts +81 -13
- package/src/react.ts +300 -28
- package/src/vue.ts +131 -14
- package/templates/class.dart.tpl +1 -1
- package/templates/react.package.json.tpl +1 -0
- package/templates/vue.components.d.ts.tpl +9 -4
- package/test/commands.test.ts +82 -2
- package/test/dart-nullable-props.test.ts +58 -0
- package/test/react-consts.test.ts +30 -0
- package/test/react-vue-nullable-props.test.ts +66 -0
- package/test/react.test.ts +46 -4
- package/test/vue.test.ts +34 -2
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,107 @@ 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
|
+
// and generate an aggregated README.md in the dist directory.
|
|
177
|
+
function copyMarkdownDocsToDist(params) {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
const { sourceRoot, distRoot, exclude } = params;
|
|
180
|
+
// Ensure dist exists
|
|
181
|
+
if (!fs_1.default.existsSync(distRoot)) {
|
|
182
|
+
return { copied: 0, skipped: 0 };
|
|
183
|
+
}
|
|
184
|
+
// Default ignore patterns similar to generator
|
|
185
|
+
const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
|
|
186
|
+
const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
|
|
187
|
+
// Find all .d.ts files and check for sibling .md files
|
|
188
|
+
const dtsFiles = glob_1.glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
|
|
189
|
+
let copied = 0;
|
|
190
|
+
let skipped = 0;
|
|
191
|
+
const readmeSections = [];
|
|
192
|
+
for (const relDts of dtsFiles) {
|
|
193
|
+
if (path_1.default.basename(relDts) === 'global.d.ts') {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const relMd = relDts.replace(/\.d\.ts$/i, '.md');
|
|
197
|
+
const absMd = path_1.default.join(sourceRoot, relMd);
|
|
198
|
+
if (!fs_1.default.existsSync(absMd)) {
|
|
199
|
+
skipped++;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
let content = '';
|
|
203
|
+
try {
|
|
204
|
+
content = fs_1.default.readFileSync(absMd, 'utf-8');
|
|
205
|
+
}
|
|
206
|
+
catch (_a) {
|
|
207
|
+
// If we cannot read the file, still attempt to copy it and skip README aggregation for this entry.
|
|
208
|
+
}
|
|
209
|
+
// Copy into dist preserving relative path
|
|
210
|
+
const destPath = path_1.default.join(distRoot, relMd);
|
|
211
|
+
const destDir = path_1.default.dirname(destPath);
|
|
212
|
+
if (!fs_1.default.existsSync(destDir)) {
|
|
213
|
+
fs_1.default.mkdirSync(destDir, { recursive: true });
|
|
214
|
+
}
|
|
215
|
+
fs_1.default.copyFileSync(absMd, destPath);
|
|
216
|
+
copied++;
|
|
217
|
+
if (content) {
|
|
218
|
+
const base = path_1.default.basename(relMd, '.md');
|
|
219
|
+
const title = base
|
|
220
|
+
.split(/[-_]+/)
|
|
221
|
+
.filter(Boolean)
|
|
222
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
223
|
+
.join(' ');
|
|
224
|
+
readmeSections.push({
|
|
225
|
+
title: title || base,
|
|
226
|
+
relPath: relMd,
|
|
227
|
+
content
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Generate an aggregated README.md inside distRoot so consumers can see component docs easily.
|
|
232
|
+
if (readmeSections.length > 0) {
|
|
233
|
+
const readmePath = path_1.default.join(distRoot, 'README.md');
|
|
234
|
+
let existing = '';
|
|
235
|
+
if (fs_1.default.existsSync(readmePath)) {
|
|
236
|
+
try {
|
|
237
|
+
existing = fs_1.default.readFileSync(readmePath, 'utf-8');
|
|
238
|
+
}
|
|
239
|
+
catch (_b) {
|
|
240
|
+
existing = '';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const headerLines = [
|
|
244
|
+
'# WebF Component Documentation',
|
|
245
|
+
'',
|
|
246
|
+
'> This README is generated from markdown docs co-located with TypeScript definitions in the Flutter package.',
|
|
247
|
+
''
|
|
248
|
+
];
|
|
249
|
+
const sectionBlocks = readmeSections.map(section => {
|
|
250
|
+
const lines = [];
|
|
251
|
+
lines.push(`## ${section.title}`);
|
|
252
|
+
lines.push('');
|
|
253
|
+
lines.push(`_Source: \`./${section.relPath}\`_`);
|
|
254
|
+
lines.push('');
|
|
255
|
+
lines.push(section.content.trim());
|
|
256
|
+
lines.push('');
|
|
257
|
+
return lines.join('\n');
|
|
258
|
+
}).join('\n');
|
|
259
|
+
let finalContent;
|
|
260
|
+
if (existing && existing.trim().length > 0) {
|
|
261
|
+
finalContent = `${existing.trim()}\n\n---\n\n${headerLines.join('\n')}${sectionBlocks}\n`;
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
finalContent = `${headerLines.join('\n')}${sectionBlocks}\n`;
|
|
265
|
+
}
|
|
266
|
+
try {
|
|
267
|
+
fs_1.default.writeFileSync(readmePath, finalContent, 'utf-8');
|
|
268
|
+
}
|
|
269
|
+
catch (_c) {
|
|
270
|
+
// If README generation fails, do not affect overall codegen.
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return { copied, skipped };
|
|
274
|
+
});
|
|
275
|
+
}
|
|
174
276
|
function validateTypeScriptEnvironment(projectPath) {
|
|
175
277
|
const errors = [];
|
|
176
278
|
// Check for TypeScript configuration
|
|
@@ -243,7 +345,8 @@ function createCommand(target, options) {
|
|
|
243
345
|
// Do not overwrite existing index.ts created by the user
|
|
244
346
|
// Leave merge to the codegen step which appends exports safely
|
|
245
347
|
}
|
|
246
|
-
|
|
348
|
+
// !no '--omit=peer' here.
|
|
349
|
+
(0, child_process_1.spawnSync)(NPM, ['install'], {
|
|
247
350
|
cwd: target,
|
|
248
351
|
stdio: 'inherit'
|
|
249
352
|
});
|
|
@@ -279,12 +382,19 @@ function generateCommand(distPath, options) {
|
|
|
279
382
|
// If distPath is not provided or is '.', create a temporary directory
|
|
280
383
|
let resolvedDistPath;
|
|
281
384
|
let isTempDir = false;
|
|
385
|
+
const isDartOnly = options.dartOnly;
|
|
282
386
|
if (!distPath || distPath === '.') {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
387
|
+
if (isDartOnly) {
|
|
388
|
+
// In Dart-only mode we don't need a temporary Node project directory
|
|
389
|
+
resolvedDistPath = path_1.default.resolve(distPath || '.');
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
// Create a temporary directory for the generated package
|
|
393
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'webf-typings-'));
|
|
394
|
+
resolvedDistPath = tempDir;
|
|
395
|
+
isTempDir = true;
|
|
396
|
+
console.log(`\nUsing temporary directory: ${tempDir}`);
|
|
397
|
+
}
|
|
288
398
|
}
|
|
289
399
|
else {
|
|
290
400
|
resolvedDistPath = path_1.default.resolve(distPath);
|
|
@@ -314,111 +424,123 @@ function generateCommand(distPath, options) {
|
|
|
314
424
|
console.log(`\nDetected Flutter package at: ${pubspecDir}`);
|
|
315
425
|
}
|
|
316
426
|
}
|
|
317
|
-
// Check if the directory exists and has required files
|
|
318
|
-
const packageJsonPath = path_1.default.join(resolvedDistPath, 'package.json');
|
|
319
|
-
const globalDtsPath = path_1.default.join(resolvedDistPath, 'global.d.ts');
|
|
320
|
-
const tsConfigPath = path_1.default.join(resolvedDistPath, 'tsconfig.json');
|
|
321
|
-
const hasPackageJson = fs_1.default.existsSync(packageJsonPath);
|
|
322
|
-
const hasGlobalDts = fs_1.default.existsSync(globalDtsPath);
|
|
323
|
-
const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
|
|
324
|
-
// Determine if we need to create a new project
|
|
325
|
-
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
326
|
-
// Track if this is an existing project (has all required files)
|
|
327
|
-
const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
328
427
|
let framework = options.framework;
|
|
329
428
|
let packageName = options.packageName;
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
if (
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
429
|
+
let isExistingProject = false;
|
|
430
|
+
if (!isDartOnly) {
|
|
431
|
+
// Check if the directory exists and has required files
|
|
432
|
+
const packageJsonPath = path_1.default.join(resolvedDistPath, 'package.json');
|
|
433
|
+
const globalDtsPath = path_1.default.join(resolvedDistPath, 'global.d.ts');
|
|
434
|
+
const tsConfigPath = path_1.default.join(resolvedDistPath, 'tsconfig.json');
|
|
435
|
+
const hasPackageJson = fs_1.default.existsSync(packageJsonPath);
|
|
436
|
+
const hasGlobalDts = fs_1.default.existsSync(globalDtsPath);
|
|
437
|
+
const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
|
|
438
|
+
// Determine if we need to create a new project
|
|
439
|
+
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
440
|
+
// Track if this is an existing project (has all required files)
|
|
441
|
+
isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
442
|
+
// Validate and sanitize package name if provided
|
|
443
|
+
if (packageName && !isValidNpmPackageName(packageName)) {
|
|
444
|
+
console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
|
|
445
|
+
const sanitized = sanitizePackageName(packageName);
|
|
446
|
+
console.log(`Using sanitized name: "${sanitized}"`);
|
|
447
|
+
packageName = sanitized;
|
|
448
|
+
}
|
|
449
|
+
if (needsProjectCreation) {
|
|
450
|
+
// If project needs creation but options are missing, prompt for them
|
|
451
|
+
if (!framework) {
|
|
452
|
+
const frameworkAnswer = yield inquirer_1.default.prompt([{
|
|
453
|
+
type: 'list',
|
|
454
|
+
name: 'framework',
|
|
455
|
+
message: 'Which framework would you like to use?',
|
|
456
|
+
choices: ['react', 'vue']
|
|
457
|
+
}]);
|
|
458
|
+
framework = frameworkAnswer.framework;
|
|
459
|
+
}
|
|
460
|
+
// Try to read Flutter package metadata if flutterPackageSrc is provided
|
|
461
|
+
let metadata = null;
|
|
462
|
+
if (options.flutterPackageSrc) {
|
|
463
|
+
metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
|
|
464
|
+
}
|
|
465
|
+
if (!packageName) {
|
|
466
|
+
// Use Flutter package name as default if available, sanitized for npm
|
|
467
|
+
const rawDefaultName = (metadata === null || metadata === void 0 ? void 0 : metadata.name) || path_1.default.basename(resolvedDistPath);
|
|
468
|
+
const defaultPackageName = sanitizePackageName(rawDefaultName);
|
|
469
|
+
const packageNameAnswer = yield inquirer_1.default.prompt([{
|
|
470
|
+
type: 'input',
|
|
471
|
+
name: 'packageName',
|
|
472
|
+
message: 'What is your package name?',
|
|
473
|
+
default: defaultPackageName,
|
|
474
|
+
validate: (input) => {
|
|
475
|
+
if (!input || input.trim() === '') {
|
|
476
|
+
return 'Package name is required';
|
|
477
|
+
}
|
|
478
|
+
// Check if it's valid as-is
|
|
479
|
+
if (isValidNpmPackageName(input)) {
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
// If not valid, show what it would be sanitized to
|
|
483
|
+
const sanitized = sanitizePackageName(input);
|
|
484
|
+
return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
|
|
369
485
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
486
|
+
}]);
|
|
487
|
+
packageName = packageNameAnswer.packageName;
|
|
488
|
+
}
|
|
489
|
+
console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
|
|
490
|
+
createCommand(resolvedDistPath, {
|
|
491
|
+
framework: framework,
|
|
492
|
+
packageName: packageName,
|
|
493
|
+
metadata: metadata || undefined
|
|
494
|
+
});
|
|
376
495
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
type: 'list',
|
|
401
|
-
name: 'framework',
|
|
402
|
-
message: 'Which framework are you using?',
|
|
403
|
-
choices: ['react', 'vue']
|
|
404
|
-
}]);
|
|
405
|
-
framework = frameworkAnswer.framework;
|
|
496
|
+
else {
|
|
497
|
+
// Validate existing project structure
|
|
498
|
+
if (hasPackageJson) {
|
|
499
|
+
try {
|
|
500
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
501
|
+
// Detect framework from existing package.json
|
|
502
|
+
if (!framework) {
|
|
503
|
+
if (((_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a.react) || ((_b = packageJson.devDependencies) === null || _b === void 0 ? void 0 : _b.react)) {
|
|
504
|
+
framework = 'react';
|
|
505
|
+
}
|
|
506
|
+
else if (((_c = packageJson.dependencies) === null || _c === void 0 ? void 0 : _c.vue) || ((_d = packageJson.devDependencies) === null || _d === void 0 ? void 0 : _d.vue)) {
|
|
507
|
+
framework = 'vue';
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
// If can't detect, prompt for it
|
|
511
|
+
const frameworkAnswer = yield inquirer_1.default.prompt([{
|
|
512
|
+
type: 'list',
|
|
513
|
+
name: 'framework',
|
|
514
|
+
message: 'Which framework are you using?',
|
|
515
|
+
choices: ['react', 'vue']
|
|
516
|
+
}]);
|
|
517
|
+
framework = frameworkAnswer.framework;
|
|
518
|
+
}
|
|
406
519
|
}
|
|
520
|
+
console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
|
|
521
|
+
}
|
|
522
|
+
catch (e) {
|
|
523
|
+
console.error('Error reading package.json:', e);
|
|
524
|
+
process.exit(1);
|
|
407
525
|
}
|
|
408
|
-
console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
|
|
409
|
-
}
|
|
410
|
-
catch (e) {
|
|
411
|
-
console.error('Error reading package.json:', e);
|
|
412
|
-
process.exit(1);
|
|
413
526
|
}
|
|
414
527
|
}
|
|
415
528
|
}
|
|
529
|
+
else {
|
|
530
|
+
// In Dart-only mode, framework/packageName are unused; ensure framework is not accidentally required later.
|
|
531
|
+
framework = options.framework;
|
|
532
|
+
}
|
|
416
533
|
// Now proceed with code generation if flutter package source is provided
|
|
417
534
|
if (!options.flutterPackageSrc) {
|
|
418
535
|
console.log('\nProject is ready for code generation.');
|
|
419
536
|
console.log('To generate code, run:');
|
|
420
537
|
const displayPath = isTempDir ? '<output-dir>' : distPath;
|
|
421
|
-
|
|
538
|
+
if (isDartOnly) {
|
|
539
|
+
console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --dart-only`);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
|
|
543
|
+
}
|
|
422
544
|
if (isTempDir) {
|
|
423
545
|
// Clean up temporary directory if we're not using it
|
|
424
546
|
fs_1.default.rmSync(resolvedDistPath, { recursive: true, force: true });
|
|
@@ -481,7 +603,23 @@ function generateCommand(distPath, options) {
|
|
|
481
603
|
process.exit(1);
|
|
482
604
|
}
|
|
483
605
|
}
|
|
484
|
-
const
|
|
606
|
+
const baseCommand = 'webf codegen';
|
|
607
|
+
const flutterPart = options.flutterPackageSrc ? ` --flutter-package-src=${options.flutterPackageSrc}` : '';
|
|
608
|
+
const modePart = isDartOnly
|
|
609
|
+
? ' --dart-only'
|
|
610
|
+
: (framework ? ` --framework=${framework}` : '');
|
|
611
|
+
const command = `${baseCommand}${flutterPart}${modePart} <distPath>`;
|
|
612
|
+
if (isDartOnly) {
|
|
613
|
+
console.log(`\nGenerating Dart bindings from ${options.flutterPackageSrc}...`);
|
|
614
|
+
yield (0, generator_1.dartGen)({
|
|
615
|
+
source: options.flutterPackageSrc,
|
|
616
|
+
target: options.flutterPackageSrc,
|
|
617
|
+
command,
|
|
618
|
+
exclude: options.exclude,
|
|
619
|
+
});
|
|
620
|
+
console.log('\nDart code generation completed successfully!');
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
485
623
|
// Auto-initialize typings in the output directory if needed
|
|
486
624
|
ensureInitialized(resolvedDistPath);
|
|
487
625
|
console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
|
|
@@ -509,7 +647,9 @@ function generateCommand(distPath, options) {
|
|
|
509
647
|
target: resolvedDistPath,
|
|
510
648
|
command,
|
|
511
649
|
exclude: options.exclude,
|
|
512
|
-
packageName
|
|
650
|
+
// Prefer CLI-provided packageName (validated/sanitized above),
|
|
651
|
+
// fallback to detected name from package.json
|
|
652
|
+
packageName: packageName || reactPackageName,
|
|
513
653
|
});
|
|
514
654
|
}
|
|
515
655
|
else if (framework === 'vue') {
|
|
@@ -525,6 +665,18 @@ function generateCommand(distPath, options) {
|
|
|
525
665
|
if (framework) {
|
|
526
666
|
try {
|
|
527
667
|
yield buildPackage(resolvedDistPath);
|
|
668
|
+
// After building React package, copy any matching .md docs next to built JS files
|
|
669
|
+
if (framework === 'react' && options.flutterPackageSrc) {
|
|
670
|
+
const distOut = path_1.default.join(resolvedDistPath, 'dist');
|
|
671
|
+
const { copied } = yield copyMarkdownDocsToDist({
|
|
672
|
+
sourceRoot: options.flutterPackageSrc,
|
|
673
|
+
distRoot: distOut,
|
|
674
|
+
exclude: options.exclude,
|
|
675
|
+
});
|
|
676
|
+
if (copied > 0) {
|
|
677
|
+
console.log(`📄 Copied ${copied} markdown docs to dist`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
528
680
|
}
|
|
529
681
|
catch (error) {
|
|
530
682
|
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
|
+
}
|