@openwebf/webf 0.23.7 → 0.24.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/README.md CHANGED
@@ -64,6 +64,34 @@ webf codegen my-typings --flutter-package-src=../webf_ui --publish-to-npm
64
64
  webf codegen my-typings --flutter-package-src=../webf_ui --publish-to-npm --npm-registry=https://custom.registry.com/
65
65
  ```
66
66
 
67
+ ### Generate Module Packages
68
+
69
+ The `webf module-codegen` command generates a typed npm package and Dart bindings for a WebF module based on a TypeScript interface file (`*.module.d.ts`) in your Flutter package.
70
+
71
+ ```bash
72
+ webf module-codegen [output-dir] [options]
73
+ ```
74
+
75
+ #### Options:
76
+ - `--flutter-package-src <path>`: Flutter module package path containing `*.module.d.ts`
77
+ - `--package-name <name>`: NPM package name for the module (defaults to a name derived from `pubspec.yaml`)
78
+ - `--publish-to-npm`: Automatically publish the generated package to npm
79
+ - `--npm-registry <url>`: Custom npm registry URL (defaults to https://registry.npmjs.org/)
80
+ - `--exclude <patterns...>`: Additional glob patterns to exclude from scanning (e.g. build directories)
81
+
82
+ #### Example:
83
+
84
+ ```bash
85
+ # From the CLI repo root
86
+ webf module-codegen ../packages/webf-share --flutter-package-src=../webf_modules/share --package-name=@openwebf/webf-share
87
+ ```
88
+
89
+ This will:
90
+ - Scaffold an npm package at `../packages/webf-share`
91
+ - Read `*.module.d.ts` from `../webf_modules/share`
92
+ - Generate `src/index.ts` and `src/types.ts` that wrap `webf.invokeModuleAsync('Share', ...)`
93
+ - Generate Dart bindings in `../webf_modules/share/lib/src/share_module_bindings_generated.dart`
94
+
67
95
  ### Interactive Mode
68
96
 
69
97
  If you don't provide all required options, the CLI will prompt you interactively:
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 } = require('../dist/commands');
5
+ const { generateCommand, generateModuleCommand } = require('../dist/commands');
6
6
 
7
7
  const program = new Command();
8
8
 
@@ -24,4 +24,15 @@ program
24
24
  .description('Generate dart abstract classes and React/Vue components (auto-creates project if needed)')
25
25
  .action(generateCommand);
26
26
 
27
+ program
28
+ .command('module-codegen')
29
+ .option('--flutter-package-src <src>', 'Flutter module package source path (for module code generation)')
30
+ .option('--package-name <name>', 'NPM package name for the WebF module')
31
+ .option('--publish-to-npm', 'Automatically publish the generated module package to npm')
32
+ .option('--npm-registry <url>', 'Custom npm registry URL (defaults to https://registry.npmjs.org/)')
33
+ .option('--exclude <patterns...>', 'Additional glob patterns to exclude from code generation')
34
+ .argument('[distPath]', 'Path to output generated files', '.')
35
+ .description('Generate NPM package and Dart bindings for a WebF module from TypeScript interfaces (*.module.d.ts)')
36
+ .action(generateModuleCommand);
37
+
27
38
  program.parse();
package/dist/commands.js CHANGED
@@ -13,11 +13,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.generateCommand = generateCommand;
16
+ exports.generateModuleCommand = generateModuleCommand;
16
17
  const child_process_1 = require("child_process");
17
18
  const fs_1 = __importDefault(require("fs"));
18
19
  const path_1 = __importDefault(require("path"));
19
20
  const os_1 = __importDefault(require("os"));
20
21
  const generator_1 = require("./generator");
22
+ const module_1 = require("./module");
23
+ const peerDeps_1 = require("./peerDeps");
21
24
  const glob_1 = require("glob");
22
25
  const lodash_1 = __importDefault(require("lodash"));
23
26
  const inquirer_1 = __importDefault(require("inquirer"));
@@ -141,6 +144,9 @@ const NPM = platform == 'win32' ? 'npm.cmd' : 'npm';
141
144
  const gloabalDts = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../global.d.ts'), 'utf-8');
142
145
  const tsConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/tsconfig.json.tpl'), 'utf-8');
143
146
  const gitignore = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/gitignore.tpl'), 'utf-8');
147
+ const modulePackageJson = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/module.package.json.tpl'), 'utf-8');
148
+ const moduleTsConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/module.tsconfig.json.tpl'), 'utf-8');
149
+ const moduleTsUpConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/module.tsup.config.ts.tpl'), 'utf-8');
144
150
  const reactPackageJson = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/react.package.json.tpl'), 'utf-8');
145
151
  const reactTsConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/react.tsconfig.json.tpl'), 'utf-8');
146
152
  const reactTsUpConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/react.tsup.config.ts.tpl'), 'utf-8');
@@ -172,6 +178,33 @@ function readFlutterPackageMetadata(packagePath) {
172
178
  return null;
173
179
  }
174
180
  }
181
+ function copyReadmeToPackageRoot(params) {
182
+ const { sourceRoot, targetRoot } = params;
183
+ const targetPath = path_1.default.join(targetRoot, 'README.md');
184
+ if (fs_1.default.existsSync(targetPath)) {
185
+ return { copied: false, targetPath };
186
+ }
187
+ const candidateNames = ['README.md', 'Readme.md', 'readme.md'];
188
+ let sourcePath = null;
189
+ for (const candidate of candidateNames) {
190
+ const abs = path_1.default.join(sourceRoot, candidate);
191
+ if (fs_1.default.existsSync(abs)) {
192
+ sourcePath = abs;
193
+ break;
194
+ }
195
+ }
196
+ if (!sourcePath) {
197
+ return { copied: false, targetPath };
198
+ }
199
+ try {
200
+ const content = fs_1.default.readFileSync(sourcePath, 'utf-8');
201
+ writeFileIfChanged(targetPath, content);
202
+ return { copied: true, sourcePath, targetPath };
203
+ }
204
+ catch (_a) {
205
+ return { copied: false, targetPath };
206
+ }
207
+ }
175
208
  // Copy markdown docs that match .d.ts basenames from source to the built dist folder,
176
209
  // and generate an aggregated README.md in the dist directory.
177
210
  function copyMarkdownDocsToDist(params) {
@@ -185,7 +218,7 @@ function copyMarkdownDocsToDist(params) {
185
218
  const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
186
219
  const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
187
220
  // Find all .d.ts files and check for sibling .md files
188
- const dtsFiles = glob_1.glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
221
+ const dtsFiles = (0, glob_1.globSync)('**/*.d.ts', { cwd: sourceRoot, ignore });
189
222
  let copied = 0;
190
223
  let skipped = 0;
191
224
  const readmeSections = [];
@@ -345,8 +378,8 @@ function createCommand(target, options) {
345
378
  // Do not overwrite existing index.ts created by the user
346
379
  // Leave merge to the codegen step which appends exports safely
347
380
  }
348
- // !no '--omit=peer' here.
349
- (0, child_process_1.spawnSync)(NPM, ['install'], {
381
+ // Ensure devDependencies are installed even if the user's shell has NODE_ENV=production.
382
+ (0, child_process_1.spawnSync)(NPM, ['install', '--production=false'], {
350
383
  cwd: target,
351
384
  stdio: 'inherit'
352
385
  });
@@ -365,17 +398,46 @@ function createCommand(target, options) {
365
398
  const gitignorePath = path_1.default.join(target, '.gitignore');
366
399
  const gitignoreContent = lodash_1.default.template(gitignore)({});
367
400
  writeFileIfChanged(gitignorePath, gitignoreContent);
368
- (0, child_process_1.spawnSync)(NPM, ['install', '@openwebf/webf-enterprise-typings'], {
369
- cwd: target,
370
- stdio: 'inherit'
371
- });
372
- (0, child_process_1.spawnSync)(NPM, ['install', 'vue', '-D'], {
401
+ // Ensure devDependencies are installed even if the user's shell has NODE_ENV=production.
402
+ (0, child_process_1.spawnSync)(NPM, ['install', '--production=false'], {
373
403
  cwd: target,
374
404
  stdio: 'inherit'
375
405
  });
376
406
  }
377
407
  console.log(`WebF ${framework} package created at: ${target}`);
378
408
  }
409
+ function createModuleProject(target, options) {
410
+ const { metadata, skipGitignore } = options;
411
+ const packageName = isValidNpmPackageName(options.packageName)
412
+ ? options.packageName
413
+ : sanitizePackageName(options.packageName);
414
+ if (!fs_1.default.existsSync(target)) {
415
+ fs_1.default.mkdirSync(target, { recursive: true });
416
+ }
417
+ const packageJsonPath = path_1.default.join(target, 'package.json');
418
+ const packageJsonContent = lodash_1.default.template(modulePackageJson)({
419
+ packageName,
420
+ version: (metadata === null || metadata === void 0 ? void 0 : metadata.version) || '0.0.1',
421
+ description: (metadata === null || metadata === void 0 ? void 0 : metadata.description) || '',
422
+ });
423
+ writeFileIfChanged(packageJsonPath, packageJsonContent);
424
+ const tsConfigPath = path_1.default.join(target, 'tsconfig.json');
425
+ const tsConfigContent = lodash_1.default.template(moduleTsConfig)({});
426
+ writeFileIfChanged(tsConfigPath, tsConfigContent);
427
+ const tsupConfigPath = path_1.default.join(target, 'tsup.config.ts');
428
+ const tsupConfigContent = lodash_1.default.template(moduleTsUpConfig)({});
429
+ writeFileIfChanged(tsupConfigPath, tsupConfigContent);
430
+ if (!skipGitignore) {
431
+ const gitignorePath = path_1.default.join(target, '.gitignore');
432
+ const gitignoreContent = lodash_1.default.template(gitignore)({});
433
+ writeFileIfChanged(gitignorePath, gitignoreContent);
434
+ }
435
+ const srcDir = path_1.default.join(target, 'src');
436
+ if (!fs_1.default.existsSync(srcDir)) {
437
+ fs_1.default.mkdirSync(srcDir, { recursive: true });
438
+ }
439
+ console.log(`WebF module package scaffold created at: ${target}`);
440
+ }
379
441
  function generateCommand(distPath, options) {
380
442
  return __awaiter(this, void 0, void 0, function* () {
381
443
  var _a, _b, _c, _d;
@@ -622,6 +684,16 @@ function generateCommand(distPath, options) {
622
684
  }
623
685
  // Auto-initialize typings in the output directory if needed
624
686
  ensureInitialized(resolvedDistPath);
687
+ // Copy README.md from the source Flutter package into the npm package root (so `npm publish` includes it).
688
+ if (options.flutterPackageSrc) {
689
+ const { copied } = copyReadmeToPackageRoot({
690
+ sourceRoot: options.flutterPackageSrc,
691
+ targetRoot: resolvedDistPath,
692
+ });
693
+ if (copied) {
694
+ console.log('šŸ“„ Copied README.md to package root');
695
+ }
696
+ }
625
697
  console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
626
698
  yield (0, generator_1.dartGen)({
627
699
  source: options.flutterPackageSrc,
@@ -736,6 +808,208 @@ function generateCommand(distPath, options) {
736
808
  }
737
809
  });
738
810
  }
811
+ function generateModuleCommand(distPath, options) {
812
+ return __awaiter(this, void 0, void 0, function* () {
813
+ let resolvedDistPath;
814
+ let isTempDir = false;
815
+ if (!distPath || distPath === '.') {
816
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'webf-module-'));
817
+ resolvedDistPath = tempDir;
818
+ isTempDir = true;
819
+ console.log(`\nUsing temporary directory for module package: ${tempDir}`);
820
+ }
821
+ else {
822
+ resolvedDistPath = path_1.default.resolve(distPath);
823
+ }
824
+ // Detect Flutter package root if not provided
825
+ if (!options.flutterPackageSrc) {
826
+ let currentDir = process.cwd();
827
+ let foundPubspec = false;
828
+ let pubspecDir = '';
829
+ for (let i = 0; i < 3; i++) {
830
+ const pubspecPath = path_1.default.join(currentDir, 'pubspec.yaml');
831
+ if (fs_1.default.existsSync(pubspecPath)) {
832
+ foundPubspec = true;
833
+ pubspecDir = currentDir;
834
+ break;
835
+ }
836
+ const parentDir = path_1.default.dirname(currentDir);
837
+ if (parentDir === currentDir)
838
+ break;
839
+ currentDir = parentDir;
840
+ }
841
+ if (!foundPubspec) {
842
+ console.error('Could not find pubspec.yaml. Please provide --flutter-package-src.');
843
+ process.exit(1);
844
+ }
845
+ options.flutterPackageSrc = pubspecDir;
846
+ console.log(`Detected Flutter package at: ${pubspecDir}`);
847
+ }
848
+ const flutterPackageSrc = path_1.default.resolve(options.flutterPackageSrc);
849
+ // Validate TS environment in the Flutter package
850
+ console.log(`\nValidating TypeScript environment in ${flutterPackageSrc}...`);
851
+ let validation = validateTypeScriptEnvironment(flutterPackageSrc);
852
+ if (!validation.isValid) {
853
+ const tsConfigPath = path_1.default.join(flutterPackageSrc, 'tsconfig.json');
854
+ if (!fs_1.default.existsSync(tsConfigPath)) {
855
+ const defaultTsConfig = {
856
+ compilerOptions: {
857
+ target: 'ES2020',
858
+ module: 'commonjs',
859
+ lib: ['ES2020'],
860
+ declaration: true,
861
+ strict: true,
862
+ esModuleInterop: true,
863
+ skipLibCheck: true,
864
+ forceConsistentCasingInFileNames: true,
865
+ resolveJsonModule: true,
866
+ moduleResolution: 'node',
867
+ },
868
+ include: ['lib/**/*.d.ts', '**/*.d.ts'],
869
+ exclude: ['node_modules', 'dist', 'build'],
870
+ };
871
+ fs_1.default.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
872
+ console.log('āœ… Created tsconfig.json for module package');
873
+ validation = validateTypeScriptEnvironment(flutterPackageSrc);
874
+ }
875
+ if (!validation.isValid) {
876
+ console.error('\nāŒ TypeScript environment validation failed:');
877
+ validation.errors.forEach(err => console.error(` - ${err}`));
878
+ console.error('\nPlease fix the above issues before running `webf module-codegen` again.');
879
+ process.exit(1);
880
+ }
881
+ }
882
+ // Read Flutter metadata for package.json
883
+ const metadata = readFlutterPackageMetadata(flutterPackageSrc);
884
+ // Determine package name
885
+ let packageName = options.packageName;
886
+ if (packageName && !isValidNpmPackageName(packageName)) {
887
+ console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
888
+ const sanitized = sanitizePackageName(packageName);
889
+ console.log(`Using sanitized name: "${sanitized}"`);
890
+ packageName = sanitized;
891
+ }
892
+ if (!packageName) {
893
+ const rawDefaultName = (metadata === null || metadata === void 0 ? void 0 : metadata.name)
894
+ ? `@openwebf/${metadata.name.replace(/^webf_/, 'webf-')}`
895
+ : '@openwebf/webf-module';
896
+ const defaultPackageName = isValidNpmPackageName(rawDefaultName)
897
+ ? rawDefaultName
898
+ : sanitizePackageName(rawDefaultName);
899
+ const packageNameAnswer = yield inquirer_1.default.prompt([{
900
+ type: 'input',
901
+ name: 'packageName',
902
+ message: 'What is your npm package name for this module?',
903
+ default: defaultPackageName,
904
+ validate: (input) => {
905
+ if (!input || input.trim() === '') {
906
+ return 'Package name is required';
907
+ }
908
+ if (isValidNpmPackageName(input)) {
909
+ return true;
910
+ }
911
+ const sanitized = sanitizePackageName(input);
912
+ return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
913
+ }
914
+ }]);
915
+ packageName = packageNameAnswer.packageName;
916
+ }
917
+ // Prevent npm scaffolding (package.json, tsup.config.ts, etc.) from being written into
918
+ // the Flutter package itself. Force users to choose a separate output directory.
919
+ if (resolvedDistPath === flutterPackageSrc) {
920
+ console.error('\nāŒ Output directory must not be the Flutter package root.');
921
+ console.error('Please choose a separate directory for the generated npm package, for example:');
922
+ console.error(' webf module-codegen ../packages/webf-share --flutter-package-src=../webf_modules/share');
923
+ process.exit(1);
924
+ }
925
+ // Scaffold npm project for the module
926
+ if (!packageName) {
927
+ throw new Error('Package name could not be resolved for module package.');
928
+ }
929
+ createModuleProject(resolvedDistPath, {
930
+ packageName,
931
+ metadata: metadata || undefined,
932
+ });
933
+ // Locate module interface file (*.module.d.ts)
934
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
935
+ const ignore = options.exclude && options.exclude.length
936
+ ? [...defaultIgnore, ...options.exclude]
937
+ : defaultIgnore;
938
+ const candidates = (0, glob_1.globSync)('**/*.module.d.ts', {
939
+ cwd: flutterPackageSrc,
940
+ ignore,
941
+ });
942
+ if (candidates.length === 0) {
943
+ console.error(`\nāŒ No module interface files (*.module.d.ts) found under ${flutterPackageSrc}.`);
944
+ console.error('Please add a TypeScript interface file describing your module API.');
945
+ process.exit(1);
946
+ }
947
+ const moduleInterfaceRel = candidates[0];
948
+ const moduleInterfacePath = path_1.default.join(flutterPackageSrc, moduleInterfaceRel);
949
+ const command = `webf module-codegen --flutter-package-src=${flutterPackageSrc} <distPath>`;
950
+ console.log(`\nGenerating module npm package and Dart bindings from ${moduleInterfaceRel}...`);
951
+ (0, module_1.generateModuleArtifacts)({
952
+ moduleInterfacePath,
953
+ npmTargetDir: resolvedDistPath,
954
+ flutterPackageDir: flutterPackageSrc,
955
+ command,
956
+ });
957
+ console.log('\nModule code generation completed successfully!');
958
+ try {
959
+ yield buildPackage(resolvedDistPath);
960
+ }
961
+ catch (error) {
962
+ console.error('\nWarning: Build failed:', error);
963
+ }
964
+ if (options.publishToNpm) {
965
+ try {
966
+ yield buildAndPublishPackage(resolvedDistPath, options.npmRegistry, false);
967
+ }
968
+ catch (error) {
969
+ console.error('\nError during npm publish:', error);
970
+ process.exit(1);
971
+ }
972
+ }
973
+ else {
974
+ const publishAnswer = yield inquirer_1.default.prompt([{
975
+ type: 'confirm',
976
+ name: 'publish',
977
+ message: 'Would you like to publish this module package to npm?',
978
+ default: false
979
+ }]);
980
+ if (publishAnswer.publish) {
981
+ const registryAnswer = yield inquirer_1.default.prompt([{
982
+ type: 'input',
983
+ name: 'registry',
984
+ message: 'NPM registry URL (leave empty for default npm registry):',
985
+ default: '',
986
+ validate: (input) => {
987
+ if (!input)
988
+ return true;
989
+ try {
990
+ new URL(input);
991
+ return true;
992
+ }
993
+ catch (_a) {
994
+ return 'Please enter a valid URL';
995
+ }
996
+ }
997
+ }]);
998
+ try {
999
+ yield buildAndPublishPackage(resolvedDistPath, registryAnswer.registry || undefined, false);
1000
+ }
1001
+ catch (error) {
1002
+ console.error('\nError during npm publish:', error);
1003
+ // Don't exit here since generation was successful
1004
+ }
1005
+ }
1006
+ }
1007
+ if (isTempDir) {
1008
+ console.log(`\nšŸ“ Generated module npm package is in: ${resolvedDistPath}`);
1009
+ console.log('šŸ’” To use it, copy this directory to your packages folder or publish it directly.');
1010
+ }
1011
+ });
1012
+ }
739
1013
  function writeFileIfChanged(filePath, content) {
740
1014
  if (fs_1.default.existsSync(filePath)) {
741
1015
  const oldContent = fs_1.default.readFileSync(filePath, 'utf-8');
@@ -765,7 +1039,7 @@ function ensureInitialized(targetPath) {
765
1039
  }
766
1040
  function buildPackage(packagePath) {
767
1041
  return __awaiter(this, void 0, void 0, function* () {
768
- var _a;
1042
+ var _a, _b, _c;
769
1043
  const packageJsonPath = path_1.default.join(packagePath, 'package.json');
770
1044
  if (!fs_1.default.existsSync(packageJsonPath)) {
771
1045
  // Skip the error in test environment to avoid console warnings
@@ -777,6 +1051,91 @@ function buildPackage(packagePath) {
777
1051
  const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
778
1052
  const packageName = packageJson.name;
779
1053
  const packageVersion = packageJson.version;
1054
+ function getInstalledPackageJsonPath(pkgName) {
1055
+ const parts = pkgName.split('/');
1056
+ return path_1.default.join(packagePath, 'node_modules', ...parts, 'package.json');
1057
+ }
1058
+ function getInstalledPackageDir(pkgName) {
1059
+ const parts = pkgName.split('/');
1060
+ return path_1.default.join(packagePath, 'node_modules', ...parts);
1061
+ }
1062
+ function findUp(startDir, relativePathToFind) {
1063
+ let dir = path_1.default.resolve(startDir);
1064
+ while (true) {
1065
+ const candidate = path_1.default.join(dir, relativePathToFind);
1066
+ if (fs_1.default.existsSync(candidate))
1067
+ return candidate;
1068
+ const parent = path_1.default.dirname(dir);
1069
+ if (parent === dir)
1070
+ return null;
1071
+ dir = parent;
1072
+ }
1073
+ }
1074
+ function ensurePeerDependencyAvailableForBuild(peerName) {
1075
+ var _a, _b;
1076
+ const installedPkgJson = getInstalledPackageJsonPath(peerName);
1077
+ if (fs_1.default.existsSync(installedPkgJson))
1078
+ return;
1079
+ const peerRange = (_a = packageJson.peerDependencies) === null || _a === void 0 ? void 0 : _a[peerName];
1080
+ const localMap = {
1081
+ '@openwebf/react-core-ui': path_1.default.join('packages', 'react-core-ui'),
1082
+ '@openwebf/vue-core-ui': path_1.default.join('packages', 'vue-core-ui'),
1083
+ };
1084
+ let installSpec = null;
1085
+ const localRel = localMap[peerName];
1086
+ if (localRel) {
1087
+ const localPath = findUp(process.cwd(), localRel);
1088
+ if (localPath) {
1089
+ if (!(0, peerDeps_1.isPackageTypesReady)(localPath)) {
1090
+ const localPkgJsonPath = path_1.default.join(localPath, 'package.json');
1091
+ if (fs_1.default.existsSync(localPkgJsonPath)) {
1092
+ const localPkgJson = (0, peerDeps_1.readJsonFile)(localPkgJsonPath);
1093
+ if ((_b = localPkgJson.scripts) === null || _b === void 0 ? void 0 : _b.build) {
1094
+ if (process.env.WEBF_CODEGEN_BUILD_LOCAL_PEERS !== '1') {
1095
+ console.warn(`\nāš ļø Local ${peerName} found at ${localPath} but type declarations are missing; falling back to registry install.`);
1096
+ }
1097
+ else {
1098
+ console.log(`\nšŸ”§ Local ${peerName} found at ${localPath} but build artifacts are missing; building it for DTS...`);
1099
+ const buildLocalResult = (0, child_process_1.spawnSync)(NPM, ['run', 'build'], {
1100
+ cwd: localPath,
1101
+ stdio: 'inherit'
1102
+ });
1103
+ if (buildLocalResult.status === 0) {
1104
+ if ((0, peerDeps_1.isPackageTypesReady)(localPath)) {
1105
+ installSpec = localPath;
1106
+ }
1107
+ else {
1108
+ console.warn(`\nāš ļø Built local ${peerName} but type declarations are still missing; falling back to registry install.`);
1109
+ }
1110
+ }
1111
+ else {
1112
+ console.warn(`\nāš ļø Failed to build local ${peerName}; falling back to registry install.`);
1113
+ }
1114
+ }
1115
+ }
1116
+ }
1117
+ }
1118
+ else {
1119
+ installSpec = localPath;
1120
+ }
1121
+ }
1122
+ }
1123
+ if (!installSpec) {
1124
+ installSpec = peerRange ? `${peerName}@${peerRange}` : peerName;
1125
+ }
1126
+ console.log(`\nšŸ“¦ Installing peer dependency for build: ${peerName}...`);
1127
+ const installResult = (0, child_process_1.spawnSync)(NPM, ['install', '--no-save', installSpec], {
1128
+ cwd: packagePath,
1129
+ stdio: 'inherit'
1130
+ });
1131
+ if (installResult.status !== 0) {
1132
+ throw new Error(`Failed to install peer dependency for build: ${peerName}`);
1133
+ }
1134
+ const installedTypesFile = (0, peerDeps_1.getPackageTypesFileFromDir)(getInstalledPackageDir(peerName));
1135
+ if (installedTypesFile && !fs_1.default.existsSync(installedTypesFile)) {
1136
+ throw new Error(`Peer dependency ${peerName} was installed but type declarations were not found at ${installedTypesFile}`);
1137
+ }
1138
+ }
780
1139
  // Check if node_modules exists
781
1140
  const nodeModulesPath = path_1.default.join(packagePath, 'node_modules');
782
1141
  if (!fs_1.default.existsSync(nodeModulesPath)) {
@@ -797,6 +1156,13 @@ function buildPackage(packagePath) {
797
1156
  }
798
1157
  // Check if package has a build script
799
1158
  if ((_a = packageJson.scripts) === null || _a === void 0 ? void 0 : _a.build) {
1159
+ // DTS build needs peer deps present locally to resolve types (even though they are not bundled).
1160
+ if ((_b = packageJson.peerDependencies) === null || _b === void 0 ? void 0 : _b['@openwebf/react-core-ui']) {
1161
+ ensurePeerDependencyAvailableForBuild('@openwebf/react-core-ui');
1162
+ }
1163
+ if ((_c = packageJson.peerDependencies) === null || _c === void 0 ? void 0 : _c['@openwebf/vue-core-ui']) {
1164
+ ensurePeerDependencyAvailableForBuild('@openwebf/vue-core-ui');
1165
+ }
800
1166
  console.log(`\nBuilding ${packageName}@${packageVersion}...`);
801
1167
  const buildResult = (0, child_process_1.spawnSync)(NPM, ['run', 'build'], {
802
1168
  cwd: packagePath,
package/dist/generator.js CHANGED
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.writeFileIfChanged = writeFileIfChanged;
15
16
  exports.dartGen = dartGen;
16
17
  exports.reactGen = reactGen;
17
18
  exports.vueGen = vueGen;
@@ -101,7 +102,7 @@ function getTypeFiles(source, excludePatterns) {
101
102
  try {
102
103
  const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
103
104
  const ignore = excludePatterns ? [...defaultIgnore, ...excludePatterns] : defaultIgnore;
104
- const files = glob_1.glob.globSync("**/*.d.ts", {
105
+ const files = (0, glob_1.globSync)("**/*.d.ts", {
105
106
  cwd: source,
106
107
  ignore: ignore
107
108
  });
@@ -377,11 +378,8 @@ function reactGen(_a) {
377
378
  (0, logger_1.warn)(`Failed to merge into existing index.ts. Skipping modifications: ${indexFilePath}`);
378
379
  }
379
380
  }
380
- (0, logger_1.timeEnd)('reactGen');
381
- (0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
382
- (0, logger_1.info)(`Output directory: ${normalizedTarget}`);
383
- (0, logger_1.info)('You can now import these components in your React project.');
384
- // Aggregate standalone type declarations (consts/enums/type aliases) into a single types.ts
381
+ // Always generate src/types.ts so generated components can safely import it.
382
+ // When there are no standalone declarations, emit an empty module (`export {};`).
385
383
  try {
386
384
  const consts = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.ConstObject));
387
385
  const enums = blobs.flatMap(b => b.objects.filter(o => o instanceof declaration_1.EnumObject));
@@ -394,38 +392,38 @@ function reactGen(_a) {
394
392
  typeAliases.forEach(t => { if (!typeAliasMap.has(t.name))
395
393
  typeAliasMap.set(t.name, t); });
396
394
  const hasAny = constMap.size > 0 || enums.length > 0 || typeAliasMap.size > 0;
397
- if (hasAny) {
398
- const constDecl = Array.from(constMap.values())
399
- .map(c => `export declare const ${c.name}: ${c.type};`)
400
- .join('\n');
401
- const enumDecl = enums
402
- .map(e => `export enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
403
- .join('\n');
404
- const typeAliasDecl = Array.from(typeAliasMap.values())
405
- .map(t => `export type ${t.name} = ${t.type};`)
406
- .join('\n');
407
- const typesContent = [
408
- '/* Generated by WebF CLI - aggregated type declarations */',
409
- typeAliasDecl,
410
- constDecl,
411
- enumDecl,
412
- ''
413
- ].filter(Boolean).join('\n');
414
- const typesPath = path_1.default.join(normalizedTarget, 'src', 'types.ts');
415
- if (writeFileIfChanged(typesPath, typesContent)) {
416
- filesChanged++;
417
- (0, logger_1.debug)(`Generated: src/types.ts`);
418
- try {
419
- const constNames = Array.from(constMap.keys());
420
- const aliasNames = Array.from(typeAliasMap.keys());
421
- const enumNames = enums.map(e => e.name);
422
- (0, logger_1.debug)(`[react] Aggregated types - consts: ${constNames.join(', ') || '(none)'}; typeAliases: ${aliasNames.join(', ') || '(none)'}; enums: ${enumNames.join(', ') || '(none)'}\n`);
423
- (0, logger_1.debug)(`[react] src/types.ts preview:\n` + typesContent.split('\n').slice(0, 20).join('\n'));
424
- }
425
- catch (_b) { }
395
+ const constDecl = Array.from(constMap.values())
396
+ .map(c => `export declare const ${c.name}: ${c.type};`)
397
+ .join('\n');
398
+ const enumDecl = enums
399
+ .map(e => `export enum ${e.name} { ${e.members.map(m => m.initializer ? `${m.name} = ${m.initializer}` : `${m.name}`).join(', ')} }`)
400
+ .join('\n');
401
+ const typeAliasDecl = Array.from(typeAliasMap.values())
402
+ .map(t => `export type ${t.name} = ${t.type};`)
403
+ .join('\n');
404
+ const typesContent = [
405
+ '/* Generated by WebF CLI - aggregated type declarations */',
406
+ hasAny ? typeAliasDecl : '',
407
+ hasAny ? constDecl : '',
408
+ hasAny ? enumDecl : '',
409
+ hasAny ? '' : 'export {};',
410
+ ''
411
+ ].filter(Boolean).join('\n');
412
+ const typesPath = path_1.default.join(normalizedTarget, 'src', 'types.ts');
413
+ if (writeFileIfChanged(typesPath, typesContent)) {
414
+ filesChanged++;
415
+ (0, logger_1.debug)(`Generated: src/types.ts`);
416
+ try {
417
+ const constNames = Array.from(constMap.keys());
418
+ const aliasNames = Array.from(typeAliasMap.keys());
419
+ const enumNames = enums.map(e => e.name);
420
+ (0, logger_1.debug)(`[react] Aggregated types - consts: ${constNames.join(', ') || '(none)'}; typeAliases: ${aliasNames.join(', ') || '(none)'}; enums: ${enumNames.join(', ') || '(none)'}\n`);
421
+ (0, logger_1.debug)(`[react] src/types.ts preview:\n` + typesContent.split('\n').slice(0, 20).join('\n'));
426
422
  }
427
- // Ensure index.ts re-exports these types so consumers get them on import.
428
- const indexFilePath = path_1.default.join(normalizedTarget, 'src', 'index.ts');
423
+ catch (_b) { }
424
+ }
425
+ // Only re-export from index.ts when there are actual declarations to surface.
426
+ if (hasAny) {
429
427
  try {
430
428
  let current = '';
431
429
  if (fs_1.default.existsSync(indexFilePath)) {
@@ -446,6 +444,10 @@ function reactGen(_a) {
446
444
  catch (e) {
447
445
  (0, logger_1.warn)('Failed to generate aggregated React types');
448
446
  }
447
+ (0, logger_1.timeEnd)('reactGen');
448
+ (0, logger_1.success)(`React code generation completed. ${filesChanged} files changed.`);
449
+ (0, logger_1.info)(`Output directory: ${normalizedTarget}`);
450
+ (0, logger_1.info)('You can now import these components in your React project.');
449
451
  });
450
452
  }
451
453
  function vueGen(_a) {