@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/src/commands.ts CHANGED
@@ -3,7 +3,8 @@ import fs from 'fs';
3
3
  import path from 'path';
4
4
  import os from 'os';
5
5
  import { dartGen, reactGen, vueGen } from './generator';
6
- import { glob } from 'glob';
6
+ import { generateModuleArtifacts } from './module';
7
+ import { globSync } from 'glob';
7
8
  import _ from 'lodash';
8
9
  import inquirer from 'inquirer';
9
10
  import yaml from 'yaml';
@@ -164,6 +165,20 @@ const gitignore = fs.readFileSync(
164
165
  'utf-8'
165
166
  );
166
167
 
168
+ const modulePackageJson = fs.readFileSync(
169
+ path.resolve(__dirname, '../templates/module.package.json.tpl'),
170
+ 'utf-8'
171
+ );
172
+
173
+ const moduleTsConfig = fs.readFileSync(
174
+ path.resolve(__dirname, '../templates/module.tsconfig.json.tpl'),
175
+ 'utf-8'
176
+ );
177
+
178
+ const moduleTsUpConfig = fs.readFileSync(
179
+ path.resolve(__dirname, '../templates/module.tsup.config.ts.tpl'),
180
+ 'utf-8'
181
+ );
167
182
  const reactPackageJson = fs.readFileSync(
168
183
  path.resolve(__dirname, '../templates/react.package.json.tpl'),
169
184
  'utf-8'
@@ -241,7 +256,7 @@ async function copyMarkdownDocsToDist(params: {
241
256
  const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
242
257
 
243
258
  // Find all .d.ts files and check for sibling .md files
244
- const dtsFiles = glob.globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
259
+ const dtsFiles = globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
245
260
  let copied = 0;
246
261
  let skipped = 0;
247
262
  const readmeSections: { title: string; relPath: string; content: string }[] = [];
@@ -426,7 +441,6 @@ function createCommand(target: string, options: { framework: string; packageName
426
441
  // Leave merge to the codegen step which appends exports safely
427
442
  }
428
443
 
429
- // !no '--omit=peer' here.
430
444
  spawnSync(NPM, ['install'], {
431
445
  cwd: target,
432
446
  stdio: 'inherit'
@@ -463,6 +477,46 @@ function createCommand(target: string, options: { framework: string; packageName
463
477
  console.log(`WebF ${framework} package created at: ${target}`);
464
478
  }
465
479
 
480
+ function createModuleProject(target: string, options: { packageName: string; metadata?: FlutterPackageMetadata; skipGitignore?: boolean }): void {
481
+ const { metadata, skipGitignore } = options;
482
+ const packageName = isValidNpmPackageName(options.packageName)
483
+ ? options.packageName
484
+ : sanitizePackageName(options.packageName);
485
+
486
+ if (!fs.existsSync(target)) {
487
+ fs.mkdirSync(target, { recursive: true });
488
+ }
489
+
490
+ const packageJsonPath = path.join(target, 'package.json');
491
+ const packageJsonContent = _.template(modulePackageJson)({
492
+ packageName,
493
+ version: metadata?.version || '0.0.1',
494
+ description: metadata?.description || '',
495
+ });
496
+ writeFileIfChanged(packageJsonPath, packageJsonContent);
497
+
498
+ const tsConfigPath = path.join(target, 'tsconfig.json');
499
+ const tsConfigContent = _.template(moduleTsConfig)({});
500
+ writeFileIfChanged(tsConfigPath, tsConfigContent);
501
+
502
+ const tsupConfigPath = path.join(target, 'tsup.config.ts');
503
+ const tsupConfigContent = _.template(moduleTsUpConfig)({});
504
+ writeFileIfChanged(tsupConfigPath, tsupConfigContent);
505
+
506
+ if (!skipGitignore) {
507
+ const gitignorePath = path.join(target, '.gitignore');
508
+ const gitignoreContent = _.template(gitignore)({});
509
+ writeFileIfChanged(gitignorePath, gitignoreContent);
510
+ }
511
+
512
+ const srcDir = path.join(target, 'src');
513
+ if (!fs.existsSync(srcDir)) {
514
+ fs.mkdirSync(srcDir, { recursive: true });
515
+ }
516
+
517
+ console.log(`WebF module package scaffold created at: ${target}`);
518
+ }
519
+
466
520
  async function generateCommand(distPath: string, options: GenerateOptions): Promise<void> {
467
521
  // If distPath is not provided or is '.', create a temporary directory
468
522
  let resolvedDistPath: string;
@@ -846,6 +900,236 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
846
900
  }
847
901
  }
848
902
 
903
+ async function generateModuleCommand(distPath: string, options: GenerateOptions): Promise<void> {
904
+ let resolvedDistPath: string;
905
+ let isTempDir = false;
906
+ if (!distPath || distPath === '.') {
907
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webf-module-'));
908
+ resolvedDistPath = tempDir;
909
+ isTempDir = true;
910
+ console.log(`\nUsing temporary directory for module package: ${tempDir}`);
911
+ } else {
912
+ resolvedDistPath = path.resolve(distPath);
913
+ }
914
+
915
+ // Detect Flutter package root if not provided
916
+ if (!options.flutterPackageSrc) {
917
+ let currentDir = process.cwd();
918
+ let foundPubspec = false;
919
+ let pubspecDir = '';
920
+
921
+ for (let i = 0; i < 3; i++) {
922
+ const pubspecPath = path.join(currentDir, 'pubspec.yaml');
923
+ if (fs.existsSync(pubspecPath)) {
924
+ foundPubspec = true;
925
+ pubspecDir = currentDir;
926
+ break;
927
+ }
928
+ const parentDir = path.dirname(currentDir);
929
+ if (parentDir === currentDir) break;
930
+ currentDir = parentDir;
931
+ }
932
+
933
+ if (!foundPubspec) {
934
+ console.error('Could not find pubspec.yaml. Please provide --flutter-package-src.');
935
+ process.exit(1);
936
+ }
937
+
938
+ options.flutterPackageSrc = pubspecDir;
939
+ console.log(`Detected Flutter package at: ${pubspecDir}`);
940
+ }
941
+
942
+ const flutterPackageSrc = path.resolve(options.flutterPackageSrc);
943
+
944
+ // Validate TS environment in the Flutter package
945
+ console.log(`\nValidating TypeScript environment in ${flutterPackageSrc}...`);
946
+ let validation = validateTypeScriptEnvironment(flutterPackageSrc);
947
+ if (!validation.isValid) {
948
+ const tsConfigPath = path.join(flutterPackageSrc, 'tsconfig.json');
949
+ if (!fs.existsSync(tsConfigPath)) {
950
+ const defaultTsConfig = {
951
+ compilerOptions: {
952
+ target: 'ES2020',
953
+ module: 'commonjs',
954
+ lib: ['ES2020'],
955
+ declaration: true,
956
+ strict: true,
957
+ esModuleInterop: true,
958
+ skipLibCheck: true,
959
+ forceConsistentCasingInFileNames: true,
960
+ resolveJsonModule: true,
961
+ moduleResolution: 'node',
962
+ },
963
+ include: ['lib/**/*.d.ts', '**/*.d.ts'],
964
+ exclude: ['node_modules', 'dist', 'build'],
965
+ };
966
+
967
+ fs.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
968
+ console.log('✅ Created tsconfig.json for module package');
969
+
970
+ validation = validateTypeScriptEnvironment(flutterPackageSrc);
971
+ }
972
+
973
+ if (!validation.isValid) {
974
+ console.error('\n❌ TypeScript environment validation failed:');
975
+ validation.errors.forEach(err => console.error(` - ${err}`));
976
+ console.error('\nPlease fix the above issues before running `webf module-codegen` again.');
977
+ process.exit(1);
978
+ }
979
+ }
980
+
981
+ // Read Flutter metadata for package.json
982
+ const metadata = readFlutterPackageMetadata(flutterPackageSrc);
983
+
984
+ // Determine package name
985
+ let packageName = options.packageName;
986
+ if (packageName && !isValidNpmPackageName(packageName)) {
987
+ console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
988
+ const sanitized = sanitizePackageName(packageName);
989
+ console.log(`Using sanitized name: "${sanitized}"`);
990
+ packageName = sanitized;
991
+ }
992
+
993
+ if (!packageName) {
994
+ const rawDefaultName = metadata?.name
995
+ ? `@openwebf/${metadata.name.replace(/^webf_/, 'webf-')}`
996
+ : '@openwebf/webf-module';
997
+
998
+ const defaultPackageName = isValidNpmPackageName(rawDefaultName)
999
+ ? rawDefaultName
1000
+ : sanitizePackageName(rawDefaultName);
1001
+
1002
+ const packageNameAnswer = await inquirer.prompt([{
1003
+ type: 'input',
1004
+ name: 'packageName',
1005
+ message: 'What is your npm package name for this module?',
1006
+ default: defaultPackageName,
1007
+ validate: (input: string) => {
1008
+ if (!input || input.trim() === '') {
1009
+ return 'Package name is required';
1010
+ }
1011
+
1012
+ if (isValidNpmPackageName(input)) {
1013
+ return true;
1014
+ }
1015
+
1016
+ const sanitized = sanitizePackageName(input);
1017
+ return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
1018
+ }
1019
+ }]);
1020
+ packageName = packageNameAnswer.packageName;
1021
+ }
1022
+
1023
+ // Prevent npm scaffolding (package.json, tsup.config.ts, etc.) from being written into
1024
+ // the Flutter package itself. Force users to choose a separate output directory.
1025
+ if (resolvedDistPath === flutterPackageSrc) {
1026
+ console.error('\n❌ Output directory must not be the Flutter package root.');
1027
+ console.error('Please choose a separate directory for the generated npm package, for example:');
1028
+ console.error(' webf module-codegen ../packages/webf-share --flutter-package-src=../webf_modules/share');
1029
+ process.exit(1);
1030
+ }
1031
+
1032
+ // Scaffold npm project for the module
1033
+ if (!packageName) {
1034
+ throw new Error('Package name could not be resolved for module package.');
1035
+ }
1036
+ createModuleProject(resolvedDistPath, {
1037
+ packageName,
1038
+ metadata: metadata || undefined,
1039
+ });
1040
+
1041
+ // Locate module interface file (*.module.d.ts)
1042
+ const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
1043
+ const ignore = options.exclude && options.exclude.length
1044
+ ? [...defaultIgnore, ...options.exclude]
1045
+ : defaultIgnore;
1046
+
1047
+ const candidates = globSync('**/*.module.d.ts', {
1048
+ cwd: flutterPackageSrc,
1049
+ ignore,
1050
+ });
1051
+
1052
+ if (candidates.length === 0) {
1053
+ console.error(
1054
+ `\n❌ No module interface files (*.module.d.ts) found under ${flutterPackageSrc}.`
1055
+ );
1056
+ console.error('Please add a TypeScript interface file describing your module API.');
1057
+ process.exit(1);
1058
+ }
1059
+
1060
+ const moduleInterfaceRel = candidates[0];
1061
+ const moduleInterfacePath = path.join(flutterPackageSrc, moduleInterfaceRel);
1062
+
1063
+ const command = `webf module-codegen --flutter-package-src=${flutterPackageSrc} <distPath>`;
1064
+
1065
+ console.log(`\nGenerating module npm package and Dart bindings from ${moduleInterfaceRel}...`);
1066
+
1067
+ generateModuleArtifacts({
1068
+ moduleInterfacePath,
1069
+ npmTargetDir: resolvedDistPath,
1070
+ flutterPackageDir: flutterPackageSrc,
1071
+ command,
1072
+ });
1073
+
1074
+ console.log('\nModule code generation completed successfully!');
1075
+
1076
+ try {
1077
+ await buildPackage(resolvedDistPath);
1078
+ } catch (error) {
1079
+ console.error('\nWarning: Build failed:', error);
1080
+ }
1081
+
1082
+ if (options.publishToNpm) {
1083
+ try {
1084
+ await buildAndPublishPackage(resolvedDistPath, options.npmRegistry, false);
1085
+ } catch (error) {
1086
+ console.error('\nError during npm publish:', error);
1087
+ process.exit(1);
1088
+ }
1089
+ } else {
1090
+ const publishAnswer = await inquirer.prompt([{
1091
+ type: 'confirm',
1092
+ name: 'publish',
1093
+ message: 'Would you like to publish this module package to npm?',
1094
+ default: false
1095
+ }]);
1096
+
1097
+ if (publishAnswer.publish) {
1098
+ const registryAnswer = await inquirer.prompt([{
1099
+ type: 'input',
1100
+ name: 'registry',
1101
+ message: 'NPM registry URL (leave empty for default npm registry):',
1102
+ default: '',
1103
+ validate: (input: string) => {
1104
+ if (!input) return true;
1105
+ try {
1106
+ new URL(input);
1107
+ return true;
1108
+ } catch {
1109
+ return 'Please enter a valid URL';
1110
+ }
1111
+ }
1112
+ }]);
1113
+
1114
+ try {
1115
+ await buildAndPublishPackage(
1116
+ resolvedDistPath,
1117
+ registryAnswer.registry || undefined,
1118
+ false
1119
+ );
1120
+ } catch (error) {
1121
+ console.error('\nError during npm publish:', error);
1122
+ // Don't exit here since generation was successful
1123
+ }
1124
+ }
1125
+ }
1126
+
1127
+ if (isTempDir) {
1128
+ console.log(`\n📁 Generated module npm package is in: ${resolvedDistPath}`);
1129
+ console.log('💡 To use it, copy this directory to your packages folder or publish it directly.');
1130
+ }
1131
+ }
1132
+
849
1133
  function writeFileIfChanged(filePath: string, content: string) {
850
1134
  if (fs.existsSync(filePath)) {
851
1135
  const oldContent = fs.readFileSync(filePath, 'utf-8')
@@ -1016,4 +1300,4 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
1016
1300
  }
1017
1301
  }
1018
1302
 
1019
- export { generateCommand };
1303
+ export { generateCommand, generateModuleCommand };
package/src/generator.ts CHANGED
@@ -2,7 +2,7 @@ import path from 'path';
2
2
  import fs from 'fs';
3
3
  import process from 'process';
4
4
  import _ from 'lodash';
5
- import { glob } from 'glob';
5
+ import { globSync } from 'glob';
6
6
  import yaml from 'yaml';
7
7
  import { IDLBlob } from './IDLBlob';
8
8
  import { ClassObject, ConstObject, EnumObject, TypeAliasObject } from './declaration';
@@ -19,7 +19,7 @@ const fileContentCache = new Map<string, string>();
19
19
  // Cache for generated content to detect changes
20
20
  const generatedContentCache = new Map<string, string>();
21
21
 
22
- function writeFileIfChanged(filePath: string, content: string): boolean {
22
+ export function writeFileIfChanged(filePath: string, content: string): boolean {
23
23
  // Check if content has changed by comparing with cache
24
24
  const cachedContent = generatedContentCache.get(filePath);
25
25
  if (cachedContent === content) {
@@ -107,7 +107,7 @@ function getTypeFiles(source: string, excludePatterns?: string[]): string[] {
107
107
  const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
108
108
  const ignore = excludePatterns ? [...defaultIgnore, ...excludePatterns] : defaultIgnore;
109
109
 
110
- const files = glob.globSync("**/*.d.ts", {
110
+ const files = globSync("**/*.d.ts", {
111
111
  cwd: source,
112
112
  ignore: ignore
113
113
  });