create-react-on-rails-app 16.4.0 → 16.5.0

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/lib/create-app.js CHANGED
@@ -9,6 +9,7 @@ exports.createApp = createApp;
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const fs_1 = __importDefault(require("fs"));
11
11
  const utils_js_1 = require("./utils.js");
12
+ const DOCS_URL = 'https://reactonrails.com/docs/';
12
13
  function cleanupAppDirectory(appPath, appName, cleanupSuccessMessage, cleanupFallbackMessage) {
13
14
  if (!fs_1.default.existsSync(appPath)) {
14
15
  return;
@@ -52,23 +53,100 @@ function buildGeneratorArgs(options) {
52
53
  if (options.rspack) {
53
54
  args.push('--rspack');
54
55
  }
56
+ // --rsc supersedes --pro because RSC mode already requires Pro and the generator accepts a single mode flag.
57
+ if (options.pro && !options.rsc) {
58
+ args.push('--pro');
59
+ }
55
60
  if (options.rsc) {
56
61
  args.push('--rsc');
57
62
  }
63
+ // --force makes the generator overwrite conflicting files without prompting,
64
+ // which is safe for a freshly scaffolded app with no custom content yet.
65
+ args.push('--force');
66
+ // The newly created app directory is not a git repo yet, so the generator's
67
+ // uncommitted-changes check would always warn. --ignore-warnings bypasses all
68
+ // generator validation warnings (git, Node version, package manager) which is
69
+ // acceptable for a fresh app where prerequisites were already checked above.
58
70
  args.push('--ignore-warnings');
59
71
  return args;
60
72
  }
73
+ function packageManagerFieldValue(packageManager) {
74
+ const version = (0, utils_js_1.getCommandVersion)(packageManager)?.replace(/^v/, '');
75
+ if (!version) {
76
+ (0, utils_js_1.logInfo)(`Could not detect ${packageManager} version; package.json "packageManager" field will omit the version.`);
77
+ }
78
+ return version ? `${packageManager}@${version}` : packageManager;
79
+ }
80
+ function updateJsonFile(filePath, updater) {
81
+ if (!fs_1.default.existsSync(filePath)) {
82
+ return;
83
+ }
84
+ let json;
85
+ try {
86
+ json = JSON.parse(fs_1.default.readFileSync(filePath, 'utf8'));
87
+ }
88
+ catch (error) {
89
+ const message = error instanceof Error ? error.message : 'invalid JSON';
90
+ throw new Error(`Could not parse ${filePath}: ${message}`);
91
+ }
92
+ const updatedJson = updater(json);
93
+ fs_1.default.writeFileSync(filePath, `${JSON.stringify(updatedJson, null, 2)}\n`, 'utf8');
94
+ }
95
+ function rewriteFileIfPresent(filePath, transform) {
96
+ if (!fs_1.default.existsSync(filePath)) {
97
+ return;
98
+ }
99
+ const original = fs_1.default.readFileSync(filePath, 'utf8');
100
+ const updated = transform(original);
101
+ if (updated !== original) {
102
+ fs_1.default.writeFileSync(filePath, updated, 'utf8');
103
+ }
104
+ }
105
+ function normalizeGeneratedPackageManager(appPath, packageManager) {
106
+ if (packageManager !== 'pnpm') {
107
+ return;
108
+ }
109
+ (0, utils_js_1.logInfo)('Normalizing generated app for pnpm...');
110
+ const packageJsonPath = path_1.default.join(appPath, 'package.json');
111
+ const packageLockPath = path_1.default.join(appPath, 'package-lock.json');
112
+ const setupPath = path_1.default.join(appPath, 'bin', 'setup');
113
+ updateJsonFile(packageJsonPath, (json) => ({
114
+ ...json,
115
+ packageManager: packageManagerFieldValue(packageManager),
116
+ }));
117
+ if (fs_1.default.existsSync(packageLockPath)) {
118
+ (0, utils_js_1.execLiveArgs)('pnpm', ['import'], appPath);
119
+ (0, utils_js_1.execLiveArgs)('pnpm', ['install'], appPath);
120
+ fs_1.default.rmSync(packageLockPath, { force: true });
121
+ }
122
+ else {
123
+ (0, utils_js_1.execLiveArgs)('pnpm', ['install'], appPath);
124
+ }
125
+ rewriteFileIfPresent(setupPath, (contents) => {
126
+ // Match both system!("npm install") and system("npm install") in bin/setup,
127
+ // preserving the bang (!) if present.
128
+ const updated = contents.replace(/system(!?)\((["'])npm install\2\)/g, 'system$1($2pnpm install$2)');
129
+ if (updated === contents && /(?<![p])npm install/.test(contents)) {
130
+ (0, utils_js_1.logInfo)('Could not auto-update bin/setup for pnpm. Replace "npm install" with "pnpm install" manually.');
131
+ }
132
+ return updated;
133
+ });
134
+ (0, utils_js_1.logStepDone)('pnpm configuration applied');
135
+ }
61
136
  function printSuccessMessage(appName, route) {
62
137
  console.log('');
63
138
  (0, utils_js_1.logSuccess)(`Created ${appName} with React on Rails!`);
64
139
  console.log('');
65
140
  (0, utils_js_1.logInfo)('Next steps:');
66
141
  console.log(` cd ${appName}`);
142
+ console.log(' bin/rails db:prepare');
67
143
  console.log(' bin/dev');
68
144
  console.log('');
145
+ (0, utils_js_1.logInfo)('Note: The generated app uses PostgreSQL by default. Start PostgreSQL before running bin/rails db:prepare.');
146
+ console.log('');
69
147
  (0, utils_js_1.logInfo)(`Then visit http://localhost:3000/${route}`);
70
148
  console.log('');
71
- (0, utils_js_1.logInfo)('Documentation: https://www.shakacode.com/react-on-rails/docs/');
149
+ (0, utils_js_1.logInfo)(`Documentation: ${DOCS_URL}`);
72
150
  console.log('');
73
151
  }
74
152
  function validateAppName(name) {
@@ -96,11 +174,19 @@ function validateAppName(name) {
96
174
  }
97
175
  function createApp(appName, options) {
98
176
  const appPath = path_1.default.resolve(process.cwd(), appName);
177
+ const proRequested = options.pro || options.rsc;
178
+ let proModeLabel = null;
179
+ if (options.rsc) {
180
+ proModeLabel = '--rsc';
181
+ }
182
+ else if (options.pro) {
183
+ proModeLabel = '--pro';
184
+ }
99
185
  const baseSteps = 3; // rails new + add react_on_rails + run generator
100
- const totalSteps = baseSteps + (options.rsc ? 1 : 0);
186
+ const totalSteps = baseSteps + (proRequested ? 1 : 0);
101
187
  let currentStep = 1;
102
188
  const reactOnRailsGemPath = localGemPath('REACT_ON_RAILS_GEM_PATH');
103
- const reactOnRailsProGemPath = options.rsc ? localGemPath('REACT_ON_RAILS_PRO_GEM_PATH') : null;
189
+ const reactOnRailsProGemPath = proRequested ? localGemPath('REACT_ON_RAILS_PRO_GEM_PATH') : null;
104
190
  // Step 1: Create Rails application
105
191
  // appName is validated by validateAppName() to be ^[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*$ only,
106
192
  // so it's always a simple directory name safe to use with rails new.
@@ -136,9 +222,9 @@ function createApp(appName, options) {
136
222
  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.`);
137
223
  process.exit(1);
138
224
  }
139
- if (options.rsc) {
225
+ if (proRequested) {
140
226
  currentStep += 1;
141
- (0, utils_js_1.logStep)(currentStep, totalSteps, 'Adding react_on_rails_pro gem (--rsc)...');
227
+ (0, utils_js_1.logStep)(currentStep, totalSteps, `Adding react_on_rails_pro gem (${proModeLabel})...`);
142
228
  try {
143
229
  const reactOnRailsProArgs = bundleAddArgs('react_on_rails_pro', reactOnRailsProGemPath, false);
144
230
  if (reactOnRailsProGemPath) {
@@ -148,11 +234,11 @@ function createApp(appName, options) {
148
234
  (0, utils_js_1.logStepDone)('react_on_rails_pro gem added');
149
235
  }
150
236
  catch (error) {
151
- (0, utils_js_1.logError)('Failed to add react_on_rails_pro gem required by --rsc.');
237
+ (0, utils_js_1.logError)(`Failed to add react_on_rails_pro gem required by ${proModeLabel}.`);
152
238
  if (error instanceof Error && error.message) {
153
239
  console.error(`Debug info: ${error.message}`);
154
240
  }
155
- 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.`);
241
+ cleanupAppDirectory(appPath, appName, `Directory removed. Ensure react_on_rails_pro is installable in your Bundler/RubyGems setup, then rerun with ${proModeLabel}.`, `Ensure react_on_rails_pro is installable, then delete the created "${appName}" directory and rerun with ${proModeLabel}.`);
156
242
  process.exit(1);
157
243
  }
158
244
  }
@@ -161,7 +247,10 @@ function createApp(appName, options) {
161
247
  currentStep += 1;
162
248
  (0, utils_js_1.logStep)(currentStep, totalSteps, 'Running React on Rails generator...');
163
249
  try {
164
- (0, utils_js_1.execLiveArgs)('bundle', ['exec', 'rails', 'generate', 'react_on_rails:install', ...generatorArgs], appPath);
250
+ (0, utils_js_1.execLiveArgs)('bundle', ['exec', 'rails', 'generate', 'react_on_rails:install', ...generatorArgs], appPath, {
251
+ ...process.env,
252
+ REACT_ON_RAILS_PACKAGE_MANAGER: options.packageManager,
253
+ });
165
254
  (0, utils_js_1.logStepDone)('React on Rails setup complete');
166
255
  }
167
256
  catch (error) {
@@ -172,6 +261,23 @@ function createApp(appName, options) {
172
261
  cleanupAppDirectory(appPath, appName, 'Directory removed. Fix the generator issue and rerun.', `Delete the created "${appName}" directory and rerun once the generator issue is resolved.`);
173
262
  process.exit(1);
174
263
  }
264
+ try {
265
+ normalizeGeneratedPackageManager(appPath, options.packageManager);
266
+ }
267
+ catch (error) {
268
+ (0, utils_js_1.logError)(`Failed to finish ${options.packageManager} setup. The app was created, but package manager normalization did not complete.`);
269
+ if (error instanceof Error && error.message) {
270
+ console.error(`Debug info: ${error.message}`);
271
+ }
272
+ (0, utils_js_1.logInfo)('To finish manually:');
273
+ if (options.packageManager === 'pnpm') {
274
+ console.log(` cd ${appName}`);
275
+ console.log(' pnpm import');
276
+ console.log(' rm -f package-lock.json');
277
+ console.log(' pnpm install');
278
+ }
279
+ process.exit(1);
280
+ }
175
281
  // Final success
176
282
  (0, utils_js_1.logStepDone)('Done!');
177
283
  printSuccessMessage(appName, options.rsc ? 'hello_server' : 'hello_world');
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
+ pro: Boolean(rawOpts.pro),
34
35
  rsc: Boolean(rawOpts.rsc),
35
36
  };
36
37
  console.log('');
@@ -41,10 +42,14 @@ function run(appName, rawOpts) {
41
42
  (0, utils_js_1.logError)(nameValidation.error ?? 'Invalid app name');
42
43
  process.exit(1);
43
44
  }
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/');
45
+ if (options.rsc && options.pro) {
46
+ (0, utils_js_1.logInfo)('Note: --rsc takes precedence over --pro; --pro will be ignored.');
47
+ }
48
+ if (options.rsc || options.pro) {
49
+ const modeFlag = options.rsc ? '--rsc' : '--pro';
50
+ (0, utils_js_1.logInfo)(`Note: ${modeFlag} installs react_on_rails_pro and requires that gem to be installable.`);
51
+ (0, utils_js_1.logInfo)(`If installation fails, verify your Bundler/RubyGems setup for react_on_rails_pro, then rerun with ${modeFlag}.`);
52
+ (0, utils_js_1.logInfo)('Pro setup docs: https://reactonrails.com/docs/pro/installation/');
48
53
  console.log('');
49
54
  }
50
55
  (0, utils_js_1.logInfo)('Checking prerequisites...');
@@ -67,7 +72,14 @@ function run(appName, rawOpts) {
67
72
  process.exit(1);
68
73
  }
69
74
  console.log('');
70
- (0, utils_js_1.logInfo)(`Creating "${appName}" with template: ${options.template}, package manager: ${options.packageManager}${options.rspack ? ', bundler: rspack' : ''}${options.rsc ? ', mode: rsc' : ''}`);
75
+ let modeLabel = '';
76
+ if (options.rsc) {
77
+ modeLabel = ', mode: rsc';
78
+ }
79
+ else if (options.pro) {
80
+ modeLabel = ', mode: pro';
81
+ }
82
+ (0, utils_js_1.logInfo)(`Creating "${appName}" with template: ${options.template}, package manager: ${options.packageManager}${options.rspack ? ', bundler: rspack' : ''}${modeLabel}`);
71
83
  (0, create_app_js_1.createApp)(appName, options);
72
84
  }
73
85
  const program = new commander_1.Command();
@@ -81,28 +93,31 @@ program
81
93
  .option('-t, --template <type>', 'javascript or typescript', 'typescript')
82
94
  .option('-p, --package-manager <pm>', 'npm or pnpm (auto-detected if not specified)')
83
95
  .option('--rspack', 'Use Rspack instead of Webpack (~20x faster builds)', false)
96
+ .option('--pro', 'Generate React on Rails Pro setup (installs react_on_rails_pro)', false)
84
97
  .option('--rsc', 'Generate React Server Components setup (installs react_on_rails_pro)', false)
85
98
  .addHelpText('after', `
86
99
  Examples:
87
100
  $ npx create-react-on-rails-app my-app
88
101
  $ npx create-react-on-rails-app my-app --template javascript
89
102
  $ npx create-react-on-rails-app my-app --rspack
103
+ $ npx create-react-on-rails-app my-app --pro
90
104
  $ npx create-react-on-rails-app my-app --rsc
105
+ $ npx create-react-on-rails-app my-app --rspack --pro
91
106
  $ npx create-react-on-rails-app my-app --rspack --rsc
92
107
  $ npx create-react-on-rails-app my-app --package-manager pnpm
93
108
 
94
109
  What it does:
95
110
  1. Creates a new Rails app with PostgreSQL
96
- 2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro for --rsc)
111
+ 2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro for --pro/--rsc)
97
112
  3. Runs the React on Rails generator (Shakapacker, components, webpack config)
98
113
 
99
114
  After setup, run bin/dev and visit:
100
- - http://localhost:3000/hello_world (default)
115
+ - http://localhost:3000/hello_world (default and --pro)
101
116
  - http://localhost:3000/hello_server (--rsc)
102
117
 
103
- --rsc supports both JavaScript and TypeScript templates.
118
+ --pro and --rsc support both JavaScript and TypeScript templates.
104
119
 
105
- Documentation: https://www.shakacode.com/react-on-rails/docs/`)
120
+ Documentation: https://reactonrails.com/docs/`)
106
121
  .action((appName, opts) => {
107
122
  try {
108
123
  run(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
+ pro: boolean;
5
6
  rsc: boolean;
6
7
  }
7
8
  export interface ValidationResult {
package/lib/utils.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * - This uses `spawnSync` with `shell: false`, so command and args are passed as literal tokens.
6
6
  * - Callers must still ensure `command` and `args` come from trusted/validated values.
7
7
  */
8
- export declare function execLiveArgs(command: string, args: string[], cwd?: string): void;
8
+ export declare function execLiveArgs(command: string, args: string[], cwd?: string, env?: NodeJS.ProcessEnv): void;
9
9
  export declare function getCommandVersion(command: string): string | null;
10
10
  export declare function detectPackageManager(): 'npm' | 'pnpm' | null;
11
11
  export declare function logStep(current: number, total: number, message: string): void;
package/lib/utils.js CHANGED
@@ -20,10 +20,11 @@ const chalk_1 = __importDefault(require("chalk"));
20
20
  * - This uses `spawnSync` with `shell: false`, so command and args are passed as literal tokens.
21
21
  * - Callers must still ensure `command` and `args` come from trusted/validated values.
22
22
  */
23
- function execLiveArgs(command, args, cwd) {
23
+ function execLiveArgs(command, args, cwd, env) {
24
24
  const result = (0, child_process_1.spawnSync)(command, args, {
25
25
  stdio: 'inherit',
26
26
  cwd,
27
+ ...(env ? { env } : {}),
27
28
  });
28
29
  if (result.error) {
29
30
  throw result.error;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-react-on-rails-app",
3
- "version": "16.4.0",
3
+ "version": "16.5.0",
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"