@openwebf/webf 0.23.7 → 0.23.10

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,13 @@ 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");
21
23
  const glob_1 = require("glob");
22
24
  const lodash_1 = __importDefault(require("lodash"));
23
25
  const inquirer_1 = __importDefault(require("inquirer"));
@@ -141,6 +143,9 @@ const NPM = platform == 'win32' ? 'npm.cmd' : 'npm';
141
143
  const gloabalDts = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../global.d.ts'), 'utf-8');
142
144
  const tsConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/tsconfig.json.tpl'), 'utf-8');
143
145
  const gitignore = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/gitignore.tpl'), 'utf-8');
146
+ const modulePackageJson = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/module.package.json.tpl'), 'utf-8');
147
+ const moduleTsConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/module.tsconfig.json.tpl'), 'utf-8');
148
+ const moduleTsUpConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/module.tsup.config.ts.tpl'), 'utf-8');
144
149
  const reactPackageJson = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/react.package.json.tpl'), 'utf-8');
145
150
  const reactTsConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/react.tsconfig.json.tpl'), 'utf-8');
146
151
  const reactTsUpConfig = fs_1.default.readFileSync(path_1.default.resolve(__dirname, '../templates/react.tsup.config.ts.tpl'), 'utf-8');
@@ -185,7 +190,7 @@ function copyMarkdownDocsToDist(params) {
185
190
  const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
186
191
  const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
187
192
  // Find all .d.ts files and check for sibling .md files
188
- const dtsFiles = glob_1.glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
193
+ const dtsFiles = (0, glob_1.globSync)('**/*.d.ts', { cwd: sourceRoot, ignore });
189
194
  let copied = 0;
190
195
  let skipped = 0;
191
196
  const readmeSections = [];
@@ -345,7 +350,6 @@ function createCommand(target, options) {
345
350
  // Do not overwrite existing index.ts created by the user
346
351
  // Leave merge to the codegen step which appends exports safely
347
352
  }
348
- // !no '--omit=peer' here.
349
353
  (0, child_process_1.spawnSync)(NPM, ['install'], {
350
354
  cwd: target,
351
355
  stdio: 'inherit'
@@ -376,6 +380,38 @@ function createCommand(target, options) {
376
380
  }
377
381
  console.log(`WebF ${framework} package created at: ${target}`);
378
382
  }
383
+ function createModuleProject(target, options) {
384
+ const { metadata, skipGitignore } = options;
385
+ const packageName = isValidNpmPackageName(options.packageName)
386
+ ? options.packageName
387
+ : sanitizePackageName(options.packageName);
388
+ if (!fs_1.default.existsSync(target)) {
389
+ fs_1.default.mkdirSync(target, { recursive: true });
390
+ }
391
+ const packageJsonPath = path_1.default.join(target, 'package.json');
392
+ const packageJsonContent = lodash_1.default.template(modulePackageJson)({
393
+ packageName,
394
+ version: (metadata === null || metadata === void 0 ? void 0 : metadata.version) || '0.0.1',
395
+ description: (metadata === null || metadata === void 0 ? void 0 : metadata.description) || '',
396
+ });
397
+ writeFileIfChanged(packageJsonPath, packageJsonContent);
398
+ const tsConfigPath = path_1.default.join(target, 'tsconfig.json');
399
+ const tsConfigContent = lodash_1.default.template(moduleTsConfig)({});
400
+ writeFileIfChanged(tsConfigPath, tsConfigContent);
401
+ const tsupConfigPath = path_1.default.join(target, 'tsup.config.ts');
402
+ const tsupConfigContent = lodash_1.default.template(moduleTsUpConfig)({});
403
+ writeFileIfChanged(tsupConfigPath, tsupConfigContent);
404
+ if (!skipGitignore) {
405
+ const gitignorePath = path_1.default.join(target, '.gitignore');
406
+ const gitignoreContent = lodash_1.default.template(gitignore)({});
407
+ writeFileIfChanged(gitignorePath, gitignoreContent);
408
+ }
409
+ const srcDir = path_1.default.join(target, 'src');
410
+ if (!fs_1.default.existsSync(srcDir)) {
411
+ fs_1.default.mkdirSync(srcDir, { recursive: true });
412
+ }
413
+ console.log(`WebF module package scaffold created at: ${target}`);
414
+ }
379
415
  function generateCommand(distPath, options) {
380
416
  return __awaiter(this, void 0, void 0, function* () {
381
417
  var _a, _b, _c, _d;
@@ -736,6 +772,208 @@ function generateCommand(distPath, options) {
736
772
  }
737
773
  });
738
774
  }
775
+ function generateModuleCommand(distPath, options) {
776
+ return __awaiter(this, void 0, void 0, function* () {
777
+ let resolvedDistPath;
778
+ let isTempDir = false;
779
+ if (!distPath || distPath === '.') {
780
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'webf-module-'));
781
+ resolvedDistPath = tempDir;
782
+ isTempDir = true;
783
+ console.log(`\nUsing temporary directory for module package: ${tempDir}`);
784
+ }
785
+ else {
786
+ resolvedDistPath = path_1.default.resolve(distPath);
787
+ }
788
+ // Detect Flutter package root if not provided
789
+ if (!options.flutterPackageSrc) {
790
+ let currentDir = process.cwd();
791
+ let foundPubspec = false;
792
+ let pubspecDir = '';
793
+ for (let i = 0; i < 3; i++) {
794
+ const pubspecPath = path_1.default.join(currentDir, 'pubspec.yaml');
795
+ if (fs_1.default.existsSync(pubspecPath)) {
796
+ foundPubspec = true;
797
+ pubspecDir = currentDir;
798
+ break;
799
+ }
800
+ const parentDir = path_1.default.dirname(currentDir);
801
+ if (parentDir === currentDir)
802
+ break;
803
+ currentDir = parentDir;
804
+ }
805
+ if (!foundPubspec) {
806
+ console.error('Could not find pubspec.yaml. Please provide --flutter-package-src.');
807
+ process.exit(1);
808
+ }
809
+ options.flutterPackageSrc = pubspecDir;
810
+ console.log(`Detected Flutter package at: ${pubspecDir}`);
811
+ }
812
+ const flutterPackageSrc = path_1.default.resolve(options.flutterPackageSrc);
813
+ // Validate TS environment in the Flutter package
814
+ console.log(`\nValidating TypeScript environment in ${flutterPackageSrc}...`);
815
+ let validation = validateTypeScriptEnvironment(flutterPackageSrc);
816
+ if (!validation.isValid) {
817
+ const tsConfigPath = path_1.default.join(flutterPackageSrc, 'tsconfig.json');
818
+ if (!fs_1.default.existsSync(tsConfigPath)) {
819
+ const defaultTsConfig = {
820
+ compilerOptions: {
821
+ target: 'ES2020',
822
+ module: 'commonjs',
823
+ lib: ['ES2020'],
824
+ declaration: true,
825
+ strict: true,
826
+ esModuleInterop: true,
827
+ skipLibCheck: true,
828
+ forceConsistentCasingInFileNames: true,
829
+ resolveJsonModule: true,
830
+ moduleResolution: 'node',
831
+ },
832
+ include: ['lib/**/*.d.ts', '**/*.d.ts'],
833
+ exclude: ['node_modules', 'dist', 'build'],
834
+ };
835
+ fs_1.default.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
836
+ console.log('✅ Created tsconfig.json for module package');
837
+ validation = validateTypeScriptEnvironment(flutterPackageSrc);
838
+ }
839
+ if (!validation.isValid) {
840
+ console.error('\n❌ TypeScript environment validation failed:');
841
+ validation.errors.forEach(err => console.error(` - ${err}`));
842
+ console.error('\nPlease fix the above issues before running `webf module-codegen` again.');
843
+ process.exit(1);
844
+ }
845
+ }
846
+ // Read Flutter metadata for package.json
847
+ const metadata = readFlutterPackageMetadata(flutterPackageSrc);
848
+ // Determine package name
849
+ let packageName = options.packageName;
850
+ if (packageName && !isValidNpmPackageName(packageName)) {
851
+ console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
852
+ const sanitized = sanitizePackageName(packageName);
853
+ console.log(`Using sanitized name: "${sanitized}"`);
854
+ packageName = sanitized;
855
+ }
856
+ if (!packageName) {
857
+ const rawDefaultName = (metadata === null || metadata === void 0 ? void 0 : metadata.name)
858
+ ? `@openwebf/${metadata.name.replace(/^webf_/, 'webf-')}`
859
+ : '@openwebf/webf-module';
860
+ const defaultPackageName = isValidNpmPackageName(rawDefaultName)
861
+ ? rawDefaultName
862
+ : sanitizePackageName(rawDefaultName);
863
+ const packageNameAnswer = yield inquirer_1.default.prompt([{
864
+ type: 'input',
865
+ name: 'packageName',
866
+ message: 'What is your npm package name for this module?',
867
+ default: defaultPackageName,
868
+ validate: (input) => {
869
+ if (!input || input.trim() === '') {
870
+ return 'Package name is required';
871
+ }
872
+ if (isValidNpmPackageName(input)) {
873
+ return true;
874
+ }
875
+ const sanitized = sanitizePackageName(input);
876
+ return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
877
+ }
878
+ }]);
879
+ packageName = packageNameAnswer.packageName;
880
+ }
881
+ // Prevent npm scaffolding (package.json, tsup.config.ts, etc.) from being written into
882
+ // the Flutter package itself. Force users to choose a separate output directory.
883
+ if (resolvedDistPath === flutterPackageSrc) {
884
+ console.error('\n❌ Output directory must not be the Flutter package root.');
885
+ console.error('Please choose a separate directory for the generated npm package, for example:');
886
+ console.error(' webf module-codegen ../packages/webf-share --flutter-package-src=../webf_modules/share');
887
+ process.exit(1);
888
+ }
889
+ // Scaffold npm project for the module
890
+ if (!packageName) {
891
+ throw new Error('Package name could not be resolved for module package.');
892
+ }
893
+ createModuleProject(resolvedDistPath, {
894
+ packageName,
895
+ metadata: metadata || undefined,
896
+ });
897
+ // Locate module interface file (*.module.d.ts)
898
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
899
+ const ignore = options.exclude && options.exclude.length
900
+ ? [...defaultIgnore, ...options.exclude]
901
+ : defaultIgnore;
902
+ const candidates = (0, glob_1.globSync)('**/*.module.d.ts', {
903
+ cwd: flutterPackageSrc,
904
+ ignore,
905
+ });
906
+ if (candidates.length === 0) {
907
+ console.error(`\n❌ No module interface files (*.module.d.ts) found under ${flutterPackageSrc}.`);
908
+ console.error('Please add a TypeScript interface file describing your module API.');
909
+ process.exit(1);
910
+ }
911
+ const moduleInterfaceRel = candidates[0];
912
+ const moduleInterfacePath = path_1.default.join(flutterPackageSrc, moduleInterfaceRel);
913
+ const command = `webf module-codegen --flutter-package-src=${flutterPackageSrc} <distPath>`;
914
+ console.log(`\nGenerating module npm package and Dart bindings from ${moduleInterfaceRel}...`);
915
+ (0, module_1.generateModuleArtifacts)({
916
+ moduleInterfacePath,
917
+ npmTargetDir: resolvedDistPath,
918
+ flutterPackageDir: flutterPackageSrc,
919
+ command,
920
+ });
921
+ console.log('\nModule code generation completed successfully!');
922
+ try {
923
+ yield buildPackage(resolvedDistPath);
924
+ }
925
+ catch (error) {
926
+ console.error('\nWarning: Build failed:', error);
927
+ }
928
+ if (options.publishToNpm) {
929
+ try {
930
+ yield buildAndPublishPackage(resolvedDistPath, options.npmRegistry, false);
931
+ }
932
+ catch (error) {
933
+ console.error('\nError during npm publish:', error);
934
+ process.exit(1);
935
+ }
936
+ }
937
+ else {
938
+ const publishAnswer = yield inquirer_1.default.prompt([{
939
+ type: 'confirm',
940
+ name: 'publish',
941
+ message: 'Would you like to publish this module package to npm?',
942
+ default: false
943
+ }]);
944
+ if (publishAnswer.publish) {
945
+ const registryAnswer = yield inquirer_1.default.prompt([{
946
+ type: 'input',
947
+ name: 'registry',
948
+ message: 'NPM registry URL (leave empty for default npm registry):',
949
+ default: '',
950
+ validate: (input) => {
951
+ if (!input)
952
+ return true;
953
+ try {
954
+ new URL(input);
955
+ return true;
956
+ }
957
+ catch (_a) {
958
+ return 'Please enter a valid URL';
959
+ }
960
+ }
961
+ }]);
962
+ try {
963
+ yield buildAndPublishPackage(resolvedDistPath, registryAnswer.registry || undefined, false);
964
+ }
965
+ catch (error) {
966
+ console.error('\nError during npm publish:', error);
967
+ // Don't exit here since generation was successful
968
+ }
969
+ }
970
+ }
971
+ if (isTempDir) {
972
+ console.log(`\n📁 Generated module npm package is in: ${resolvedDistPath}`);
973
+ console.log('💡 To use it, copy this directory to your packages folder or publish it directly.');
974
+ }
975
+ });
976
+ }
739
977
  function writeFileIfChanged(filePath, content) {
740
978
  if (fs_1.default.existsSync(filePath)) {
741
979
  const oldContent = fs_1.default.readFileSync(filePath, 'utf-8');
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
  });