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 +114 -8
- package/lib/index.js +24 -9
- package/lib/types.d.ts +1 -0
- package/lib/utils.d.ts +1 -1
- package/lib/utils.js +2 -1
- package/package.json +1 -1
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)(
|
|
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 + (
|
|
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 =
|
|
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 (
|
|
225
|
+
if (proRequested) {
|
|
140
226
|
currentStep += 1;
|
|
141
|
-
(0, utils_js_1.logStep)(currentStep, totalSteps,
|
|
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)(
|
|
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,
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
|
118
|
+
--pro and --rsc support both JavaScript and TypeScript templates.
|
|
104
119
|
|
|
105
|
-
Documentation: https://
|
|
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
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