@openwebf/webf 0.22.11 → 0.23.0

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
@@ -233,10 +233,16 @@ function createCommand(target, options) {
233
233
  fs_1.default.mkdirSync(srcDir, { recursive: true });
234
234
  }
235
235
  const indexFilePath = path_1.default.join(srcDir, 'index.ts');
236
- const indexContent = lodash_1.default.template(reactIndexTpl)({
237
- components: [],
238
- });
239
- writeFileIfChanged(indexFilePath, indexContent);
236
+ if (!fs_1.default.existsSync(indexFilePath)) {
237
+ const indexContent = lodash_1.default.template(reactIndexTpl)({
238
+ components: [],
239
+ });
240
+ writeFileIfChanged(indexFilePath, indexContent);
241
+ }
242
+ else {
243
+ // Do not overwrite existing index.ts created by the user
244
+ // Leave merge to the codegen step which appends exports safely
245
+ }
240
246
  (0, child_process_1.spawnSync)(NPM, ['install', '--omit=peer'], {
241
247
  cwd: target,
242
248
  stdio: 'inherit'
@@ -260,7 +266,7 @@ function createCommand(target, options) {
260
266
  cwd: target,
261
267
  stdio: 'inherit'
262
268
  });
263
- (0, child_process_1.spawnSync)(NPM, ['install', '@types/vue', '-D'], {
269
+ (0, child_process_1.spawnSync)(NPM, ['install', 'vue', '-D'], {
264
270
  cwd: target,
265
271
  stdio: 'inherit'
266
272
  });
package/dist/dart.js CHANGED
@@ -80,6 +80,12 @@ ${enumValues};
80
80
  }`;
81
81
  }
82
82
  function generateReturnType(type, enumName) {
83
+ // Handle union types first (e.g., 'left' | 'center' | 'right')
84
+ // so we don't incorrectly treat string literal unions as pointer types.
85
+ if (Array.isArray(type.value)) {
86
+ // If we have an enum name, use it; otherwise fall back to String
87
+ return enumName || 'String';
88
+ }
83
89
  if ((0, utils_1.isPointerType)(type)) {
84
90
  const pointerType = (0, utils_1.getPointerType)(type);
85
91
  return pointerType;
@@ -87,11 +93,6 @@ function generateReturnType(type, enumName) {
87
93
  if (type.isArray && typeof type.value === 'object' && !Array.isArray(type.value)) {
88
94
  return `${(0, utils_1.getPointerType)(type.value)}[]`;
89
95
  }
90
- // Handle union types (e.g., 'left' | 'center' | 'right')
91
- if (Array.isArray(type.value)) {
92
- // If we have an enum name, use it; otherwise fall back to String
93
- return enumName || 'String';
94
- }
95
96
  // Handle when type.value is a ParameterType object (nested type)
96
97
  if (typeof type.value === 'object' && !Array.isArray(type.value) && type.value !== null) {
97
98
  // This might be a nested ParameterType, recurse
@@ -250,7 +251,7 @@ interface ${object.name} {
250
251
  }
251
252
  // Generate enums for union types
252
253
  const enums = [];
253
- const enumMap = new Map(); // prop name -> enum name
254
+ const enumMap = new Map(); // camelCase prop name -> enum name
254
255
  if (componentProperties) {
255
256
  for (const prop of componentProperties.props) {
256
257
  if (isStringUnionType(prop.type)) {
@@ -261,7 +262,8 @@ interface ${object.name} {
261
262
  name: enumName,
262
263
  definition: generateDartEnum(enumName, values)
263
264
  });
264
- enumMap.set(prop.name, enumName);
265
+ // Store by camelCase prop name to match template usage
266
+ enumMap.set(lodash_1.default.camelCase(prop.name), enumName);
265
267
  }
266
268
  }
267
269
  }
package/dist/generator.js CHANGED
@@ -29,6 +29,7 @@ const dart_1 = require("./dart");
29
29
  const react_1 = require("./react");
30
30
  const vue_1 = require("./vue");
31
31
  const logger_1 = require("./logger");
32
+ const typescript_1 = __importDefault(require("typescript"));
32
33
  // Cache for file content to avoid redundant reads
33
34
  const fileContentCache = new Map();
34
35
  // Cache for generated content to detect changes
@@ -287,12 +288,91 @@ function reactGen(_a) {
287
288
  (0, logger_1.error)(`Error generating React component for ${blob.filename}`, err);
288
289
  }
289
290
  }));
290
- // Generate index file (always regenerate to avoid duplicates)
291
+ // Generate/merge index file
291
292
  const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
292
- const newExports = (0, react_1.generateReactIndex)(blobs);
293
- if (writeFileIfChanged(indexFilePath, newExports)) {
294
- filesChanged++;
295
- (0, logger_1.debug)(`Generated: index.ts`);
293
+ // Build desired export map: moduleSpecifier -> Set of names
294
+ const desiredExports = new Map();
295
+ const components = blobs.flatMap(blob => {
296
+ const classObjects = blob.objects.filter(obj => obj instanceof declaration_1.ClassObject);
297
+ const properties = classObjects.filter(object => object.name.endsWith('Properties'));
298
+ const events = classObjects.filter(object => object.name.endsWith('Events'));
299
+ const componentMap = new Map();
300
+ properties.forEach(prop => componentMap.set(prop.name.replace(/Properties$/, ''), true));
301
+ events.forEach(evt => componentMap.set(evt.name.replace(/Events$/, ''), true));
302
+ return Array.from(componentMap.keys()).map(className => ({
303
+ className,
304
+ fileName: blob.filename,
305
+ relativeDir: blob.relativeDir,
306
+ }));
307
+ });
308
+ // Deduplicate by className
309
+ const unique = new Map();
310
+ for (const c of components) {
311
+ if (!unique.has(c.className))
312
+ unique.set(c.className, c);
313
+ }
314
+ for (const { className, fileName, relativeDir } of unique.values()) {
315
+ const spec = `./${relativeDir ? `${relativeDir}/` : ''}${fileName}`;
316
+ if (!desiredExports.has(spec))
317
+ desiredExports.set(spec, new Set());
318
+ const set = desiredExports.get(spec);
319
+ set.add(className);
320
+ set.add(`${className}Element`);
321
+ }
322
+ if (!fs_1.default.existsSync(indexFilePath)) {
323
+ // No index.ts -> generate fresh file from template
324
+ const newExports = (0, react_1.generateReactIndex)(blobs);
325
+ if (writeFileIfChanged(indexFilePath, newExports)) {
326
+ filesChanged++;
327
+ (0, logger_1.debug)(`Generated: index.ts`);
328
+ }
329
+ }
330
+ else {
331
+ // Merge into existing index.ts without removing user code
332
+ try {
333
+ const existing = fs_1.default.readFileSync(indexFilePath, 'utf-8');
334
+ const sourceFile = typescript_1.default.createSourceFile(indexFilePath, existing, typescript_1.default.ScriptTarget.ES2020, true, typescript_1.default.ScriptKind.TS);
335
+ // Track which names already exported per module
336
+ for (const stmt of sourceFile.statements) {
337
+ if (typescript_1.default.isExportDeclaration(stmt) && stmt.exportClause && typescript_1.default.isNamedExports(stmt.exportClause)) {
338
+ const moduleSpecifier = stmt.moduleSpecifier && typescript_1.default.isStringLiteral(stmt.moduleSpecifier)
339
+ ? stmt.moduleSpecifier.text
340
+ : undefined;
341
+ if (!moduleSpecifier)
342
+ continue;
343
+ const desired = desiredExports.get(moduleSpecifier);
344
+ if (!desired)
345
+ continue;
346
+ for (const el of stmt.exportClause.elements) {
347
+ const name = el.name.getText(sourceFile);
348
+ if (desired.has(name))
349
+ desired.delete(name);
350
+ }
351
+ }
352
+ }
353
+ // Prepare new export lines for any remaining names
354
+ const lines = [];
355
+ for (const [spec, names] of desiredExports) {
356
+ const missing = Array.from(names);
357
+ if (missing.length === 0)
358
+ continue;
359
+ const specEscaped = spec.replace(/\\/g, '/');
360
+ lines.push(`export { ${missing.join(', ')} } from "${specEscaped}";`);
361
+ }
362
+ if (lines.length > 0) {
363
+ const appended = (existing.endsWith('\n') ? '' : '\n') + lines.join('\n') + '\n';
364
+ if (writeFileIfChanged(indexFilePath, existing + appended)) {
365
+ filesChanged++;
366
+ (0, logger_1.debug)(`Merged exports into existing index.ts`);
367
+ }
368
+ }
369
+ else {
370
+ (0, logger_1.debug)(`index.ts is up to date; no merge needed.`);
371
+ }
372
+ }
373
+ catch (err) {
374
+ (0, logger_1.warn)(`Failed to merge into existing index.ts. Skipping modifications: ${indexFilePath}`);
375
+ }
296
376
  }
297
377
  (0, logger_1.timeEnd)('reactGen');
298
378
  (0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
package/dist/vue.js CHANGED
@@ -51,6 +51,10 @@ function generateEventHandlerType(type) {
51
51
  if (pointerType === 'CustomEvent') {
52
52
  return 'CustomEvent';
53
53
  }
54
+ // Handle generic types like CustomEvent<T>
55
+ if (pointerType.startsWith('CustomEvent<')) {
56
+ return pointerType;
57
+ }
54
58
  throw new Error('Unknown event type: ' + pointerType);
55
59
  }
56
60
  function generateMethodDeclaration(method) {
@@ -65,6 +69,10 @@ function generateMethodDeclaration(method) {
65
69
  }
66
70
  function generateVueComponent(blob) {
67
71
  const classObjects = blob.objects;
72
+ // Skip if no class objects
73
+ if (!classObjects || classObjects.length === 0) {
74
+ return '';
75
+ }
68
76
  const classObjectDictionary = Object.fromEntries(classObjects.map(object => {
69
77
  return [object.name, object];
70
78
  }));
@@ -79,6 +87,9 @@ function generateVueComponent(blob) {
79
87
  && !object.name.endsWith('Events');
80
88
  });
81
89
  const dependencies = others.map(object => {
90
+ if (!object || !object.props) {
91
+ return '';
92
+ }
82
93
  const props = object.props.map(prop => {
83
94
  if (prop.optional) {
84
95
  return `${prop.name}?: ${generateReturnType(prop.type)};`;
@@ -89,7 +100,7 @@ function generateVueComponent(blob) {
89
100
  interface ${object.name} {
90
101
  ${props}
91
102
  }`;
92
- }).join('\n\n');
103
+ }).filter(dep => dep.trim() !== '').join('\n\n');
93
104
  const componentProperties = properties.length > 0 ? properties[0] : undefined;
