@openwebf/webf 0.22.13 → 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'
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,32 +288,90 @@ 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
291
- // Avoid overriding a user-managed index.ts. Only write when:
292
- // - index.ts does not exist, or
293
- // - it contains the auto-generated marker from our template
291
+ // Generate/merge index file
294
292
  const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
295
- const newExports = (0, react_1.generateReactIndex)(blobs);
296
- let shouldWriteIndex = true;
297
- if (fs_1.default.existsSync(indexFilePath)) {
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
298
332
  try {
299
333
  const existing = fs_1.default.readFileSync(indexFilePath, 'utf-8');
300
- const isAutoGenerated = existing.includes('Generated by TSDL');
301
- if (!isAutoGenerated) {
302
- shouldWriteIndex = false;
303
- (0, logger_1.warn)(`Found existing user-managed index.ts at ${indexFilePath}; skipping overwrite.`);
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.`);
304
371
  }
305
372
  }
306
373
  catch (err) {
307
- // If we cannot read the file for some reason, be conservative and skip overwriting
308
- shouldWriteIndex = false;
309
- (0, logger_1.warn)(`Unable to read existing index.ts; skipping overwrite: ${indexFilePath}`);
310
- }
311
- }
312
- if (shouldWriteIndex) {
313
- if (writeFileIfChanged(indexFilePath, newExports)) {
314
- filesChanged++;
315
- (0, logger_1.debug)(`Generated: index.ts`);
374
+ (0, logger_1.warn)(`Failed to merge into existing index.ts. Skipping modifications: ${indexFilePath}`);
316
375
  }
317
376
  }
318
377
  (0, logger_1.timeEnd)('reactGen');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwebf/webf",
3
- "version": "0.22.13",
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,
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,34 +322,88 @@ export async function reactGen({ source, target, exclude, packageName }: Generat
321
322
  }
322
323
  });
323
324
 
324
- // Generate index file
325
- // Avoid overriding a user-managed index.ts. Only write when:
326
- // - index.ts does not exist, or
327
- // - it contains the auto-generated marker from our template
325
+ // Generate/merge index file
328
326
  const indexFilePath = path.join(normalizedTarget, 'src', 'index.ts');
329
- const newExports = generateReactIndex(blobs);
330
327
 
331
- let shouldWriteIndex = true;
332
- if (fs.existsSync(indexFilePath)) {
333
- try {
334
- const existing = fs.readFileSync(indexFilePath, 'utf-8');
335
- const isAutoGenerated = existing.includes('Generated by TSDL');
336
- if (!isAutoGenerated) {
337
- shouldWriteIndex = false;
338
- warn(`Found existing user-managed index.ts at ${indexFilePath}; skipping overwrite.`);
339
- }
340
- } catch (err) {
341
- // If we cannot read the file for some reason, be conservative and skip overwriting
342
- shouldWriteIndex = false;
343
- warn(`Unable to read existing index.ts; skipping overwrite: ${indexFilePath}`);
344
- }
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`);
345
355
  }
346
356
 
347
- if (shouldWriteIndex) {
357
+ if (!fs.existsSync(indexFilePath)) {
358
+ // No index.ts -> generate fresh file from template
359
+ const newExports = generateReactIndex(blobs);
348
360
  if (writeFileIfChanged(indexFilePath, newExports)) {
349
361
  filesChanged++;
350
362
  debug(`Generated: index.ts`);
351
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
+ }
352
407
  }
353
408
 
354
409
  timeEnd('reactGen');
@@ -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
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"