create-react-on-rails-app 16.4.0-rc.5 → 16.4.0-rc.7

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.
@@ -1,4 +1,5 @@
1
1
  import { CliOptions } from './types.js';
2
+ export declare function buildGeneratorArgs(options: CliOptions): string[];
2
3
  export declare function validateAppName(name: string): {
3
4
  success: boolean;
4
5
  error?: string;
package/lib/create-app.js CHANGED
@@ -3,12 +3,47 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildGeneratorArgs = buildGeneratorArgs;
6
7
  exports.validateAppName = validateAppName;
7
8
  exports.createApp = createApp;
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const fs_1 = __importDefault(require("fs"));
10
11
  const utils_js_1 = require("./utils.js");
11
- const TOTAL_STEPS = 4;
12
+ function cleanupAppDirectory(appPath, appName, cleanupSuccessMessage, cleanupFallbackMessage) {
13
+ if (!fs_1.default.existsSync(appPath)) {
14
+ return;
15
+ }
16
+ (0, utils_js_1.logInfo)(`Cleaning up "${appName}" directory...`);
17
+ try {
18
+ fs_1.default.rmSync(appPath, { recursive: true, force: true });
19
+ (0, utils_js_1.logInfo)(cleanupSuccessMessage);
20
+ }
21
+ catch {
22
+ (0, utils_js_1.logError)(cleanupFallbackMessage);
23
+ }
24
+ }
25
+ function localGemPath(envVarName) {
26
+ const value = process.env[envVarName];
27
+ if (!value || value.trim() === '') {
28
+ return null;
29
+ }
30
+ const resolvedPath = path_1.default.resolve(value);
31
+ if (!fs_1.default.existsSync(resolvedPath)) {
32
+ (0, utils_js_1.logError)(`Local gem path from ${envVarName} does not exist: ${resolvedPath}`);
33
+ process.exit(1);
34
+ }
35
+ return resolvedPath;
36
+ }
37
+ function bundleAddArgs(gemName, localPath, strict = true) {
38
+ const args = ['add', gemName];
39
+ if (localPath) {
40
+ args.push('--path', localPath);
41
+ }
42
+ else if (strict) {
43
+ args.push('--strict');
44
+ }
45
+ return args;
46
+ }
12
47
  function buildGeneratorArgs(options) {
13
48
  const args = [];
14
49
  if (options.template === 'typescript') {
@@ -17,10 +52,13 @@ function buildGeneratorArgs(options) {
17
52
  if (options.rspack) {
18
53
  args.push('--rspack');
19
54
  }
55
+ if (options.rsc) {
56
+ args.push('--rsc');
57
+ }
20
58
  args.push('--ignore-warnings');
21
59
  return args;
22
60
  }
23
- function printSuccessMessage(appName) {
61
+ function printSuccessMessage(appName, route) {
24
62
  console.log('');
25
63
  (0, utils_js_1.logSuccess)(`Created ${appName} with React on Rails!`);
26
64
  console.log('');
@@ -28,7 +66,7 @@ function printSuccessMessage(appName) {
28
66
  console.log(` cd ${appName}`);
29
67
  console.log(' bin/dev');
30
68
  console.log('');
31
- (0, utils_js_1.logInfo)('Then visit http://localhost:3000/hello_world');
69
+ (0, utils_js_1.logInfo)(`Then visit http://localhost:3000/${route}`);
32
70
  console.log('');
33
71
  (0, utils_js_1.logInfo)('Documentation: https://www.shakacode.com/react-on-rails/docs/');
34
72
  console.log('');
@@ -37,10 +75,10 @@ function validateAppName(name) {
37
75
  if (!name || name.trim() === '') {
38
76
  return { success: false, error: 'App name is required.' };
39
77
  }
40
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
78
+ if (!/^[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*$/.test(name)) {
41
79
  return {
42
80
  success: false,
43
- error: 'App name can only contain letters, numbers, hyphens, and underscores.',
81
+ error: 'App name must start with a letter, end with a letter or number, and may contain hyphens or underscores only between alphanumeric characters.',
44
82
  };
45
83
  }
46
84
  const appPath = path_1.default.resolve(process.cwd(), name);
@@ -54,10 +92,15 @@ function validateAppName(name) {
54
92
  }
55
93
  function createApp(appName, options) {
56
94
  const appPath = path_1.default.resolve(process.cwd(), appName);
95
+ const baseSteps = 3; // rails new + add react_on_rails + run generator
96
+ const totalSteps = baseSteps + (options.rsc ? 1 : 0);
97
+ let currentStep = 1;
98
+ const reactOnRailsGemPath = localGemPath('REACT_ON_RAILS_GEM_PATH');
99
+ const reactOnRailsProGemPath = options.rsc ? localGemPath('REACT_ON_RAILS_PRO_GEM_PATH') : null;
57
100
  // Step 1: Create Rails application
58
- // appName is validated by validateAppName() to be [a-zA-Z0-9_-]+ only,
101
+ // appName is validated by validateAppName() to be ^[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*$ only,
59
102
  // so it's always a simple directory name safe to use with rails new.
60
- (0, utils_js_1.logStep)(1, TOTAL_STEPS, 'Creating Rails application...');
103
+ (0, utils_js_1.logStep)(currentStep, totalSteps, 'Creating Rails application...');
61
104
  try {
62
105
  (0, utils_js_1.execLiveArgs)('rails', ['new', appName, '--database=postgresql', '--skip-javascript']);
63
106
  (0, utils_js_1.logStepDone)('Rails application created');
@@ -67,12 +110,18 @@ function createApp(appName, options) {
67
110
  if (error instanceof Error && error.message) {
68
111
  console.error(`Debug info: ${error.message}`);
69
112
  }
113
+ cleanupAppDirectory(appPath, appName, 'Directory removed. Fix the Rails app creation issue and rerun.', `Delete the created "${appName}" directory and rerun once the Rails app creation issue is resolved.`);
70
114
  process.exit(1);
71
115
  }
72
116
  // Step 2: Add react_on_rails gem
73
- (0, utils_js_1.logStep)(2, TOTAL_STEPS, 'Adding react_on_rails gem...');
117
+ currentStep += 1;
118
+ (0, utils_js_1.logStep)(currentStep, totalSteps, 'Adding react_on_rails gem...');
74
119
  try {
75
- (0, utils_js_1.execLiveArgs)('bundle', ['add', 'react_on_rails', '--strict'], appPath);
120
+ const reactOnRailsArgs = bundleAddArgs('react_on_rails', reactOnRailsGemPath);
121
+ if (reactOnRailsGemPath) {
122
+ (0, utils_js_1.logInfo)(`Using local react_on_rails gem path: ${reactOnRailsGemPath}`);
123
+ }
124
+ (0, utils_js_1.execLiveArgs)('bundle', reactOnRailsArgs, appPath);
76
125
  (0, utils_js_1.logStepDone)('react_on_rails gem added');
77
126
  }
78
127
  catch (error) {
@@ -80,11 +129,33 @@ function createApp(appName, options) {
80
129
  if (error instanceof Error && error.message) {
81
130
  console.error(`Debug info: ${error.message}`);
82
131
  }
132
+ cleanupAppDirectory(appPath, appName, 'Directory removed. Fix gem installation access or connectivity issues and rerun.', `Delete the created "${appName}" directory and rerun once gem installation issues are resolved.`);
83
133
  process.exit(1);
84
134
  }
85
- // Step 3: Run react_on_rails generator
135
+ if (options.rsc) {
136
+ currentStep += 1;
137
+ (0, utils_js_1.logStep)(currentStep, totalSteps, 'Adding react_on_rails_pro gem (--rsc)...');
138
+ try {
139
+ const reactOnRailsProArgs = bundleAddArgs('react_on_rails_pro', reactOnRailsProGemPath, false);
140
+ if (reactOnRailsProGemPath) {
141
+ (0, utils_js_1.logInfo)(`Using local react_on_rails_pro gem path: ${reactOnRailsProGemPath}`);
142
+ }
143
+ (0, utils_js_1.execLiveArgs)('bundle', reactOnRailsProArgs, appPath);
144
+ (0, utils_js_1.logStepDone)('react_on_rails_pro gem added');
145
+ }
146
+ catch (error) {
147
+ (0, utils_js_1.logError)('Failed to add react_on_rails_pro gem required by --rsc.');
148
+ if (error instanceof Error && error.message) {
149
+ console.error(`Debug info: ${error.message}`);
150
+ }
151
+ cleanupAppDirectory(appPath, appName, 'Directory removed. Ensure react_on_rails_pro is installable in your Bundler/RubyGems setup, then rerun with --rsc.', `Ensure react_on_rails_pro is installable, then delete the created "${appName}" directory and rerun with --rsc.`);
152
+ process.exit(1);
153
+ }
154
+ }
155
+ // Final generator step
86
156
  const generatorArgs = buildGeneratorArgs(options);
87
- (0, utils_js_1.logStep)(3, TOTAL_STEPS, 'Running React on Rails generator...');
157
+ currentStep += 1;
158
+ (0, utils_js_1.logStep)(currentStep, totalSteps, 'Running React on Rails generator...');
88
159
  try {
89
160
  (0, utils_js_1.execLiveArgs)('bundle', ['exec', 'rails', 'generate', 'react_on_rails:install', ...generatorArgs], appPath);
90
161
  (0, utils_js_1.logStepDone)('React on Rails setup complete');
@@ -94,10 +165,11 @@ function createApp(appName, options) {
94
165
  if (error instanceof Error && error.message) {
95
166
  console.error(`Debug info: ${error.message}`);
96
167
  }
168
+ cleanupAppDirectory(appPath, appName, 'Directory removed. Fix the generator issue and rerun.', `Delete the created "${appName}" directory and rerun once the generator issue is resolved.`);
97
169
  process.exit(1);
98
170
  }
99
- // Step 4: Success
100
- (0, utils_js_1.logStep)(4, TOTAL_STEPS, 'Done!');
101
- printSuccessMessage(appName);
171
+ // Final success
172
+ (0, utils_js_1.logStepDone)('Done!');
173
+ printSuccessMessage(appName, options.rsc ? 'hello_server' : 'hello_world');
102
174
  }
103
175
  //# sourceMappingURL=create-app.js.map
package/lib/index.js CHANGED
@@ -31,6 +31,7 @@ function run(appName, rawOpts) {
31
31
  template,
32
32
  packageManager: packageManager,
33
33
  rspack: Boolean(rawOpts.rspack),
34
+ rsc: Boolean(rawOpts.rsc),
34
35
  };
35
36
  console.log('');
36
37
  console.log(`${chalk_1.default.bold('create-react-on-rails-app')} v${packageJson.version}`);
@@ -40,6 +41,12 @@ function run(appName, rawOpts) {
40
41
  (0, utils_js_1.logError)(nameValidation.error ?? 'Invalid app name');
41
42
  process.exit(1);
42
43
  }
44
+ if (options.rsc) {
45
+ (0, utils_js_1.logInfo)('Note: --rsc installs react_on_rails_pro and requires that gem to be installable.');
46
+ (0, utils_js_1.logInfo)('If installation fails, verify your Bundler/RubyGems setup for react_on_rails_pro, then rerun with --rsc.');
47
+ (0, utils_js_1.logInfo)('Pro setup docs: https://www.shakacode.com/react-on-rails-pro/docs/installation/');
48
+ console.log('');
49
+ }
43
50
  (0, utils_js_1.logInfo)('Checking prerequisites...');
44
51
  const { allValid, results } = (0, validators_js_1.validateAll)(options.packageManager);
45
52
  for (const { name, result } of results) {
@@ -60,7 +67,7 @@ function run(appName, rawOpts) {
60
67
  process.exit(1);
61
68
  }
62
69
  console.log('');
63
- (0, utils_js_1.logInfo)(`Creating "${appName}" with template: ${options.template}, package manager: ${options.packageManager}${options.rspack ? ', bundler: rspack' : ''}`);
70
+ (0, utils_js_1.logInfo)(`Creating "${appName}" with template: ${options.template}, package manager: ${options.packageManager}${options.rspack ? ', bundler: rspack' : ''}${options.rsc ? ', mode: rsc' : ''}`);
64
71
  (0, create_app_js_1.createApp)(appName, options);
65
72
  }
66
73
  const program = new commander_1.Command();
@@ -74,19 +81,26 @@ program
74
81
  .option('-t, --template <type>', 'javascript or typescript', 'typescript')
75
82
  .option('-p, --package-manager <pm>', 'npm or pnpm (auto-detected if not specified)')
76
83
  .option('--rspack', 'Use Rspack instead of Webpack (~20x faster builds)', false)
84
+ .option('--rsc', 'Generate React Server Components setup (installs react_on_rails_pro)', false)
77
85
  .addHelpText('after', `
78
86
  Examples:
79
87
  $ npx create-react-on-rails-app my-app
80
88
  $ npx create-react-on-rails-app my-app --template javascript
81
89
  $ npx create-react-on-rails-app my-app --rspack
90
+ $ npx create-react-on-rails-app my-app --rsc
91
+ $ npx create-react-on-rails-app my-app --rspack --rsc
82
92
  $ npx create-react-on-rails-app my-app --package-manager pnpm
83
93
 
84
94
  What it does:
85
95
  1. Creates a new Rails app with PostgreSQL
86
- 2. Adds the react_on_rails gem
96
+ 2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro for --rsc)
87
97
  3. Runs the React on Rails generator (Shakapacker, components, webpack config)
88
98
 
89
- After setup, run bin/dev and visit http://localhost:3000/hello_world
99
+ After setup, run bin/dev and visit:
100
+ - http://localhost:3000/hello_world (default)
101
+ - http://localhost:3000/hello_server (--rsc)
102
+
103
+ --rsc supports both JavaScript and TypeScript templates.
90
104
 
91
105
  Documentation: https://www.shakacode.com/react-on-rails/docs/`)
92
106
  .action((appName, opts) => {
package/lib/types.d.ts CHANGED
@@ -2,6 +2,7 @@ export interface CliOptions {
2
2
  template: 'javascript' | 'typescript';
3
3
  packageManager: 'npm' | 'pnpm';
4
4
  rspack: boolean;
5
+ rsc: boolean;
5
6
  }
6
7
  export interface ValidationResult {
7
8
  valid: boolean;
package/lib/utils.d.ts CHANGED
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Execute a command and stream output to the current terminal.
3
+ *
4
+ * Security contract:
5
+ * - This uses `spawnSync` with `shell: false`, so command and args are passed as literal tokens.
6
+ * - Callers must still ensure `command` and `args` come from trusted/validated values.
7
+ */
1
8
  export declare function execLiveArgs(command: string, args: string[], cwd?: string): void;
2
9
  export declare function getCommandVersion(command: string): string | null;
3
10
  export declare function detectPackageManager(): 'npm' | 'pnpm' | null;
package/lib/utils.js CHANGED
@@ -13,6 +13,13 @@ exports.logSuccess = logSuccess;
13
13
  exports.logInfo = logInfo;
14
14
  const child_process_1 = require("child_process");
15
15
  const chalk_1 = __importDefault(require("chalk"));
16
+ /**
17
+ * Execute a command and stream output to the current terminal.
18
+ *
19
+ * Security contract:
20
+ * - This uses `spawnSync` with `shell: false`, so command and args are passed as literal tokens.
21
+ * - Callers must still ensure `command` and `args` come from trusted/validated values.
22
+ */
16
23
  function execLiveArgs(command, args, cwd) {
17
24
  const result = (0, child_process_1.spawnSync)(command, args, {
18
25
  stdio: 'inherit',
package/lib/validators.js CHANGED
@@ -9,6 +9,8 @@ const utils_js_1 = require("./utils.js");
9
9
  const MIN_NODE_VERSION = 18;
10
10
  const MIN_RUBY_MAJOR = 3;
11
11
  const MIN_RUBY_MINOR = 0;
12
+ const MIN_RAILS_MAJOR = 7;
13
+ const MIN_RAILS_MINOR = 0;
12
14
  function validateNode() {
13
15
  const version = process.versions.node;
14
16
  const major = parseInt(version.split('.')[0], 10);
@@ -64,7 +66,24 @@ function validateRails() {
64
66
  'Then try again.',
65
67
  };
66
68
  }
67
- return { valid: true, message: railsVersion.split('\n')[0].trim() };
69
+ const firstLine = railsVersion.split('\n')[0].trim();
70
+ const match = firstLine.match(/rails\s+(\d+)\.(\d+)/i);
71
+ if (!match) {
72
+ return {
73
+ valid: false,
74
+ message: `Could not parse Rails version from: "${firstLine}". Please ensure Rails ${MIN_RAILS_MAJOR}.${MIN_RAILS_MINOR}+ is installed.`,
75
+ };
76
+ }
77
+ const major = parseInt(match[1], 10);
78
+ const minor = parseInt(match[2], 10);
79
+ // minor check is 0 now; kept for parity with Ruby validator and easy bumping later
80
+ if (major < MIN_RAILS_MAJOR || (major === MIN_RAILS_MAJOR && minor < MIN_RAILS_MINOR)) {
81
+ return {
82
+ valid: false,
83
+ message: `${firstLine} detected. React on Rails requires Rails ${MIN_RAILS_MAJOR}.${MIN_RAILS_MINOR}+.`,
84
+ };
85
+ }
86
+ return { valid: true, message: firstLine };
68
87
  }
69
88
  function validatePackageManager(pm) {
70
89
  const version = (0, utils_js_1.getCommandVersion)(pm);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-react-on-rails-app",
3
- "version": "16.4.0-rc.5",
3
+ "version": "16.4.0-rc.7",
4
4
  "description": "Create React on Rails applications with a single command",
5
5
  "bin": {
6
6
  "create-react-on-rails-app": "./bin/create-react-on-rails-app.js"
@@ -44,7 +44,7 @@
44
44
  "scripts": {
45
45
  "build": "pnpm run clean && tsc",
46
46
  "build-watch": "pnpm run clean && tsc --watch",
47
- "clean": "rm -rf ./lib",
47
+ "clean": "node -e \"require('fs').rmSync('./lib',{recursive:true,force:true})\"",
48
48
  "test": "jest tests",
49
49
  "type-check": "tsc --noEmit --noErrorTruncation"
50
50
  }