@openwebf/webf 0.24.0 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,14 @@ npm install -g @openwebf/webf
10
10
 
11
11
  ## Usage
12
12
 
13
+ ### Initialize Claude Code Skills
14
+
15
+ The `webf agents init` command injects WebF Claude Code skills (from `@openwebf/claude-code-skills`) into your project and updates `CLAUDE.md` to reference them.
16
+
17
+ ```bash
18
+ webf agents init [project-dir]
19
+ ```
20
+
13
21
  ### Generate Code
14
22
 
15
23
  The `webf codegen` command generates Dart abstract classes and React/Vue components from TypeScript definitions. It automatically creates a project if needed.
@@ -142,7 +150,7 @@ my-webf-app/
142
150
  │ └── index.ts
143
151
  ├── package.json
144
152
  ├── tsconfig.json
145
- ├── tsup.config.ts
153
+ ├── tsdown.config.ts
146
154
  ├── global.d.ts
147
155
  └── .gitignore
148
156
  ```
package/bin/webf.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const { Command } = require('commander');
4
4
  const version = require('../package.json').version;
5
- const { generateCommand, generateModuleCommand } = require('../dist/commands');
5
+ const { generateCommand, generateModuleCommand, agentsInitCommand } = require('../dist/commands');
6
6
 
7
7
  const program = new Command();
8
8
 
@@ -35,4 +35,12 @@ program
35
35
  .description('Generate NPM package and Dart bindings for a WebF module from TypeScript interfaces (*.module.d.ts)')
36
36
  .action(generateModuleCommand);
37
37
 
38
+ const agents = program.command('agents').description('Manage Claude Code agent configs');
39
+
40
+ agents
41
+ .command('init')
42
+ .argument('[projectDir]', 'Target project directory', '.')
43
+ .description('Initialize Claude Code skills config for a project')
44
+ .action(agentsInitCommand);
45
+
38
46
  program.parse();
package/dist/agents.js ADDED
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.agentsInitCommand = agentsInitCommand;
16
+ const fs_1 = __importDefault(require("fs"));
17
+ const os_1 = __importDefault(require("os"));
18
+ const path_1 = __importDefault(require("path"));
19
+ const yaml_1 = __importDefault(require("yaml"));
20
+ const WEBF_AGENTS_BLOCK_START = '<!-- webf-agents:init start -->';
21
+ const WEBF_AGENTS_BLOCK_END = '<!-- webf-agents:init end -->';
22
+ function ensureDirSync(dirPath) {
23
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
24
+ }
25
+ function readFileIfExists(filePath) {
26
+ try {
27
+ return fs_1.default.readFileSync(filePath);
28
+ }
29
+ catch (error) {
30
+ if ((error === null || error === void 0 ? void 0 : error.code) === 'ENOENT')
31
+ return null;
32
+ throw error;
33
+ }
34
+ }
35
+ function backupFileSync(filePath) {
36
+ const timestamp = new Date()
37
+ .toISOString()
38
+ .replace(/[:.]/g, '')
39
+ .replace('T', '')
40
+ .replace('Z', 'Z');
41
+ const backupPath = `${filePath}.bak.${timestamp}`;
42
+ fs_1.default.copyFileSync(filePath, backupPath);
43
+ return backupPath;
44
+ }
45
+ function copyFileWithBackupSync(srcPath, destPath) {
46
+ const src = fs_1.default.readFileSync(srcPath);
47
+ const dest = readFileIfExists(destPath);
48
+ if (dest && Buffer.compare(src, dest) === 0)
49
+ return { changed: false, backupPath: null };
50
+ ensureDirSync(path_1.default.dirname(destPath));
51
+ let backupPath = null;
52
+ if (dest) {
53
+ backupPath = backupFileSync(destPath);
54
+ }
55
+ fs_1.default.writeFileSync(destPath, src);
56
+ return { changed: true, backupPath };
57
+ }
58
+ function copyDirRecursiveSync(srcDir, destDir, stats) {
59
+ ensureDirSync(destDir);
60
+ const entries = fs_1.default.readdirSync(srcDir, { withFileTypes: true });
61
+ for (const entry of entries) {
62
+ const srcPath = path_1.default.join(srcDir, entry.name);
63
+ const destPath = path_1.default.join(destDir, entry.name);
64
+ if (entry.isDirectory()) {
65
+ copyDirRecursiveSync(srcPath, destPath, stats);
66
+ continue;
67
+ }
68
+ if (entry.isFile()) {
69
+ const { changed, backupPath } = copyFileWithBackupSync(srcPath, destPath);
70
+ if (changed)
71
+ stats.filesWritten += 1;
72
+ else
73
+ stats.filesUnchanged += 1;
74
+ if (backupPath)
75
+ stats.backupsCreated += 1;
76
+ }
77
+ }
78
+ }
79
+ function listFilesRecursiveSync(dirPath) {
80
+ const out = [];
81
+ const entries = fs_1.default.readdirSync(dirPath, { withFileTypes: true });
82
+ for (const entry of entries) {
83
+ const entryPath = path_1.default.join(dirPath, entry.name);
84
+ if (entry.isDirectory()) {
85
+ out.push(...listFilesRecursiveSync(entryPath));
86
+ continue;
87
+ }
88
+ if (entry.isFile())
89
+ out.push(entryPath);
90
+ }
91
+ return out;
92
+ }
93
+ function toPosixRelativePath(fromDir, absolutePath) {
94
+ const rel = path_1.default.relative(fromDir, absolutePath);
95
+ return rel.split(path_1.default.sep).join('/');
96
+ }
97
+ function parseSkillFrontmatter(skillMd) {
98
+ var _a;
99
+ const trimmed = skillMd.trimStart();
100
+ if (!trimmed.startsWith('---'))
101
+ return {};
102
+ const endIndex = trimmed.indexOf('\n---', 3);
103
+ if (endIndex === -1)
104
+ return {};
105
+ const fm = trimmed.slice(3, endIndex).trim();
106
+ try {
107
+ const parsed = (_a = yaml_1.default.parse(fm)) !== null && _a !== void 0 ? _a : {};
108
+ return { name: parsed === null || parsed === void 0 ? void 0 : parsed.name, description: parsed === null || parsed === void 0 ? void 0 : parsed.description };
109
+ }
110
+ catch (_b) {
111
+ return {};
112
+ }
113
+ }
114
+ function updateOrAppendMarkedBlock(existing, newBlock) {
115
+ const startIndex = existing.indexOf(WEBF_AGENTS_BLOCK_START);
116
+ const endIndex = existing.indexOf(WEBF_AGENTS_BLOCK_END);
117
+ if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
118
+ const before = existing.slice(0, startIndex).trimEnd();
119
+ const after = existing.slice(endIndex + WEBF_AGENTS_BLOCK_END.length).trimStart();
120
+ const next = [before, newBlock.trim(), after].filter(Boolean).join('\n\n');
121
+ return { content: next + '\n', action: 'replaced' };
122
+ }
123
+ const content = existing.trimEnd();
124
+ return { content: (content ? content + '\n\n' : '') + newBlock.trim() + '\n', action: 'appended' };
125
+ }
126
+ function buildClaudeBlock(sourcePackageName, sourcePackageVersion, skills) {
127
+ const lines = [];
128
+ lines.push(WEBF_AGENTS_BLOCK_START);
129
+ lines.push('## WebF Claude Code Skills');
130
+ lines.push('');
131
+ lines.push(`Source: \`${sourcePackageName}@${sourcePackageVersion}\``);
132
+ lines.push('');
133
+ lines.push('### Skills');
134
+ for (const skill of skills) {
135
+ const desc = skill.description ? ` — ${skill.description}` : '';
136
+ lines.push(`- \`${skill.name}\`${desc} (\`${skill.skillFileRelativePath}\`)`);
137
+ }
138
+ const anyReferences = skills.some(s => s.referenceRelativePaths.length > 0);
139
+ if (anyReferences) {
140
+ lines.push('');
141
+ lines.push('### References');
142
+ for (const skill of skills) {
143
+ if (skill.referenceRelativePaths.length === 0)
144
+ continue;
145
+ const refs = skill.referenceRelativePaths.map(r => `\`${r}\``).join(', ');
146
+ lines.push(`- \`${skill.name}\`: ${refs}`);
147
+ }
148
+ }
149
+ lines.push(WEBF_AGENTS_BLOCK_END);
150
+ return lines.join('\n');
151
+ }
152
+ function resolveSkillsPackageRoot() {
153
+ var _a, _b;
154
+ const packageJsonPath = require.resolve('@openwebf/claude-code-skills/package.json');
155
+ const packageRoot = path_1.default.dirname(packageJsonPath);
156
+ const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
157
+ return {
158
+ packageRoot,
159
+ packageName: (_a = packageJson === null || packageJson === void 0 ? void 0 : packageJson.name) !== null && _a !== void 0 ? _a : '@openwebf/claude-code-skills',
160
+ packageVersion: (_b = packageJson === null || packageJson === void 0 ? void 0 : packageJson.version) !== null && _b !== void 0 ? _b : 'unknown',
161
+ };
162
+ }
163
+ function listSkillDirectories(skillsPackageRoot) {
164
+ const entries = fs_1.default.readdirSync(skillsPackageRoot, { withFileTypes: true });
165
+ const dirs = entries
166
+ .filter(e => e.isDirectory())
167
+ .map(e => e.name)
168
+ .filter(name => fs_1.default.existsSync(path_1.default.join(skillsPackageRoot, name, 'SKILL.md')))
169
+ .sort();
170
+ return dirs;
171
+ }
172
+ function collectSkillInfo(projectRoot, skillsDir, skillDirectoryName) {
173
+ var _a;
174
+ const skillDirAbs = path_1.default.join(skillsDir, skillDirectoryName);
175
+ const skillMdAbs = path_1.default.join(skillDirAbs, 'SKILL.md');
176
+ const skillMd = fs_1.default.readFileSync(skillMdAbs, 'utf-8');
177
+ const fm = parseSkillFrontmatter(skillMd);
178
+ const referenceRelativePaths = [];
179
+ const files = listFilesRecursiveSync(skillDirAbs);
180
+ for (const fileAbs of files) {
181
+ if (path_1.default.basename(fileAbs) === 'SKILL.md')
182
+ continue;
183
+ referenceRelativePaths.push(toPosixRelativePath(projectRoot, fileAbs));
184
+ }
185
+ referenceRelativePaths.sort();
186
+ return {
187
+ directoryName: skillDirectoryName,
188
+ name: (_a = fm.name) !== null && _a !== void 0 ? _a : skillDirectoryName,
189
+ description: fm.description,
190
+ skillFileRelativePath: toPosixRelativePath(projectRoot, skillMdAbs),
191
+ referenceRelativePaths,
192
+ };
193
+ }
194
+ function agentsInitCommand(projectDir) {
195
+ return __awaiter(this, void 0, void 0, function* () {
196
+ var _a, _b;
197
+ const startedAt = Date.now();
198
+ const resolvedProjectDir = path_1.default.resolve(process.cwd(), projectDir || '.');
199
+ const claudeMdPath = path_1.default.join(resolvedProjectDir, 'CLAUDE.md');
200
+ const claudeDir = path_1.default.join(resolvedProjectDir, '.claude');
201
+ const projectSkillsDir = path_1.default.join(claudeDir, 'skills');
202
+ const hasClaudeMd = fs_1.default.existsSync(claudeMdPath);
203
+ const hasClaudeDir = fs_1.default.existsSync(claudeDir);
204
+ const isNewProject = !hasClaudeMd && !hasClaudeDir;
205
+ console.log('webf agents init');
206
+ console.log(`Project: ${resolvedProjectDir}`);
207
+ if (isNewProject) {
208
+ console.log('Detected: no CLAUDE.md and no .claude/ (new project)');
209
+ }
210
+ else {
211
+ console.log(`Detected: CLAUDE.md=${hasClaudeMd ? 'yes' : 'no'}, .claude/=${hasClaudeDir ? 'yes' : 'no'} (existing project)`);
212
+ }
213
+ const { packageRoot, packageName, packageVersion } = resolveSkillsPackageRoot();
214
+ const skillDirectories = listSkillDirectories(packageRoot);
215
+ if (skillDirectories.length === 0) {
216
+ throw new Error(`No skills found in ${packageName} (resolved at ${packageRoot}).`);
217
+ }
218
+ console.log(`Skills source: ${packageName}@${packageVersion}`);
219
+ console.log(`Skills destination: ${toPosixRelativePath(resolvedProjectDir, projectSkillsDir)}`);
220
+ ensureDirSync(projectSkillsDir);
221
+ const copyStats = { filesWritten: 0, filesUnchanged: 0, backupsCreated: 0 };
222
+ for (const skillDirName of skillDirectories) {
223
+ const srcSkillDir = path_1.default.join(packageRoot, skillDirName);
224
+ const destSkillDir = path_1.default.join(projectSkillsDir, skillDirName);
225
+ copyDirRecursiveSync(srcSkillDir, destSkillDir, copyStats);
226
+ }
227
+ const installedSkills = skillDirectories.map(skillDirName => collectSkillInfo(resolvedProjectDir, projectSkillsDir, skillDirName));
228
+ const block = buildClaudeBlock(packageName, packageVersion, installedSkills);
229
+ if (isNewProject) {
230
+ const initial = ['# Claude Code', '', block, ''].join('\n');
231
+ fs_1.default.writeFileSync(claudeMdPath, initial, 'utf-8');
232
+ console.log(`Created ${toPosixRelativePath(resolvedProjectDir, claudeMdPath)}`);
233
+ }
234
+ else {
235
+ const existing = (_b = (_a = readFileIfExists(claudeMdPath)) === null || _a === void 0 ? void 0 : _a.toString('utf-8')) !== null && _b !== void 0 ? _b : '';
236
+ const { content, action } = updateOrAppendMarkedBlock(existing, block);
237
+ fs_1.default.writeFileSync(claudeMdPath, content, 'utf-8');
238
+ console.log(`${action === 'replaced' ? 'Updated' : 'Appended'} WebF skills block in ${toPosixRelativePath(resolvedProjectDir, claudeMdPath)}`);
239
+ }
240
+ const versionFilePath = path_1.default.join(claudeDir, 'webf-claude-code-skills.version');
241
+ fs_1.default.writeFileSync(versionFilePath, `${packageName}@${packageVersion}${os_1.default.EOL}`, 'utf-8');
242
+ console.log(`Wrote ${toPosixRelativePath(resolvedProjectDir, versionFilePath)}`);
243
+ console.log(`Installed ${installedSkills.length} skills (${copyStats.filesWritten} files written, ${copyStats.filesUnchanged} unchanged, ${copyStats.backupsCreated} backups) in ${Date.now() - startedAt}ms`);
244
+ });
245
+ }
package/dist/analyzer.js CHANGED
@@ -365,134 +365,196 @@ function handleGenericWrapper(typeReference, mode) {
365
365
  const argument = typeReference.typeArguments[0];
366
366
  return getParameterBaseType(argument, mode);
367
367
  }
