create-react-on-rails-app 17.0.0-rc.0 → 17.0.0-rc.10
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 +32 -11
- package/lib/index.js +88 -62
- package/lib/mode.d.ts +33 -0
- package/lib/mode.js +49 -0
- package/lib/types.d.ts +3 -0
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +70 -6
- package/lib/validators.js +2 -2
- package/package.json +3 -3
- package/lib/prompt.d.ts +0 -7
- package/lib/prompt.js +0 -72
package/lib/create-app.js
CHANGED
|
@@ -123,11 +123,20 @@ function localGemPath(envVarName) {
|
|
|
123
123
|
}
|
|
124
124
|
return resolvedPath;
|
|
125
125
|
}
|
|
126
|
-
function
|
|
126
|
+
function rubygemsPrereleaseVersion(cliVersion) {
|
|
127
|
+
if (!cliVersion || !cliVersion.includes('-')) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
return cliVersion.replace('-', '.');
|
|
131
|
+
}
|
|
132
|
+
function bundleAddArgs(gemName, localPath, strict = true, version = null) {
|
|
127
133
|
const args = ['add', gemName];
|
|
128
134
|
if (localPath) {
|
|
129
135
|
args.push('--path', localPath);
|
|
130
136
|
}
|
|
137
|
+
else if (version) {
|
|
138
|
+
args.push('--version', `= ${version}`);
|
|
139
|
+
}
|
|
131
140
|
else if (strict) {
|
|
132
141
|
args.push('--strict');
|
|
133
142
|
}
|
|
@@ -138,8 +147,12 @@ function buildGeneratorArgs(options) {
|
|
|
138
147
|
if (options.template === 'typescript') {
|
|
139
148
|
args.push('--typescript');
|
|
140
149
|
}
|
|
141
|
-
|
|
142
|
-
|
|
150
|
+
// Always forward an explicit --rspack / --no-rspack so the generator never falls back to
|
|
151
|
+
// its own default. This keeps create-app's resolved choice authoritative regardless of what
|
|
152
|
+
// the generator's fresh-install default happens to be (now, or if it changes again later).
|
|
153
|
+
args.push(options.rspack ? '--rspack' : '--no-rspack');
|
|
154
|
+
if (options.tailwind) {
|
|
155
|
+
args.push('--tailwind');
|
|
143
156
|
}
|
|
144
157
|
// --rsc supersedes --pro because RSC mode already requires Pro and the generator accepts a single mode flag.
|
|
145
158
|
if (options.pro && !options.rsc) {
|
|
@@ -148,6 +161,11 @@ function buildGeneratorArgs(options) {
|
|
|
148
161
|
if (options.rsc) {
|
|
149
162
|
args.push('--rsc');
|
|
150
163
|
}
|
|
164
|
+
// Agent files default ON in the generator, so only forward the opt-out. This keeps
|
|
165
|
+
// the install generator the single source of truth for the AGENTS.md template.
|
|
166
|
+
if (!options.agentFiles) {
|
|
167
|
+
args.push('--no-agent-files');
|
|
168
|
+
}
|
|
151
169
|
// --force makes the generator overwrite conflicting files without prompting,
|
|
152
170
|
// which is safe for a freshly scaffolded app with no custom content yet.
|
|
153
171
|
args.push('--force');
|
|
@@ -309,21 +327,23 @@ function reactOnRailsProCommitMessage(modeFlag) {
|
|
|
309
327
|
function generatorCommitMessage(options) {
|
|
310
328
|
const language = options.template === 'typescript' ? 'TypeScript' : 'JavaScript';
|
|
311
329
|
const bundler = options.rspack ? 'Rspack' : 'Webpack';
|
|
330
|
+
const setup = options.tailwind ? `${language}, ${bundler}, and Tailwind CSS` : `${language} and ${bundler}`;
|
|
331
|
+
const tailwindFlag = options.tailwind ? ' --tailwind' : '';
|
|
312
332
|
if (options.rsc) {
|
|
313
333
|
return {
|
|
314
|
-
subject: `Install React Server Components with ${
|
|
315
|
-
body:
|
|
334
|
+
subject: `Install React Server Components with ${setup}`,
|
|
335
|
+
body: `Run react_on_rails:install --rsc${tailwindFlag} to add the generated home page, HelloServer example, and Pro RSC wiring.`,
|
|
316
336
|
};
|
|
317
337
|
}
|
|
318
338
|
if (options.pro) {
|
|
319
339
|
return {
|
|
320
|
-
subject: `Install React on Rails Pro with ${
|
|
321
|
-
body:
|
|
340
|
+
subject: `Install React on Rails Pro with ${setup}`,
|
|
341
|
+
body: `Run react_on_rails:install --pro${tailwindFlag} to add the generated home page, SSR example, and Pro Node renderer wiring.`,
|
|
322
342
|
};
|
|
323
343
|
}
|
|
324
344
|
return {
|
|
325
|
-
subject: `Install React on Rails with ${
|
|
326
|
-
body:
|
|
345
|
+
subject: `Install React on Rails with ${setup}`,
|
|
346
|
+
body: `Run react_on_rails:install${tailwindFlag} to add the generated home page, SSR example, and development workflow.`,
|
|
327
347
|
};
|
|
328
348
|
}
|
|
329
349
|
function pnpmCommitMessage() {
|
|
@@ -394,6 +414,7 @@ function createApp(appName, options) {
|
|
|
394
414
|
let currentStep = 1;
|
|
395
415
|
const reactOnRailsGemPath = localGemPath('REACT_ON_RAILS_GEM_PATH');
|
|
396
416
|
const reactOnRailsProGemPath = proRequested ? localGemPath('REACT_ON_RAILS_PRO_GEM_PATH') : null;
|
|
417
|
+
const prereleaseGemVersion = rubygemsPrereleaseVersion(options.cliVersion);
|
|
397
418
|
function educationalGitEnvForCommits() {
|
|
398
419
|
if (!cachedEducationalGitEnv) {
|
|
399
420
|
// The baseline commit happens before git init; in that case git config falls back
|
|
@@ -440,7 +461,7 @@ function createApp(appName, options) {
|
|
|
440
461
|
currentStep += 1;
|
|
441
462
|
(0, utils_js_1.logStep)(currentStep, totalSteps, 'Adding react_on_rails gem...');
|
|
442
463
|
try {
|
|
443
|
-
const reactOnRailsArgs = bundleAddArgs('react_on_rails', reactOnRailsGemPath);
|
|
464
|
+
const reactOnRailsArgs = bundleAddArgs('react_on_rails', reactOnRailsGemPath, true, prereleaseGemVersion);
|
|
444
465
|
if (reactOnRailsGemPath) {
|
|
445
466
|
(0, utils_js_1.logInfo)(`Using local react_on_rails gem path: ${reactOnRailsGemPath}`);
|
|
446
467
|
}
|
|
@@ -461,7 +482,7 @@ function createApp(appName, options) {
|
|
|
461
482
|
currentStep += 1;
|
|
462
483
|
(0, utils_js_1.logStep)(currentStep, totalSteps, `Adding react_on_rails_pro gem (${proModeLabel})...`);
|
|
463
484
|
try {
|
|
464
|
-
const reactOnRailsProArgs = bundleAddArgs('react_on_rails_pro', reactOnRailsProGemPath, false);
|
|
485
|
+
const reactOnRailsProArgs = bundleAddArgs('react_on_rails_pro', reactOnRailsProGemPath, false, prereleaseGemVersion);
|
|
465
486
|
if (reactOnRailsProGemPath) {
|
|
466
487
|
(0, utils_js_1.logInfo)(`Using local react_on_rails_pro gem path: ${reactOnRailsProGemPath}`);
|
|
467
488
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.ready = void 0;
|
|
7
4
|
const commander_1 = require("commander");
|
|
8
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
5
|
const validators_js_1 = require("./validators.js");
|
|
10
6
|
const create_app_js_1 = require("./create-app.js");
|
|
7
|
+
const mode_js_1 = require("./mode.js");
|
|
11
8
|
const utils_js_1 = require("./utils.js");
|
|
12
|
-
const prompt_js_1 = require("./prompt.js");
|
|
13
9
|
// Use require() for CJS compatibility - avoids __dirname + fs.readFileSync
|
|
14
10
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, global-require
|
|
15
11
|
const packageJson = require('../package.json');
|
|
16
|
-
|
|
17
|
-
const { template } = rawOpts;
|
|
18
|
-
if (typeof
|
|
19
|
-
(0, utils_js_1.logError)(`Invalid template "${String(
|
|
12
|
+
function run(appName, rawOpts, command) {
|
|
13
|
+
const { template: rawTemplate } = rawOpts;
|
|
14
|
+
if (typeof rawTemplate !== 'string' || (rawTemplate !== 'javascript' && rawTemplate !== 'typescript')) {
|
|
15
|
+
(0, utils_js_1.logError)(`Invalid template "${String(rawTemplate)}". Must be "javascript" or "typescript".`);
|
|
20
16
|
process.exit(1);
|
|
17
|
+
return;
|
|
21
18
|
}
|
|
19
|
+
const template = rawTemplate;
|
|
22
20
|
let packageManager = rawOpts.packageManager;
|
|
23
21
|
if (packageManager) {
|
|
24
22
|
if (packageManager !== 'npm' && packageManager !== 'pnpm') {
|
|
@@ -29,58 +27,74 @@ async function run(appName, rawOpts) {
|
|
|
29
27
|
else {
|
|
30
28
|
packageManager = (0, utils_js_1.detectPackageManager)() ?? 'npm';
|
|
31
29
|
}
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
let setupMode;
|
|
31
|
+
try {
|
|
32
|
+
setupMode = (0, mode_js_1.resolveSetupMode)(rawOpts);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
(0, utils_js_1.logError)(error instanceof Error ? error.message : String(error));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// --webpack is a friendly alias for --no-rspack. Commander reports --no-rspack's declared
|
|
40
|
+
// default as rawOpts.rspack === true, so we use the option source to distinguish an explicit
|
|
41
|
+
// --rspack from the default and reject contradictory flags (--rspack --webpack).
|
|
42
|
+
const rspackExplicitlyTrue = rawOpts.rspack === true && command?.getOptionValueSource('rspack') === 'cli';
|
|
43
|
+
const webpackRequested = rawOpts.webpack === true;
|
|
44
|
+
if (webpackRequested && rspackExplicitlyTrue) {
|
|
45
|
+
(0, utils_js_1.logError)('Conflicting bundler flags: pass either --rspack or --webpack (alias for --no-rspack), not both.');
|
|
34
46
|
process.exit(1);
|
|
35
47
|
}
|
|
36
|
-
let pro = Boolean(rawOpts.pro);
|
|
37
|
-
let rsc = Boolean(rawOpts.rsc);
|
|
38
48
|
console.log('');
|
|
39
|
-
console.log(`${
|
|
49
|
+
console.log(`${utils_js_1.pc.bold('create-react-on-rails-app')} v${packageJson.version}`);
|
|
40
50
|
console.log('');
|
|
41
51
|
const nameValidation = (0, create_app_js_1.validateAppName)(appName);
|
|
42
52
|
if (!nameValidation.success) {
|
|
43
53
|
(0, utils_js_1.logError)(nameValidation.error ?? 'Invalid app name');
|
|
44
54
|
process.exit(1);
|
|
45
55
|
}
|
|
46
|
-
// When no mode flag is explicitly passed, prompt interactively (TTY only).
|
|
47
|
-
// Non-interactive environments (CI, pipes) fall back to standard mode.
|
|
48
|
-
const modeExplicit = rawOpts.pro !== undefined || rawOpts.rsc !== undefined || rawOpts.standard !== undefined;
|
|
49
|
-
if (!modeExplicit) {
|
|
50
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
51
|
-
const choice = await (0, prompt_js_1.promptForMode)();
|
|
52
|
-
pro = choice.pro;
|
|
53
|
-
rsc = choice.rsc;
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
(0, utils_js_1.logInfo)('No mode flag specified and not running interactively (stdin/stdout is not a TTY); using standard mode.');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
56
|
const options = {
|
|
60
57
|
template,
|
|
61
58
|
packageManager: packageManager,
|
|
62
|
-
rspack
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
// Rspack is the default; an explicit --no-rspack or its --webpack alias selects Webpack.
|
|
60
|
+
// (rawOpts.rspack ?? true) makes the three inputs explicit: undefined (no flag) -> true,
|
|
61
|
+
// true (--rspack) -> true, false (--no-rspack) -> false.
|
|
62
|
+
// Footgun: re-adding `.option('--rspack', desc, false)` would default rawOpts.rspack to
|
|
63
|
+
// false and silently flip the default back to Webpack.
|
|
64
|
+
// Note: buildGeneratorArgs always forwards an explicit --rspack/--no-rspack to the Ruby
|
|
65
|
+
// generator, so the generator's own fresh-install fallback (fresh_install_rspack_default,
|
|
66
|
+
// which consults the Shakapacker version) is never reached via create-react-on-rails-app.
|
|
67
|
+
rspack: webpackRequested ? false : (rawOpts.rspack ?? true) === true,
|
|
68
|
+
tailwind: Boolean(rawOpts.tailwind),
|
|
69
|
+
pro: setupMode.pro,
|
|
70
|
+
rsc: setupMode.rsc,
|
|
71
|
+
// Commander's `--no-agent-files` declares a default of true on rawOpts.agentFiles,
|
|
72
|
+
// so undefined (no flag) and true (default) both map to true; only --no-agent-files
|
|
73
|
+
// sets it false.
|
|
74
|
+
agentFiles: (rawOpts.agentFiles ?? true) === true,
|
|
75
|
+
cliVersion: packageJson.version,
|
|
65
76
|
};
|
|
66
|
-
if (
|
|
67
|
-
(0, utils_js_1.logInfo)('
|
|
77
|
+
if (setupMode.defaulted) {
|
|
78
|
+
(0, utils_js_1.logInfo)('Default setup: React on Rails Pro for React 19.2 support. Use --standard only when you intentionally want an open-source-only setup.');
|
|
68
79
|
}
|
|
69
|
-
if (
|
|
80
|
+
if (setupMode.requiresPro) {
|
|
70
81
|
const modeFlag = options.rsc ? '--rsc' : '--pro';
|
|
71
|
-
|
|
72
|
-
(0, utils_js_1.logInfo)(`
|
|
82
|
+
const setupLabel = setupMode.defaulted ? 'The default setup' : modeFlag;
|
|
83
|
+
(0, utils_js_1.logInfo)(`Note: ${setupLabel} adds react_on_rails_pro and uses the Pro generator path.`);
|
|
84
|
+
(0, utils_js_1.logInfo)('If installation fails, verify your Bundler/RubyGems setup, then rerun the command.');
|
|
73
85
|
(0, utils_js_1.logInfo)('Pro setup docs: https://reactonrails.com/docs/pro/installation/');
|
|
86
|
+
(0, utils_js_1.logInfo)('Pro pricing and sign up: https://pro.reactonrails.com/');
|
|
87
|
+
(0, utils_js_1.logInfo)('License: no token is required for development, test, CI/CD, or staging. Production Pro deployments need a paid license, with free or low-cost options for startups and small projects.');
|
|
74
88
|
console.log('');
|
|
75
89
|
}
|
|
76
90
|
(0, utils_js_1.logInfo)('Checking prerequisites...');
|
|
77
91
|
const { allValid, results } = (0, validators_js_1.validateAll)(options.packageManager);
|
|
78
92
|
for (const { name, result } of results) {
|
|
79
93
|
if (result.valid) {
|
|
80
|
-
console.log(
|
|
94
|
+
console.log(utils_js_1.pc.green(` ✓ ${name}: ${result.message}`));
|
|
81
95
|
}
|
|
82
96
|
else {
|
|
83
|
-
console.log(
|
|
97
|
+
console.log(utils_js_1.pc.red(` ✗ ${name}`));
|
|
84
98
|
}
|
|
85
99
|
}
|
|
86
100
|
if (!allValid) {
|
|
@@ -95,12 +109,16 @@ async function run(appName, rawOpts) {
|
|
|
95
109
|
console.log('');
|
|
96
110
|
let modeLabel = '';
|
|
97
111
|
if (options.rsc) {
|
|
98
|
-
modeLabel = ',
|
|
112
|
+
modeLabel = ', setup: pro with RSC example';
|
|
99
113
|
}
|
|
100
114
|
else if (options.pro) {
|
|
101
|
-
modeLabel = ',
|
|
115
|
+
modeLabel = ', setup: pro without RSC example';
|
|
102
116
|
}
|
|
103
|
-
|
|
117
|
+
else {
|
|
118
|
+
modeLabel = ', setup: open-source-only';
|
|
119
|
+
}
|
|
120
|
+
const tailwindLabel = options.tailwind ? ', Tailwind CSS v4' : '';
|
|
121
|
+
(0, utils_js_1.logInfo)(`Creating "${appName}" with template: ${options.template}, package manager: ${options.packageManager}, bundler: ${options.rspack ? 'rspack' : 'webpack'}${modeLabel}${tailwindLabel}`);
|
|
104
122
|
(0, create_app_js_1.createApp)(appName, options);
|
|
105
123
|
}
|
|
106
124
|
const program = new commander_1.Command();
|
|
@@ -113,35 +131,47 @@ program
|
|
|
113
131
|
.argument('<app-name>', 'Name of the application to create')
|
|
114
132
|
.option('-t, --template <type>', 'javascript or typescript', 'typescript')
|
|
115
133
|
.option('-p, --package-manager <pm>', 'npm or pnpm (auto-detected if not specified)')
|
|
116
|
-
.option('--rspack', 'Use Rspack
|
|
117
|
-
.option('--
|
|
118
|
-
.option('--
|
|
119
|
-
.option('--
|
|
134
|
+
.option('--rspack', 'Use Rspack as the bundler (default, ~20x faster builds)')
|
|
135
|
+
.option('--no-rspack', 'Use Webpack instead of Rspack')
|
|
136
|
+
.option('--webpack', 'Use Webpack as the bundler (alias for --no-rspack)')
|
|
137
|
+
.option('--tailwind', 'Install Tailwind CSS v4 and style the generated SSR example')
|
|
138
|
+
.option('--standard', 'Advanced: generate open-source React on Rails without Pro React 19.2 features')
|
|
139
|
+
.option('--pro', 'Generate the default React on Rails Pro setup explicitly')
|
|
140
|
+
.option('--rsc', 'Advanced: generate React on Rails Pro with the RSC example')
|
|
141
|
+
.option('--no-agent-files', 'Skip AI-agent guidance files (AGENTS.md + editor pointers)')
|
|
120
142
|
.addHelpText('after', `
|
|
121
143
|
Examples:
|
|
122
|
-
$ npx create-react-on-rails-app my-app #
|
|
123
|
-
$ npx create-react-on-rails-app my-app --rsc # skip prompt, use RSC
|
|
124
|
-
$ npx create-react-on-rails-app my-app --pro # skip prompt, use Pro
|
|
125
|
-
$ npx create-react-on-rails-app my-app --standard # skip prompt, use Standard
|
|
144
|
+
$ npx create-react-on-rails-app my-app # default: Pro for React 19.2 support
|
|
126
145
|
$ npx create-react-on-rails-app my-app --template javascript
|
|
127
|
-
$ npx create-react-on-rails-app my-app --rspack
|
|
128
|
-
$ npx create-react-on-rails-app my-app --
|
|
146
|
+
$ npx create-react-on-rails-app my-app --no-rspack # use Webpack instead of Rspack
|
|
147
|
+
$ npx create-react-on-rails-app my-app --webpack # same as --no-rspack
|
|
148
|
+
$ npx create-react-on-rails-app my-app --tailwind
|
|
149
|
+
$ npx create-react-on-rails-app my-app --pro # explicit default Pro setup
|
|
150
|
+
$ npx create-react-on-rails-app my-app --rsc # advanced: Pro with RSC example
|
|
151
|
+
$ npx create-react-on-rails-app my-app --standard # advanced: OSS-only setup
|
|
152
|
+
$ npx create-react-on-rails-app my-app --no-rspack --rsc
|
|
129
153
|
$ npx create-react-on-rails-app my-app --package-manager pnpm
|
|
154
|
+
$ npx create-react-on-rails-app my-app --no-agent-files # skip AGENTS.md + editor pointers
|
|
155
|
+
|
|
156
|
+
No setup questions are asked. New apps use React on Rails Pro by default because
|
|
157
|
+
that is where React 19.2 feature support lives. Use --standard only when you
|
|
158
|
+
intentionally want an open-source-only scaffold. Use --rsc when you want the
|
|
159
|
+
generated React Server Components example.
|
|
130
160
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
161
|
+
Pro license note: no token is required for development, test, CI/CD, or
|
|
162
|
+
staging. Production Pro deployments need a paid license, with free or low-cost
|
|
163
|
+
options for startups and small projects. See pricing and sign up:
|
|
164
|
+
https://pro.reactonrails.com/
|
|
135
165
|
|
|
136
166
|
What it does:
|
|
137
167
|
1. Creates a new Rails app with PostgreSQL
|
|
138
|
-
2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro
|
|
168
|
+
2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro unless --standard)
|
|
139
169
|
3. Runs the React on Rails generator (Shakapacker, components, webpack config)
|
|
140
170
|
4. Creates educational git commits for each major scaffold step
|
|
141
171
|
|
|
142
172
|
After setup, run bin/dev and visit:
|
|
143
173
|
- http://localhost:3000 (generated home page)
|
|
144
|
-
- /hello_world (default and --pro example page)
|
|
174
|
+
- /hello_world (default, --standard, and --pro example page)
|
|
145
175
|
- /hello_server (--rsc example page)
|
|
146
176
|
|
|
147
177
|
Inspect the generated setup history with:
|
|
@@ -152,15 +182,11 @@ The generated app includes one git commit per logical setup step.`)
|
|
|
152
182
|
--pro and --rsc support both JavaScript and TypeScript templates.
|
|
153
183
|
|
|
154
184
|
Documentation: https://reactonrails.com/docs/`)
|
|
155
|
-
.action(
|
|
185
|
+
.action((appName, opts, command) => {
|
|
156
186
|
try {
|
|
157
|
-
|
|
187
|
+
run(appName, opts, command);
|
|
158
188
|
}
|
|
159
189
|
catch (error) {
|
|
160
|
-
if (error instanceof Error && error.message === prompt_js_1.PROMPT_CANCELLED) {
|
|
161
|
-
console.log('');
|
|
162
|
-
process.exit(0);
|
|
163
|
-
}
|
|
164
190
|
(0, utils_js_1.logError)(error instanceof Error ? error.message : String(error));
|
|
165
191
|
process.exit(1);
|
|
166
192
|
}
|
package/lib/mode.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type SetupMode = 'standard' | 'pro' | 'rsc';
|
|
2
|
+
interface ResolvedSetupModeBase {
|
|
3
|
+
defaulted: boolean;
|
|
4
|
+
mode: SetupMode;
|
|
5
|
+
}
|
|
6
|
+
interface StandardResolvedSetupMode extends ResolvedSetupModeBase {
|
|
7
|
+
defaulted: false;
|
|
8
|
+
mode: 'standard';
|
|
9
|
+
requiresPro: false;
|
|
10
|
+
pro: false;
|
|
11
|
+
rsc: false;
|
|
12
|
+
}
|
|
13
|
+
interface ProResolvedSetupMode extends ResolvedSetupModeBase {
|
|
14
|
+
mode: 'pro';
|
|
15
|
+
/** True when the selected setup installs react_on_rails_pro. */
|
|
16
|
+
requiresPro: true;
|
|
17
|
+
/** True for the Pro-without-RSC generator path. RSC mode uses its own Pro path. */
|
|
18
|
+
pro: true;
|
|
19
|
+
rsc: false;
|
|
20
|
+
}
|
|
21
|
+
interface RscResolvedSetupMode extends ResolvedSetupModeBase {
|
|
22
|
+
defaulted: false;
|
|
23
|
+
mode: 'rsc';
|
|
24
|
+
/** True when the selected setup installs react_on_rails_pro. */
|
|
25
|
+
requiresPro: true;
|
|
26
|
+
/** RSC mode uses its own Pro generator path, so this generator flag is false. */
|
|
27
|
+
pro: false;
|
|
28
|
+
rsc: true;
|
|
29
|
+
}
|
|
30
|
+
export type ResolvedSetupMode = StandardResolvedSetupMode | ProResolvedSetupMode | RscResolvedSetupMode;
|
|
31
|
+
export declare function resolveSetupMode(rawOpts: Record<string, unknown>): ResolvedSetupMode;
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=mode.d.ts.map
|
package/lib/mode.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveSetupMode = resolveSetupMode;
|
|
4
|
+
function makeSetupMode(mode, defaulted) {
|
|
5
|
+
if (mode === 'standard') {
|
|
6
|
+
return {
|
|
7
|
+
defaulted: false,
|
|
8
|
+
mode,
|
|
9
|
+
requiresPro: false,
|
|
10
|
+
pro: false,
|
|
11
|
+
rsc: false,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (mode === 'rsc') {
|
|
15
|
+
return {
|
|
16
|
+
defaulted: false,
|
|
17
|
+
mode,
|
|
18
|
+
requiresPro: true,
|
|
19
|
+
pro: false,
|
|
20
|
+
rsc: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
defaulted,
|
|
25
|
+
mode,
|
|
26
|
+
requiresPro: true,
|
|
27
|
+
pro: true,
|
|
28
|
+
rsc: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function resolveSetupMode(rawOpts) {
|
|
32
|
+
const standard = Boolean(rawOpts.standard);
|
|
33
|
+
const pro = Boolean(rawOpts.pro);
|
|
34
|
+
const rsc = Boolean(rawOpts.rsc);
|
|
35
|
+
if ([standard, pro, rsc].filter(Boolean).length > 1) {
|
|
36
|
+
throw new Error('Choose only one setup mode: --standard, --pro, or --rsc.');
|
|
37
|
+
}
|
|
38
|
+
if (standard) {
|
|
39
|
+
return makeSetupMode('standard', false);
|
|
40
|
+
}
|
|
41
|
+
if (rsc) {
|
|
42
|
+
return makeSetupMode('rsc', false);
|
|
43
|
+
}
|
|
44
|
+
if (pro) {
|
|
45
|
+
return makeSetupMode('pro', false);
|
|
46
|
+
}
|
|
47
|
+
return makeSetupMode('pro', true);
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=mode.js.map
|
package/lib/types.d.ts
CHANGED
|
@@ -2,8 +2,11 @@ export interface CliOptions {
|
|
|
2
2
|
template: 'javascript' | 'typescript';
|
|
3
3
|
packageManager: 'npm' | 'pnpm';
|
|
4
4
|
rspack: boolean;
|
|
5
|
+
tailwind: boolean;
|
|
5
6
|
pro: boolean;
|
|
6
7
|
rsc: boolean;
|
|
8
|
+
agentFiles: boolean;
|
|
9
|
+
cliVersion?: string;
|
|
7
10
|
}
|
|
8
11
|
export interface ValidationResult {
|
|
9
12
|
valid: boolean;
|
package/lib/utils.d.ts
CHANGED
package/lib/utils.js
CHANGED
|
@@ -3,6 +3,7 @@ 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.pc = void 0;
|
|
6
7
|
exports.execLiveArgs = execLiveArgs;
|
|
7
8
|
exports.execCaptureArgs = execCaptureArgs;
|
|
8
9
|
exports.getCommandVersion = getCommandVersion;
|
|
@@ -13,7 +14,70 @@ exports.logError = logError;
|
|
|
13
14
|
exports.logSuccess = logSuccess;
|
|
14
15
|
exports.logInfo = logInfo;
|
|
15
16
|
const child_process_1 = require("child_process");
|
|
16
|
-
const
|
|
17
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
18
|
+
const CHALK_CI_COLOR_ENV_KEYS = [
|
|
19
|
+
'TRAVIS',
|
|
20
|
+
'CIRCLECI',
|
|
21
|
+
'APPVEYOR',
|
|
22
|
+
'GITLAB_CI',
|
|
23
|
+
'GITHUB_ACTIONS',
|
|
24
|
+
'BUILDKITE',
|
|
25
|
+
];
|
|
26
|
+
const CHALK_TEAMCITY_COLOR_PATTERN = /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/;
|
|
27
|
+
const CHALK_TERM_256_COLOR_PATTERN = /-256(color)?$/i;
|
|
28
|
+
const CHALK_TERM_COLOR_PATTERN = /^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i;
|
|
29
|
+
function hasChalkCompatibleTtyColorSignal() {
|
|
30
|
+
const { CI, CI_NAME, COLORTERM, TEAMCITY_VERSION, TERM, TERM_PROGRAM } = process.env;
|
|
31
|
+
if (TERM === 'dumb') {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
if (process.platform === 'win32') {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (CI !== undefined) {
|
|
38
|
+
return CHALK_CI_COLOR_ENV_KEYS.some((key) => process.env[key] !== undefined) || CI_NAME === 'codeship';
|
|
39
|
+
}
|
|
40
|
+
if (TEAMCITY_VERSION !== undefined) {
|
|
41
|
+
return CHALK_TEAMCITY_COLOR_PATTERN.test(TEAMCITY_VERSION);
|
|
42
|
+
}
|
|
43
|
+
if (COLORTERM === 'truecolor') {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (TERM_PROGRAM === 'iTerm.app' || TERM_PROGRAM === 'Apple_Terminal') {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
const term = TERM ?? '';
|
|
50
|
+
return (CHALK_TERM_256_COLOR_PATTERN.test(term) || CHALK_TERM_COLOR_PATTERN.test(term) || COLORTERM !== undefined);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Decide whether to colorize output, matching what `chalk@4` produced before the
|
|
54
|
+
* chalk→picocolors swap. The scaffolder is a user's first contact with the
|
|
55
|
+
* framework and its logs are often captured, so keeping the same basic
|
|
56
|
+
* colorize/plain decision avoids surprising behavior changes.
|
|
57
|
+
*
|
|
58
|
+
* picocolors' own `isColorSupported` bakes in a different precedence than chalk
|
|
59
|
+
* (it lets NO_COLOR override FORCE_COLOR, and enables color on a bare CI even for
|
|
60
|
+
* piped stdout), so instead of deferring to it we reproduce the relevant chalk@4
|
|
61
|
+
* environment precedence directly and only use picocolors for the actual escape codes:
|
|
62
|
+
*
|
|
63
|
+
* 1. FORCE_COLOR present → `0`/`false` disables; any other value (incl. empty)
|
|
64
|
+
* forces color ON, overriding NO_COLOR and a non-TTY stdout.
|
|
65
|
+
* 2. else NO_COLOR present (any value, incl. empty) → disabled.
|
|
66
|
+
* 3. else colorize only for an interactive TTY with one of chalk@4's positive
|
|
67
|
+
* environment signals. A bare TTY with unset or unrecognized TERM stays
|
|
68
|
+
* plain, matching supports-color@7.2.0's fallback behavior.
|
|
69
|
+
*/
|
|
70
|
+
function chalkCompatibleColorEnabled() {
|
|
71
|
+
const { FORCE_COLOR, NO_COLOR } = process.env;
|
|
72
|
+
if (FORCE_COLOR !== undefined) {
|
|
73
|
+
return FORCE_COLOR !== '0' && FORCE_COLOR !== 'false';
|
|
74
|
+
}
|
|
75
|
+
if (NO_COLOR !== undefined) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return (process.stdout?.isTTY ?? false) && hasChalkCompatibleTtyColorSignal();
|
|
79
|
+
}
|
|
80
|
+
exports.pc = picocolors_1.default.createColors(chalkCompatibleColorEnabled());
|
|
17
81
|
function childEnv(env) {
|
|
18
82
|
// Always inherit PATH, HOME, and the rest of process.env; callers only add/override keys.
|
|
19
83
|
return env ? { ...process.env, ...env } : process.env;
|
|
@@ -85,18 +149,18 @@ function detectPackageManager() {
|
|
|
85
149
|
return null;
|
|
86
150
|
}
|
|
87
151
|
function logStep(current, total, message) {
|
|
88
|
-
console.log(
|
|
152
|
+
console.log(exports.pc.cyan(`\n[${current}/${total}] ${message}`));
|
|
89
153
|
}
|
|
90
154
|
function logStepDone(message) {
|
|
91
|
-
console.log(
|
|
155
|
+
console.log(exports.pc.green(` ✓ ${message}`));
|
|
92
156
|
}
|
|
93
157
|
function logError(message) {
|
|
94
|
-
console.error(
|
|
158
|
+
console.error(exports.pc.red(`\nError: ${message}\n`));
|
|
95
159
|
}
|
|
96
160
|
function logSuccess(message) {
|
|
97
|
-
console.log(
|
|
161
|
+
console.log(exports.pc.green(message));
|
|
98
162
|
}
|
|
99
163
|
function logInfo(message) {
|
|
100
|
-
console.log(
|
|
164
|
+
console.log(exports.pc.cyan(message));
|
|
101
165
|
}
|
|
102
166
|
//# sourceMappingURL=utils.js.map
|
package/lib/validators.js
CHANGED
|
@@ -9,7 +9,7 @@ exports.validateAll = validateAll;
|
|
|
9
9
|
const utils_js_1 = require("./utils.js");
|
|
10
10
|
const MIN_NODE_VERSION = 18;
|
|
11
11
|
const MIN_RUBY_MAJOR = 3;
|
|
12
|
-
const MIN_RUBY_MINOR =
|
|
12
|
+
const MIN_RUBY_MINOR = 3;
|
|
13
13
|
const MIN_RAILS_MAJOR = 7;
|
|
14
14
|
const MIN_RAILS_MINOR = 0;
|
|
15
15
|
function validateNode() {
|
|
@@ -30,7 +30,7 @@ function validateRuby() {
|
|
|
30
30
|
return {
|
|
31
31
|
valid: false,
|
|
32
32
|
message: 'Ruby is not installed or not found in PATH.\n\n' +
|
|
33
|
-
|
|
33
|
+
`React on Rails requires Ruby ${MIN_RUBY_MAJOR}.${MIN_RUBY_MINOR}+.\n\n` +
|
|
34
34
|
'Popular installation options:\n' +
|
|
35
35
|
' - mise: https://mise.jdx.dev/ (recommended)\n' +
|
|
36
36
|
' - rbenv: https://github.com/rbenv/rbenv\n' +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-react-on-rails-app",
|
|
3
|
-
"version": "17.0.0-rc.
|
|
3
|
+
"version": "17.0.0-rc.10",
|
|
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"
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
"lib/**/*.d.ts"
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"
|
|
16
|
-
"
|
|
15
|
+
"commander": "^12.1.0",
|
|
16
|
+
"picocolors": "^1.1.1"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^20.17.16",
|
package/lib/prompt.d.ts
DELETED
package/lib/prompt.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.PROMPT_CANCELLED = void 0;
|
|
7
|
-
exports.promptForMode = promptForMode;
|
|
8
|
-
const readline_1 = __importDefault(require("readline"));
|
|
9
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
-
exports.PROMPT_CANCELLED = 'Prompt cancelled by user';
|
|
11
|
-
const MODES = [
|
|
12
|
-
{
|
|
13
|
-
key: '1',
|
|
14
|
-
label: 'Standard',
|
|
15
|
-
desc: 'Open-source React on Rails with SSR',
|
|
16
|
-
pro: false,
|
|
17
|
-
rsc: false,
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
key: '2',
|
|
21
|
-
label: 'Pro',
|
|
22
|
-
desc: 'Adds Node.js server rendering (requires react_on_rails_pro)',
|
|
23
|
-
pro: true,
|
|
24
|
-
rsc: false,
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
key: '3',
|
|
28
|
-
label: 'RSC',
|
|
29
|
-
desc: 'React Server Components (requires react_on_rails_pro)',
|
|
30
|
-
pro: false,
|
|
31
|
-
rsc: true,
|
|
32
|
-
},
|
|
33
|
-
];
|
|
34
|
-
const DEFAULT_KEY = '3';
|
|
35
|
-
function promptForMode() {
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
const rl = readline_1.default.createInterface({
|
|
38
|
-
input: process.stdin,
|
|
39
|
-
output: process.stdout,
|
|
40
|
-
});
|
|
41
|
-
let answered = false;
|
|
42
|
-
rl.on('SIGINT', () => {
|
|
43
|
-
answered = true;
|
|
44
|
-
rl.close();
|
|
45
|
-
reject(new Error(exports.PROMPT_CANCELLED));
|
|
46
|
-
});
|
|
47
|
-
rl.once('close', () => {
|
|
48
|
-
if (!answered) {
|
|
49
|
-
reject(new Error(exports.PROMPT_CANCELLED));
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
console.log(chalk_1.default.bold('Select a setup mode:\n'));
|
|
53
|
-
for (const mode of MODES) {
|
|
54
|
-
const recommended = mode.key === DEFAULT_KEY ? chalk_1.default.cyan(' (recommended)') : '';
|
|
55
|
-
console.log(` ${mode.key}. ${chalk_1.default.bold(mode.label.padEnd(10))} ${mode.desc}${recommended}`);
|
|
56
|
-
}
|
|
57
|
-
console.log('');
|
|
58
|
-
rl.question(`Choice (1-3) [${DEFAULT_KEY}]: `, (answer) => {
|
|
59
|
-
answered = true;
|
|
60
|
-
rl.close();
|
|
61
|
-
const key = answer.trim() || DEFAULT_KEY;
|
|
62
|
-
const selected = MODES.find((m) => m.key === key);
|
|
63
|
-
if (!selected) {
|
|
64
|
-
console.log(chalk_1.default.yellow(`Invalid choice "${key}", defaulting to RSC.`));
|
|
65
|
-
resolve({ pro: false, rsc: true });
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
resolve({ pro: selected.pro, rsc: selected.rsc });
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
//# sourceMappingURL=prompt.js.map
|