94
105
  const componentEvents = events.length > 0 ? events[0] : undefined;
95
106
  const className = (() => {
@@ -149,7 +160,11 @@ function generateVueTypings(blobs) {
149
160
  }).filter(component => {
150
161
  return component.length > 0;
151
162
  }).join('\n\n');
152
- const content = lodash_1.default.template(readTemplate('vue.components.d.ts'))({
163
+ const content = lodash_1.default.template(readTemplate('vue.components.d.ts'), {
164
+ interpolate: /<%=([\s\S]+?)%>/g,
165
+ evaluate: /<%([\s\S]+?)%>/g,
166
+ escape: /<%-([\s\S]+?)%>/g
167
+ })({
153
168
  componentNames,
154
169
  components,
155
170
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwebf/webf",
3
- "version": "0.22.11",
3
+ "version": "0.23.0",
4
4
  "description": "Command line tools for WebF",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -30,6 +30,7 @@
30
30
  "@types/inquirer": "^8.2.11",
31
31
  "@types/jest": "^29.5.12",
32
32
  "@types/lodash": "^4.17.17",
33
+ "@types/minimatch": "^5.1.2",
33
34
  "@types/node": "^22.15.21",
34
35
  "@types/yaml": "^1.9.6",
35
36
  "jest": "^29.7.0",
@@ -39,7 +40,7 @@
39
40
  "@microsoft/tsdoc": "^0.15.1",
40
41
  "@microsoft/tsdoc-config": "^0.17.1",
41
42
  "commander": "^14.0.0",
42
- "glob": "^10.3.10",
43
+ "glob": "^10.4.5",
43
44
  "inquirer": "^8.2.6",
44
45
  "lodash": "^4.17.21",
45
46
  "typescript": "^5.8.3",
package/src/commands.ts CHANGED
@@ -300,10 +300,15 @@ function createCommand(target: string, options: { framework: string; packageName
300
300
  }
301
301
 
302
302
  const indexFilePath = path.join(srcDir, 'index.ts');
303
- const indexContent = _.template(reactIndexTpl)({
304
- components: [],
305
- });
306
- writeFileIfChanged(indexFilePath, indexContent);
303
+ if (!fs.existsSync(indexFilePath)) {
304
+ const indexContent = _.template(reactIndexTpl)({
305
+ components: [],
306
+ });
307
+ writeFileIfChanged(indexFilePath, indexContent);
308
+ } else {
309
+ // Do not overwrite existing index.ts created by the user
310
+ // Leave merge to the codegen step which appends exports safely
311
+ }
307
312
 
308
313
  spawnSync(NPM, ['install', '--omit=peer'], {
309
314
  cwd: target,
@@ -332,7 +337,7 @@ function createCommand(target: string, options: { framework: string; packageName
332
337
  stdio: 'inherit'
333
338
  });
334
339
 
335
- spawnSync(NPM, ['install', '@types/vue', '-D'], {
340
+ spawnSync(NPM, ['install', 'vue', '-D'], {
336
341
  cwd: target,
337
342
  stdio: 'inherit'
338
343
  });
package/src/dart.ts CHANGED
@@ -83,6 +83,13 @@ ${enumValues};
83
83
  }
84
84
 
85
85
  function generateReturnType(type: ParameterType, enumName?: string) {
86
+ // Handle union types first (e.g., 'left' | 'center' | 'right')
87
+ // so we don't incorrectly treat string literal unions as pointer types.
88
+ if (Array.isArray(type.value)) {
89
+ // If we have an enum name, use it; otherwise fall back to String
90
+ return enumName || 'String';
91
+ }
92
+
86
93
  if (isPointerType(type)) {
87
94
  const pointerType = getPointerType(type);
88
95
  return pointerType;
@@ -91,12 +98,6 @@ function generateReturnType(type: ParameterType, enumName?: string) {
91
98
  return `${getPointerType(type.value)}[]`;
92
99
  }
93
100
 
94
- // Handle union types (e.g., 'left' | 'center' | 'right')
95
- if (Array.isArray(type.value)) {
96
- // If we have an enum name, use it; otherwise fall back to String
97
- return enumName || 'String';
98
- }
99
-
100
101
  // Handle when type.value is a ParameterType object (nested type)
101
102
  if (typeof type.value === 'object' && !Array.isArray(type.value) && type.value !== null) {
102
103
  // This might be a nested ParameterType, recurse
@@ -278,7 +279,7 @@ interface ${object.name} {
278
279
 
279
280
  // Generate enums for union types
280
281
  const enums: { name: string; definition: string }[] = [];
281
- const enumMap: Map<string, string> = new Map(); // prop name -> enum name
282
+ const enumMap: Map<string, string> = new Map(); // camelCase prop name -> enum name
282
283
 
283
284
  if (componentProperties) {
284
285
  for (const prop of componentProperties.props) {
@@ -290,7 +291,8 @@ interface ${object.name} {
290
291
  name: enumName,
291
292
  definition: generateDartEnum(enumName, values)
292
293
  });
293
- enumMap.set(prop.name, enumName);
294
+ // Store by camelCase prop name to match template usage
295
+ enumMap.set(_.camelCase(prop.name), enumName);
294
296
  }
295
297
  }
296
298
  }
package/src/generator.ts CHANGED
@@ -11,6 +11,7 @@ import { generateDartClass } from './dart';
11
11
  import { generateReactComponent, generateReactIndex } from './react';
12
12
  import { generateVueTypings } from './vue';
13
13
  import { logger, debug, info, success, warn, error, group, progress, time, timeEnd } from './logger';
14
+ import ts from 'typescript';
14
15
 
15
16
  // Cache for file content to avoid redundant reads
16
17
  const fileContentCache = new Map<string, string>();
@@ -321,13 +322,88 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
321
322
  }
322
323
  });
323
324
 
324
- // Generate index file (always regenerate to avoid duplicates)
325
+ // Generate/merge index file
325
326
  const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
326
- const newExports = generateReactIndex(blobs);
327
-
328
- if (writeFileIfChanged(indexFilePath, newExports)) {
329
- filesChanged++;
330
- debug(`Generated: index.ts`);
327
+
328
+ // Build desired export map: moduleSpecifier -> Set of names
329
+ const desiredExports = new Map<string, Set<string>>();
330
+ const components = blobs.flatMap(blob => {
331
+ const classObjects = blob.objects.filter(obj => obj instanceof ClassObject) as ClassObject[];
332
+ const properties = classObjects.filter(object => object.name.endsWith('Properties'));
333
+ const events = classObjects.filter(object => object.name.endsWith('Events'));
334
+ const componentMap = new Map<string, boolean>();
335
+ properties.forEach(prop => componentMap.set(prop.name.replace(/Properties$/, ''), true));
336
+ events.forEach(evt => componentMap.set(evt.name.replace(/Events$/, ''), true));
337
+ return Array.from(componentMap.keys()).map(className => ({
338
+ className,
339
+ fileName: blob.filename,
340
+ relativeDir: blob.relativeDir,
341
+ }));
342
+ });
343
+
344
+ // Deduplicate by className
345
+ const unique = new Map<string, { className: string; fileName: string; relativeDir: string }>();
346
+ for (const c of components) {
347
+ if (!unique.has(c.className)) unique.set(c.className, c);
348
+ }
349
+ for (const { className, fileName, relativeDir } of unique.values()) {
350
+ const spec = `./${relativeDir ? `${relativeDir}/` : ''}${fileName}`;
351
+ if (!desiredExports.has(spec)) desiredExports.set(spec, new Set());
352
+ const set = desiredExports.get(spec)!;
353
+ set.add(className);
354
+ set.add(`${className}Element`);
355
+ }
356
+
357
+ if (!fs.existsSync(indexFilePath)) {
358
+ // No index.ts -> generate fresh file from template
359
+ const newExports = generateReactIndex(blobs);
360
+ if (writeFileIfChanged(indexFilePath, newExports)) {
361
+ filesChanged++;
362
+ debug(`Generated: index.ts`);
363
+ }
364
+ } else {
365
+ // Merge into existing index.ts without removing user code
366
+ try {
367
+ const existing = fs.readFileSync(indexFilePath, 'utf-8');
368
+ const sourceFile = ts.createSourceFile(indexFilePath, existing, ts.ScriptTarget.ES2020, true, ts.ScriptKind.TS);
369
+
370
+ // Track which names already exported per module
371
+ for (const stmt of sourceFile.statements) {
372
+ if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
373
+ const moduleSpecifier = stmt.moduleSpecifier && ts.isStringLiteral(stmt.moduleSpecifier)
374
+ ? stmt.moduleSpecifier.text
375
+ : undefined;
376
+ if (!moduleSpecifier) continue;
377
+ const desired = desiredExports.get(moduleSpecifier);
378
+ if (!desired) continue;
379
+ for (const el of stmt.exportClause.elements) {
380
+ const name = el.name.getText(sourceFile);
381
+ if (desired.has(name)) desired.delete(name);
382
+ }
383
+ }
384
+ }
385
+
386
+ // Prepare new export lines for any remaining names
387
+ const lines: string[] = [];
388
+ for (const [spec, names] of desiredExports) {
389
+ const missing = Array.from(names);
390
+ if (missing.length === 0) continue;
391
+ const specEscaped = spec.replace(/\\/g, '/');
392
+ lines.push(`export { ${missing.join(', ')} } from "${specEscaped}";`);
393
+ }
394
+
395
+ if (lines.length > 0) {
396
+ const appended = (existing.endsWith('\n') ? '' : '\n') + lines.join('\n') + '\n';
397
+ if (writeFileIfChanged(indexFilePath, existing + appended)) {
398
+ filesChanged++;
399
+ debug(`Merged exports into existing index.ts`);
400
+ }
401
+ } else {
402
+ debug(`index.ts is up to date; no merge needed.`);
403
+ }
404
+ } catch (err) {
405
+ warn(`Failed to merge into existing index.ts. Skipping modifications: ${indexFilePath}`);
406
+ }
331
407
  }
332
408
 
333
409
  timeEnd('reactGen');
package/src/vue.ts CHANGED
@@ -50,6 +50,10 @@ function generateEventHandlerType(type: ParameterType) {
50
50
  if (pointerType === 'CustomEvent') {
51
51
  return 'CustomEvent';
52
52
  }
53
+ // Handle generic types like CustomEvent<T>
54
+ if (pointerType.startsWith('CustomEvent<')) {
55
+ return pointerType;
56
+ }
53
57
  throw new Error('Unknown event type: ' + pointerType);
54
58
  }
55
59
 
@@ -66,6 +70,11 @@ function generateMethodDeclaration(method: FunctionDeclaration) {
66
70
 
67
71
  function generateVueComponent(blob: IDLBlob) {
68
72
  const classObjects = blob.objects as ClassObject[];
73
+
74
+ // Skip if no class objects
75
+ if (!classObjects || classObjects.length === 0) {
76
+ return '';
77
+ }
69
78
  const classObjectDictionary = Object.fromEntries(
70
79
  classObjects.map(object => {
71
80
  return [object.name, object];
@@ -85,6 +94,9 @@ function generateVueComponent(blob: IDLBlob) {
85
94
  });
86
95
 
87
96
  const dependencies = others.map(object => {
97
+ if (!object || !object.props) {
98
+ return '';
99
+ }
88
100
  const props = object.props.map(prop => {
89
101
  if (prop.optional) {
90
102
  return `${prop.name}?: ${generateReturnType(prop.type)};`;
@@ -96,7 +108,7 @@ function generateVueComponent(blob: IDLBlob) {
96
108
  interface ${object.name} {
97
109
  ${props}
98
110
  }`;
99
- }).join('\n\n');
111
+ }).filter(dep => dep.trim() !== '').join('\n\n');
100
112
 
101
113
  const componentProperties = properties.length > 0 ? properties[0] : undefined;
102
114
  const componentEvents = events.length > 0 ? events[0] : undefined;
@@ -165,7 +177,11 @@ export function generateVueTypings(blobs: IDLBlob[]) {
165
177
  return component.length > 0;
166
178
  }).join('\n\n');
167
179
 
168
- const content = _.template(readTemplate('vue.components.d.ts'))({
180
+ const content = _.template(readTemplate('vue.components.d.ts'), {
181
+ interpolate: /<%=([\s\S]+?)%>/g,
182
+ evaluate: /<%([\s\S]+?)%>/g,
183
+ escape: /<%-([\s\S]+?)%>/g
184
+ })({
169
185
  componentNames,
170
186
  components,
171
187
  });
@@ -1,6 +1,6 @@
1
1
  // AUTO GENERATED FILE, DO NOT EDIT.
2
2
  //
3
- // Generated by <%= command %>
3
+ // Generated by `webf codegen`
4
4
 
5
5
  // ignore_for_file: avoid_unused_constructor_parameters
6
6
  // ignore_for_file: non_constant_identifier_names
@@ -14,18 +14,23 @@ type VueEmit<T extends EventMap> = EmitFn<{
14
14
  [K in keyof T]: (event: T[K]) => void
15
15
  }>
16
16
 
17
+ // Vue 3 event listener properties for template usage
18
+ type VueEventListeners<T extends EventMap> = {
19
+ [K in keyof T as `on${Capitalize<string & K>}`]?: (event: T[K]) => any
20
+ }
21
+
17
22
  type DefineCustomElement<
18
23
  ElementType,
19
24
  Events extends EventMap = {},
20
25
  SelectedAttributes extends keyof ElementType = keyof ElementType
21
- > = new () => ElementType & {
26
+ > = new () => ElementType & VueEventListeners<Events> & {
22
27
  // Use $props to define the properties exposed to template type checking. Vue
23
28
  // specifically reads prop definitions from the `$props` type. Note that we
24
29
  // combine the element's props with the global HTML props and Vue's special
25
30
  // props.
26
31
  /** @deprecated Do not use the $props property on a Custom Element ref,
27
32
  this is for template prop types only. */
28
- $props: Partial<Pick<ElementType, SelectedAttributes>> & PublicProps
33
+ $props: Partial<Pick<ElementType, SelectedAttributes>> & PublicProps & VueEventListeners<Events>
29
34
 
30
35
  // Use $emit to specifically define event types. Vue specifically reads event
31
36
  // types from the `$emit` type. Note that `$emit` expects a particular format
@@ -40,7 +45,7 @@ type DefineCustomElement<
40
45
  declare module 'vue' {
41
46
  interface GlobalComponents {
42
47
  <% componentNames.forEach(name => { %>
43
- '<%= _.kebabCase(name) %>': DefineCustomElement<
48
+ '<%= name %>': DefineCustomElement<
44
49
  <%= name %>Props,
45
50
  <%= name %>Events
46
51
  >
@@ -7,5 +7,11 @@
7
7
  "files": ["index.d.ts"],
8
8
  "keywords": [],
9
9
  "author": "",
10
- "license": "ISC"
10
+ "license": "ISC",
11
+ "peerDependencies": {
12
+ "vue": "^3.0.0"
13
+ },
14
+ "devDependencies": {
15
+ "vue": "^3.0.0"
16
+ }
11
17
  }
@@ -284,10 +284,10 @@ describe('Commands', () => {
284
284
  { cwd: target, stdio: 'inherit' }
285
285
  );
286
286
 
287
- // Should install Vue types as dev dependency
287
+ // Should install Vue 3 as dev dependency
288
288
  expect(mockSpawnSync).toHaveBeenCalledWith(
289
289
  expect.stringMatching(/npm(\.cmd)?/),
290
- ['install', '@types/vue', '-D'],
290
+ ['install', 'vue', '-D'],
291
291
  { cwd: target, stdio: 'inherit' }
292
292
  );
293
293
  });
@@ -336,6 +336,12 @@ describe('Generator', () => {
336
336
  'export { Test, TestElement } from "./lib/src/html/test";\n' +
337
337
  'export { Component, ComponentElement } from "./lib/src/html/component";'
338
338
  );
339
+ // Ensure index.ts does not exist so it will be generated
340
+ mockFs.existsSync.mockImplementation((p: any) => {
341
+ const s = p.toString();
342
+ if (s.includes(path.join('/test/target', 'src', 'index.ts'))) return false;
343
+ return true;
344
+ });
339
345
 
340
346
  await reactGen({
341
347
  source: '/test/source',
@@ -357,6 +363,29 @@ describe('Generator', () => {
357
363
  expect(indexCall![1]).toContain('export { Test, TestElement }');
358
364
  expect(indexCall![1]).toContain('export { Component, ComponentElement }');
359
365
  });
366
+
367
+ it('should not overwrite user-managed index.ts', async () => {
368
+ // Existing index.ts that does not contain auto-generated marker
369
+ mockFs.existsSync.mockImplementation((p: any) => {
370
+ const s = p.toString();
371
+ if (s.includes(path.join('/test/target', 'src', 'index.ts'))) return true;
372
+ return true;
373
+ });
374
+ mockFs.readFileSync.mockImplementation((p: any) => {
375
+ const s = p.toString();
376
+ if (s.includes(path.join('/test/target', 'src', 'index.ts'))) return '// custom index file';
377
+ return 'test content';
378
+ });
379
+
380
+ await reactGen({
381
+ source: '/test/source',
382
+ target: '/test/target',
383
+ command: 'test command'
384
+ });
385
+
386
+ const indexWrite = mockFs.writeFileSync.mock.calls.find(call => call[0].toString().includes('index.ts'));
387
+ expect(indexWrite).toBeUndefined();
388
+ });
360
389
  });
361
390
 
362
391
  describe('vueGen', () => {
@@ -478,4 +507,4 @@ describe('Generator', () => {
478
507
  expect(mockAnalyzer.clearCaches).toHaveBeenCalled();
479
508
  });
480
509
  });
481
- });
510
+ });
package/tsconfig.json CHANGED
@@ -20,7 +20,10 @@
20
20
  "esModuleInterop": true,
21
21
  "allowSyntheticDefaultImports": true,
22
22
  "declaration": false,
23
- "outDir": "./dist"
23
+ "outDir": "./dist",
24
+ "typeRoots": [
25
+ "./node_modules/@types"
26
+ ]
24
27
  },
25
28
  "include": [
26
29
  "src/**/*.ts"