368
- function handleCustomEventType(typeReference) {
369
- var _a;
370
- // Handle CustomEvent<T> by returning the full type with generic parameter
371
- if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
372
- return 'CustomEvent';
368
+ const customEventTypePrinter = typescript_1.default.createPrinter({ removeComments: true });
369
+ function mapTypeReferenceIdentifierToTsType(identifier) {
370
+ const mappedType = TYPE_REFERENCE_MAP[identifier];
371
+ if (mappedType === undefined)
372
+ return null;
373
+ switch (mappedType) {
374
+ case declaration_1.FunctionArgumentType.boolean:
375
+ return 'boolean';
376
+ case declaration_1.FunctionArgumentType.dom_string:
377
+ return 'string';
378
+ case declaration_1.FunctionArgumentType.double:
379
+ case declaration_1.FunctionArgumentType.int:
380
+ return 'number';
381
+ case declaration_1.FunctionArgumentType.any:
382
+ return 'any';
383
+ case declaration_1.FunctionArgumentType.void:
384
+ return 'void';
385
+ case declaration_1.FunctionArgumentType.function:
386
+ return 'Function';
387
+ case declaration_1.FunctionArgumentType.promise:
388
+ return 'Promise<any>';
389
+ default:
390
+ return null;
373
391
  }
374
- const argument = typeReference.typeArguments[0];
375
- let genericType;
376
- // Preserve simple union/compound generic types (e.g., boolean | null)
377
- if (typescript_1.default.isUnionTypeNode(argument) || typescript_1.default.isIntersectionTypeNode(argument)) {
378
- const unionTypes = (_a = argument.types) !== null && _a !== void 0 ? _a : [];
379
- const parts = unionTypes.map(t => {
380
- // Literal union members: handle null/undefined explicitly
381
- if (typescript_1.default.isLiteralTypeNode(t)) {
382
- const lit = t.literal;
383
- if (lit.kind === typescript_1.default.SyntaxKind.NullKeyword)
384
- return 'null';
385
- if (lit.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
386
- return 'undefined';
387
- if (typescript_1.default.isStringLiteral(lit))
388
- return JSON.stringify(lit.text);
389
- return 'any';
390
- }
391
- // Basic keywords: boolean, string, number, null, undefined
392
- const basic = BASIC_TYPE_MAP[t.kind];
393
- if (basic !== undefined) {
394
- switch (basic) {
395
- case declaration_1.FunctionArgumentType.boolean:
396
- return 'boolean';
397
- case declaration_1.FunctionArgumentType.dom_string:
398
- return 'string';
399
- case declaration_1.FunctionArgumentType.double:
400
- case declaration_1.FunctionArgumentType.int:
401
- return 'number';
402
- case declaration_1.FunctionArgumentType.null:
403
- return 'null';
404
- case declaration_1.FunctionArgumentType.undefined:
405
- return 'undefined';
406
- default:
407
- return 'any';
408
- }
409
- }
410
- // Literal null/undefined keywords that BASIC_TYPE_MAP may not cover
411
- if (t.kind === typescript_1.default.SyntaxKind.NullKeyword)
412
- return 'null';
413
- if (t.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
414
- return 'undefined';
415
- // Fallback: rely on toString of node kind
392
+ }
393
+ function getBasicTypeKindAsTsType(kind) {
394
+ const basicType = BASIC_TYPE_MAP[kind];
395
+ if (basicType === undefined)
396
+ return null;
397
+ switch (basicType) {
398
+ case declaration_1.FunctionArgumentType.boolean:
399
+ return 'boolean';
400
+ case declaration_1.FunctionArgumentType.dom_string:
401
+ return 'string';
402
+ case declaration_1.FunctionArgumentType.double:
403
+ case declaration_1.FunctionArgumentType.int:
404
+ return 'number';
405
+ case declaration_1.FunctionArgumentType.any:
416
406
  return 'any';
417
- });
418
- genericType = parts.join(' | ');
407
+ case declaration_1.FunctionArgumentType.void:
408
+ return 'void';
409
+ case declaration_1.FunctionArgumentType.null:
410
+ return 'null';
411
+ case declaration_1.FunctionArgumentType.undefined:
412
+ return 'undefined';
413
+ default:
414
+ return null;
419
415
  }
420
- else if (typescript_1.default.isTypeReferenceNode(argument) && typescript_1.default.isIdentifier(argument.typeName)) {
421
- const typeName = argument.typeName.text;
422
- // Check if it's a mapped type reference like 'int' or 'double'
423
- const mappedType = TYPE_REFERENCE_MAP[typeName];
424
- if (mappedType !== undefined) {
425
- switch (mappedType) {
426
- case declaration_1.FunctionArgumentType.boolean:
427
- genericType = 'boolean';
428
- break;
429
- case declaration_1.FunctionArgumentType.dom_string:
430
- genericType = 'string';
431
- break;
432
- case declaration_1.FunctionArgumentType.double:
433
- case declaration_1.FunctionArgumentType.int:
434
- genericType = 'number';
435
- break;
436
- case declaration_1.FunctionArgumentType.any:
437
- genericType = 'any';
438
- break;
439
- case declaration_1.FunctionArgumentType.void:
440
- genericType = 'void';
441
- break;
442
- case declaration_1.FunctionArgumentType.function:
443
- genericType = 'Function';
444
- break;
445
- case declaration_1.FunctionArgumentType.promise:
446
- genericType = 'Promise<any>';
447
- break;
448
- default:
449
- genericType = typeName;
416
+ }
417
+ function stringifyEntityName(name) {
418
+ if (typescript_1.default.isIdentifier(name))
419
+ return name.text;
420
+ return `${stringifyEntityName(name.left)}.${name.right.text}`;
421
+ }
422
+ function safePrintCustomEventNode(node) {
423
+ const sourceFile = node.getSourceFile();
424
+ const printed = customEventTypePrinter.printNode(typescript_1.default.EmitHint.Unspecified, node, sourceFile);
425
+ // Ensure WebF IDL-like aliases used in type definitions do not leak into generated TypeScript packages.
426
+ return printed.replace(/\bint\b/g, 'number').replace(/\bdouble\b/g, 'number');
427
+ }
428
+ function stringifyCustomEventGenericTypeNode(typeNode) {
429
+ if (typescript_1.default.isParenthesizedTypeNode(typeNode)) {
430
+ const inner = stringifyCustomEventGenericTypeNode(typeNode.type);
431
+ return inner ? `(${inner})` : null;
432
+ }
433
+ if (typescript_1.default.isUnionTypeNode(typeNode)) {
434
+ const parts = typeNode.types.map(t => stringifyCustomEventGenericTypeNode(t)).filter((t) => Boolean(t));
435
+ return parts.length === typeNode.types.length ? parts.join(' | ') : null;
436
+ }
437
+ if (typescript_1.default.isIntersectionTypeNode(typeNode)) {
438
+ const parts = typeNode.types.map(t => stringifyCustomEventGenericTypeNode(t)).filter((t) => Boolean(t));
439
+ return parts.length === typeNode.types.length ? parts.join(' & ') : null;
440
+ }
441
+ if (typescript_1.default.isArrayTypeNode(typeNode)) {
442
+ const element = stringifyCustomEventGenericTypeNode(typeNode.elementType);
443
+ return element ? `${element}[]` : null;
444
+ }
445
+ if (typescript_1.default.isTupleTypeNode(typeNode)) {
446
+ const elements = typeNode.elements.map(e => stringifyCustomEventGenericTypeNode(e)).filter((t) => Boolean(t));
447
+ return elements.length === typeNode.elements.length ? `[${elements.join(', ')}]` : null;
448
+ }
449
+ if (typescript_1.default.isLiteralTypeNode(typeNode)) {
450
+ const literal = typeNode.literal;
451
+ if (literal.kind === typescript_1.default.SyntaxKind.NullKeyword)
452
+ return 'null';
453
+ if (literal.kind === typescript_1.default.SyntaxKind.UndefinedKeyword)
454
+ return 'undefined';
455
+ if (literal.kind === typescript_1.default.SyntaxKind.TrueKeyword)
456
+ return 'true';
457
+ if (literal.kind === typescript_1.default.SyntaxKind.FalseKeyword)
458
+ return 'false';
459
+ if (typescript_1.default.isStringLiteral(literal))
460
+ return JSON.stringify(literal.text);
461
+ if (typescript_1.default.isNumericLiteral(literal))
462
+ return literal.text;
463
+ return null;
464
+ }
465
+ const basic = getBasicTypeKindAsTsType(typeNode.kind);
466
+ if (basic)
467
+ return basic;
468
+ if (typescript_1.default.isTypeReferenceNode(typeNode)) {
469
+ const typeName = stringifyEntityName(typeNode.typeName);
470
+ // Unwrap internal helpers used by WebF typings.
471
+ if (typeName === 'DartImpl' && typeNode.typeArguments && typeNode.typeArguments[0]) {
472
+ return stringifyCustomEventGenericTypeNode(typeNode.typeArguments[0]);
473
+ }
474
+ if (typeName === 'Promise') {
475
+ if (!typeNode.typeArguments || !typeNode.typeArguments[0])
476
+ return 'Promise<any>';
477
+ const inner = stringifyCustomEventGenericTypeNode(typeNode.typeArguments[0]);
478
+ return inner ? `Promise<${inner}>` : null;
479
+ }
480
+ const mapped = mapTypeReferenceIdentifierToTsType(typeName);
481
+ if (mapped)
482
+ return mapped;
483
+ if (!typeNode.typeArguments || typeNode.typeArguments.length === 0) {
484
+ return typeName;
485
+ }
486
+ const args = typeNode.typeArguments
487
+ .map(arg => stringifyCustomEventGenericTypeNode(arg))
488
+ .filter((t) => Boolean(t));
489
+ if (args.length !== typeNode.typeArguments.length)
490
+ return null;
491
+ return `${typeName}<${args.join(', ')}>`;
492
+ }
493
+ if (typescript_1.default.isTypeLiteralNode(typeNode)) {
494
+ const members = [];
495
+ for (const member of typeNode.members) {
496
+ if (typescript_1.default.isPropertySignature(member) && member.type) {
497
+ const typeString = stringifyCustomEventGenericTypeNode(member.type);
498
+ if (!typeString)
499
+ return null;
500
+ let nameText;
501
+ if (typescript_1.default.isIdentifier(member.name))
502
+ nameText = member.name.text;
503
+ else if (typescript_1.default.isStringLiteral(member.name))
504
+ nameText = JSON.stringify(member.name.text);
505
+ else if (typescript_1.default.isNumericLiteral(member.name))
506
+ nameText = member.name.text;
507
+ else
508
+ nameText = member.name.getText();
509
+ const optional = member.questionToken ? '?' : '';
510
+ members.push(`${nameText}${optional}: ${typeString}`);
511
+ continue;
450
512
  }
513
+ if (typescript_1.default.isIndexSignatureDeclaration(member) && member.type && member.parameters.length === 1) {
514
+ const param = member.parameters[0];
515
+ const paramName = typescript_1.default.isIdentifier(param.name) ? param.name.text : param.name.getText();
516
+ const paramType = param.type ? stringifyCustomEventGenericTypeNode(param.type) : 'string';
517
+ const valueType = stringifyCustomEventGenericTypeNode(member.type);
518
+ if (!paramType || !valueType)
519
+ return null;
520
+ members.push(`[${paramName}: ${paramType}]: ${valueType}`);
521
+ continue;
522
+ }
523
+ // Fallback for uncommon members (call signatures, method signatures, etc.).
524
+ members.push(safePrintCustomEventNode(member));
451
525
  }
452
- else {
453
- // For other type references, use the type name directly
454
- genericType = typeName;
455
- }
526
+ return `{ ${members.join('; ')} }`;
456
527
  }
457
- else if (typescript_1.default.isLiteralTypeNode(argument) && typescript_1.default.isStringLiteral(argument.literal)) {
458
- genericType = argument.literal.text;
528
+ if (typescript_1.default.isTypeOperatorNode(typeNode)) {
529
+ const inner = stringifyCustomEventGenericTypeNode(typeNode.type);
530
+ if (!inner)
531
+ return null;
532
+ const operator = typeNode.operator === typescript_1.default.SyntaxKind.KeyOfKeyword ? 'keyof' :
533
+ typeNode.operator === typescript_1.default.SyntaxKind.ReadonlyKeyword ? 'readonly' :
534
+ typeNode.operator === typescript_1.default.SyntaxKind.UniqueKeyword ? 'unique' :
535
+ null;
536
+ return operator ? `${operator} ${inner}` : null;
537
+ }
538
+ if (typescript_1.default.isIndexedAccessTypeNode(typeNode)) {
539
+ const objectType = stringifyCustomEventGenericTypeNode(typeNode.objectType);
540
+ const indexType = stringifyCustomEventGenericTypeNode(typeNode.indexType);
541
+ if (!objectType || !indexType)
542
+ return null;
543
+ return `${objectType}[${indexType}]`;
459
544
  }
460
- else {
461
- // Handle basic types (boolean, string, number, etc.)
462
- const basicType = BASIC_TYPE_MAP[argument.kind];
463
- if (basicType !== undefined) {
464
- switch (basicType) {
465
- case declaration_1.FunctionArgumentType.boolean:
466
- genericType = 'boolean';
467
- break;
468
- case declaration_1.FunctionArgumentType.dom_string:
469
- genericType = 'string';
470
- break;
471
- case declaration_1.FunctionArgumentType.double:
472
- case declaration_1.FunctionArgumentType.int:
473
- genericType = 'number';
474
- break;
475
- case declaration_1.FunctionArgumentType.any:
476
- genericType = 'any';
477
- break;
478
- case declaration_1.FunctionArgumentType.void:
479
- genericType = 'void';
480
- break;
481
- case declaration_1.FunctionArgumentType.null:
482
- genericType = 'null';
483
- break;
484
- case declaration_1.FunctionArgumentType.undefined:
485
- genericType = 'undefined';
486
- break;
487
- default:
488
- genericType = 'any';
489
- }
490
- }
491
- else {
492
- // For truly complex types, fallback to 'any' to avoid errors
493
- console.warn('Complex generic type in CustomEvent, using any');
494
- genericType = 'any';
495
- }
545
+ // As a last resort, keep the original syntax but normalize known WebF aliases.
546
+ return safePrintCustomEventNode(typeNode);
547
+ }
548
+ function handleCustomEventType(typeReference) {
549
+ // Handle CustomEvent<T> by returning the full type with generic parameter
550
+ if (!typeReference.typeArguments || !typeReference.typeArguments[0]) {
551
+ return 'CustomEvent';
552
+ }
553
+ const argument = typeReference.typeArguments[0];
554
+ const genericType = stringifyCustomEventGenericTypeNode(argument);
555
+ if (!genericType) {
556
+ console.warn('Complex generic type in CustomEvent, using any');
557
+ return 'CustomEvent<any>';
496
558
  }
497
559
  return `CustomEvent<${genericType}>`;
498
560
  }