@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/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';
|
|
@@ -15,6 +16,7 @@ interface GenerateOptions {
|
|
|
15
16
|
publishToNpm?: boolean;
|
|
16
17
|
npmRegistry?: string;
|
|
17
18
|
exclude?: string[];
|
|
19
|
+
dartOnly?: boolean;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
interface FlutterPackageMetadata {
|
|
@@ -39,12 +41,12 @@ interface FlutterPackageMetadata {
|
|
|
39
41
|
function sanitizePackageName(name: string): string {
|
|
40
42
|
// Remove any leading/trailing whitespace
|
|
41
43
|
let sanitized = name.trim();
|
|
42
|
-
|
|
44
|
+
|
|
43
45
|
// Check if it's a scoped package
|
|
44
46
|
const isScoped = sanitized.startsWith('@');
|
|
45
47
|
let scope = '';
|
|
46
48
|
let packageName = sanitized;
|
|
47
|
-
|
|
49
|
+
|
|
48
50
|
if (isScoped) {
|
|
49
51
|
const parts = sanitized.split('/');
|
|
50
52
|
if (parts.length >= 2) {
|
|
@@ -55,7 +57,7 @@ function sanitizePackageName(name: string): string {
|
|
|
55
57
|
packageName = sanitized.substring(1);
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
// Sanitize scope if present
|
|
60
62
|
if (scope) {
|
|
61
63
|
scope = scope.toLowerCase();
|
|
@@ -65,7 +67,7 @@ function sanitizePackageName(name: string): string {
|
|
|
65
67
|
scope = '@pkg'; // Default scope if only @ remains
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
// Sanitize package name part
|
|
70
72
|
packageName = packageName.toLowerCase();
|
|
71
73
|
packageName = packageName.replace(/\s+/g, '-');
|
|
@@ -74,20 +76,20 @@ function sanitizePackageName(name: string): string {
|
|
|
74
76
|
packageName = packageName.replace(/[._]+$/, '');
|
|
75
77
|
packageName = packageName.replace(/[-_.]{2,}/g, '-');
|
|
76
78
|
packageName = packageName.replace(/^-+/, '').replace(/-+$/, '');
|
|
77
|
-
|
|
79
|
+
|
|
78
80
|
// Ensure package name is not empty
|
|
79
81
|
if (!packageName) {
|
|
80
82
|
packageName = 'package';
|
|
81
83
|
}
|
|
82
|
-
|
|
84
|
+
|
|
83
85
|
// Ensure it starts with a letter or number
|
|
84
86
|
if (!/^[a-z0-9]/.test(packageName)) {
|
|
85
87
|
packageName = 'pkg-' + packageName;
|
|
86
88
|
}
|
|
87
|
-
|
|
89
|
+
|
|
88
90
|
// Combine scope and package name
|
|
89
91
|
let result = scope ? `${scope}/${packageName}` : packageName;
|
|
90
|
-
|
|
92
|
+
|
|
91
93
|
// Truncate to 214 characters (npm limit)
|
|
92
94
|
if (result.length > 214) {
|
|
93
95
|
if (scope) {
|
|
@@ -101,7 +103,7 @@ function sanitizePackageName(name: string): string {
|
|
|
101
103
|
result = result.replace(/[._-]+$/, '');
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
|
-
|
|
106
|
+
|
|
105
107
|
return result;
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -112,36 +114,36 @@ function isValidNpmPackageName(name: string): boolean {
|
|
|
112
114
|
// Check basic rules
|
|
113
115
|
if (!name || name.length === 0 || name.length > 214) return false;
|
|
114
116
|
if (name.trim() !== name) return false;
|
|
115
|
-
|
|
117
|
+
|
|
116
118
|
// Check if it's a scoped package
|
|
117
119
|
if (name.startsWith('@')) {
|
|
118
120
|
const parts = name.split('/');
|
|
119
121
|
if (parts.length !== 2) return false; // Scoped packages must have exactly one /
|
|
120
|
-
|
|
122
|
+
|
|
121
123
|
const scope = parts[0];
|
|
122
124
|
const packageName = parts[1];
|
|
123
|
-
|
|
125
|
+
|
|
124
126
|
// Validate scope
|
|
125
127
|
if (!/^@[a-z0-9][a-z0-9-]*$/.test(scope)) return false;
|
|
126
|
-
|
|
128
|
+
|
|
127
129
|
// Validate package name part
|
|
128
130
|
return isValidNpmPackageName(packageName);
|
|
129
131
|
}
|
|
130
|
-
|
|
132
|
+
|
|
131
133
|
// For non-scoped packages
|
|
132
134
|
if (name !== name.toLowerCase()) return false;
|
|
133
135
|
if (name.startsWith('.') || name.startsWith('_')) return false;
|
|
134
|
-
|
|
136
|
+
|
|
135
137
|
// Check for valid characters (letters, numbers, hyphens, underscores, dots)
|
|
136
138
|
if (!/^[a-z0-9][a-z0-9\-_.]*$/.test(name)) return false;
|
|
137
|
-
|
|
139
|
+
|
|
138
140
|
// Check for URL-safe characters
|
|
139
141
|
try {
|
|
140
142
|
if (encodeURIComponent(name) !== name) return false;
|
|
141
143
|
} catch {
|
|
142
144
|
return false;
|
|
143
145
|
}
|
|
144
|
-
|
|
146
|
+
|
|
145
147
|
return true;
|
|
146
148
|
}
|
|
147
149
|
|
|
@@ -163,6 +165,20 @@ const gitignore = fs.readFileSync(
|
|
|
163
165
|
'utf-8'
|
|
164
166
|
);
|
|
165
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
|
+
);
|
|
166
182
|
const reactPackageJson = fs.readFileSync(
|
|
167
183
|
path.resolve(__dirname, '../templates/react.package.json.tpl'),
|
|
168
184
|
'utf-8'
|
|
@@ -200,15 +216,15 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
|
|
|
200
216
|
console.warn(`Warning: pubspec.yaml not found at ${pubspecPath}. Using default metadata.`);
|
|
201
217
|
return null;
|
|
202
218
|
}
|
|
203
|
-
|
|
219
|
+
|
|
204
220
|
const pubspecContent = fs.readFileSync(pubspecPath, 'utf-8');
|
|
205
221
|
const pubspec = yaml.parse(pubspecContent);
|
|
206
|
-
|
|
222
|
+
|
|
207
223
|
// Validate required fields
|
|
208
224
|
if (!pubspec.name) {
|
|
209
225
|
console.warn(`Warning: Flutter package name not found in ${pubspecPath}. Using default name.`);
|
|
210
226
|
}
|
|
211
|
-
|
|
227
|
+
|
|
212
228
|
return {
|
|
213
229
|
name: pubspec.name || '',
|
|
214
230
|
version: pubspec.version || '0.0.1',
|
|
@@ -221,7 +237,8 @@ function readFlutterPackageMetadata(packagePath: string): FlutterPackageMetadata
|
|
|
221
237
|
}
|
|
222
238
|
}
|
|
223
239
|
|
|
224
|
-
// Copy markdown docs that match .d.ts basenames from source to the built dist folder
|
|
240
|
+
// Copy markdown docs that match .d.ts basenames from source to the built dist folder,
|
|
241
|
+
// and generate an aggregated README.md in the dist directory.
|
|
225
242
|
async function copyMarkdownDocsToDist(params: {
|
|
226
243
|
sourceRoot: string;
|
|
227
244
|
distRoot: string;
|
|
@@ -239,9 +256,10 @@ async function copyMarkdownDocsToDist(params: {
|
|
|
239
256
|
const ignore = exclude && exclude.length ? [...defaultIgnore, ...exclude] : defaultIgnore;
|
|
240
257
|
|
|
241
258
|
// Find all .d.ts files and check for sibling .md files
|
|
242
|
-
const dtsFiles =
|
|
259
|
+
const dtsFiles = globSync('**/*.d.ts', { cwd: sourceRoot, ignore });
|
|
243
260
|
let copied = 0;
|
|
244
261
|
let skipped = 0;
|
|
262
|
+
const readmeSections: { title: string; relPath: string; content: string }[] = [];
|
|
245
263
|
|
|
246
264
|
for (const relDts of dtsFiles) {
|
|
247
265
|
if (path.basename(relDts) === 'global.d.ts') {
|
|
@@ -255,6 +273,13 @@ async function copyMarkdownDocsToDist(params: {
|
|
|
255
273
|
continue;
|
|
256
274
|
}
|
|
257
275
|
|
|
276
|
+
let content = '';
|
|
277
|
+
try {
|
|
278
|
+
content = fs.readFileSync(absMd, 'utf-8');
|
|
279
|
+
} catch {
|
|
280
|
+
// If we cannot read the file, still attempt to copy it and skip README aggregation for this entry.
|
|
281
|
+
}
|
|
282
|
+
|
|
258
283
|
// Copy into dist preserving relative path
|
|
259
284
|
const destPath = path.join(distRoot, relMd);
|
|
260
285
|
const destDir = path.dirname(destPath);
|
|
@@ -263,6 +288,64 @@ async function copyMarkdownDocsToDist(params: {
|
|
|
263
288
|
}
|
|
264
289
|
fs.copyFileSync(absMd, destPath);
|
|
265
290
|
copied++;
|
|
291
|
+
|
|
292
|
+
if (content) {
|
|
293
|
+
const base = path.basename(relMd, '.md');
|
|
294
|
+
const title = base
|
|
295
|
+
.split(/[-_]+/)
|
|
296
|
+
.filter(Boolean)
|
|
297
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
298
|
+
.join(' ');
|
|
299
|
+
readmeSections.push({
|
|
300
|
+
title: title || base,
|
|
301
|
+
relPath: relMd,
|
|
302
|
+
content
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Generate an aggregated README.md inside distRoot so consumers can see component docs easily.
|
|
308
|
+
if (readmeSections.length > 0) {
|
|
309
|
+
const readmePath = path.join(distRoot, 'README.md');
|
|
310
|
+
let existing = '';
|
|
311
|
+
if (fs.existsSync(readmePath)) {
|
|
312
|
+
try {
|
|
313
|
+
existing = fs.readFileSync(readmePath, 'utf-8');
|
|
314
|
+
} catch {
|
|
315
|
+
existing = '';
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const headerLines: string[] = [
|
|
320
|
+
'# WebF Component Documentation',
|
|
321
|
+
'',
|
|
322
|
+
'> This README is generated from markdown docs co-located with TypeScript definitions in the Flutter package.',
|
|
323
|
+
''
|
|
324
|
+
];
|
|
325
|
+
|
|
326
|
+
const sectionBlocks = readmeSections.map(section => {
|
|
327
|
+
const lines: string[] = [];
|
|
328
|
+
lines.push(`## ${section.title}`);
|
|
329
|
+
lines.push('');
|
|
330
|
+
lines.push(`_Source: \`./${section.relPath}\`_`);
|
|
331
|
+
lines.push('');
|
|
332
|
+
lines.push(section.content.trim());
|
|
333
|
+
lines.push('');
|
|
334
|
+
return lines.join('\n');
|
|
335
|
+
}).join('\n');
|
|
336
|
+
|
|
337
|
+
let finalContent: string;
|
|
338
|
+
if (existing && existing.trim().length > 0) {
|
|
339
|
+
finalContent = `${existing.trim()}\n\n---\n\n${headerLines.join('\n')}${sectionBlocks}\n`;
|
|
340
|
+
} else {
|
|
341
|
+
finalContent = `${headerLines.join('\n')}${sectionBlocks}\n`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
fs.writeFileSync(readmePath, finalContent, 'utf-8');
|
|
346
|
+
} catch {
|
|
347
|
+
// If README generation fails, do not affect overall codegen.
|
|
348
|
+
}
|
|
266
349
|
}
|
|
267
350
|
|
|
268
351
|
return { copied, skipped };
|
|
@@ -270,40 +353,40 @@ async function copyMarkdownDocsToDist(params: {
|
|
|
270
353
|
|
|
271
354
|
function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean; errors: string[] } {
|
|
272
355
|
const errors: string[] = [];
|
|
273
|
-
|
|
356
|
+
|
|
274
357
|
// Check for TypeScript configuration
|
|
275
358
|
const tsConfigPath = path.join(projectPath, 'tsconfig.json');
|
|
276
359
|
if (!fs.existsSync(tsConfigPath)) {
|
|
277
360
|
errors.push('Missing tsconfig.json - TypeScript configuration is required for type definitions');
|
|
278
361
|
}
|
|
279
|
-
|
|
362
|
+
|
|
280
363
|
// Check for .d.ts files - this is critical
|
|
281
364
|
const libPath = path.join(projectPath, 'lib');
|
|
282
365
|
let hasDtsFiles = false;
|
|
283
|
-
|
|
366
|
+
|
|
284
367
|
if (fs.existsSync(libPath)) {
|
|
285
368
|
// Check in lib directory
|
|
286
|
-
hasDtsFiles = fs.readdirSync(libPath).some(file =>
|
|
287
|
-
file.endsWith('.d.ts') ||
|
|
288
|
-
(fs.statSync(path.join(libPath, file)).isDirectory() &&
|
|
369
|
+
hasDtsFiles = fs.readdirSync(libPath).some(file =>
|
|
370
|
+
file.endsWith('.d.ts') ||
|
|
371
|
+
(fs.statSync(path.join(libPath, file)).isDirectory() &&
|
|
289
372
|
fs.readdirSync(path.join(libPath, file)).some(f => f.endsWith('.d.ts')))
|
|
290
373
|
);
|
|
291
374
|
}
|
|
292
|
-
|
|
375
|
+
|
|
293
376
|
// Also check in root directory
|
|
294
377
|
if (!hasDtsFiles) {
|
|
295
|
-
hasDtsFiles = fs.readdirSync(projectPath).some(file =>
|
|
296
|
-
file.endsWith('.d.ts') ||
|
|
297
|
-
(fs.statSync(path.join(projectPath, file)).isDirectory() &&
|
|
298
|
-
file !== 'node_modules' &&
|
|
378
|
+
hasDtsFiles = fs.readdirSync(projectPath).some(file =>
|
|
379
|
+
file.endsWith('.d.ts') ||
|
|
380
|
+
(fs.statSync(path.join(projectPath, file)).isDirectory() &&
|
|
381
|
+
file !== 'node_modules' &&
|
|
299
382
|
fs.existsSync(path.join(projectPath, file, 'index.d.ts')))
|
|
300
383
|
);
|
|
301
384
|
}
|
|
302
|
-
|
|
385
|
+
|
|
303
386
|
if (!hasDtsFiles) {
|
|
304
387
|
errors.push('No TypeScript definition files (.d.ts) found in the project - Please create .d.ts files for your components');
|
|
305
388
|
}
|
|
306
|
-
|
|
389
|
+
|
|
307
390
|
return {
|
|
308
391
|
isValid: errors.length === 0,
|
|
309
392
|
errors
|
|
@@ -313,8 +396,8 @@ function validateTypeScriptEnvironment(projectPath: string): { isValid: boolean;
|
|
|
313
396
|
function createCommand(target: string, options: { framework: string; packageName: string; metadata?: FlutterPackageMetadata }): void {
|
|
314
397
|
const { framework, metadata } = options;
|
|
315
398
|
// Ensure package name is always valid
|
|
316
|
-
const packageName = isValidNpmPackageName(options.packageName)
|
|
317
|
-
? options.packageName
|
|
399
|
+
const packageName = isValidNpmPackageName(options.packageName)
|
|
400
|
+
? options.packageName
|
|
318
401
|
: sanitizePackageName(options.packageName);
|
|
319
402
|
|
|
320
403
|
if (!fs.existsSync(target)) {
|
|
@@ -394,28 +477,74 @@ function createCommand(target: string, options: { framework: string; packageName
|
|
|
394
477
|
console.log(`WebF ${framework} package created at: ${target}`);
|
|
395
478
|
}
|
|
396
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
|
+
|
|
397
520
|
async function generateCommand(distPath: string, options: GenerateOptions): Promise<void> {
|
|
398
521
|
// If distPath is not provided or is '.', create a temporary directory
|
|
399
522
|
let resolvedDistPath: string;
|
|
400
523
|
let isTempDir = false;
|
|
401
|
-
|
|
524
|
+
const isDartOnly = options.dartOnly;
|
|
525
|
+
|
|
402
526
|
if (!distPath || distPath === '.') {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
527
|
+
if (isDartOnly) {
|
|
528
|
+
// In Dart-only mode we don't need a temporary Node project directory
|
|
529
|
+
resolvedDistPath = path.resolve(distPath || '.');
|
|
530
|
+
} else {
|
|
531
|
+
// Create a temporary directory for the generated package
|
|
532
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'webf-typings-'));
|
|
533
|
+
resolvedDistPath = tempDir;
|
|
534
|
+
isTempDir = true;
|
|
535
|
+
console.log(`\nUsing temporary directory: ${tempDir}`);
|
|
536
|
+
}
|
|
408
537
|
} else {
|
|
409
538
|
resolvedDistPath = path.resolve(distPath);
|
|
410
539
|
}
|
|
411
|
-
|
|
540
|
+
|
|
412
541
|
// First, check if we're in a Flutter package directory when flutter-package-src is not provided
|
|
413
542
|
if (!options.flutterPackageSrc) {
|
|
414
543
|
// Check if current directory or parent directories contain pubspec.yaml
|
|
415
544
|
let currentDir = process.cwd();
|
|
416
545
|
let foundPubspec = false;
|
|
417
546
|
let pubspecDir = '';
|
|
418
|
-
|
|
547
|
+
|
|
419
548
|
// Search up to 3 levels up for pubspec.yaml
|
|
420
549
|
for (let i = 0; i < 3; i++) {
|
|
421
550
|
const pubspecPath = path.join(currentDir, 'pubspec.yaml');
|
|
@@ -428,141 +557,151 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
428
557
|
if (parentDir === currentDir) break; // Reached root
|
|
429
558
|
currentDir = parentDir;
|
|
430
559
|
}
|
|
431
|
-
|
|
560
|
+
|
|
432
561
|
if (foundPubspec) {
|
|
433
562
|
// Use the directory containing pubspec.yaml as the flutter package source
|
|
434
563
|
options.flutterPackageSrc = pubspecDir;
|
|
435
564
|
console.log(`\nDetected Flutter package at: ${pubspecDir}`);
|
|
436
565
|
}
|
|
437
566
|
}
|
|
438
|
-
|
|
439
|
-
// Check if the directory exists and has required files
|
|
440
|
-
const packageJsonPath = path.join(resolvedDistPath, 'package.json');
|
|
441
|
-
const globalDtsPath = path.join(resolvedDistPath, 'global.d.ts');
|
|
442
|
-
const tsConfigPath = path.join(resolvedDistPath, 'tsconfig.json');
|
|
443
|
-
|
|
444
|
-
const hasPackageJson = fs.existsSync(packageJsonPath);
|
|
445
|
-
const hasGlobalDts = fs.existsSync(globalDtsPath);
|
|
446
|
-
const hasTsConfig = fs.existsSync(tsConfigPath);
|
|
447
|
-
|
|
448
|
-
// Determine if we need to create a new project
|
|
449
|
-
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
450
|
-
|
|
451
|
-
// Track if this is an existing project (has all required files)
|
|
452
|
-
const isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
453
|
-
|
|
567
|
+
|
|
454
568
|
let framework = options.framework;
|
|
455
569
|
let packageName = options.packageName;
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
570
|
+
let isExistingProject = false;
|
|
571
|
+
|
|
572
|
+
if (!isDartOnly) {
|
|
573
|
+
// Check if the directory exists and has required files
|
|
574
|
+
const packageJsonPath = path.join(resolvedDistPath, 'package.json');
|
|
575
|
+
const globalDtsPath = path.join(resolvedDistPath, 'global.d.ts');
|
|
576
|
+
const tsConfigPath = path.join(resolvedDistPath, 'tsconfig.json');
|
|
577
|
+
|
|
578
|
+
const hasPackageJson = fs.existsSync(packageJsonPath);
|
|
579
|
+
const hasGlobalDts = fs.existsSync(globalDtsPath);
|
|
580
|
+
const hasTsConfig = fs.existsSync(tsConfigPath);
|
|
581
|
+
|
|
582
|
+
// Determine if we need to create a new project
|
|
583
|
+
const needsProjectCreation = !hasPackageJson || !hasGlobalDts || !hasTsConfig;
|
|
584
|
+
|
|
585
|
+
// Track if this is an existing project (has all required files)
|
|
586
|
+
isExistingProject = hasPackageJson && hasGlobalDts && hasTsConfig;
|
|
587
|
+
|
|
588
|
+
// Validate and sanitize package name if provided
|
|
589
|
+
if (packageName && !isValidNpmPackageName(packageName)) {
|
|
590
|
+
console.warn(`Warning: Package name "${packageName}" is not valid for npm.`);
|
|
591
|
+
const sanitized = sanitizePackageName(packageName);
|
|
592
|
+
console.log(`Using sanitized name: "${sanitized}"`);
|
|
593
|
+
packageName = sanitized;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
if (needsProjectCreation) {
|
|
597
|
+
// If project needs creation but options are missing, prompt for them
|
|
598
|
+
if (!framework) {
|
|
599
|
+
const frameworkAnswer = await inquirer.prompt([{
|
|
600
|
+
type: 'list',
|
|
601
|
+
name: 'framework',
|
|
602
|
+
message: 'Which framework would you like to use?',
|
|
603
|
+
choices: ['react', 'vue']
|
|
604
|
+
}]);
|
|
605
|
+
framework = frameworkAnswer.framework;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Try to read Flutter package metadata if flutterPackageSrc is provided
|
|
609
|
+
let metadata: FlutterPackageMetadata | null = null;
|
|
610
|
+
if (options.flutterPackageSrc) {
|
|
611
|
+
metadata = readFlutterPackageMetadata(options.flutterPackageSrc);
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (!packageName) {
|
|
615
|
+
// Use Flutter package name as default if available, sanitized for npm
|
|
616
|
+
const rawDefaultName = metadata?.name || path.basename(resolvedDistPath);
|
|
617
|
+
const defaultPackageName = sanitizePackageName(rawDefaultName);
|
|
618
|
+
|
|
619
|
+
const packageNameAnswer = await inquirer.prompt([{
|
|
620
|
+
type: 'input',
|
|
621
|
+
name: 'packageName',
|
|
622
|
+
message: 'What is your package name?',
|
|
623
|
+
default: defaultPackageName,
|
|
624
|
+
validate: (input: string) => {
|
|
625
|
+
if (!input || input.trim() === '') {
|
|
626
|
+
return 'Package name is required';
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Check if it's valid as-is
|
|
630
|
+
if (isValidNpmPackageName(input)) {
|
|
631
|
+
return true;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// If not valid, show what it would be sanitized to
|
|
635
|
+
const sanitized = sanitizePackageName(input);
|
|
636
|
+
return `Invalid npm package name. Would be sanitized to: "${sanitized}". Please enter a valid name.`;
|
|
501
637
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
message: 'Which framework are you using?',
|
|
535
|
-
choices: ['react', 'vue']
|
|
536
|
-
}]);
|
|
537
|
-
framework = frameworkAnswer.framework;
|
|
638
|
+
}]);
|
|
639
|
+
packageName = packageNameAnswer.packageName;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
console.log(`\nCreating new ${framework} project in ${resolvedDistPath}...`);
|
|
643
|
+
createCommand(resolvedDistPath, {
|
|
644
|
+
framework: framework!,
|
|
645
|
+
packageName: packageName!,
|
|
646
|
+
metadata: metadata || undefined
|
|
647
|
+
});
|
|
648
|
+
} else {
|
|
649
|
+
// Validate existing project structure
|
|
650
|
+
if (hasPackageJson) {
|
|
651
|
+
try {
|
|
652
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
653
|
+
|
|
654
|
+
// Detect framework from existing package.json
|
|
655
|
+
if (!framework) {
|
|
656
|
+
if (packageJson.dependencies?.react || packageJson.devDependencies?.react) {
|
|
657
|
+
framework = 'react';
|
|
658
|
+
} else if (packageJson.dependencies?.vue || packageJson.devDependencies?.vue) {
|
|
659
|
+
framework = 'vue';
|
|
660
|
+
} else {
|
|
661
|
+
// If can't detect, prompt for it
|
|
662
|
+
const frameworkAnswer = await inquirer.prompt([{
|
|
663
|
+
type: 'list',
|
|
664
|
+
name: 'framework',
|
|
665
|
+
message: 'Which framework are you using?',
|
|
666
|
+
choices: ['react', 'vue']
|
|
667
|
+
}]);
|
|
668
|
+
framework = frameworkAnswer.framework;
|
|
669
|
+
}
|
|
538
670
|
}
|
|
671
|
+
|
|
672
|
+
console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
|
|
673
|
+
} catch (e) {
|
|
674
|
+
console.error('Error reading package.json:', e);
|
|
675
|
+
process.exit(1);
|
|
539
676
|
}
|
|
540
|
-
|
|
541
|
-
console.log(`\nDetected existing ${framework} project in ${resolvedDistPath}`);
|
|
542
|
-
} catch (e) {
|
|
543
|
-
console.error('Error reading package.json:', e);
|
|
544
|
-
process.exit(1);
|
|
545
677
|
}
|
|
546
678
|
}
|
|
679
|
+
} else {
|
|
680
|
+
// In Dart-only mode, framework/packageName are unused; ensure framework is not accidentally required later.
|
|
681
|
+
framework = options.framework;
|
|
547
682
|
}
|
|
548
|
-
|
|
683
|
+
|
|
549
684
|
// Now proceed with code generation if flutter package source is provided
|
|
550
685
|
if (!options.flutterPackageSrc) {
|
|
551
686
|
console.log('\nProject is ready for code generation.');
|
|
552
687
|
console.log('To generate code, run:');
|
|
553
688
|
const displayPath = isTempDir ? '<output-dir>' : distPath;
|
|
554
|
-
|
|
689
|
+
if (isDartOnly) {
|
|
690
|
+
console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --dart-only`);
|
|
691
|
+
} else {
|
|
692
|
+
console.log(` webf codegen ${displayPath} --flutter-package-src=<path> --framework=${framework}`);
|
|
693
|
+
}
|
|
555
694
|
if (isTempDir) {
|
|
556
695
|
// Clean up temporary directory if we're not using it
|
|
557
696
|
fs.rmSync(resolvedDistPath, { recursive: true, force: true });
|
|
558
697
|
}
|
|
559
698
|
return;
|
|
560
699
|
}
|
|
561
|
-
|
|
700
|
+
|
|
562
701
|
// Validate TypeScript environment in the Flutter package
|
|
563
702
|
console.log(`\nValidating TypeScript environment in ${options.flutterPackageSrc}...`);
|
|
564
703
|
const validation = validateTypeScriptEnvironment(options.flutterPackageSrc);
|
|
565
|
-
|
|
704
|
+
|
|
566
705
|
if (!validation.isValid) {
|
|
567
706
|
// Check specifically for missing tsconfig.json
|
|
568
707
|
const tsConfigPath = path.join(options.flutterPackageSrc, 'tsconfig.json');
|
|
@@ -573,7 +712,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
573
712
|
message: 'No tsconfig.json found. Would you like me to create one for you?',
|
|
574
713
|
default: true
|
|
575
714
|
}]);
|
|
576
|
-
|
|
715
|
+
|
|
577
716
|
if (createTsConfigAnswer.createTsConfig) {
|
|
578
717
|
// Create a default tsconfig.json
|
|
579
718
|
const defaultTsConfig = {
|
|
@@ -592,10 +731,10 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
592
731
|
include: ['lib/**/*.d.ts', '**/*.d.ts'],
|
|
593
732
|
exclude: ['node_modules', 'dist', 'build']
|
|
594
733
|
};
|
|
595
|
-
|
|
734
|
+
|
|
596
735
|
fs.writeFileSync(tsConfigPath, JSON.stringify(defaultTsConfig, null, 2), 'utf-8');
|
|
597
736
|
console.log('ā
Created tsconfig.json');
|
|
598
|
-
|
|
737
|
+
|
|
599
738
|
// Re-validate after creating tsconfig
|
|
600
739
|
const newValidation = validateTypeScriptEnvironment(options.flutterPackageSrc);
|
|
601
740
|
if (!newValidation.isValid) {
|
|
@@ -617,21 +756,40 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
617
756
|
process.exit(1);
|
|
618
757
|
}
|
|
619
758
|
}
|
|
620
|
-
|
|
621
|
-
const
|
|
622
|
-
|
|
759
|
+
|
|
760
|
+
const baseCommand = 'webf codegen';
|
|
761
|
+
const flutterPart = options.flutterPackageSrc ? ` --flutter-package-src=${options.flutterPackageSrc}` : '';
|
|
762
|
+
const modePart = isDartOnly
|
|
763
|
+
? ' --dart-only'
|
|
764
|
+
: (framework ? ` --framework=${framework}` : '');
|
|
765
|
+
const command = `${baseCommand}${flutterPart}${modePart} <distPath>`;
|
|
766
|
+
|
|
767
|
+
if (isDartOnly) {
|
|
768
|
+
console.log(`\nGenerating Dart bindings from ${options.flutterPackageSrc}...`);
|
|
769
|
+
|
|
770
|
+
await dartGen({
|
|
771
|
+
source: options.flutterPackageSrc,
|
|
772
|
+
target: options.flutterPackageSrc,
|
|
773
|
+
command,
|
|
774
|
+
exclude: options.exclude,
|
|
775
|
+
});
|
|
776
|
+
|
|
777
|
+
console.log('\nDart code generation completed successfully!');
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
|
|
623
781
|
// Auto-initialize typings in the output directory if needed
|
|
624
782
|
ensureInitialized(resolvedDistPath);
|
|
625
|
-
|
|
783
|
+
|
|
626
784
|
console.log(`\nGenerating ${framework} code from ${options.flutterPackageSrc}...`);
|
|
627
|
-
|
|
785
|
+
|
|
628
786
|
await dartGen({
|
|
629
787
|
source: options.flutterPackageSrc,
|
|
630
788
|
target: options.flutterPackageSrc,
|
|
631
789
|
command,
|
|
632
790
|
exclude: options.exclude,
|
|
633
791
|
});
|
|
634
|
-
|
|
792
|
+
|
|
635
793
|
if (framework === 'react') {
|
|
636
794
|
// Get the package name from package.json if it exists
|
|
637
795
|
let reactPackageName: string | undefined;
|
|
@@ -644,7 +802,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
644
802
|
} catch (e) {
|
|
645
803
|
// Ignore errors
|
|
646
804
|
}
|
|
647
|
-
|
|
805
|
+
|
|
648
806
|
await reactGen({
|
|
649
807
|
source: options.flutterPackageSrc,
|
|
650
808
|
target: resolvedDistPath,
|
|
@@ -662,9 +820,9 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
662
820
|
exclude: options.exclude,
|
|
663
821
|
});
|
|
664
822
|
}
|
|
665
|
-
|
|
823
|
+
|
|
666
824
|
console.log('\nCode generation completed successfully!');
|
|
667
|
-
|
|
825
|
+
|
|
668
826
|
// Automatically build the generated package
|
|
669
827
|
if (framework) {
|
|
670
828
|
try {
|
|
@@ -686,7 +844,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
686
844
|
// Don't exit here since generation was successful
|
|
687
845
|
}
|
|
688
846
|
}
|
|
689
|
-
|
|
847
|
+
|
|
690
848
|
// Handle npm publishing if requested via command line option
|
|
691
849
|
if (options.publishToNpm && framework) {
|
|
692
850
|
try {
|
|
@@ -703,7 +861,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
703
861
|
message: 'Would you like to publish this package to npm?',
|
|
704
862
|
default: false
|
|
705
863
|
}]);
|
|
706
|
-
|
|
864
|
+
|
|
707
865
|
if (publishAnswer.publish) {
|
|
708
866
|
// Ask for registry
|
|
709
867
|
const registryAnswer = await inquirer.prompt([{
|
|
@@ -721,10 +879,10 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
721
879
|
}
|
|
722
880
|
}
|
|
723
881
|
}]);
|
|
724
|
-
|
|
882
|
+
|
|
725
883
|
try {
|
|
726
884
|
await buildAndPublishPackage(
|
|
727
|
-
resolvedDistPath,
|
|
885
|
+
resolvedDistPath,
|
|
728
886
|
registryAnswer.registry || undefined,
|
|
729
887
|
isExistingProject
|
|
730
888
|
);
|
|
@@ -734,7 +892,7 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
734
892
|
}
|
|
735
893
|
}
|
|
736
894
|
}
|
|
737
|
-
|
|
895
|
+
|
|
738
896
|
// If using a temporary directory, remind the user where the files are
|
|
739
897
|
if (isTempDir) {
|
|
740
898
|
console.log(`\nš Generated files are in: ${resolvedDistPath}`);
|
|
@@ -742,6 +900,236 @@ async function generateCommand(distPath: string, options: GenerateOptions): Prom
|
|
|
742
900
|
}
|
|
743
901
|
}
|
|
744
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
|
+
|
|
745
1133
|
function writeFileIfChanged(filePath: string, content: string) {
|
|
746
1134
|
if (fs.existsSync(filePath)) {
|
|
747
1135
|
const oldContent = fs.readFileSync(filePath, 'utf-8')
|
|
@@ -756,19 +1144,19 @@ function writeFileIfChanged(filePath: string, content: string) {
|
|
|
756
1144
|
function ensureInitialized(targetPath: string): void {
|
|
757
1145
|
const globalDtsPath = path.join(targetPath, 'global.d.ts');
|
|
758
1146
|
const tsConfigPath = path.join(targetPath, 'tsconfig.json');
|
|
759
|
-
|
|
1147
|
+
|
|
760
1148
|
// Check if initialization files already exist
|
|
761
1149
|
const needsInit = !fs.existsSync(globalDtsPath) || !fs.existsSync(tsConfigPath);
|
|
762
|
-
|
|
1150
|
+
|
|
763
1151
|
if (needsInit) {
|
|
764
1152
|
console.log('Initializing WebF typings...');
|
|
765
1153
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
766
|
-
|
|
1154
|
+
|
|
767
1155
|
if (!fs.existsSync(globalDtsPath)) {
|
|
768
1156
|
fs.writeFileSync(globalDtsPath, gloabalDts, 'utf-8');
|
|
769
1157
|
console.log('Created global.d.ts');
|
|
770
1158
|
}
|
|
771
|
-
|
|
1159
|
+
|
|
772
1160
|
if (!fs.existsSync(tsConfigPath)) {
|
|
773
1161
|
fs.writeFileSync(tsConfigPath, tsConfig, 'utf-8');
|
|
774
1162
|
console.log('Created tsconfig.json');
|
|
@@ -778,7 +1166,7 @@ function ensureInitialized(targetPath: string): void {
|
|
|
778
1166
|
|
|
779
1167
|
async function buildPackage(packagePath: string): Promise<void> {
|
|
780
1168
|
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
781
|
-
|
|
1169
|
+
|
|
782
1170
|
if (!fs.existsSync(packageJsonPath)) {
|
|
783
1171
|
// Skip the error in test environment to avoid console warnings
|
|
784
1172
|
if (process.env.NODE_ENV === 'test' || process.env.JEST_WORKER_ID !== undefined) {
|
|
@@ -786,34 +1174,34 @@ async function buildPackage(packagePath: string): Promise<void> {
|
|
|
786
1174
|
}
|
|
787
1175
|
throw new Error(`No package.json found in ${packagePath}`);
|
|
788
1176
|
}
|
|
789
|
-
|
|
1177
|
+
|
|
790
1178
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
791
1179
|
const packageName = packageJson.name;
|
|
792
1180
|
const packageVersion = packageJson.version;
|
|
793
|
-
|
|
1181
|
+
|
|
794
1182
|
// Check if node_modules exists
|
|
795
1183
|
const nodeModulesPath = path.join(packagePath, 'node_modules');
|
|
796
1184
|
if (!fs.existsSync(nodeModulesPath)) {
|
|
797
1185
|
console.log(`\nš¦ Installing dependencies for ${packageName}...`);
|
|
798
|
-
|
|
1186
|
+
|
|
799
1187
|
// Check if yarn.lock exists to determine package manager
|
|
800
1188
|
const yarnLockPath = path.join(packagePath, 'yarn.lock');
|
|
801
1189
|
const useYarn = fs.existsSync(yarnLockPath);
|
|
802
|
-
|
|
1190
|
+
|
|
803
1191
|
const installCommand = useYarn ? 'yarn' : NPM;
|
|
804
1192
|
const installArgs = useYarn ? [] : ['install'];
|
|
805
|
-
|
|
1193
|
+
|
|
806
1194
|
const installResult = spawnSync(installCommand, installArgs, {
|
|
807
1195
|
cwd: packagePath,
|
|
808
1196
|
stdio: 'inherit'
|
|
809
1197
|
});
|
|
810
|
-
|
|
1198
|
+
|
|
811
1199
|
if (installResult.status !== 0) {
|
|
812
1200
|
throw new Error('Failed to install dependencies');
|
|
813
1201
|
}
|
|
814
1202
|
console.log('ā
Dependencies installed successfully!');
|
|
815
1203
|
}
|
|
816
|
-
|
|
1204
|
+
|
|
817
1205
|
// Check if package has a build script
|
|
818
1206
|
if (packageJson.scripts?.build) {
|
|
819
1207
|
console.log(`\nBuilding ${packageName}@${packageVersion}...`);
|
|
@@ -821,7 +1209,7 @@ async function buildPackage(packagePath: string): Promise<void> {
|
|
|
821
1209
|
cwd: packagePath,
|
|
822
1210
|
stdio: 'inherit'
|
|
823
1211
|
});
|
|
824
|
-
|
|
1212
|
+
|
|
825
1213
|
if (buildResult.status !== 0) {
|
|
826
1214
|
throw new Error('Build failed');
|
|
827
1215
|
}
|
|
@@ -833,18 +1221,18 @@ async function buildPackage(packagePath: string): Promise<void> {
|
|
|
833
1221
|
|
|
834
1222
|
async function buildAndPublishPackage(packagePath: string, registry?: string, isExistingProject: boolean = false): Promise<void> {
|
|
835
1223
|
const packageJsonPath = path.join(packagePath, 'package.json');
|
|
836
|
-
|
|
1224
|
+
|
|
837
1225
|
if (!fs.existsSync(packageJsonPath)) {
|
|
838
1226
|
throw new Error(`No package.json found in ${packagePath}`);
|
|
839
1227
|
}
|
|
840
|
-
|
|
1228
|
+
|
|
841
1229
|
let packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
842
1230
|
const packageName = packageJson.name;
|
|
843
1231
|
let packageVersion = packageJson.version;
|
|
844
|
-
|
|
1232
|
+
|
|
845
1233
|
// First, ensure dependencies are installed and build the package
|
|
846
1234
|
await buildPackage(packagePath);
|
|
847
|
-
|
|
1235
|
+
|
|
848
1236
|
// If this is an existing project, increment the patch version before publishing
|
|
849
1237
|
if (isExistingProject) {
|
|
850
1238
|
console.log(`\nIncrementing version for existing project...`);
|
|
@@ -853,18 +1241,18 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
|
|
|
853
1241
|
encoding: 'utf-8',
|
|
854
1242
|
stdio: 'pipe'
|
|
855
1243
|
});
|
|
856
|
-
|
|
1244
|
+
|
|
857
1245
|
if (versionResult.status !== 0) {
|
|
858
1246
|
console.error('Failed to increment version:', versionResult.stderr);
|
|
859
1247
|
throw new Error('Failed to increment version');
|
|
860
1248
|
}
|
|
861
|
-
|
|
1249
|
+
|
|
862
1250
|
// Re-read package.json to get the new version
|
|
863
1251
|
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
864
1252
|
packageVersion = packageJson.version;
|
|
865
1253
|
console.log(`Version updated to ${packageVersion}`);
|
|
866
1254
|
}
|
|
867
|
-
|
|
1255
|
+
|
|
868
1256
|
// Set registry if provided
|
|
869
1257
|
if (registry) {
|
|
870
1258
|
console.log(`\nUsing npm registry: ${registry}`);
|
|
@@ -872,38 +1260,38 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
|
|
|
872
1260
|
cwd: packagePath,
|
|
873
1261
|
stdio: 'inherit'
|
|
874
1262
|
});
|
|
875
|
-
|
|
1263
|
+
|
|
876
1264
|
if (setRegistryResult.status !== 0) {
|
|
877
1265
|
throw new Error('Failed to set npm registry');
|
|
878
1266
|
}
|
|
879
1267
|
}
|
|
880
|
-
|
|
1268
|
+
|
|
881
1269
|
// Check if user is logged in to npm
|
|
882
1270
|
const whoamiResult = spawnSync(NPM, ['whoami'], {
|
|
883
1271
|
cwd: packagePath,
|
|
884
1272
|
encoding: 'utf-8'
|
|
885
1273
|
});
|
|
886
|
-
|
|
1274
|
+
|
|
887
1275
|
if (whoamiResult.status !== 0) {
|
|
888
1276
|
console.error('\nError: You must be logged in to npm to publish packages.');
|
|
889
1277
|
console.error('Please run "npm login" first.');
|
|
890
1278
|
throw new Error('Not logged in to npm');
|
|
891
1279
|
}
|
|
892
|
-
|
|
1280
|
+
|
|
893
1281
|
console.log(`\nPublishing ${packageName}@${packageVersion} to npm...`);
|
|
894
|
-
|
|
1282
|
+
|
|
895
1283
|
// Publish the package
|
|
896
1284
|
const publishResult = spawnSync(NPM, ['publish'], {
|
|
897
1285
|
cwd: packagePath,
|
|
898
1286
|
stdio: 'inherit'
|
|
899
1287
|
});
|
|
900
|
-
|
|
1288
|
+
|
|
901
1289
|
if (publishResult.status !== 0) {
|
|
902
1290
|
throw new Error('Publish failed');
|
|
903
1291
|
}
|
|
904
|
-
|
|
1292
|
+
|
|
905
1293
|
console.log(`\nā
Successfully published ${packageName}@${packageVersion}`);
|
|
906
|
-
|
|
1294
|
+
|
|
907
1295
|
// Reset registry to default if it was changed
|
|
908
1296
|
if (registry) {
|
|
909
1297
|
spawnSync(NPM, ['config', 'delete', 'registry'], {
|
|
@@ -912,4 +1300,4 @@ async function buildAndPublishPackage(packagePath: string, registry?: string, is
|
|
|
912
1300
|
}
|
|
913
1301
|
}
|
|
914
1302
|
|
|
915
|
-
export { generateCommand };
|
|
1303
|
+
export { generateCommand, generateModuleCommand };
|