create-react-on-rails-app 17.0.0-rc.1 → 17.0.0-rc.11

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
@@ -123,11 +123,20 @@ function localGemPath(envVarName) {
123
123
  }
124
124
  return resolvedPath;
125
125
  }
126
- function bundleAddArgs(gemName, localPath, strict = true) {
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
  }
@@ -142,6 +151,9 @@ function buildGeneratorArgs(options) {
142
151
  // its own default. This keeps create-app's resolved choice authoritative regardless of what
143
152
  // the generator's fresh-install default happens to be (now, or if it changes again later).
144
153
  args.push(options.rspack ? '--rspack' : '--no-rspack');
154
+ if (options.tailwind) {
155
+ args.push('--tailwind');
156
+ }
145
157
  // --rsc supersedes --pro because RSC mode already requires Pro and the generator accepts a single mode flag.
146
158
  if (options.pro && !options.rsc) {
147
159
  args.push('--pro');
@@ -149,6 +161,11 @@ function buildGeneratorArgs(options) {
149
161
  if (options.rsc) {
150
162
  args.push('--rsc');
151
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
+ }
152
169
  // --force makes the generator overwrite conflicting files without prompting,
153
170
  // which is safe for a freshly scaffolded app with no custom content yet.
154
171
  args.push('--force');
@@ -310,21 +327,23 @@ function reactOnRailsProCommitMessage(modeFlag) {
310
327
  function generatorCommitMessage(options) {
311
328
  const language = options.template === 'typescript' ? 'TypeScript' : 'JavaScript';
312
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' : '';
313
332
  if (options.rsc) {
314
333
  return {
315
- subject: `Install React Server Components with ${language} and ${bundler}`,
316
- body: 'Run react_on_rails:install --rsc to add the generated home page, HelloServer example, and Pro RSC wiring.',
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.`,
317
336
  };
318
337
  }
319
338
  if (options.pro) {
320
339
  return {
321
- subject: `Install React on Rails Pro with ${language} and ${bundler}`,
322
- body: 'Run react_on_rails:install --pro to add the generated home page, SSR example, and Pro Node renderer wiring.',
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.`,
323
342
  };
324
343
  }
325
344
  return {
326
- subject: `Install React on Rails with ${language} and ${bundler}`,
327
- body: 'Run react_on_rails:install to add the generated home page, SSR example, and development workflow.',
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.`,
328
347
  };
329
348
  }
330
349
  function pnpmCommitMessage() {
@@ -395,6 +414,7 @@ function createApp(appName, options) {
395
414
  let currentStep = 1;
396
415
  const reactOnRailsGemPath = localGemPath('REACT_ON_RAILS_GEM_PATH');
397
416
  const reactOnRailsProGemPath = proRequested ? localGemPath('REACT_ON_RAILS_PRO_GEM_PATH') : null;
417
+ const prereleaseGemVersion = rubygemsPrereleaseVersion(options.cliVersion);
398
418
  function educationalGitEnvForCommits() {
399
419
  if (!cachedEducationalGitEnv) {
400
420
  // The baseline commit happens before git init; in that case git config falls back
@@ -441,7 +461,7 @@ function createApp(appName, options) {
441
461
  currentStep += 1;
442
462
  (0, utils_js_1.logStep)(currentStep, totalSteps, 'Adding react_on_rails gem...');
443
463
  try {
444
- const reactOnRailsArgs = bundleAddArgs('react_on_rails', reactOnRailsGemPath);
464
+ const reactOnRailsArgs = bundleAddArgs('react_on_rails', reactOnRailsGemPath, true, prereleaseGemVersion);
445
465
  if (reactOnRailsGemPath) {
446
466
  (0, utils_js_1.logInfo)(`Using local react_on_rails gem path: ${reactOnRailsGemPath}`);
447
467
  }
@@ -462,7 +482,7 @@ function createApp(appName, options) {
462
482
  currentStep += 1;
463
483
  (0, utils_js_1.logStep)(currentStep, totalSteps, `Adding react_on_rails_pro gem (${proModeLabel})...`);
464
484
  try {
465
- const reactOnRailsProArgs = bundleAddArgs('react_on_rails_pro', reactOnRailsProGemPath, false);
485
+ const reactOnRailsProArgs = bundleAddArgs('react_on_rails_pro', reactOnRailsProGemPath, false, prereleaseGemVersion);
466
486
  if (reactOnRailsProGemPath) {
467
487
  (0, utils_js_1.logInfo)(`Using local react_on_rails_pro gem path: ${reactOnRailsProGemPath}`);
468
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
- async function run(appName, rawOpts, command) {
17
- const { template } = rawOpts;
18
- if (typeof template !== 'string' || (template !== 'javascript' && template !== 'typescript')) {
19
- (0, utils_js_1.logError)(`Invalid template "${String(template)}". Must be "javascript" or "typescript".`);
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,9 +27,14 @@ async function run(appName, rawOpts, command) {
29
27
  else {
30
28
  packageManager = (0, utils_js_1.detectPackageManager)() ?? 'npm';
31
29
  }
32
- if (rawOpts.standard && (rawOpts.pro || rawOpts.rsc)) {
33
- (0, utils_js_1.logError)('--standard cannot be combined with --pro or --rsc.');
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));
34
36
  process.exit(1);
37
+ return;
35
38
  }
36
39
  // --webpack is a friendly alias for --no-rspack. Commander reports --no-rspack's declared
37
40
  // default as rawOpts.rspack === true, so we use the option source to distinguish an explicit
@@ -42,29 +45,14 @@ async function run(appName, rawOpts, command) {
42
45
  (0, utils_js_1.logError)('Conflicting bundler flags: pass either --rspack or --webpack (alias for --no-rspack), not both.');
43
46
  process.exit(1);
44
47
  }
45
- let pro = Boolean(rawOpts.pro);
46
- let rsc = Boolean(rawOpts.rsc);
47
48
  console.log('');
48
- console.log(`${chalk_1.default.bold('create-react-on-rails-app')} v${packageJson.version}`);
49
+ console.log(`${utils_js_1.pc.bold('create-react-on-rails-app')} v${packageJson.version}`);
49
50
  console.log('');
50
51
  const nameValidation = (0, create_app_js_1.validateAppName)(appName);
51
52
  if (!nameValidation.success) {
52
53
  (0, utils_js_1.logError)(nameValidation.error ?? 'Invalid app name');
53
54
  process.exit(1);
54
55
  }
55
- // When no mode flag is explicitly passed, prompt interactively (TTY only).
56
- // Non-interactive environments (CI, pipes) fall back to standard mode.
57
- const modeExplicit = rawOpts.pro !== undefined || rawOpts.rsc !== undefined || rawOpts.standard !== undefined;
58
- if (!modeExplicit) {
59
- if (process.stdin.isTTY && process.stdout.isTTY) {
60
- const choice = await (0, prompt_js_1.promptForMode)();
61
- pro = choice.pro;
62
- rsc = choice.rsc;
63
- }
64
- else {
65
- (0, utils_js_1.logInfo)('No mode flag specified and not running interactively (stdin/stdout is not a TTY); using standard mode.');
66
- }
67
- }
68
56
  const options = {
69
57
  template,
70
58
  packageManager: packageManager,
@@ -77,27 +65,36 @@ async function run(appName, rawOpts, command) {
77
65
  // generator, so the generator's own fresh-install fallback (fresh_install_rspack_default,
78
66
  // which consults the Shakapacker version) is never reached via create-react-on-rails-app.
79
67
  rspack: webpackRequested ? false : (rawOpts.rspack ?? true) === true,
80
- pro,
81
- rsc,
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,
82
76
  };
83
- if (options.rsc && options.pro) {
84
- (0, utils_js_1.logInfo)('Note: --rsc takes precedence over --pro; --pro will be ignored.');
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.');
85
79
  }
86
- if (options.rsc || options.pro) {
80
+ if (setupMode.requiresPro) {
87
81
  const modeFlag = options.rsc ? '--rsc' : '--pro';
88
- (0, utils_js_1.logInfo)(`Note: ${modeFlag} installs react_on_rails_pro and requires that gem to be installable.`);
89
- (0, utils_js_1.logInfo)(`If installation fails, verify your Bundler/RubyGems setup for react_on_rails_pro, then rerun with ${modeFlag}.`);
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.');
90
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.');
91
88
  console.log('');
92
89
  }
93
90
  (0, utils_js_1.logInfo)('Checking prerequisites...');
94
91
  const { allValid, results } = (0, validators_js_1.validateAll)(options.packageManager);
95
92
  for (const { name, result } of results) {
96
93
  if (result.valid) {
97
- console.log(chalk_1.default.green(` ✓ ${name}: ${result.message}`));
94
+ console.log(utils_js_1.pc.green(` ✓ ${name}: ${result.message}`));
98
95
  }
99
96
  else {
100
- console.log(chalk_1.default.red(` ✗ ${name}`));
97
+ console.log(utils_js_1.pc.red(` ✗ ${name}`));
101
98
  }
102
99
  }
103
100
  if (!allValid) {
@@ -112,12 +109,16 @@ async function run(appName, rawOpts, command) {
112
109
  console.log('');
113
110
  let modeLabel = '';
114
111
  if (options.rsc) {
115
- modeLabel = ', mode: rsc';
112
+ modeLabel = ', setup: pro with RSC example';
116
113
  }
117
114
  else if (options.pro) {
118
- modeLabel = ', mode: pro';
115
+ modeLabel = ', setup: pro without RSC example';
116
+ }
117
+ else {
118
+ modeLabel = ', setup: open-source-only';
119
119
  }
120
- (0, utils_js_1.logInfo)(`Creating "${appName}" with template: ${options.template}, package manager: ${options.packageManager}, bundler: ${options.rspack ? 'rspack' : 'webpack'}${modeLabel}`);
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}`);
121
122
  (0, create_app_js_1.createApp)(appName, options);
122
123
  }
123
124
  const program = new commander_1.Command();
@@ -133,35 +134,44 @@ program
133
134
  .option('--rspack', 'Use Rspack as the bundler (default, ~20x faster builds)')
134
135
  .option('--no-rspack', 'Use Webpack instead of Rspack')
135
136
  .option('--webpack', 'Use Webpack as the bundler (alias for --no-rspack)')
136
- .option('--standard', 'Generate open-source React on Rails setup (skip prompt)')
137
- .option('--pro', 'Generate React on Rails Pro setup (installs react_on_rails_pro)')
138
- .option('--rsc', 'Generate React Server Components setup (installs react_on_rails_pro)')
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)')
139
142
  .addHelpText('after', `
140
143
  Examples:
141
- $ npx create-react-on-rails-app my-app # prompts for mode
142
- $ npx create-react-on-rails-app my-app --rsc # skip prompt, use RSC
143
- $ npx create-react-on-rails-app my-app --pro # skip prompt, use Pro
144
- $ 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
145
145
  $ npx create-react-on-rails-app my-app --template javascript
146
146
  $ npx create-react-on-rails-app my-app --no-rspack # use Webpack instead of Rspack
147
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
148
152
  $ npx create-react-on-rails-app my-app --no-rspack --rsc
149
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.
150
160
 
151
- When no mode flag (--standard, --pro, or --rsc) is given, an interactive prompt
152
- lets you choose between Standard, Pro, and RSC modes (default: RSC). When stdin
153
- or stdout is not a TTY (for example in CI, piped input, or redirected output),
154
- standard mode is used automatically.
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/
155
165
 
156
166
  What it does:
157
167
  1. Creates a new Rails app with PostgreSQL
158
- 2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro for Pro/RSC)
168
+ 2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro unless --standard)
159
169
  3. Runs the React on Rails generator (Shakapacker, components, webpack config)
160
170
  4. Creates educational git commits for each major scaffold step
161
171
 
162
172
  After setup, run bin/dev and visit:
163
173
  - http://localhost:3000 (generated home page)
164
- - /hello_world (default and --pro example page)
174
+ - /hello_world (default, --standard, and --pro example page)
165
175
  - /hello_server (--rsc example page)
166
176
 
167
177
  Inspect the generated setup history with:
@@ -172,15 +182,11 @@ The generated app includes one git commit per logical setup step.`)
172
182
  --pro and --rsc support both JavaScript and TypeScript templates.
173
183
 
174
184
  Documentation: https://reactonrails.com/docs/`)
175
- .action(async (appName, opts, command) => {
185
+ .action((appName, opts, command) => {
176
186
  try {
177
- await run(appName, opts, command);
187
+ run(appName, opts, command);
178
188
  }
179
189
  catch (error) {
180
- if (error instanceof Error && error.message === prompt_js_1.PROMPT_CANCELLED) {
181
- console.log('');
182
- process.exit(0);
183
- }
184
190
  (0, utils_js_1.logError)(error instanceof Error ? error.message : String(error));
185
191
  process.exit(1);
186
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
@@ -1,3 +1,4 @@
1
+ export declare const pc: import("picocolors/types").Colors;
1
2
  /**
2
3
  * Execute a command and stream output to the current terminal.
3
4
  *
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 chalk_1 = __importDefault(require("chalk"));
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(chalk_1.default.cyan(`\n[${current}/${total}] ${message}`));
152
+ console.log(exports.pc.cyan(`\n[${current}/${total}] ${message}`));
89
153
  }
90
154
  function logStepDone(message) {
91
- console.log(chalk_1.default.green(` ✓ ${message}`));
155
+ console.log(exports.pc.green(` ✓ ${message}`));
92
156
  }
93
157
  function logError(message) {
94
- console.error(chalk_1.default.red(`\nError: ${message}\n`));
158
+ console.error(exports.pc.red(`\nError: ${message}\n`));
95
159
  }
96
160
  function logSuccess(message) {
97
- console.log(chalk_1.default.green(message));
161
+ console.log(exports.pc.green(message));
98
162
  }
99
163
  function logInfo(message) {
100
- console.log(chalk_1.default.cyan(message));
164
+ console.log(exports.pc.cyan(message));
101
165
  }
102
166
  //# sourceMappingURL=utils.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-react-on-rails-app",
3
- "version": "17.0.0-rc.1",
3
+ "version": "17.0.0-rc.11",
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
- "chalk": "^4.1.2",
16
- "commander": "^12.1.0"
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
@@ -1,7 +0,0 @@
1
- export interface ModeChoice {
2
- pro: boolean;
3
- rsc: boolean;
4
- }
5
- export declare const PROMPT_CANCELLED = "Prompt cancelled by user";
6
- export declare function promptForMode(): Promise<ModeChoice>;
7
- //# sourceMappingURL=prompt.d.ts.map
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