@fecommunity/reactpress 2.0.0-beta-4-beta.1

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.
@@ -0,0 +1,945 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const chalk = require('chalk');
7
+ const inquirer = require('inquirer');
8
+ const crypto = require('crypto');
9
+
10
+ // Package build order (dependencies first)
11
+ const packages = [
12
+ {
13
+ name: '@fecommunity/reactpress',
14
+ path: '.',
15
+ description: 'Main ReactPress package'
16
+ },
17
+ {
18
+ name: '@fecommunity/reactpress-server',
19
+ path: 'server',
20
+ description: 'Backend API server package'
21
+ },
22
+ {
23
+ name: '@fecommunity/reactpress-client',
24
+ path: 'client',
25
+ description: 'Frontend application package'
26
+ },
27
+ {
28
+ name: '@fecommunity/reactpress-toolkit',
29
+ path: 'toolkit',
30
+ description: 'API client and utilities toolkit'
31
+ },
32
+ {
33
+ name: '@fecommunity/reactpress-template-hello-world',
34
+ path: 'templates/hello-world',
35
+ description: 'Hello World template for ReactPress'
36
+ },
37
+ {
38
+ name: '@fecommunity/reactpress-template-twentytwentyfive',
39
+ path: 'templates/twentytwentyfive',
40
+ description: 'Twenty Twenty Five blog template for ReactPress'
41
+ }
42
+ ];
43
+
44
+ // Generate a hash for a file or directory
45
+ function generateHash(filePath) {
46
+ try {
47
+ if (fs.statSync(filePath).isDirectory()) {
48
+ const files = fs.readdirSync(filePath);
49
+ const hashes = files
50
+ .filter(file => !file.startsWith('.') && file !== 'node_modules' && file !== 'dist' && file !== '.next') // Ignore hidden files and build directories
51
+ .map(file => generateHash(path.join(filePath, file)))
52
+ .sort();
53
+ return crypto.createHash('md5').update(hashes.join('')).digest('hex');
54
+ } else {
55
+ const content = fs.readFileSync(filePath);
56
+ return crypto.createHash('md5').update(content).digest('hex');
57
+ }
58
+ } catch (error) {
59
+ return '';
60
+ }
61
+ }
62
+
63
+ // Get package content hash
64
+ function getPackageHash(packagePath) {
65
+ const fullPath = path.join(process.cwd(), packagePath);
66
+ return generateHash(fullPath);
67
+ }
68
+
69
+ // Check if package has meaningful changes (for build)
70
+ function hasMeaningfulChangesForBuild(packagePath, packageName) {
71
+ try {
72
+ // Create a hash file path to store previous hash in node_modules
73
+ const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`);
74
+
75
+ // Generate current hash
76
+ const currentHash = getPackageHash(packagePath);
77
+
78
+ // Check if we have a previous hash
79
+ if (fs.existsSync(hashFilePath)) {
80
+ const previousHash = fs.readFileSync(hashFilePath, 'utf8').trim();
81
+ return currentHash !== previousHash;
82
+ }
83
+
84
+ // If no previous hash, consider it as having changes
85
+ return true;
86
+ } catch (error) {
87
+ console.log(chalk.yellow(`āš ļø Could not check changes for ${packageName}, assuming changes exist`));
88
+ return true;
89
+ }
90
+ }
91
+
92
+ // Check if package has meaningful changes (for publish)
93
+ function hasMeaningfulChangesForPublish(packagePath, packageName) {
94
+ try {
95
+ // Create a hash file path to store previous hash in node_modules
96
+ const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`);
97
+
98
+ // Generate current hash
99
+ const currentHash = getPackageHash(packagePath);
100
+
101
+ // Check if we have a previous hash
102
+ if (fs.existsSync(hashFilePath)) {
103
+ const previousHash = fs.readFileSync(hashFilePath, 'utf8').trim();
104
+ return currentHash !== previousHash;
105
+ }
106
+
107
+ // If no previous hash, consider it as having changes
108
+ return true;
109
+ } catch (error) {
110
+ console.log(chalk.yellow(`āš ļø Could not check changes for ${packageName}, assuming changes exist`));
111
+ return true;
112
+ }
113
+ }
114
+
115
+ // Save package hash (for build)
116
+ function savePackageHashForBuild(packagePath, packageName) {
117
+ try {
118
+ const hashFilePath = path.join(process.cwd(), 'node_modules', '.build-cache', `${packageName.replace('/', '_')}.hash`);
119
+
120
+ // Ensure cache directory exists
121
+ const cacheDir = path.dirname(hashFilePath);
122
+ if (!fs.existsSync(cacheDir)) {
123
+ fs.mkdirSync(cacheDir, { recursive: true });
124
+ }
125
+
126
+ // Generate and save current hash
127
+ const currentHash = getPackageHash(packagePath);
128
+ fs.writeFileSync(hashFilePath, currentHash);
129
+ } catch (error) {
130
+ console.log(chalk.yellow(`āš ļø Could not save hash for ${packageName}`));
131
+ }
132
+ }
133
+
134
+ // Save package hash (for publish)
135
+ function savePackageHashForPublish(packagePath, packageName) {
136
+ try {
137
+ const hashFilePath = path.join(process.cwd(), 'node_modules', '.publish-cache', `${packageName.replace('/', '_')}.hash`);
138
+
139
+ // Ensure cache directory exists
140
+ const cacheDir = path.dirname(hashFilePath);
141
+ if (!fs.existsSync(cacheDir)) {
142
+ fs.mkdirSync(cacheDir, { recursive: true });
143
+ }
144
+
145
+ // Generate and save current hash
146
+ const currentHash = getPackageHash(packagePath);
147
+ fs.writeFileSync(hashFilePath, currentHash);
148
+ } catch (error) {
149
+ console.log(chalk.yellow(`āš ļø Could not save hash for ${packageName}`));
150
+ }
151
+ }
152
+
153
+ // Get current versions
154
+ function getCurrentVersion(packagePath) {
155
+ try {
156
+ const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
157
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
158
+ return pkg.version;
159
+ } catch (error) {
160
+ return 'unknown';
161
+ }
162
+ }
163
+
164
+ // Increment version based on type
165
+ function incrementVersion(version, type) {
166
+ const parts = version.split('-')[0].split('.');
167
+ const major = parseInt(parts[0]);
168
+ const minor = parseInt(parts[1]);
169
+ const patch = parseInt(parts[2]);
170
+
171
+ switch (type) {
172
+ case 'major':
173
+ return `${major + 1}.0.0`;
174
+ case 'minor':
175
+ return `${major}.${minor + 1}.0`;
176
+ case 'patch':
177
+ return `${major}.${minor}.${patch + 1}`;
178
+ case 'beta':
179
+ // For beta, we increment the beta number or add beta.1 if not present
180
+ const match = version.match(/^(.*)-beta\.(\d+)$/);
181
+ if (match) {
182
+ const baseVersion = match[1];
183
+ const betaNumber = parseInt(match[2]);
184
+ return `${baseVersion}-beta.${betaNumber + 1}`;
185
+ } else {
186
+ // If no beta version exists, add beta.1
187
+ return `${version}-beta.1`;
188
+ }
189
+ case 'alpha':
190
+ // For alpha, we increment the alpha number or add alpha.1 if not present
191
+ const alphaMatch = version.match(/^(.*)-alpha\.(\d+)$/);
192
+ if (alphaMatch) {
193
+ const baseVersion = alphaMatch[1];
194
+ const alphaNumber = parseInt(alphaMatch[2]);
195
+ return `${baseVersion}-alpha.${alphaNumber + 1}`;
196
+ } else {
197
+ // If no alpha version exists, add alpha.1
198
+ return `${version}-alpha.1`;
199
+ }
200
+ default:
201
+ return version;
202
+ }
203
+ }
204
+
205
+ // Get next available version from npm registry
206
+ function getNextAvailableVersion(packageName, currentVersion, versionType) {
207
+ try {
208
+ // First, increment the version locally
209
+ let nextVersion = incrementVersion(currentVersion, versionType);
210
+
211
+ // Check if this version already exists on npm
212
+ let versionExists = true;
213
+ let attempts = 0;
214
+ const maxAttempts = 100; // Prevent infinite loop
215
+
216
+ while (versionExists && attempts < maxAttempts) {
217
+ try {
218
+ execSync(`npm view ${packageName}@${nextVersion} version`, { stdio: 'ignore' });
219
+ // If we get here, the version exists, so we need to increment again
220
+ nextVersion = incrementVersion(nextVersion, versionType);
221
+ attempts++;
222
+ } catch (error) {
223
+ // If we get an error, the version doesn't exist, which is what we want
224
+ versionExists = false;
225
+ }
226
+ }
227
+
228
+ if (attempts >= maxAttempts) {
229
+ throw new Error('Too many attempts to find available version');
230
+ }
231
+
232
+ return nextVersion;
233
+ } catch (error) {
234
+ // Fallback to simple increment if npm view fails
235
+ console.log(chalk.yellow(`āš ļø Could not check npm registry, using local increment for ${packageName}`));
236
+ return incrementVersion(currentVersion, versionType);
237
+ }
238
+ }
239
+
240
+ // Update package version
241
+ function updateVersion(packagePath, newVersion) {
242
+ console.log(chalk.blue(`\nāœļø Updating version to ${newVersion}...`));
243
+
244
+ const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
245
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
246
+
247
+ const oldVersion = pkg.version;
248
+ pkg.version = newVersion;
249
+
250
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
251
+ console.log(chalk.green(`āœ… Version updated from ${oldVersion} to ${newVersion}`));
252
+ }
253
+
254
+ // Fix workspace dependencies for build
255
+ function fixWorkspaceDependenciesForBuild(packagePath) {
256
+ console.log(chalk.blue(`šŸ”§ Fixing workspace dependencies for build: ${packagePath}...`));
257
+
258
+ const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
259
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
260
+
261
+ // Fix dependencies
262
+ const depTypes = ['dependencies', 'devDependencies', 'peerDependencies'];
263
+
264
+ depTypes.forEach(depType => {
265
+ if (pkg[depType]) {
266
+ Object.keys(pkg[depType]).forEach(depName => {
267
+ // Check if it's a workspace dependency
268
+ if (pkg[depType][depName] === 'workspace:*' || pkg[depType][depName].startsWith('workspace:')) {
269
+ // For build purposes, we'll use the file: protocol to reference local packages
270
+ const depPackage = packages.find(p => p.name === depName);
271
+ if (depPackage) {
272
+ console.log(chalk.gray(` Replacing ${depName} workspace dependency with file reference`));
273
+ pkg[depType][depName] = `file:../${depPackage.path}`;
274
+ }
275
+ }
276
+ });
277
+ }
278
+ });
279
+
280
+ // Write the updated package.json
281
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
282
+ console.log(chalk.green(`āœ… Workspace dependencies fixed for build: ${packagePath}`));
283
+ }
284
+
285
+ // Restore workspace dependencies after build
286
+ function restoreWorkspaceDependenciesAfterBuild(packagePath) {
287
+ console.log(chalk.blue(`šŸ”„ Restoring workspace dependencies after build: ${packagePath}...`));
288
+
289
+ const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
290
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
291
+
292
+ // Restore dependencies
293
+ const depTypes = ['dependencies', 'devDependencies', 'peerDependencies'];
294
+
295
+ depTypes.forEach(depType => {
296
+ if (pkg[depType]) {
297
+ Object.keys(pkg[depType]).forEach(depName => {
298
+ // Check if this is one of our internal packages referenced with file:
299
+ const depPackage = packages.find(p => p.name === depName);
300
+ if (depPackage && pkg[depType][depName].startsWith('file:')) {
301
+ console.log(chalk.gray(` Restoring ${depName} to workspace dependency`));
302
+ pkg[depType][depName] = 'workspace:*';
303
+ }
304
+ });
305
+ }
306
+ });
307
+
308
+ // Write the updated package.json
309
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
310
+ console.log(chalk.green(`āœ… Workspace dependencies restored after build: ${packagePath}`));
311
+ }
312
+
313
+ // Fix workspace dependencies for publish
314
+ function fixWorkspaceDependenciesForPublish(packagePath, packageVersions) {
315
+ console.log(chalk.blue(`šŸ”§ Fixing workspace dependencies for publish: ${packagePath}...`));
316
+
317
+ const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
318
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
319
+
320
+ // Fix dependencies
321
+ const depTypes = ['dependencies', 'devDependencies', 'peerDependencies'];
322
+
323
+ depTypes.forEach(depType => {
324
+ if (pkg[depType]) {
325
+ Object.keys(pkg[depType]).forEach(depName => {
326
+ // Check if it's a workspace dependency
327
+ if (pkg[depType][depName] === 'workspace:*' || pkg[depType][depName].startsWith('workspace:')) {
328
+ // Replace with actual version
329
+ const depPackage = packages.find(p => p.name === depName);
330
+ if (depPackage && packageVersions[depName]) {
331
+ console.log(chalk.gray(` Replacing ${depName} workspace dependency with version ${packageVersions[depName]}`));
332
+ pkg[depType][depName] = packageVersions[depName];
333
+ }
334
+ }
335
+ });
336
+ }
337
+ });
338
+
339
+ // Write the updated package.json
340
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
341
+ console.log(chalk.green(`āœ… Workspace dependencies fixed for publish: ${packagePath}`));
342
+ }
343
+
344
+ // Restore workspace dependencies after publish
345
+ function restoreWorkspaceDependenciesAfterPublish(packagePath) {
346
+ console.log(chalk.blue(`šŸ”„ Restoring workspace dependencies for ${packagePath}...`));
347
+
348
+ const pkgPath = path.join(process.cwd(), packagePath, 'package.json');
349
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
350
+
351
+ // Restore dependencies
352
+ const depTypes = ['dependencies', 'devDependencies', 'peerDependencies'];
353
+
354
+ depTypes.forEach(depType => {
355
+ if (pkg[depType]) {
356
+ Object.keys(pkg[depType]).forEach(depName => {
357
+ // Check if this is one of our internal packages
358
+ const depPackage = packages.find(p => p.name === depName);
359
+ if (depPackage) {
360
+ console.log(chalk.gray(` Restoring ${depName} to workspace dependency`));
361
+ pkg[depType][depName] = 'workspace:*';
362
+ }
363
+ });
364
+ }
365
+ });
366
+
367
+ // Write the updated package.json
368
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
369
+ console.log(chalk.green(`āœ… Workspace dependencies restored for ${packagePath}`));
370
+ }
371
+
372
+ // Build package
373
+ function buildPackage(pkg) {
374
+ console.log(chalk.blue(`\nšŸ”Ø Building ${pkg.name} (${pkg.description})...`));
375
+
376
+ try {
377
+ // Fix workspace dependencies for build
378
+ fixWorkspaceDependenciesForBuild(pkg.path);
379
+
380
+ try {
381
+ if (pkg.path === 'config') {
382
+ execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
383
+ } else if (pkg.path === 'server') {
384
+ execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
385
+ } else if (pkg.path === 'client') {
386
+ execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
387
+ } else if (pkg.path === 'toolkit') {
388
+ execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
389
+ } else if (pkg.path === 'templates/hello-world' || pkg.path === 'templates/twentytwentyfive') {
390
+ // Templates don't need to be built, just validate package.json
391
+ console.log(chalk.gray(' Templates do not require building, skipping...'));
392
+ }
393
+ console.log(chalk.green(`āœ… ${pkg.name} built successfully`));
394
+ } finally {
395
+ // Always restore workspace dependencies
396
+ restoreWorkspaceDependenciesAfterBuild(pkg.path);
397
+ }
398
+ } catch (error) {
399
+ console.log(chalk.red(`āŒ Failed to build ${pkg.name}`));
400
+ throw error;
401
+ }
402
+ }
403
+
404
+ // Publish package
405
+ function publishPackage(packagePath, packageName, tag = 'latest') {
406
+ console.log(chalk.blue(`\nšŸš€ Publishing ${packageName} with tag ${tag}...`));
407
+
408
+ try {
409
+ const command = `pnpm publish --access public --tag ${tag} --registry https://registry.npmjs.org --no-git-checks`;
410
+ execSync(command, { cwd: path.join(process.cwd(), packagePath), stdio: 'inherit' });
411
+ console.log(chalk.green(`āœ… ${packageName} published successfully!`));
412
+ } catch (error) {
413
+ console.log(chalk.red(`āŒ Failed to publish ${packageName}`));
414
+ throw error;
415
+ }
416
+ }
417
+
418
+ // Create GitHub release
419
+ function createGitHubRelease(tagName, releaseNotes) {
420
+ console.log(chalk.blue(`\nšŸ“ Creating GitHub release ${tagName}...`));
421
+
422
+ try {
423
+ // Create release using GitHub CLI if available
424
+ const command = `gh release create ${tagName} --title "${tagName}" --notes "${releaseNotes}"`;
425
+ execSync(command, { stdio: 'inherit' });
426
+ console.log(chalk.green(`āœ… GitHub release ${tagName} created successfully!`));
427
+ } catch (error) {
428
+ console.log(chalk.yellow(`āš ļø Failed to create GitHub release (GitHub CLI may not be installed or configured)`));
429
+ console.log(chalk.gray('You can manually create the release at: https://github.com/fecommunity/reactpress/releases/new'));
430
+ }
431
+ }
432
+
433
+ // Check environment
434
+ function checkEnvironment() {
435
+ // Check if pnpm is installed
436
+ try {
437
+ execSync('pnpm --version', { stdio: 'ignore' });
438
+ } catch (error) {
439
+ console.log(chalk.red('āŒ pnpm is not installed. Please install pnpm first.'));
440
+ return false;
441
+ }
442
+
443
+ // Check if logged in to npm
444
+ try {
445
+ execSync('pnpm whoami --registry https://registry.npmjs.org', { stdio: 'ignore' });
446
+ } catch (error) {
447
+ console.log(chalk.red('āŒ Not logged in to npm. Please run "pnpm login --registry https://registry.npmjs.org" first.'));
448
+ return false;
449
+ }
450
+
451
+ return true;
452
+ }
453
+
454
+ // Build packages function
455
+ async function buildPackages() {
456
+ console.log(chalk.blue('šŸ—ļø ReactPress Package Builder\n'));
457
+
458
+ // Show current versions
459
+ console.log(chalk.cyan('šŸ“‹ Current package versions:'));
460
+ packages.forEach(pkg => {
461
+ const version = getCurrentVersion(pkg.path);
462
+ console.log(chalk.gray(` ${pkg.name}: ${version}`));
463
+ });
464
+ console.log();
465
+
466
+ try {
467
+ // Track which packages actually need to be built
468
+ const packagesToBuild = [];
469
+
470
+ // Check for meaningful changes in each package
471
+ for (const pkg of packages) {
472
+ if (fs.existsSync(path.join(process.cwd(), pkg.path))) {
473
+ if (hasMeaningfulChangesForBuild(pkg.path, pkg.name)) {
474
+ packagesToBuild.push(pkg);
475
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be built`));
476
+ } else {
477
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
478
+ }
479
+ } else {
480
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
481
+ }
482
+ }
483
+
484
+ if (packagesToBuild.length === 0) {
485
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to build!'));
486
+ return;
487
+ }
488
+
489
+ // Build packages that have changes
490
+ for (const pkg of packagesToBuild) {
491
+ await buildPackage(pkg);
492
+ // Save the hash after successful build
493
+ savePackageHashForBuild(pkg.path, pkg.name);
494
+ }
495
+
496
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToBuild.length} package(s) built successfully!`));
497
+ } catch (error) {
498
+ console.error(chalk.red('āŒ Build failed:'), error);
499
+ process.exit(1);
500
+ }
501
+ }
502
+
503
+ // Publish packages function
504
+ async function publishPackages() {
505
+ // Check if called with --no-build flag
506
+ const noBuild = process.argv.includes('--no-build');
507
+
508
+ console.log(chalk.blue('šŸ“¦ ReactPress Package Publisher\n'));
509
+
510
+ // Run environment checks
511
+ if (!checkEnvironment()) {
512
+ process.exit(1);
513
+ }
514
+
515
+ // Show current versions
516
+ console.log(chalk.cyan('šŸ“‹ Current package versions:'));
517
+ packages.forEach(pkg => {
518
+ const version = getCurrentVersion(pkg.path);
519
+ console.log(chalk.gray(` ${pkg.name}: ${version}`));
520
+ });
521
+ console.log();
522
+
523
+ // Ask for publishing options
524
+ const { action } = await inquirer.prompt([
525
+ {
526
+ type: 'list',
527
+ name: 'action',
528
+ message: 'What would you like to do?',
529
+ choices: [
530
+ { name: 'šŸš€ Publish all packages with version bump', value: 'publish-all' },
531
+ { name: 'šŸ“¦ Publish specific package', value: 'publish-one' },
532
+ { name: 'šŸ”Ø Build all packages only', value: 'build-all' },
533
+ { name: 'šŸ·ļø Publish as beta/alpha', value: 'publish-prerelease' },
534
+ { name: 'āŒ Cancel', value: 'cancel' }
535
+ ]
536
+ }
537
+ ]);
538
+
539
+ if (action === 'cancel') {
540
+ console.log(chalk.yellow('Operation cancelled.'));
541
+ return;
542
+ }
543
+
544
+ if (action === 'build-all') {
545
+ console.log(chalk.blue('šŸ”Ø Building all packages...\n'));
546
+
547
+ // Track which packages actually need to be built
548
+ const packagesToBuild = [];
549
+
550
+ // Check for meaningful changes in each package
551
+ for (const pkg of packages) {
552
+ if (fs.existsSync(path.join(process.cwd(), pkg.path))) {
553
+ if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) {
554
+ packagesToBuild.push(pkg);
555
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be built`));
556
+ } else {
557
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
558
+ }
559
+ } else {
560
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
561
+ }
562
+ }
563
+
564
+ if (packagesToBuild.length === 0) {
565
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to build!'));
566
+ return;
567
+ }
568
+
569
+ // Build packages that have changes
570
+ for (const pkg of packagesToBuild) {
571
+ buildPackage(pkg);
572
+ // Save the hash after successful build
573
+ savePackageHashForPublish(pkg.path, pkg.name);
574
+ }
575
+
576
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToBuild.length} package(s) built successfully!`));
577
+ return;
578
+ }
579
+
580
+ if (action === 'publish-one') {
581
+ const { selectedPackage } = await inquirer.prompt([
582
+ {
583
+ type: 'list',
584
+ name: 'selectedPackage',
585
+ message: 'Which package would you like to publish?',
586
+ choices: packages.map(pkg => ({
587
+ name: `${pkg.name} (${pkg.description})`,
588
+ value: pkg
589
+ }))
590
+ }
591
+ ]);
592
+
593
+ // Check if the selected package has meaningful changes
594
+ if (!hasMeaningfulChangesForPublish(selectedPackage.path, selectedPackage.name)) {
595
+ console.log(chalk.gray(`\nā­ļø ${selectedPackage.name} has no meaningful changes, skipping...`));
596
+ console.log(chalk.green('āœ… Nothing to publish!'));
597
+ return;
598
+ }
599
+
600
+ const { versionType } = await inquirer.prompt([
601
+ {
602
+ type: 'list',
603
+ name: 'versionType',
604
+ message: 'Version bump type:',
605
+ choices: [
606
+ { name: 'Beta (1.0.0-beta.1 -> 1.0.0-beta.2)', value: 'beta' },
607
+ { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' },
608
+ { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' },
609
+ { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' },
610
+ { name: 'Custom version', value: 'custom' }
611
+ ]
612
+ }
613
+ ]);
614
+
615
+ let newVersion;
616
+ const currentVersion = getCurrentVersion(selectedPackage.path);
617
+
618
+ if (versionType === 'custom') {
619
+ const { customVersion } = await inquirer.prompt([
620
+ {
621
+ type: 'input',
622
+ name: 'customVersion',
623
+ message: `Enter new version for ${selectedPackage.name} (current: ${currentVersion}):`,
624
+ validate: (input) => {
625
+ const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/;
626
+ return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)';
627
+ }
628
+ }
629
+ ]);
630
+ newVersion = customVersion;
631
+ } else {
632
+ newVersion = getNextAvailableVersion(selectedPackage.name, currentVersion, versionType);
633
+ }
634
+
635
+ // Get all package versions for dependency resolution
636
+ const packageVersions = {};
637
+ packages.forEach(pkg => {
638
+ packageVersions[pkg.name] = getCurrentVersion(pkg.path);
639
+ });
640
+ // Update the selected package version
641
+ packageVersions[selectedPackage.name] = newVersion;
642
+
643
+ // Fix workspace dependencies before publishing
644
+ fixWorkspaceDependenciesForPublish(selectedPackage.path, packageVersions);
645
+
646
+ try {
647
+ updateVersion(selectedPackage.path, newVersion);
648
+ // Only build if not disabled
649
+ if (!noBuild) {
650
+ buildPackage(selectedPackage);
651
+ }
652
+
653
+ // Determine tag based on version type
654
+ const tag = versionType === 'beta' ? 'beta' : 'latest';
655
+ publishPackage(selectedPackage.path, selectedPackage.name, tag);
656
+
657
+ // Save the hash after successful publish
658
+ savePackageHashForPublish(selectedPackage.path, selectedPackage.name);
659
+
660
+ console.log(chalk.green(`\nšŸŽ‰ ${selectedPackage.name} v${newVersion} published successfully!`));
661
+ } finally {
662
+ // Always restore workspace dependencies
663
+ restoreWorkspaceDependenciesAfterPublish(selectedPackage.path);
664
+ }
665
+
666
+ return;
667
+ }
668
+
669
+ if (action === 'publish-prerelease') {
670
+ const { tag } = await inquirer.prompt([
671
+ {
672
+ type: 'list',
673
+ name: 'tag',
674
+ message: 'Select prerelease tag:',
675
+ choices: ['beta', 'alpha', 'rc', 'next']
676
+ }
677
+ ]);
678
+
679
+ const { versionType } = await inquirer.prompt([
680
+ {
681
+ type: 'list',
682
+ name: 'versionType',
683
+ message: 'Version bump type:',
684
+ choices: [
685
+ { name: `Prerelease (${tag})`, value: tag },
686
+ { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' },
687
+ { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' },
688
+ { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' },
689
+ { name: 'Custom version', value: 'custom' }
690
+ ]
691
+ }
692
+ ]);
693
+
694
+ // Get all package versions for dependency resolution
695
+ const packageVersions = {};
696
+ packages.forEach(pkg => {
697
+ const currentVersion = getCurrentVersion(pkg.path);
698
+ packageVersions[pkg.name] = currentVersion;
699
+ });
700
+
701
+ // Track which packages actually need to be published
702
+ const packagesToPublish = [];
703
+
704
+ // Check for meaningful changes in each package
705
+ for (const pkg of packages) {
706
+ if (!fs.existsSync(path.join(process.cwd(), pkg.path))) {
707
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
708
+ continue;
709
+ }
710
+
711
+ if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) {
712
+ packagesToPublish.push(pkg);
713
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be published`));
714
+ } else {
715
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
716
+ }
717
+ }
718
+
719
+ if (packagesToPublish.length === 0) {
720
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to publish!'));
721
+ return;
722
+ }
723
+
724
+ // Process each package that has changes
725
+ for (const pkg of packagesToPublish) {
726
+ let newVersion;
727
+ const currentVersion = getCurrentVersion(pkg.path);
728
+
729
+ if (versionType === 'custom') {
730
+ const { customVersion } = await inquirer.prompt([
731
+ {
732
+ type: 'input',
733
+ name: 'customVersion',
734
+ message: `Enter version for ${pkg.name} (current: ${currentVersion}):`,
735
+ default: currentVersion
736
+ }
737
+ ]);
738
+ newVersion = customVersion;
739
+ } else {
740
+ newVersion = getNextAvailableVersion(pkg.name, currentVersion, versionType);
741
+ }
742
+
743
+ // Update package version in our tracking
744
+ packageVersions[pkg.name] = newVersion;
745
+
746
+ // Fix workspace dependencies before publishing
747
+ fixWorkspaceDependenciesForPublish(pkg.path, packageVersions);
748
+
749
+ try {
750
+ updateVersion(pkg.path, newVersion);
751
+ // Only build if not disabled
752
+ if (!noBuild) {
753
+ buildPackage(pkg);
754
+ }
755
+ publishPackage(pkg.path, pkg.name, tag);
756
+
757
+ // Save the hash after successful publish
758
+ savePackageHashForPublish(pkg.path, pkg.name);
759
+ } finally {
760
+ // Always restore workspace dependencies
761
+ restoreWorkspaceDependenciesAfterPublish(pkg.path);
762
+ }
763
+ }
764
+
765
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToPublish.length} package(s) published with ${tag} tag!`));
766
+ return;
767
+ }
768
+
769
+ if (action === 'publish-all') {
770
+ // Check if we're on master branch for final release
771
+ let isMasterBranch = false;
772
+ try {
773
+ const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
774
+ isMasterBranch = branch === 'master' || branch === 'main';
775
+ } catch (error) {
776
+ console.log(chalk.yellow('āš ļø Unable to determine current branch'));
777
+ }
778
+
779
+ const { versionType } = await inquirer.prompt([
780
+ {
781
+ type: 'list',
782
+ name: 'versionType',
783
+ message: 'Version bump type:',
784
+ choices: [
785
+ { name: `Beta ${isMasterBranch ? '(will publish as final)' : '(will publish as beta)'}`, value: 'beta' },
786
+ { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' },
787
+ { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' },
788
+ { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' },
789
+ { name: 'Custom version', value: 'custom' }
790
+ ]
791
+ }
792
+ ]);
793
+
794
+ // Get all package versions for dependency resolution
795
+ const packageVersions = {};
796
+ const originalVersions = {};
797
+
798
+ packages.forEach(pkg => {
799
+ const currentVersion = getCurrentVersion(pkg.path);
800
+ originalVersions[pkg.name] = currentVersion;
801
+ packageVersions[pkg.name] = currentVersion;
802
+ });
803
+
804
+ let baseVersion;
805
+ if (versionType === 'custom') {
806
+ const { customVersion } = await inquirer.prompt([
807
+ {
808
+ type: 'input',
809
+ name: 'customVersion',
810
+ message: 'Enter new version for all packages:',
811
+ validate: (input) => {
812
+ const semverRegex = /^\d+\.\d+\.\d+$/;
813
+ return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)';
814
+ }
815
+ }
816
+ ]);
817
+ baseVersion = customVersion;
818
+ } else {
819
+ // Use the highest current version as base and increment
820
+ const nextVersion = getNextAvailableVersion(packages[0].name, originalVersions[packages[0].name], versionType);
821
+ baseVersion = nextVersion;
822
+ }
823
+
824
+ console.log(chalk.cyan(`\nšŸ“‹ Will publish all packages with version: ${baseVersion}\n`));
825
+
826
+ const { confirm } = await inquirer.prompt([
827
+ {
828
+ type: 'confirm',
829
+ name: 'confirm',
830
+ message: 'Are you sure you want to proceed?',
831
+ default: false
832
+ }
833
+ ]);
834
+
835
+ if (!confirm) {
836
+ console.log(chalk.yellow('Operation cancelled.'));
837
+ return;
838
+ }
839
+
840
+ // Track which packages actually need to be published
841
+ const packagesToPublish = [];
842
+
843
+ // Check for meaningful changes in each package
844
+ for (const pkg of packages) {
845
+ if (!fs.existsSync(path.join(process.cwd(), pkg.path))) {
846
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
847
+ continue;
848
+ }
849
+
850
+ if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) {
851
+ packagesToPublish.push(pkg);
852
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be published`));
853
+ } else {
854
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
855
+ }
856
+ }
857
+
858
+ if (packagesToPublish.length === 0) {
859
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to publish!'));
860
+ return;
861
+ }
862
+
863
+ // Update versions, build and publish only packages with changes
864
+ for (const pkg of packagesToPublish) {
865
+ console.log(chalk.blue(`\nšŸ“¦ Processing ${pkg.name}...`));
866
+
867
+ // For publish-all, we use the same version for all packages
868
+ const pkgVersion = baseVersion;
869
+ packageVersions[pkg.name] = pkgVersion;
870
+
871
+ // Fix workspace dependencies before publishing
872
+ fixWorkspaceDependenciesForPublish(pkg.path, packageVersions);
873
+
874
+ try {
875
+ updateVersion(pkg.path, pkgVersion);
876
+ // Only build if not disabled
877
+ if (!noBuild) {
878
+ buildPackage(pkg);
879
+ }
880
+
881
+ // Determine tag based on version type and branch
882
+ const tag = (versionType === 'beta' && !isMasterBranch) ? 'beta' : 'latest';
883
+ publishPackage(pkg.path, pkg.name, tag);
884
+
885
+ // Save the hash after successful publish
886
+ savePackageHashForPublish(pkg.path, pkg.name);
887
+ } finally {
888
+ // Always restore workspace dependencies
889
+ restoreWorkspaceDependenciesAfterPublish(pkg.path);
890
+ }
891
+ }
892
+
893
+ // Create GitHub release if on master and we actually published something
894
+ if (isMasterBranch && packagesToPublish.length > 0) {
895
+ const tagName = `v${baseVersion}`;
896
+ const releaseNotes = `Release ${baseVersion}
897
+
898
+ Packages released:
899
+ ${Object.entries(packageVersions).map(([name, version]) => `- ${name}@${version}`).join('\n')}`;
900
+ createGitHubRelease(tagName, releaseNotes);
901
+ }
902
+
903
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToPublish.length} package(s) published successfully with version ${baseVersion}!`));
904
+ console.log(chalk.cyan('\nšŸ“‹ Next steps:'));
905
+ console.log(chalk.gray('1. Create a git tag: git tag v' + baseVersion));
906
+ console.log(chalk.gray('2. Push changes: git push && git push --tags'));
907
+ }
908
+ }
909
+
910
+ // Main function
911
+ async function main() {
912
+ // Check command line arguments
913
+ const args = process.argv.slice(2);
914
+
915
+ if (args.includes('--publish')) {
916
+ // When called with --publish, start the publish process
917
+ console.log(chalk.blue('šŸ—ļø ReactPress Package Builder\n'));
918
+
919
+ // Show current versions
920
+ console.log(chalk.cyan('šŸ“‹ Current package versions:'));
921
+ packages.forEach(pkg => {
922
+ const version = getCurrentVersion(pkg.path);
923
+ console.log(chalk.gray(` ${pkg.name}: ${version}`));
924
+ });
925
+ console.log();
926
+
927
+ // Start the publish process
928
+ await publishPackages();
929
+ } else if (args.includes('--build')) {
930
+ // When called with --build, just build packages
931
+ await buildPackages();
932
+ } else {
933
+ // Default behavior - show help
934
+ console.log(chalk.blue('šŸ—ļø ReactPress CLI\n'));
935
+ console.log('Usage:');
936
+ console.log(' node scripts/reactpress-cli.js --build Build all packages');
937
+ console.log(' node scripts/reactpress-cli.js --publish Publish packages (interactive)');
938
+ console.log('');
939
+ }
940
+ }
941
+
942
+ main().catch(error => {
943
+ console.error(chalk.red('āŒ Operation failed:'), error);
944
+ process.exit(1);
945
+ });