@fecommunity/reactpress-cli 0.1.0 → 0.2.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.
Files changed (70) hide show
  1. package/README.md +7 -0
  2. package/bin/reactpress.js +246 -0
  3. package/lib/api-dev-runner.js +6 -0
  4. package/lib/api-dev.js +84 -0
  5. package/lib/bootstrap.js +109 -0
  6. package/lib/build.js +23 -0
  7. package/lib/dev.js +109 -0
  8. package/lib/docker.js +156 -0
  9. package/lib/http.js +80 -0
  10. package/lib/lifecycle.js +138 -0
  11. package/lib/paths.js +75 -0
  12. package/lib/pm2.js +31 -0
  13. package/lib/process.js +45 -0
  14. package/lib/publish.js +947 -0
  15. package/lib/root.js +31 -0
  16. package/lib/spawn.js +85 -0
  17. package/lib/status.js +54 -0
  18. package/package.json +33 -52
  19. package/ui/banner.js +16 -0
  20. package/ui/interactive.js +158 -0
  21. package/ui/theme.js +25 -0
  22. package/toolkit/dist/api/Article.d.ts +0 -123
  23. package/toolkit/dist/api/Article.js +0 -188
  24. package/toolkit/dist/api/Auth.d.ts +0 -32
  25. package/toolkit/dist/api/Auth.js +0 -58
  26. package/toolkit/dist/api/Category.d.ts +0 -51
  27. package/toolkit/dist/api/Category.js +0 -84
  28. package/toolkit/dist/api/Comment.d.ts +0 -60
  29. package/toolkit/dist/api/Comment.js +0 -97
  30. package/toolkit/dist/api/File.d.ts +0 -42
  31. package/toolkit/dist/api/File.js +0 -71
  32. package/toolkit/dist/api/HttpClient.d.ts +0 -41
  33. package/toolkit/dist/api/HttpClient.js +0 -96
  34. package/toolkit/dist/api/Knowledge.d.ts +0 -78
  35. package/toolkit/dist/api/Knowledge.js +0 -123
  36. package/toolkit/dist/api/Page.d.ts +0 -60
  37. package/toolkit/dist/api/Page.js +0 -97
  38. package/toolkit/dist/api/Search.d.ts +0 -32
  39. package/toolkit/dist/api/Search.js +0 -58
  40. package/toolkit/dist/api/Setting.d.ts +0 -24
  41. package/toolkit/dist/api/Setting.js +0 -45
  42. package/toolkit/dist/api/Smtp.d.ts +0 -33
  43. package/toolkit/dist/api/Smtp.js +0 -58
  44. package/toolkit/dist/api/Tag.d.ts +0 -60
  45. package/toolkit/dist/api/Tag.js +0 -97
  46. package/toolkit/dist/api/User.d.ts +0 -43
  47. package/toolkit/dist/api/User.js +0 -72
  48. package/toolkit/dist/api/View.d.ts +0 -51
  49. package/toolkit/dist/api/View.js +0 -84
  50. package/toolkit/dist/api/index.d.ts +0 -13
  51. package/toolkit/dist/api/index.js +0 -30
  52. package/toolkit/dist/api/instance.d.ts +0 -47
  53. package/toolkit/dist/api/instance.js +0 -71
  54. package/toolkit/dist/config/env.d.ts +0 -1
  55. package/toolkit/dist/config/env.js +0 -89
  56. package/toolkit/dist/config/global.d.ts +0 -34
  57. package/toolkit/dist/config/global.js +0 -382
  58. package/toolkit/dist/config/i18n.d.ts +0 -4
  59. package/toolkit/dist/config/i18n.js +0 -43
  60. package/toolkit/dist/config/index.d.ts +0 -15
  61. package/toolkit/dist/config/index.js +0 -25
  62. package/toolkit/dist/index.d.ts +0 -11
  63. package/toolkit/dist/index.js +0 -38
  64. package/toolkit/dist/locales/en.json +0 -97
  65. package/toolkit/dist/locales/zh.json +0 -97
  66. package/toolkit/dist/types/index.d.ts +0 -245
  67. package/toolkit/dist/types/index.js +0 -12
  68. package/toolkit/dist/utils/index.d.ts +0 -33
  69. package/toolkit/dist/utils/index.js +0 -59
  70. package/toolkit/package.json +0 -19
