@openwebf/webf 0.23.2 → 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 +33 -1
- package/bin/webf.js +13 -1
- package/dist/analyzer.js +65 -1
- package/dist/commands.js +437 -99
- package/dist/dart.js +91 -25
- package/dist/declaration.js +1 -0
- package/dist/generator.js +28 -18
- package/dist/module.js +458 -0
- package/dist/react.js +272 -25
- package/dist/vue.js +89 -11
- package/package.json +2 -2
- package/src/analyzer.ts +58 -2
- package/src/commands.ts +587 -199
- package/src/dart.ts +95 -20
- package/src/declaration.ts +1 -0
- package/src/generator.ts +27 -19
- package/src/module.ts +600 -0
- package/src/react.ts +288 -29
- package/src/vue.ts +100 -13
- package/templates/class.dart.tpl +1 -1
- 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/templates/vue.components.d.ts.tpl +2 -0
- package/test/commands.test.ts +86 -4
- package/test/dart-nullable-props.test.ts +58 -0
- package/test/generator.test.ts +16 -14
- package/test/react-consts.test.ts +1 -1
- package/test/react-vue-nullable-props.test.ts +66 -0
- package/test/react.test.ts +46 -4
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');
|
|
@@ -172,7 +177,8 @@ function readFlutterPackageMetadata(packagePath) {
|
|
|
172
177
|
return null;
|
|
173
178
|
}
|
|
174
179
|
}
|
|
175
|
-
// Copy markdown docs that match .d.ts basenames from source to the built dist folder
|
|
180
|
+
// Copy markdown docs that match .d.ts basenames from source to the built dist folder,
|
|
181
|
+
// and generate an aggregated README.md in the dist directory.
|
|
176
182
|
function copyMarkdownDocsToDist(params) {
|
|
177
183
|
return __awaiter(this, void 0, void 0, function* () {
|
|
178
184
|
const { sourceRoot, distRoot, exclude } = params;
|
|
@@ -184,9 +190,10 @@ function copyMarkdownDocsToDist(params) {
|
|
|
184
190
|
const defaultIgnore = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/example/**'];
|
|
185
191
|
const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
|
|
186
192
|
// Find all .d.ts files and check for sibling .md files
|
|
187
|
-
const dtsFiles = glob_1.
|
|
193
|
+
const dtsFiles = (0, glob_1.globSync)('**/*.d.ts', { cwd: sourceRoot, ignore });
|
|
188
194
|
let copied = 0;
|
|
189
195
|
let skipped = 0;
|
|
196
|
+
const readmeSections = [];
|
|
190
197
|
for (const relDts of dtsFiles) {
|
|
191
198
|
if (path_1.default.basename(relDts) === 'global.d.ts') {
|
|
192
199
|
continue;
|
|
@@ -197,6 +204,13 @@ function copyMarkdownDocsToDist(params) {
|
|
|
197
204
|
skipped++;
|
|
198
205
|
continue;
|
|
199
206
|
}
|
|
207
|
+
let content = '';
|
|
208
|
+
try {
|
|
209
|
+
content = fs_1.default.readFileSync(absMd, 'utf-8');
|
|
210
|
+
}
|
|
211
|
+
catch (_a) {
|
|
212
|
+
// If we cannot read the file, still attempt to copy it and skip README aggregation for this entry.
|
|
213
|
+
}
|
|
200
214
|
// Copy into dist preserving relative path
|
|
201
215
|
const destPath = path_1.default.join(distRoot, relMd);
|
|
202
216
|
const destDir = path_1.default.dirname(destPath);
|
|
@@ -205,6 +219,61 @@ function copyMarkdownDocsToDist(params) {
|
|
|
205
219
|
}
|
|
206
220
|
fs_1.default.copyFileSync(absMd, destPath);
|
|
207
221
|
copied++;
|
|
222
|
+
if (content) {
|
|
223
|
+
const base = path_1.default.basename(relMd, '.md');
|
|
224
|
+
const title = base
|
|
225
|
+
.split(/[-_]+/)
|
|
226
|
+
.filter(Boolean)
|
|
227
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
228
|
+
.join(' ');
|
|
229
|
+
readmeSections.push({
|
|
230
|
+
title: title || base,
|
|
231
|
+
relPath: relMd,
|
|
232
|
+
content
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Generate an aggregated README.md inside distRoot so consumers can see component docs easily.
|
|
237
|
+
if (readmeSections.length > 0) {
|
|
238
|
+
const readmePath = path_1.default.join(distRoot, 'README.md');
|
|
239
|
+
let existing = '';
|
|
240
|
+
if (fs_1.default.existsSync(readmePath)) {
|
|
241
|
+
try {
|
|
242
|
+
existing = fs_1.default.readFileSync(readmePath, 'utf-8');
|
|
243
|
+
}
|
|
244
|
+
catch (_b) {
|
|
245
|
+
existing = '';
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const headerLines = [
|
|
249
|
+
'# WebF Component Documentation',
|
|
250
|
+
'',
|
|
251
|
+
'> This README is generated from markdown docs co-located with TypeScript definitions in the Flutter package.',
|
|
252
|
+
''
|
|
253
|
+
];
|
|
254
|
+
const sectionBlocks = readmeSections.map(section => {
|
|
255
|
+
const lines = [];
|
|
256
|
+
lines.push(`## ${section.title}`);
|
|
257
|
+
lines.push('');
|
|
258
|
+
lines.push(`_Source: \`./${section.relPath}\`_`);
|
|
259
|
+
lines.push('');
|
|
260
|
+
lines.push(section.content.trim());
|
|
261
|
+
lines.push('');
|
|
262
|
+
return lines.join('\n');
|
|
263
|
+
}).join('\n');
|
|
264
|
+
let finalContent;
|
|
265
|
+
if (existing && existing.trim().length > 0) {
|
|
266
|
+
finalContent = `${existing.trim()}\n\n---\n\n${headerLines.join('\n')}${sectionBlocks}\n`;
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
finalContent = `${headerLines.join('\n')}${sectionBlocks}\n`;
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
fs_1.default.writeFileSync(readmePath, finalContent, 'utf-8');
|
|
273
|
+
}
|
|
274
|
+
catch (_c) {
|
|
275
|
+
// If README generation fails, do not affect overall codegen.
|
|
276
|
+
}
|
|
208
277
|
}
|
|
209
278
|
return { copied, skipped };
|
|
210
279
|
});
|
|
@@ -311,18 +380,57 @@ function createCommand(target, options) {
|
|
|
311
380
|
}
|
|
312
381
|
console.log(`WebF ${framework} package created at: ${target}`);
|
|
313
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
|
+
}
|
|
314
415
|
function generateCommand(distPath, options) {
|
|
315
416
|
return __awaiter(this, void 0, void 0, function* () {
|
|
316
417
|
var _a, _b, _c, _d;
|
|
317
418
|
// If distPath is not provided or is '.', create a temporary directory
|
|
318
419
|
let resolvedDistPath;
|
|
319
420
|
let isTempDir = false;
|
|
421
|
+
const isDartOnly = options.dartOnly;
|
|
320
422
|
if (!distPath || distPath === '.') {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
423
|
+
if (isDartOnly) {
|
|
424
|
+
// In Dart-only mode we don't need a temporary Node project directory
|
|
425
|
+
resolvedDistPath = path_1.default.resolve(distPath || '.');
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
// Create a temporary directory for the generated package
|
|
429
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'webf-typings-'));
|
|
430
|
+
resolvedDistPath = tempDir;
|
|
431
|
+
isTempDir = true;
|
|
432
|
+
console.log(`\nUsing temporary directory: ${tempDir}`);
|
|
433
|
+
}
|
|
326
434
|
}
|
|
327
435
|
else {
|
|
328
436
|
resolvedDistPath = path_1.default.resolve(distPath);
|
|
@@ -352,111 +460,123 @@ function generateCommand(distPath, options) {
|
|
|
352
460
|
console.log(`\nDetected Flutter package at: ${pubspecDir}`);
|
|
353
461
|
}
|
|
354
462
|
}
|
|
355
|
-
// Check if the directory exists and has required files
|
|
356
|
-
const packageJsonPath = path_1.default.join(resolvedDistPath, 'package.json');
|
|
357
|
-
const globalDtsPath = path_1.default.join(resolvedDistPath, 'global.d.ts');
|
|
358
|
-
const tsConfigPath = path_1.default.join(resolvedDistPath, 'tsconfig.json');
|
|
359
|
-
const hasPackageJson = fs_1.default.existsSync(packageJsonPath);
|
|
360
|
-
const hasGlobalDts = fs_1.default.existsSync(globalDtsPath);
|
|
361
|
-
const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
|
|
362
|
-
// Determine if we need to create a new project
|
|
363
|
-
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
364
|
-
// Track if this is an existing project (has all required files)
|
|
365
|
-
const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
366
463
|
let framework = options.framework;
|
|
367
464
|
let packageName = options.packageName;
|
|
368
|
-
|
|
369
|
-
if (
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
if
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
465
|
+
let isExistingProject = false;
|
|
466
|
+
if (!isDartOnly) {
|
|
467
|
+
// Check if the directory exists and has required files
|
|
468
|
+
const packageJsonPath = path_1.default.join(resolvedDistPath, 'package.json');
|
|
469
|
+
const globalDtsPath = path_1.default.join(resolvedDistPath, 'global.d.ts');
|
|
470
|
+
const tsConfigPath = path_1.default.join(resolvedDistPath, 'tsconfig.json');
|
|
471
|
+
const hasPackageJson = fs_1.default.existsSync(packageJsonPath);
|
|
472
|
+
const hasGlobalDts = fs_1.default.existsSync(globalDtsPath);
|
|
473
|
+
const hasTsConfig = fs_1.default.existsSync(tsConfigPath);
|
|
474
|
+
// Determine if we need to create a new project
|
|
475
|
+
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
476
|
+
// Track if this is an existing project (has all required files)
|
|
477
|
+
isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
478
|
+
// Validate and sanitize package name if provided
|
|
479
|
+
if (packageName && !isValidNpmPackageName(packageName)) {
|
|
480
|
+
console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
|
|
481
|
+
const sanitized = sanitizePackageName(packageName);
|
|
482
|
+
console.log(`Using sanitized name: "${sanitized}"`);
|
|
483
|
+
packageName = sanitized;
|
|
484
|
+
}
|
|
485
|
+
if (needsProjectCreation) {
|
|
486
|
+
// If project needs creation but options are missing, prompt for them
|
|
487
|
+
if (!framework) {
|
|
488
|
+
const frameworkAnswer = yield inquirer_1.default.prompt([{
|
|
489
|
+
type: 'list',
|
|
490
|
+
name: 'framework',
|
|
491
|
+
message: 'Which framework would you like to use?',
|
|
492
|
+
choices: ['react', 'vue']
|
|
493
|
+
}]);
|
|
494
|
+
framework = frameworkAnswer.framework;
|
|
495
|
+
}
|
|
496
|
+
// Try to read Flutter package metadata if flutterPackageSrc is provided
|
|
497
|
+
let metadata = null;
|
|
498
|
+
if (options.flutterPackageSrc) {
|
|
499
|
+
metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
|
|
500
|
+
}
|
|
501
|
+
if (!packageName) {
|
|
502
|
+
// Use Flutter package name as default if available, sanitized for npm
|
|
503
|
+
const rawDefaultName = (metadata === null || metadata === void 0 ? void 0 : metadata.name) || path_1.default.basename(resolvedDistPath);
|
|
504
|
+
const defaultPackageName = sanitizePackageName(rawDefaultName);
|
|
505
|
+
const packageNameAnswer = yield inquirer_1.default.prompt([{
|
|
506
|
+
type: 'input',
|
|
507
|
+
name: 'packageName',
|
|
508
|
+
message: 'What is your package name?',
|
|
509
|
+
default: defaultPackageName,
|
|
510
|
+
validate: (input) => {
|
|
511
|
+
if (!input || input.trim() === '') {
|
|
512
|
+
return 'Package name is required';
|
|
513
|
+
}
|
|
514
|
+
// Check if it's valid as-is
|
|
515
|
+
if (isValidNpmPackageName(input)) {
|
|
516
|
+
return true;
|
|
517
|
+
}
|
|
518
|
+
// If not valid, show what it would be sanitized to
|
|
519
|
+
const sanitized = sanitizePackageName(input);
|
|
520
|
+
return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
|
|
407
521
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
522
|
+
}]);
|
|
523
|
+
packageName = packageNameAnswer.packageName;
|
|
524
|
+
}
|
|
525
|
+
console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
|
|
526
|
+
createCommand(resolvedDistPath, {
|
|
527
|
+
framework: framework,
|
|
528
|
+
packageName: packageName,
|
|
529
|
+
metadata: metadata || undefined
|
|
530
|
+
});
|
|
414
531
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
type: 'list',
|
|
439
|
-
name: 'framework',
|
|
440
|
-
message: 'Which framework are you using?',
|
|
441
|
-
choices: ['react', 'vue']
|
|
442
|
-
}]);
|
|
443
|
-
framework = frameworkAnswer.framework;
|
|
532
|
+
else {
|
|
533
|
+
// Validate existing project structure
|
|
534
|
+
if (hasPackageJson) {
|
|
535
|
+
try {
|
|
536
|
+
const packageJson = JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'));
|
|
537
|
+
// Detect framework from existing package.json
|
|
538
|
+
if (!framework) {
|
|
539
|
+
if (((_a = packageJson.dependencies) === null || _a === void 0 ? void 0 : _a.react) || ((_b = packageJson.devDependencies) === null || _b === void 0 ? void 0 : _b.react)) {
|
|
540
|
+
framework = 'react';
|
|
541
|
+
}
|
|
542
|
+
else if (((_c = packageJson.dependencies) === null || _c === void 0 ? void 0 : _c.vue) || ((_d = packageJson.devDependencies) === null || _d === void 0 ? void 0 : _d.vue)) {
|
|
543
|
+
framework = 'vue';
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
// If can't detect, prompt for it
|
|
547
|
+
const frameworkAnswer = yield inquirer_1.default.prompt([{
|
|
548
|
+
type: 'list',
|
|
549
|
+
name: 'framework',
|
|
550
|
+
message: 'Which framework are you using?',
|
|
551
|
+
choices: ['react', 'vue']
|
|
552
|
+
}]);
|
|
553
|
+
framework = frameworkAnswer.framework;
|
|
554
|
+
}
|
|
444
555
|
}
|
|
556
|
+
console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
|
|
557
|
+
}
|
|
558
|
+
catch (e) {
|
|
559
|
+
console.error('Error reading package.json:', e);
|
|
560
|
+
process.exit(1);
|
|
445
561
|
}
|
|
446
|
-
console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
|
|
447
|
-
}
|
|
448
|
-
catch (e) {
|
|
449
|
-
console.error('Error reading package.json:', e);
|
|
450
|
-
process.exit(1);
|
|
451
562
|
}
|
|
452
563
|
}
|
|
453
564
|
}
|
|
565
|
+
else {
|
|
566
|
+
// In Dart-only mode, framework/packageName are unused; ensure framework is not accidentally required later.
|
|
567
|
+
framework = options.framework;
|
|
568
|
+
}
|
|
454
569
|
// Now proceed with code generation if flutter package source is provided
|
|
455
570
|
if (!options.flutterPackageSrc) {
|
|
456
571
|
console.log('\nProject is ready for code generation.');
|
|
457
572
|
console.log('To generate code, run:');
|
|
458
573
|
const displayPath = isTempDir ? '<output-dir>' : distPath;
|
|
459
|
-
|
|
574
|
+
if (isDartOnly) {
|
|
575
|
+
console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --dart-only`);
|
|
576
|
+
}
|
|
577
|
+
else {
|
|
578
|
+
console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
|
|
579
|
+
}
|
|
460
580
|
if (isTempDir) {
|
|
461
581
|
// Clean up temporary directory if we're not using it
|
|
462
582
|
fs_1.default.rmSync(resolvedDistPath, { recursive: true, force: true });
|
|
@@ -519,7 +639,23 @@ function generateCommand(distPath, options) {
|
|
|
519
639
|
process.exit(1);
|
|
520
640
|
}
|
|
521
641
|
}
|
|
522
|
-
const
|
|
642
|
+
const baseCommand = 'webf codegen';
|
|
643
|
+
const flutterPart = options.flutterPackageSrc ? ` --flutter-package-src=${options.flutterPackageSrc}` : '';
|
|
644
|
+
const modePart = isDartOnly
|
|
645
|
+
? ' --dart-only'
|
|
646
|
+
: (framework ? ` --framework=${framework}` : '');
|
|
647
|
+
const command = `${baseCommand}${flutterPart}${modePart} <distPath>`;
|
|
648
|
+
if (isDartOnly) {
|
|
649
|
+
console.log(`\nGenerating Dart bindings from ${options.flutterPackageSrc}...`);
|
|
650
|
+
yield (0, generator_1.dartGen)({
|
|
651
|
+
source: options.flutterPackageSrc,
|
|
652
|
+
target: options.flutterPackageSrc,
|
|
653
|
+
command,
|
|
654
|
+
exclude: options.exclude,
|
|
655
|
+
});
|
|
656
|
+
console.log('\nDart code generation completed successfully!');
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
523
659
|
// Auto-initialize typings in the output directory if needed
|
|
524
660
|
ensureInitialized(resolvedDistPath);
|
|
525
661
|
console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
|
|
@@ -636,6 +772,208 @@ function generateCommand(distPath, options) {
|
|
|
636
772
|
}
|
|
637
773
|
});
|
|
638
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
|
+
}
|
|
639
977
|
function writeFileIfChanged(filePath, content) {
|
|
640
978
|
if (fs_1.default.existsSync(filePath)) {
|
|
641
979
|
const oldContent = fs_1.default.readFileSync(filePath, 'utf-8');
|