@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 +28 -0
- package/bin/webf.js +12 -1
- package/dist/commands.js +240 -2
- package/dist/generator.js +2 -1
- package/dist/module.js +458 -0
- package/dist/vue.js +17 -2
- package/package.json +2 -2
- package/src/commands.ts +288 -4
- package/src/generator.ts +3 -3
- package/src/module.ts +600 -0
- package/src/vue.ts +17 -2
- package/templates/module.package.json.tpl +36 -0
- package/templates/module.tsconfig.json.tpl +25 -0
- package/templates/module.tsup.config.ts.tpl +13 -0
- package/test/commands.test.ts +10 -8
- package/test/generator.test.ts +16 -14
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 {
|
|
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 =
|
|
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 {
|
|
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 =
|
|
110
|
+
const files = globSync("**/*.d.ts", {
|
|
111
111
|
cwd: source,
|
|
112
112
|
ignore: ignore
|
|
113
113
|
});
|