package/lib/publish.js ADDED
@@ -0,0 +1,947 @@
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: 'NestJS backend API'
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 === 'client') {
384
+ execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
385
+ } else if (pkg.path === 'toolkit') {
386
+ execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
387
+ } else if (pkg.path === 'templates/hello-world' || pkg.path === 'templates/twentytwentyfive') {
388
+ // Templates don't need to be built, just validate package.json
389
+ console.log(chalk.gray(' Templates do not require building, skipping...'));
390
+ }
391
+ console.log(chalk.green(`āœ… ${pkg.name} built successfully`));
392
+ } finally {
393
+ // Always restore workspace dependencies
394
+ restoreWorkspaceDependenciesAfterBuild(pkg.path);
395
+ }
396
+ } catch (error) {
397
+ console.log(chalk.red(`āŒ Failed to build ${pkg.name}`));
398
+ throw error;
399
+ }
400
+ }
401
+
402
+ // Publish package
403
+ function publishPackage(packagePath, packageName, tag = 'latest') {
404
+ console.log(chalk.blue(`\nšŸš€ Publishing ${packageName} with tag ${tag}...`));
405
+
406
+ try {
407
+ const command = `pnpm publish --access public --tag ${tag} --registry https://registry.npmjs.org --no-git-checks`;
408
+ execSync(command, { cwd: path.join(process.cwd(), packagePath), stdio: 'inherit' });
409
+ console.log(chalk.green(`āœ… ${packageName} published successfully!`));
410
+ } catch (error) {
411
+ console.log(chalk.red(`āŒ Failed to publish ${packageName}`));
412
+ throw error;
413
+ }
414
+ }
415
+
416
+ // Create GitHub release
417
+ function createGitHubRelease(tagName, releaseNotes) {
418
+ console.log(chalk.blue(`\nšŸ“ Creating GitHub release ${tagName}...`));
419
+
420
+ try {
421
+ // Create release using GitHub CLI if available
422
+ const command = `gh release create ${tagName} --title "${tagName}" --notes "${releaseNotes}"`;
423
+ execSync(command, { stdio: 'inherit' });
424
+ console.log(chalk.green(`āœ… GitHub release ${tagName} created successfully!`));
425
+ } catch (error) {
426
+ console.log(chalk.yellow(`āš ļø Failed to create GitHub release (GitHub CLI may not be installed or configured)`));
427
+ console.log(chalk.gray('You can manually create the release at: https://github.com/fecommunity/reactpress/releases/new'));
428
+ }
429
+ }
430
+
431
+ // Check environment
432
+ function checkEnvironment() {
433
+ // Check if pnpm is installed
434
+ try {
435
+ execSync('pnpm --version', { stdio: 'ignore' });
436
+ } catch (error) {
437
+ console.log(chalk.red('āŒ pnpm is not installed. Please install pnpm first.'));
438
+ return false;
439
+ }
440
+
441
+ // Check if logged in to npm
442
+ try {
443
+ execSync('pnpm whoami --registry https://registry.npmjs.org', { stdio: 'ignore' });
444
+ } catch (error) {
445
+ console.log(chalk.red('āŒ Not logged in to npm. Please run "pnpm login --registry https://registry.npmjs.org" first.'));
446
+ return false;
447
+ }
448
+
449
+ return true;
450
+ }
451
+
452
+ // Build packages function
453
+ async function buildPackages() {
454
+ console.log(chalk.blue('šŸ—ļø ReactPress Package Builder\n'));
455
+
456
+ // Show current versions
457
+ console.log(chalk.cyan('šŸ“‹ Current package versions:'));
458
+ packages.forEach(pkg => {
459
+ const version = getCurrentVersion(pkg.path);
460
+ console.log(chalk.gray(` ${pkg.name}: ${version}`));
461
+ });
462
+ console.log();
463
+
464
+ try {
465
+ // Track which packages actually need to be built
466
+ const packagesToBuild = [];
467
+
468
+ // Check for meaningful changes in each package
469
+ for (const pkg of packages) {
470
+ if (fs.existsSync(path.join(process.cwd(), pkg.path))) {
471
+ if (hasMeaningfulChangesForBuild(pkg.path, pkg.name)) {
472
+ packagesToBuild.push(pkg);
473
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be built`));
474
+ } else {
475
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
476
+ }
477
+ } else {
478
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
479
+ }
480
+ }
481
+
482
+ if (packagesToBuild.length === 0) {
483
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to build!'));
484
+ return;
485
+ }
486
+
487
+ // Build packages that have changes
488
+ for (const pkg of packagesToBuild) {
489
+ await buildPackage(pkg);
490
+ // Save the hash after successful build
491
+ savePackageHashForBuild(pkg.path, pkg.name);
492
+ }
493
+
494
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToBuild.length} package(s) built successfully!`));
495
+ } catch (error) {
496
+ console.error(chalk.red('āŒ Build failed:'), error);
497
+ process.exit(1);
498
+ }
499
+ }
500
+
501
+ // Publish packages function
502
+ async function publishPackages() {
503
+ // Check if called with --no-build flag
504
+ const noBuild = process.argv.includes('--no-build');
505
+
506
+ console.log(chalk.blue('šŸ“¦ ReactPress Package Publisher\n'));
507
+
508
+ // Run environment checks
509
+ if (!checkEnvironment()) {
510
+ process.exit(1);
511
+ }
512
+
513
+ // Show current versions
514
+ console.log(chalk.cyan('šŸ“‹ Current package versions:'));
515
+ packages.forEach(pkg => {
516
+ const version = getCurrentVersion(pkg.path);
517
+ console.log(chalk.gray(` ${pkg.name}: ${version}`));
518
+ });
519
+ console.log();
520
+
521
+ // Ask for publishing options
522
+ const { action } = await inquirer.prompt([
523
+ {
524
+ type: 'list',
525
+ name: 'action',
526
+ message: 'What would you like to do?',
527
+ choices: [
528
+ { name: 'šŸš€ Publish all packages with version bump', value: 'publish-all' },
529
+ { name: 'šŸ“¦ Publish specific package', value: 'publish-one' },
530
+ { name: 'šŸ”Ø Build all packages only', value: 'build-all' },
531
+ { name: 'šŸ·ļø Publish as beta/alpha', value: 'publish-prerelease' },
532
+ { name: 'āŒ Cancel', value: 'cancel' }
533
+ ]
534
+ }
535
+ ]);
536
+
537
+ if (action === 'cancel') {
538
+ console.log(chalk.yellow('Operation cancelled.'));
539
+ return;
540
+ }
541
+
542
+ if (action === 'build-all') {
543
+ console.log(chalk.blue('šŸ”Ø Building all packages...\n'));
544
+
545
+ // Track which packages actually need to be built
546
+ const packagesToBuild = [];
547
+
548
+ // Check for meaningful changes in each package
549
+ for (const pkg of packages) {
550
+ if (fs.existsSync(path.join(process.cwd(), pkg.path))) {
551
+ if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) {
552
+ packagesToBuild.push(pkg);
553
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be built`));
554
+ } else {
555
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
556
+ }
557
+ } else {
558
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
559
+ }
560
+ }
561
+
562
+ if (packagesToBuild.length === 0) {
563
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to build!'));
564
+ return;
565
+ }
566
+
567
+ // Build packages that have changes
568
+ for (const pkg of packagesToBuild) {
569
+ buildPackage(pkg);
570
+ // Save the hash after successful build
571
+ savePackageHashForPublish(pkg.path, pkg.name);
572
+ }
573
+
574
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToBuild.length} package(s) built successfully!`));
575
+ return;
576
+ }
577
+
578
+ if (action === 'publish-one') {
579
+ const { selectedPackage } = await inquirer.prompt([
580
+ {
581
+ type: 'list',
582
+ name: 'selectedPackage',
583
+ message: 'Which package would you like to publish?',
584
+ choices: packages.map(pkg => ({
585
+ name: `${pkg.name} (${pkg.description})`,
586
+ value: pkg
587
+ }))
588
+ }
589
+ ]);
590
+
591
+ // Check if the selected package has meaningful changes
592
+ if (!hasMeaningfulChangesForPublish(selectedPackage.path, selectedPackage.name)) {
593
+ console.log(chalk.gray(`\nā­ļø ${selectedPackage.name} has no meaningful changes, skipping...`));
594
+ console.log(chalk.green('āœ… Nothing to publish!'));
595
+ return;
596
+ }
597
+
598
+ const { versionType } = await inquirer.prompt([
599
+ {
600
+ type: 'list',
601
+ name: 'versionType',
602
+ message: 'Version bump type:',
603
+ choices: [
604
+ { name: 'Beta (1.0.0-beta.1 -> 1.0.0-beta.2)', value: 'beta' },
605
+ { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' },
606
+ { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' },
607
+ { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' },
608
+ { name: 'Custom version', value: 'custom' }
609
+ ]
610
+ }
611
+ ]);
612
+
613
+ let newVersion;
614
+ const currentVersion = getCurrentVersion(selectedPackage.path);
615
+
616
+ if (versionType === 'custom') {
617
+ const { customVersion } = await inquirer.prompt([
618
+ {
619
+ type: 'input',
620
+ name: 'customVersion',
621
+ message: `Enter new version for ${selectedPackage.name} (current: ${currentVersion}):`,
622
+ validate: (input) => {
623
+ const semverRegex = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/;
624
+ return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)';
625
+ }
626
+ }
627
+ ]);
628
+ newVersion = customVersion;
629
+ } else {
630
+ newVersion = getNextAvailableVersion(selectedPackage.name, currentVersion, versionType);
631
+ }
632
+
633
+ // Get all package versions for dependency resolution
634
+ const packageVersions = {};
635
+ packages.forEach(pkg => {
636
+ packageVersions[pkg.name] = getCurrentVersion(pkg.path);
637
+ });
638
+ // Update the selected package version
639
+ packageVersions[selectedPackage.name] = newVersion;
640
+
641
+ // Fix workspace dependencies before publishing
642
+ fixWorkspaceDependenciesForPublish(selectedPackage.path, packageVersions);
643
+
644
+ try {
645
+ updateVersion(selectedPackage.path, newVersion);
646
+ // Only build if not disabled
647
+ if (!noBuild) {
648
+ buildPackage(selectedPackage);
649
+ }
650
+
651
+ // Determine tag based on version type
652
+ const tag = versionType === 'beta' ? 'beta' : 'latest';
653
+ publishPackage(selectedPackage.path, selectedPackage.name, tag);
654
+
655
+ // Save the hash after successful publish
656
+ savePackageHashForPublish(selectedPackage.path, selectedPackage.name);
657
+
658
+ console.log(chalk.green(`\nšŸŽ‰ ${selectedPackage.name} v${newVersion} published successfully!`));
659
+ } finally {
660
+ // Always restore workspace dependencies
661
+ restoreWorkspaceDependenciesAfterPublish(selectedPackage.path);
662
+ }
663
+
664
+ return;
665
+ }
666
+
667
+ if (action === 'publish-prerelease') {
668
+ const { tag } = await inquirer.prompt([
669
+ {
670
+ type: 'list',
671
+ name: 'tag',
672
+ message: 'Select prerelease tag:',
673
+ choices: ['beta', 'alpha', 'rc', 'next']
674
+ }
675
+ ]);
676
+
677
+ const { versionType } = await inquirer.prompt([
678
+ {
679
+ type: 'list',
680
+ name: 'versionType',
681
+ message: 'Version bump type:',
682
+ choices: [
683
+ { name: `Prerelease (${tag})`, value: tag },
684
+ { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' },
685
+ { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' },
686
+ { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' },
687
+ { name: 'Custom version', value: 'custom' }
688
+ ]
689
+ }
690
+ ]);
691
+
692
+ // Get all package versions for dependency resolution
693
+ const packageVersions = {};
694
+ packages.forEach(pkg => {
695
+ const currentVersion = getCurrentVersion(pkg.path);
696
+ packageVersions[pkg.name] = currentVersion;
697
+ });
698
+
699
+ // Track which packages actually need to be published
700
+ const packagesToPublish = [];
701
+
702
+ // Check for meaningful changes in each package
703
+ for (const pkg of packages) {
704
+ if (!fs.existsSync(path.join(process.cwd(), pkg.path))) {
705
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
706
+ continue;
707
+ }
708
+
709
+ if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) {
710
+ packagesToPublish.push(pkg);
711
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be published`));
712
+ } else {
713
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
714
+ }
715
+ }
716
+
717
+ if (packagesToPublish.length === 0) {
718
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to publish!'));
719
+ return;
720
+ }
721
+
722
+ // Process each package that has changes
723
+ for (const pkg of packagesToPublish) {
724
+ let newVersion;
725
+ const currentVersion = getCurrentVersion(pkg.path);
726
+
727
+ if (versionType === 'custom') {
728
+ const { customVersion } = await inquirer.prompt([
729
+ {
730
+ type: 'input',
731
+ name: 'customVersion',
732
+ message: `Enter version for ${pkg.name} (current: ${currentVersion}):`,
733
+ default: currentVersion
734
+ }
735
+ ]);
736
+ newVersion = customVersion;
737
+ } else {
738
+ newVersion = getNextAvailableVersion(pkg.name, currentVersion, versionType);
739
+ }
740
+
741
+ // Update package version in our tracking
742
+ packageVersions[pkg.name] = newVersion;
743
+
744
+ // Fix workspace dependencies before publishing
745
+ fixWorkspaceDependenciesForPublish(pkg.path, packageVersions);
746
+
747
+ try {
748
+ updateVersion(pkg.path, newVersion);
749
+ // Only build if not disabled
750
+ if (!noBuild) {
751
+ buildPackage(pkg);
752
+ }
753
+ publishPackage(pkg.path, pkg.name, tag);
754
+
755
+ // Save the hash after successful publish
756
+ savePackageHashForPublish(pkg.path, pkg.name);
757
+ } finally {
758
+ // Always restore workspace dependencies
759
+ restoreWorkspaceDependenciesAfterPublish(pkg.path);
760
+ }
761
+ }
762
+
763
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToPublish.length} package(s) published with ${tag} tag!`));
764
+ return;
765
+ }
766
+
767
+ if (action === 'publish-all') {
768
+ // Check if we're on master branch for final release
769
+ let isMasterBranch = false;
770
+ try {
771
+ const branch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
772
+ isMasterBranch = branch === 'master' || branch === 'main';
773
+ } catch (error) {
774
+ console.log(chalk.yellow('āš ļø Unable to determine current branch'));
775
+ }
776
+
777
+ const { versionType } = await inquirer.prompt([
778
+ {
779
+ type: 'list',
780
+ name: 'versionType',
781
+ message: 'Version bump type:',
782
+ choices: [
783
+ { name: `Beta ${isMasterBranch ? '(will publish as final)' : '(will publish as beta)'}`, value: 'beta' },
784
+ { name: 'Patch (1.0.0 -> 1.0.1)', value: 'patch' },
785
+ { name: 'Minor (1.0.0 -> 1.1.0)', value: 'minor' },
786
+ { name: 'Major (1.0.0 -> 2.0.0)', value: 'major' },
787
+ { name: 'Custom version', value: 'custom' }
788
+ ]
789
+ }
790
+ ]);
791
+
792
+ // Get all package versions for dependency resolution
793
+ const packageVersions = {};
794
+ const originalVersions = {};
795
+
796
+ packages.forEach(pkg => {
797
+ const currentVersion = getCurrentVersion(pkg.path);
798
+ originalVersions[pkg.name] = currentVersion;
799
+ packageVersions[pkg.name] = currentVersion;
800
+ });
801
+
802
+ let baseVersion;
803
+ if (versionType === 'custom') {
804
+ const { customVersion } = await inquirer.prompt([
805
+ {
806
+ type: 'input',
807
+ name: 'customVersion',
808
+ message: 'Enter new version for all packages:',
809
+ validate: (input) => {
810
+ const semverRegex = /^\d+\.\d+\.\d+$/;
811
+ return semverRegex.test(input) || 'Please enter a valid semver version (e.g., 1.0.0)';
812
+ }
813
+ }
814
+ ]);
815
+ baseVersion = customVersion;
816
+ } else {
817
+ // Use the highest current version as base and increment
818
+ const nextVersion = getNextAvailableVersion(packages[0].name, originalVersions[packages[0].name], versionType);
819
+ baseVersion = nextVersion;
820
+ }
821
+
822
+ console.log(chalk.cyan(`\nšŸ“‹ Will publish all packages with version: ${baseVersion}\n`));
823
+
824
+ const { confirm } = await inquirer.prompt([
825
+ {
826
+ type: 'confirm',
827
+ name: 'confirm',
828
+ message: 'Are you sure you want to proceed?',
829
+ default: false
830
+ }
831
+ ]);
832
+
833
+ if (!confirm) {
834
+ console.log(chalk.yellow('Operation cancelled.'));
835
+ return;
836
+ }
837
+
838
+ // Track which packages actually need to be published
839
+ const packagesToPublish = [];
840
+
841
+ // Check for meaningful changes in each package
842
+ for (const pkg of packages) {
843
+ if (!fs.existsSync(path.join(process.cwd(), pkg.path))) {
844
+ console.log(chalk.yellow(`āš ļø Package ${pkg.name} directory not found, skipping...`));
845
+ continue;
846
+ }
847
+
848
+ if (hasMeaningfulChangesForPublish(pkg.path, pkg.name)) {
849
+ packagesToPublish.push(pkg);
850
+ console.log(chalk.blue(`\nšŸ“¦ ${pkg.name} has changes, will be published`));
851
+ } else {
852
+ console.log(chalk.gray(`\nā­ļø ${pkg.name} has no meaningful changes, skipping...`));
853
+ }
854
+ }
855
+
856
+ if (packagesToPublish.length === 0) {
857
+ console.log(chalk.green('\nāœ… No packages have meaningful changes. Nothing to publish!'));
858
+ return;
859
+ }
860
+
861
+ // Update versions, build and publish only packages with changes
862
+ for (const pkg of packagesToPublish) {
863
+ console.log(chalk.blue(`\nšŸ“¦ Processing ${pkg.name}...`));
864
+
865
+ // For publish-all, we use the same version for all packages
866
+ const pkgVersion = baseVersion;
867
+ packageVersions[pkg.name] = pkgVersion;
868
+
869
+ // Fix workspace dependencies before publishing
870
+ fixWorkspaceDependenciesForPublish(pkg.path, packageVersions);
871
+
872
+ try {
873
+ updateVersion(pkg.path, pkgVersion);
874
+ // Only build if not disabled
875
+ if (!noBuild) {
876
+ buildPackage(pkg);
877
+ }
878
+
879
+ // Determine tag based on version type and branch
880
+ const tag = (versionType === 'beta' && !isMasterBranch) ? 'beta' : 'latest';
881
+ publishPackage(pkg.path, pkg.name, tag);
882
+
883
+ // Save the hash after successful publish
884
+ savePackageHashForPublish(pkg.path, pkg.name);
885
+ } finally {
886
+ // Always restore workspace dependencies
887
+ restoreWorkspaceDependenciesAfterPublish(pkg.path);
888
+ }
889
+ }
890
+
891
+ // Create GitHub release if on master and we actually published something
892
+ if (isMasterBranch && packagesToPublish.length > 0) {
893
+ const tagName = `v${baseVersion}`;
894
+ const releaseNotes = `Release ${baseVersion}
895
+
896
+ Packages released:
897
+ ${Object.entries(packageVersions).map(([name, version]) => `- ${name}@${version}`).join('\n')}`;
898
+ createGitHubRelease(tagName, releaseNotes);
899
+ }
900
+
901
+ console.log(chalk.green(`\nšŸŽ‰ ${packagesToPublish.length} package(s) published successfully with version ${baseVersion}!`));
902
+ console.log(chalk.cyan('\nšŸ“‹ Next steps:'));
903
+ console.log(chalk.gray('1. Create a git tag: git tag v' + baseVersion));
904
+ console.log(chalk.gray('2. Push changes: git push && git push --tags'));
905
+ }
906
+ }
907
+
908
+ // Main function
909
+ async function main() {
910
+ // Check command line arguments
911
+ const args = process.argv.slice(2);
912
+
913
+ if (args.includes('--publish')) {
914
+ // When called with --publish, start the publish process
915
+ console.log(chalk.blue('šŸ—ļø ReactPress Package Builder\n'));
916
+
917
+ // Show current versions
918
+ console.log(chalk.cyan('šŸ“‹ Current package versions:'));
919
+ packages.forEach(pkg => {
920
+ const version = getCurrentVersion(pkg.path);
921
+ console.log(chalk.gray(` ${pkg.name}: ${version}`));
922
+ });
923
+ console.log();
924
+
925
+ // Start the publish process
926
+ await publishPackages();
927
+ } else if (args.includes('--build')) {
928
+ // When called with --build, just build packages
929
+ await buildPackages();
930
+ } else {
931
+ // Default behavior - show help
932
+ console.log(chalk.blue('šŸ—ļø ReactPress CLI\n'));
933
+ console.log('Usage:');
934
+ console.log(' reactpress publish --build Build all packages');
935
+ console.log(' reactpress publish --publish Publish packages (interactive)');
936
+ console.log('');
937
+ }
938
+ }
939
+
940
+ module.exports = { main, buildPackages, publishPackages };
941
+
942
+ if (require.main === module) {
943
+ main().catch((error) => {
944
+ console.error(chalk.red('āŒ Operation failed:'), error);
945
+ process.exit(1);
946
+ });
947
+ }