@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/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
- (0, child_process_1.spawnSync)(NPM, ['install', '--omit=peer'], {
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
- // Create a temporary directory for the generated package
284
- const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'webf-typings-'));
285
- resolvedDistPath = tempDir;
286
- isTempDir = true;
287
- console.log(`\nUsing temporary directory: ${tempDir}`);
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
- // Validate and sanitize package name if provided
331
- if (packageName && !isValidNpmPackageName(packageName)) {
332
- console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
333
- const sanitized = sanitizePackageName(packageName);
334
- console.log(`Using sanitized name: "${sanitized}"`);
335
- packageName = sanitized;
336
- }
337
- if (needsProjectCreation) {
338
- // If project needs creation but options are missing, prompt for them
339
- if (!framework) {
340
- const frameworkAnswer = yield inquirer_1.default.prompt([{
341
- type: 'list',
342
- name: 'framework',
343
- message: 'Which framework would you like to use?',
344
- choices: ['react', 'vue']
345
- }]);
346
- framework = frameworkAnswer.framework;
347
- }
348
- // Try to read Flutter package metadata if flutterPackageSrc is provided
349
- let metadata = null;
350
- if (options.flutterPackageSrc) {
351
- metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
352
- }
353
- if (!packageName) {
354
- // Use Flutter package name as default if available, sanitized for npm
355
- const rawDefaultName = (metadata === null || metadata === void 0 ? void 0 : metadata.name) || path_1.default.basename(resolvedDistPath);
356
- const defaultPackageName = sanitizePackageName(rawDefaultName);
357
- const packageNameAnswer = yield inquirer_1.default.prompt([{
358
- type: 'input',
359
- name: 'packageName',
360
- message: 'What is your package name?',
361
- default: defaultPackageName,
362
- validate: (input) => {
363
- if (!input || input.trim() === '') {
364
- return 'Package name is required';
365
- }
366
- // Check if it's valid as-is
367
- if (isValidNpmPackageName(input)) {
368
- return true;
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
- // If not valid, show what it would be sanitized to
371
- const sanitized = sanitizePackageName(input);
372
- return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
373
- }
374
- }]);
375
- packageName = packageNameAnswer.packageName;
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
- console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
378
- createCommand(resolvedDistPath, {
379
- framework: framework,
380
- packageName: packageName,
381
- metadata: metadata || undefined
382
- });
383
- }
384
- else {
385
- // Validate existing project structure
386
- if (hasPackageJson) {
387
- try {
388
- const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
389
- // Detect framework from existing package.json
390
- if (!framework) {
391
- if (((_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a.react) || ((_b = packageJson.devDependencies) === null || _b === void 0 ? void 0 : _b.react)) {
392
- framework = 'react';
393
- }
394
- else if (((_c = packageJson.dependencies) === null || _c === void 0 ? void 0 : _c.vue) || ((_d = packageJson.devDependencies) === null || _d === void 0 ? void 0 : _d.vue)) {
395
- framework = 'vue';
396
- }
397
- else {
398
- // If can't detect, prompt for it
399
- const frameworkAnswer = yield inquirer_1.default.prompt([{
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
- console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
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 command = `webf codegen --flutter-package-src=${options.flutterPackageSrc} --framework=${framework} <distPath>`;
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: reactPackageName,
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
+ }