create-react-on-rails-app 17.0.0-rc.1 → 17.0.0-rc.3
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 +29 -9
- package/lib/index.js +17 -1
- package/lib/prompt.d.ts +1 -0
- package/lib/prompt.js +42 -16
- package/lib/types.d.ts +3 -0
- package/package.json +1 -1
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
|
}
|
|
@@ -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 ${
|
|
316
|
-
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.`,
|
|
317
336
|
};
|
|
318
337
|
}
|
|
319
338
|
if (options.pro) {
|
|
320
339
|
return {
|
|
321
|
-
subject: `Install React on Rails Pro with ${
|
|
322
|
-
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.`,
|
|
323
342
|
};
|
|
324
343
|
}
|
|
325
344
|
return {
|
|
326
|
-
subject: `Install React on Rails with ${
|
|
327
|
-
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.`,
|
|
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
|
@@ -44,6 +44,7 @@ async function run(appName, rawOpts, command) {
|
|
|
44
44
|
}
|
|
45
45
|
let pro = Boolean(rawOpts.pro);
|
|
46
46
|
let rsc = Boolean(rawOpts.rsc);
|
|
47
|
+
let tailwind = Boolean(rawOpts.tailwind);
|
|
47
48
|
console.log('');
|
|
48
49
|
console.log(`${chalk_1.default.bold('create-react-on-rails-app')} v${packageJson.version}`);
|
|
49
50
|
console.log('');
|
|
@@ -60,6 +61,10 @@ async function run(appName, rawOpts, command) {
|
|
|
60
61
|
const choice = await (0, prompt_js_1.promptForMode)();
|
|
61
62
|
pro = choice.pro;
|
|
62
63
|
rsc = choice.rsc;
|
|
64
|
+
// Explicit mode flags keep the command deterministic; no-flag interactive runs get the Tailwind prompt.
|
|
65
|
+
if (rawOpts.tailwind === undefined) {
|
|
66
|
+
tailwind = await (0, prompt_js_1.promptForTailwind)();
|
|
67
|
+
}
|
|
63
68
|
}
|
|
64
69
|
else {
|
|
65
70
|
(0, utils_js_1.logInfo)('No mode flag specified and not running interactively (stdin/stdout is not a TTY); using standard mode.');
|
|
@@ -77,8 +82,14 @@ async function run(appName, rawOpts, command) {
|
|
|
77
82
|
// generator, so the generator's own fresh-install fallback (fresh_install_rspack_default,
|
|
78
83
|
// which consults the Shakapacker version) is never reached via create-react-on-rails-app.
|
|
79
84
|
rspack: webpackRequested ? false : (rawOpts.rspack ?? true) === true,
|
|
85
|
+
tailwind,
|
|
80
86
|
pro,
|
|
81
87
|
rsc,
|
|
88
|
+
// Commander's `--no-agent-files` declares a default of true on rawOpts.agentFiles,
|
|
89
|
+
// so undefined (no flag) and true (default) both map to true; only --no-agent-files
|
|
90
|
+
// sets it false.
|
|
91
|
+
agentFiles: (rawOpts.agentFiles ?? true) === true,
|
|
92
|
+
cliVersion: packageJson.version,
|
|
82
93
|
};
|
|
83
94
|
if (options.rsc && options.pro) {
|
|
84
95
|
(0, utils_js_1.logInfo)('Note: --rsc takes precedence over --pro; --pro will be ignored.');
|
|
@@ -117,7 +128,8 @@ async function run(appName, rawOpts, command) {
|
|
|
117
128
|
else if (options.pro) {
|
|
118
129
|
modeLabel = ', mode: pro';
|
|
119
130
|
}
|
|
120
|
-
|
|
131
|
+
const tailwindLabel = options.tailwind ? ', Tailwind CSS v4' : '';
|
|
132
|
+
(0, utils_js_1.logInfo)(`Creating "${appName}" with template: ${options.template}, package manager: ${options.packageManager}, bundler: ${options.rspack ? 'rspack' : 'webpack'}${modeLabel}${tailwindLabel}`);
|
|
121
133
|
(0, create_app_js_1.createApp)(appName, options);
|
|
122
134
|
}
|
|
123
135
|
const program = new commander_1.Command();
|
|
@@ -133,9 +145,11 @@ program
|
|
|
133
145
|
.option('--rspack', 'Use Rspack as the bundler (default, ~20x faster builds)')
|
|
134
146
|
.option('--no-rspack', 'Use Webpack instead of Rspack')
|
|
135
147
|
.option('--webpack', 'Use Webpack as the bundler (alias for --no-rspack)')
|
|
148
|
+
.option('--tailwind', 'Install Tailwind CSS v4 and style the generated SSR example')
|
|
136
149
|
.option('--standard', 'Generate open-source React on Rails setup (skip prompt)')
|
|
137
150
|
.option('--pro', 'Generate React on Rails Pro setup (installs react_on_rails_pro)')
|
|
138
151
|
.option('--rsc', 'Generate React Server Components setup (installs react_on_rails_pro)')
|
|
152
|
+
.option('--no-agent-files', 'Skip AI-agent guidance files (AGENTS.md + editor pointers)')
|
|
139
153
|
.addHelpText('after', `
|
|
140
154
|
Examples:
|
|
141
155
|
$ npx create-react-on-rails-app my-app # prompts for mode
|
|
@@ -145,8 +159,10 @@ Examples:
|
|
|
145
159
|
$ npx create-react-on-rails-app my-app --template javascript
|
|
146
160
|
$ npx create-react-on-rails-app my-app --no-rspack # use Webpack instead of Rspack
|
|
147
161
|
$ npx create-react-on-rails-app my-app --webpack # same as --no-rspack
|
|
162
|
+
$ npx create-react-on-rails-app my-app --tailwind
|
|
148
163
|
$ npx create-react-on-rails-app my-app --no-rspack --rsc
|
|
149
164
|
$ npx create-react-on-rails-app my-app --package-manager pnpm
|
|
165
|
+
$ npx create-react-on-rails-app my-app --no-agent-files # skip AGENTS.md + editor pointers
|
|
150
166
|
|
|
151
167
|
When no mode flag (--standard, --pro, or --rsc) is given, an interactive prompt
|
|
152
168
|
lets you choose between Standard, Pro, and RSC modes (default: RSC). When stdin
|
package/lib/prompt.d.ts
CHANGED
package/lib/prompt.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.PROMPT_CANCELLED = void 0;
|
|
7
7
|
exports.promptForMode = promptForMode;
|
|
8
|
+
exports.promptForTailwind = promptForTailwind;
|
|
8
9
|
const readline_1 = __importDefault(require("readline"));
|
|
9
10
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
11
|
exports.PROMPT_CANCELLED = 'Prompt cancelled by user';
|
|
@@ -32,23 +33,32 @@ const MODES = [
|
|
|
32
33
|
},
|
|
33
34
|
];
|
|
34
35
|
const DEFAULT_KEY = '3';
|
|
36
|
+
function createPromptInterface() {
|
|
37
|
+
return readline_1.default.createInterface({
|
|
38
|
+
input: process.stdin,
|
|
39
|
+
output: process.stdout,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function rejectWhenClosed(rl, reject) {
|
|
43
|
+
let answered = false;
|
|
44
|
+
rl.on('SIGINT', () => {
|
|
45
|
+
answered = true;
|
|
46
|
+
rl.close();
|
|
47
|
+
reject(new Error(exports.PROMPT_CANCELLED));
|
|
48
|
+
});
|
|
49
|
+
rl.once('close', () => {
|
|
50
|
+
if (!answered) {
|
|
51
|
+
reject(new Error(exports.PROMPT_CANCELLED));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
return () => {
|
|
55
|
+
answered = true;
|
|
56
|
+
};
|
|
57
|
+
}
|
|
35
58
|
function promptForMode() {
|
|
36
59
|
return new Promise((resolve, reject) => {
|
|
37
|
-
const rl =
|
|
38
|
-
|
|
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
|
-
});
|
|
60
|
+
const rl = createPromptInterface();
|
|
61
|
+
const markAnswered = rejectWhenClosed(rl, reject);
|
|
52
62
|
console.log(chalk_1.default.bold('Select a setup mode:\n'));
|
|
53
63
|
for (const mode of MODES) {
|
|
54
64
|
const recommended = mode.key === DEFAULT_KEY ? chalk_1.default.cyan(' (recommended)') : '';
|
|
@@ -56,7 +66,7 @@ function promptForMode() {
|
|
|
56
66
|
}
|
|
57
67
|
console.log('');
|
|
58
68
|
rl.question(`Choice (1-3) [${DEFAULT_KEY}]: `, (answer) => {
|
|
59
|
-
|
|
69
|
+
markAnswered();
|
|
60
70
|
rl.close();
|
|
61
71
|
const key = answer.trim() || DEFAULT_KEY;
|
|
62
72
|
const selected = MODES.find((m) => m.key === key);
|
|
@@ -69,4 +79,20 @@ function promptForMode() {
|
|
|
69
79
|
});
|
|
70
80
|
});
|
|
71
81
|
}
|
|
82
|
+
function promptForTailwind() {
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const rl = createPromptInterface();
|
|
85
|
+
const markAnswered = rejectWhenClosed(rl, reject);
|
|
86
|
+
console.log(chalk_1.default.bold('Add styling:\n'));
|
|
87
|
+
console.log(` ${chalk_1.default.bold('Y. Tailwind CSS v4')} ${chalk_1.default.cyan('(recommended)')}`);
|
|
88
|
+
console.log(' n. No Tailwind CSS');
|
|
89
|
+
console.log('');
|
|
90
|
+
rl.question('Add Tailwind CSS v4? [Y/n]: ', (answer) => {
|
|
91
|
+
markAnswered();
|
|
92
|
+
rl.close();
|
|
93
|
+
const normalized = answer.trim().toLowerCase();
|
|
94
|
+
resolve(!(normalized === 'n' || normalized === 'no'));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
72
98
|
//# sourceMappingURL=prompt.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/package.json
CHANGED