create-react-on-rails-app 16.5.1 → 16.6.0-rc.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 +257 -14
- package/lib/index.js +9 -2
- package/lib/utils.d.ts +1 -0
- package/lib/utils.js +30 -1
- package/lib/validators.d.ts +1 -0
- package/lib/validators.js +15 -0
- package/package.json +1 -1
package/lib/create-app.js
CHANGED
|
@@ -10,6 +10,94 @@ const path_1 = __importDefault(require("path"));
|
|
|
10
10
|
const fs_1 = __importDefault(require("fs"));
|
|
11
11
|
const utils_js_1 = require("./utils.js");
|
|
12
12
|
const DOCS_URL = 'https://reactonrails.com/docs/';
|
|
13
|
+
const DEFAULT_GIT_AUTHOR_NAME = 'React on Rails Generator';
|
|
14
|
+
const DEFAULT_GIT_AUTHOR_EMAIL = 'generator@reactonrails.invalid';
|
|
15
|
+
const SUPPORTED_RAILTIES_MAJORS = [7, 8];
|
|
16
|
+
// Keep this renderer intentionally minimal. It renders Rails' installed
|
|
17
|
+
// gitignore/gitattributes templates and falls back to bundled defaults when:
|
|
18
|
+
// - railties is unavailable in the current Ruby environment, or
|
|
19
|
+
// - railties major version is outside SUPPORTED_RAILTIES_MAJORS.
|
|
20
|
+
const RAILS_GIT_TEMPLATE_RENDERER = `
|
|
21
|
+
require "erb"
|
|
22
|
+
|
|
23
|
+
class ReactOnRailsGitTemplateOptions
|
|
24
|
+
def api? = false
|
|
25
|
+
|
|
26
|
+
def [](key)
|
|
27
|
+
key == :skip_active_record ? false : nil
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class ReactOnRailsGitTemplateContext
|
|
32
|
+
def keeps? = true
|
|
33
|
+
|
|
34
|
+
def skip_storage? = false
|
|
35
|
+
|
|
36
|
+
def options = ReactOnRailsGitTemplateOptions.new
|
|
37
|
+
|
|
38
|
+
def get_binding
|
|
39
|
+
binding
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
spec = Gem::Specification.find_by_name("railties")
|
|
44
|
+
railties_major = spec.version.segments.first
|
|
45
|
+
unless [${SUPPORTED_RAILTIES_MAJORS.join(', ')}].include?(railties_major)
|
|
46
|
+
warn "Unsupported railties major version for git template rendering: #{spec.version}"
|
|
47
|
+
exit 1
|
|
48
|
+
end
|
|
49
|
+
template_path = File.join(spec.gem_dir, "lib/rails/generators/rails/app/templates", ARGV[0])
|
|
50
|
+
template = File.read(template_path)
|
|
51
|
+
print ERB.new(template, trim_mode: "-").result(ReactOnRailsGitTemplateContext.new.get_binding)
|
|
52
|
+
`;
|
|
53
|
+
// Fallback copy of the Rails git scaffold (validated against Rails 7.2 templates)
|
|
54
|
+
// for environments where rendering the installed railties templates is unavailable.
|
|
55
|
+
const RAILS_GITIGNORE_FALLBACK = `# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
|
56
|
+
#
|
|
57
|
+
# Temporary files generated by your text editor or operating system
|
|
58
|
+
# belong in git's global ignore instead:
|
|
59
|
+
# \`$XDG_CONFIG_HOME/git/ignore\` or \`~/.config/git/ignore\`
|
|
60
|
+
|
|
61
|
+
# Ignore bundler config.
|
|
62
|
+
/.bundle
|
|
63
|
+
|
|
64
|
+
# Ignore all environment files.
|
|
65
|
+
/.env*
|
|
66
|
+
|
|
67
|
+
# Ignore all logfiles and tempfiles.
|
|
68
|
+
/log/*
|
|
69
|
+
/tmp/*
|
|
70
|
+
!/log/.keep
|
|
71
|
+
!/tmp/.keep
|
|
72
|
+
|
|
73
|
+
# Ignore node modules.
|
|
74
|
+
/node_modules
|
|
75
|
+
|
|
76
|
+
# Ignore pidfiles, but keep the directory.
|
|
77
|
+
/tmp/pids/*
|
|
78
|
+
!/tmp/pids/
|
|
79
|
+
!/tmp/pids/.keep
|
|
80
|
+
|
|
81
|
+
# Ignore storage (uploaded files in development and any SQLite databases).
|
|
82
|
+
/storage/*
|
|
83
|
+
!/storage/.keep
|
|
84
|
+
/tmp/storage/*
|
|
85
|
+
!/tmp/storage/
|
|
86
|
+
!/tmp/storage/.keep
|
|
87
|
+
|
|
88
|
+
/public/assets
|
|
89
|
+
|
|
90
|
+
# Ignore key files for decrypting credentials and more.
|
|
91
|
+
/config/*.key
|
|
92
|
+
`;
|
|
93
|
+
const RAILS_GITATTRIBUTES_FALLBACK = `# See https://git-scm.com/docs/gitattributes for more about git attribute files.
|
|
94
|
+
|
|
95
|
+
# Mark the database schema as having been generated.
|
|
96
|
+
db/schema.rb linguist-generated
|
|
97
|
+
|
|
98
|
+
# Mark any vendored files as having been vendored.
|
|
99
|
+
vendor/* linguist-vendored
|
|
100
|
+
`;
|
|
13
101
|
function cleanupAppDirectory(appPath, appName, cleanupSuccessMessage, cleanupFallbackMessage) {
|
|
14
102
|
if (!fs_1.default.existsSync(appPath)) {
|
|
15
103
|
return;
|
|
@@ -46,7 +134,7 @@ function bundleAddArgs(gemName, localPath, strict = true) {
|
|
|
46
134
|
return args;
|
|
47
135
|
}
|
|
48
136
|
function buildGeneratorArgs(options) {
|
|
49
|
-
const args = [];
|
|
137
|
+
const args = ['--new-app'];
|
|
50
138
|
if (options.template === 'typescript') {
|
|
51
139
|
args.push('--typescript');
|
|
52
140
|
}
|
|
@@ -63,10 +151,9 @@ function buildGeneratorArgs(options) {
|
|
|
63
151
|
// --force makes the generator overwrite conflicting files without prompting,
|
|
64
152
|
// which is safe for a freshly scaffolded app with no custom content yet.
|
|
65
153
|
args.push('--force');
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// acceptable for a fresh app where prerequisites were already checked above.
|
|
154
|
+
// Keep the create-app flow non-interactive. --ignore-warnings bypasses generator
|
|
155
|
+
// validation warnings (git, Node version, package manager) because the CLI
|
|
156
|
+
// already checked prerequisites and is driving a fresh-app automation path.
|
|
70
157
|
args.push('--ignore-warnings');
|
|
71
158
|
return args;
|
|
72
159
|
}
|
|
@@ -102,6 +189,30 @@ function rewriteFileIfPresent(filePath, transform) {
|
|
|
102
189
|
fs_1.default.writeFileSync(filePath, updated, 'utf8');
|
|
103
190
|
}
|
|
104
191
|
}
|
|
192
|
+
function readInstalledRailsGitTemplate(appPath, templateName) {
|
|
193
|
+
try {
|
|
194
|
+
const renderedTemplate = (0, utils_js_1.execCaptureArgs)('ruby', ['-e', RAILS_GIT_TEMPLATE_RENDERER, templateName], appPath);
|
|
195
|
+
return renderedTemplate.length > 0 ? `${renderedTemplate}\n` : null;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function railsGitScaffold(appPath) {
|
|
202
|
+
const renderedGitignore = readInstalledRailsGitTemplate(appPath, 'gitignore.tt');
|
|
203
|
+
const renderedGitattributes = readInstalledRailsGitTemplate(appPath, 'gitattributes.tt');
|
|
204
|
+
return {
|
|
205
|
+
gitignore: renderedGitignore ?? RAILS_GITIGNORE_FALLBACK,
|
|
206
|
+
gitattributes: renderedGitattributes ?? RAILS_GITATTRIBUTES_FALLBACK,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function restoreRailsGitScaffold(appPath) {
|
|
210
|
+
// Rails omits .gitignore/.gitattributes when rails new runs with --skip-git.
|
|
211
|
+
// Recreate the defaults so the educational commit history stays clean.
|
|
212
|
+
const scaffold = railsGitScaffold(appPath);
|
|
213
|
+
fs_1.default.writeFileSync(path_1.default.join(appPath, '.gitignore'), scaffold.gitignore, 'utf8');
|
|
214
|
+
fs_1.default.writeFileSync(path_1.default.join(appPath, '.gitattributes'), scaffold.gitattributes, 'utf8');
|
|
215
|
+
}
|
|
105
216
|
function normalizeGeneratedPackageManager(appPath, packageManager) {
|
|
106
217
|
if (packageManager !== 'pnpm') {
|
|
107
218
|
return;
|
|
@@ -133,7 +244,95 @@ function normalizeGeneratedPackageManager(appPath, packageManager) {
|
|
|
133
244
|
});
|
|
134
245
|
(0, utils_js_1.logStepDone)('pnpm configuration applied');
|
|
135
246
|
}
|
|
136
|
-
function
|
|
247
|
+
function ensureGitRepository(appPath) {
|
|
248
|
+
if (fs_1.default.existsSync(path_1.default.join(appPath, '.git'))) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
(0, utils_js_1.execLiveArgs)('git', ['init'], appPath);
|
|
252
|
+
}
|
|
253
|
+
function readGitConfig(appPath, key) {
|
|
254
|
+
try {
|
|
255
|
+
const value = (0, utils_js_1.execCaptureArgs)('git', ['config', '--get', key], appPath);
|
|
256
|
+
return value === '' ? null : value;
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function educationalGitEnv(appPath) {
|
|
263
|
+
const userName = readGitConfig(appPath, 'user.name') ?? DEFAULT_GIT_AUTHOR_NAME;
|
|
264
|
+
const userEmail = readGitConfig(appPath, 'user.email') ?? DEFAULT_GIT_AUTHOR_EMAIL;
|
|
265
|
+
return {
|
|
266
|
+
...process.env,
|
|
267
|
+
GIT_AUTHOR_NAME: userName,
|
|
268
|
+
GIT_AUTHOR_EMAIL: userEmail,
|
|
269
|
+
GIT_COMMITTER_NAME: userName,
|
|
270
|
+
GIT_COMMITTER_EMAIL: userEmail,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function gitHasPendingChanges(appPath) {
|
|
274
|
+
try {
|
|
275
|
+
return (0, utils_js_1.execCaptureArgs)('git', ['status', '--porcelain'], appPath) !== '';
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function createEducationalCommit(appPath, subject, body, educationalGitEnvForCommit) {
|
|
282
|
+
ensureGitRepository(appPath);
|
|
283
|
+
if (!gitHasPendingChanges(appPath)) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
// The educational history intentionally snapshots each full scaffold step.
|
|
287
|
+
(0, utils_js_1.execLiveArgs)('git', ['add', '-A'], appPath);
|
|
288
|
+
(0, utils_js_1.execLiveArgs)('git', ['-c', 'commit.gpgsign=false', 'commit', '-m', subject, '-m', body], appPath, educationalGitEnvForCommit());
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
function railsBaselineCommitMessage() {
|
|
292
|
+
return {
|
|
293
|
+
subject: 'Create Rails app with PostgreSQL',
|
|
294
|
+
body: 'Generate the fresh Rails baseline before adding React on Rails.',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function reactOnRailsGemCommitMessage() {
|
|
298
|
+
return {
|
|
299
|
+
subject: 'Add react_on_rails gem',
|
|
300
|
+
body: 'Install the OSS gem so the next commit can run the React on Rails generator.',
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
function reactOnRailsProCommitMessage(modeFlag) {
|
|
304
|
+
return {
|
|
305
|
+
subject: 'Add react_on_rails_pro gem',
|
|
306
|
+
body: `Install the Pro gem required by ${modeFlag} before running the generator.`,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function generatorCommitMessage(options) {
|
|
310
|
+
const language = options.template === 'typescript' ? 'TypeScript' : 'JavaScript';
|
|
311
|
+
const bundler = options.rspack ? 'Rspack' : 'Webpack';
|
|
312
|
+
if (options.rsc) {
|
|
313
|
+
return {
|
|
314
|
+
subject: `Install React Server Components with ${language} and ${bundler}`,
|
|
315
|
+
body: 'Run react_on_rails:install --rsc to add the generated home page, HelloServer example, and Pro RSC wiring.',
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (options.pro) {
|
|
319
|
+
return {
|
|
320
|
+
subject: `Install React on Rails Pro with ${language} and ${bundler}`,
|
|
321
|
+
body: 'Run react_on_rails:install --pro to add the generated home page, SSR example, and Pro Node renderer wiring.',
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
return {
|
|
325
|
+
subject: `Install React on Rails with ${language} and ${bundler}`,
|
|
326
|
+
body: 'Run react_on_rails:install to add the generated home page, SSR example, and development workflow.',
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function pnpmCommitMessage() {
|
|
330
|
+
return {
|
|
331
|
+
subject: 'Normalize the generated app for pnpm',
|
|
332
|
+
body: 'Convert npm artifacts to pnpm, remove package-lock.json, and update bin/setup to use pnpm install.',
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function printSuccessMessage(appName, educationalCommitsEnabled) {
|
|
137
336
|
console.log('');
|
|
138
337
|
(0, utils_js_1.logSuccess)(`Created ${appName} with React on Rails!`);
|
|
139
338
|
console.log('');
|
|
@@ -144,7 +343,16 @@ function printSuccessMessage(appName, route) {
|
|
|
144
343
|
console.log('');
|
|
145
344
|
(0, utils_js_1.logInfo)('Note: The generated app uses PostgreSQL by default. Start PostgreSQL before running bin/rails db:prepare.');
|
|
146
345
|
console.log('');
|
|
147
|
-
(0, utils_js_1.logInfo)(
|
|
346
|
+
(0, utils_js_1.logInfo)('Then visit http://localhost:3000');
|
|
347
|
+
(0, utils_js_1.logInfo)('bin/dev will try to open the generated home page on first successful boot.');
|
|
348
|
+
console.log('');
|
|
349
|
+
if (educationalCommitsEnabled) {
|
|
350
|
+
(0, utils_js_1.logInfo)('Educational commits skip GPG signing so scaffold automation does not block on local signer prompts.');
|
|
351
|
+
(0, utils_js_1.logInfo)('Educational git history: git log --oneline --reverse');
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
(0, utils_js_1.logInfo)('Educational git history is partial because git commit automation was skipped after an earlier failure.');
|
|
355
|
+
}
|
|
148
356
|
console.log('');
|
|
149
357
|
(0, utils_js_1.logInfo)(`Documentation: ${DOCS_URL}`);
|
|
150
358
|
console.log('');
|
|
@@ -175,24 +383,49 @@ function validateAppName(name) {
|
|
|
175
383
|
function createApp(appName, options) {
|
|
176
384
|
const appPath = path_1.default.resolve(process.cwd(), appName);
|
|
177
385
|
const proRequested = options.pro || options.rsc;
|
|
386
|
+
let educationalCommitsEnabled = true;
|
|
387
|
+
let cachedEducationalGitEnv = null;
|
|
178
388
|
let proModeLabel = null;
|
|
179
|
-
if (
|
|
180
|
-
proModeLabel = '--rsc';
|
|
181
|
-
}
|
|
182
|
-
else if (options.pro) {
|
|
183
|
-
proModeLabel = '--pro';
|
|
389
|
+
if (proRequested) {
|
|
390
|
+
proModeLabel = options.rsc ? '--rsc' : '--pro';
|
|
184
391
|
}
|
|
185
392
|
const baseSteps = 3; // rails new + add react_on_rails + run generator
|
|
186
393
|
const totalSteps = baseSteps + (proRequested ? 1 : 0);
|
|
187
394
|
let currentStep = 1;
|
|
188
395
|
const reactOnRailsGemPath = localGemPath('REACT_ON_RAILS_GEM_PATH');
|
|
189
396
|
const reactOnRailsProGemPath = proRequested ? localGemPath('REACT_ON_RAILS_PRO_GEM_PATH') : null;
|
|
397
|
+
function educationalGitEnvForCommits() {
|
|
398
|
+
if (!cachedEducationalGitEnv) {
|
|
399
|
+
// The baseline commit happens before git init; in that case git config falls back
|
|
400
|
+
// to the user's global identity, which is the behavior we want here.
|
|
401
|
+
cachedEducationalGitEnv = educationalGitEnv(appPath);
|
|
402
|
+
}
|
|
403
|
+
return cachedEducationalGitEnv;
|
|
404
|
+
}
|
|
405
|
+
function recordEducationalCommit(subject, body) {
|
|
406
|
+
if (!educationalCommitsEnabled) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
try {
|
|
410
|
+
createEducationalCommit(appPath, subject, body, educationalGitEnvForCommits);
|
|
411
|
+
}
|
|
412
|
+
catch (error) {
|
|
413
|
+
educationalCommitsEnabled = false;
|
|
414
|
+
(0, utils_js_1.logInfo)(`Educational git history paused after "${subject}" because git commit automation failed. The app scaffold will continue.`);
|
|
415
|
+
if (error instanceof Error && error.message) {
|
|
416
|
+
console.error(`Git debug info: ${error.message}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
190
420
|
// Step 1: Create Rails application
|
|
191
421
|
// appName is validated by validateAppName() to be ^[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*$ only,
|
|
192
422
|
// so it's always a simple directory name safe to use with rails new.
|
|
193
423
|
(0, utils_js_1.logStep)(currentStep, totalSteps, 'Creating Rails application...');
|
|
194
424
|
try {
|
|
195
|
-
(0, utils_js_1.execLiveArgs)('rails', ['new', appName, '--database=postgresql', '--skip-javascript']);
|
|
425
|
+
(0, utils_js_1.execLiveArgs)('rails', ['new', appName, '--database=postgresql', '--skip-javascript', '--skip-git']);
|
|
426
|
+
restoreRailsGitScaffold(appPath);
|
|
427
|
+
const { subject, body } = railsBaselineCommitMessage();
|
|
428
|
+
recordEducationalCommit(subject, body);
|
|
196
429
|
(0, utils_js_1.logStepDone)('Rails application created');
|
|
197
430
|
}
|
|
198
431
|
catch (error) {
|
|
@@ -212,6 +445,8 @@ function createApp(appName, options) {
|
|
|
212
445
|
(0, utils_js_1.logInfo)(`Using local react_on_rails gem path: ${reactOnRailsGemPath}`);
|
|
213
446
|
}
|
|
214
447
|
(0, utils_js_1.execLiveArgs)('bundle', reactOnRailsArgs, appPath);
|
|
448
|
+
const { subject, body } = reactOnRailsGemCommitMessage();
|
|
449
|
+
recordEducationalCommit(subject, body);
|
|
215
450
|
(0, utils_js_1.logStepDone)('react_on_rails gem added');
|
|
216
451
|
}
|
|
217
452
|
catch (error) {
|
|
@@ -231,6 +466,8 @@ function createApp(appName, options) {
|
|
|
231
466
|
(0, utils_js_1.logInfo)(`Using local react_on_rails_pro gem path: ${reactOnRailsProGemPath}`);
|
|
232
467
|
}
|
|
233
468
|
(0, utils_js_1.execLiveArgs)('bundle', reactOnRailsProArgs, appPath);
|
|
469
|
+
const { subject, body } = reactOnRailsProCommitMessage(proModeLabel ?? '--pro');
|
|
470
|
+
recordEducationalCommit(subject, body);
|
|
234
471
|
(0, utils_js_1.logStepDone)('react_on_rails_pro gem added');
|
|
235
472
|
}
|
|
236
473
|
catch (error) {
|
|
@@ -251,6 +488,8 @@ function createApp(appName, options) {
|
|
|
251
488
|
...process.env,
|
|
252
489
|
REACT_ON_RAILS_PACKAGE_MANAGER: options.packageManager,
|
|
253
490
|
});
|
|
491
|
+
const { subject, body } = generatorCommitMessage(options);
|
|
492
|
+
recordEducationalCommit(subject, body);
|
|
254
493
|
(0, utils_js_1.logStepDone)('React on Rails setup complete');
|
|
255
494
|
}
|
|
256
495
|
catch (error) {
|
|
@@ -263,6 +502,10 @@ function createApp(appName, options) {
|
|
|
263
502
|
}
|
|
264
503
|
try {
|
|
265
504
|
normalizeGeneratedPackageManager(appPath, options.packageManager);
|
|
505
|
+
if (options.packageManager === 'pnpm') {
|
|
506
|
+
const { subject, body } = pnpmCommitMessage();
|
|
507
|
+
recordEducationalCommit(subject, body);
|
|
508
|
+
}
|
|
266
509
|
}
|
|
267
510
|
catch (error) {
|
|
268
511
|
(0, utils_js_1.logError)(`Failed to finish ${options.packageManager} setup. The app was created, but package manager normalization did not complete.`);
|
|
@@ -280,6 +523,6 @@ function createApp(appName, options) {
|
|
|
280
523
|
}
|
|
281
524
|
// Final success
|
|
282
525
|
(0, utils_js_1.logStepDone)('Done!');
|
|
283
|
-
printSuccessMessage(appName,
|
|
526
|
+
printSuccessMessage(appName, educationalCommitsEnabled);
|
|
284
527
|
}
|
|
285
528
|
//# sourceMappingURL=create-app.js.map
|
package/lib/index.js
CHANGED
|
@@ -110,11 +110,18 @@ What it does:
|
|
|
110
110
|
1. Creates a new Rails app with PostgreSQL
|
|
111
111
|
2. Adds required gem(s) (react_on_rails, plus react_on_rails_pro for --pro/--rsc)
|
|
112
112
|
3. Runs the React on Rails generator (Shakapacker, components, webpack config)
|
|
113
|
+
4. Creates educational git commits for each major scaffold step
|
|
113
114
|
|
|
114
115
|
After setup, run bin/dev and visit:
|
|
115
|
-
- http://localhost:3000
|
|
116
|
-
-
|
|
116
|
+
- http://localhost:3000 (generated home page)
|
|
117
|
+
- /hello_world (default and --pro example page)
|
|
118
|
+
- /hello_server (--rsc example page)
|
|
117
119
|
|
|
120
|
+
Inspect the generated setup history with:
|
|
121
|
+
- git log --oneline --reverse
|
|
122
|
+
|
|
123
|
+
The generated app includes one git commit per logical setup step.`)
|
|
124
|
+
.addHelpText('after', `
|
|
118
125
|
--pro and --rsc support both JavaScript and TypeScript templates.
|
|
119
126
|
|
|
120
127
|
Documentation: https://reactonrails.com/docs/`)
|
package/lib/utils.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - Callers must still ensure `command` and `args` come from trusted/validated values.
|
|
7
7
|
*/
|
|
8
8
|
export declare function execLiveArgs(command: string, args: string[], cwd?: string, env?: NodeJS.ProcessEnv): void;
|
|
9
|
+
export declare function execCaptureArgs(command: string, args: string[], cwd?: string, env?: NodeJS.ProcessEnv): string;
|
|
9
10
|
export declare function getCommandVersion(command: string): string | null;
|
|
10
11
|
export declare function detectPackageManager(): 'npm' | 'pnpm' | null;
|
|
11
12
|
export declare function logStep(current: number, total: number, message: string): void;
|
package/lib/utils.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.execLiveArgs = execLiveArgs;
|
|
7
|
+
exports.execCaptureArgs = execCaptureArgs;
|
|
7
8
|
exports.getCommandVersion = getCommandVersion;
|
|
8
9
|
exports.detectPackageManager = detectPackageManager;
|
|
9
10
|
exports.logStep = logStep;
|
|
@@ -13,6 +14,10 @@ exports.logSuccess = logSuccess;
|
|
|
13
14
|
exports.logInfo = logInfo;
|
|
14
15
|
const child_process_1 = require("child_process");
|
|
15
16
|
const chalk_1 = __importDefault(require("chalk"));
|
|
17
|
+
function childEnv(env) {
|
|
18
|
+
// Always inherit PATH, HOME, and the rest of process.env; callers only add/override keys.
|
|
19
|
+
return env ? { ...process.env, ...env } : process.env;
|
|
20
|
+
}
|
|
16
21
|
/**
|
|
17
22
|
* Execute a command and stream output to the current terminal.
|
|
18
23
|
*
|
|
@@ -24,15 +29,39 @@ function execLiveArgs(command, args, cwd, env) {
|
|
|
24
29
|
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
25
30
|
stdio: 'inherit',
|
|
26
31
|
cwd,
|
|
27
|
-
|
|
32
|
+
env: childEnv(env),
|
|
28
33
|
});
|
|
29
34
|
if (result.error) {
|
|
30
35
|
throw result.error;
|
|
31
36
|
}
|
|
37
|
+
if (result.status === null) {
|
|
38
|
+
throw new Error(`Command "${command}" was terminated by ${result.signal ?? 'unknown signal'}`);
|
|
39
|
+
}
|
|
32
40
|
if (result.status !== 0) {
|
|
33
41
|
throw new Error(`Command "${command}" exited with code ${result.status}`);
|
|
34
42
|
}
|
|
35
43
|
}
|
|
44
|
+
function execCaptureArgs(command, args, cwd, env) {
|
|
45
|
+
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
46
|
+
stdio: 'pipe',
|
|
47
|
+
encoding: 'utf8',
|
|
48
|
+
cwd,
|
|
49
|
+
env: childEnv(env),
|
|
50
|
+
});
|
|
51
|
+
if (result.error) {
|
|
52
|
+
throw result.error;
|
|
53
|
+
}
|
|
54
|
+
if (result.status === null) {
|
|
55
|
+
throw new Error(`Command "${command}" was terminated by ${result.signal ?? 'unknown signal'}`);
|
|
56
|
+
}
|
|
57
|
+
if (result.status !== 0) {
|
|
58
|
+
const stderr = result.stderr?.trim();
|
|
59
|
+
throw new Error(stderr && stderr.length > 0
|
|
60
|
+
? `Command "${command}" exited with code ${result.status}: ${stderr}`
|
|
61
|
+
: `Command "${command}" exited with code ${result.status}`);
|
|
62
|
+
}
|
|
63
|
+
return result.stdout?.trim() ?? '';
|
|
64
|
+
}
|
|
36
65
|
function getCommandVersion(command) {
|
|
37
66
|
try {
|
|
38
67
|
return (0, child_process_1.execFileSync)(command, ['--version'], { encoding: 'utf8', stdio: 'pipe' }).trim();
|
package/lib/validators.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ValidationResult } from './types.js';
|
|
|
2
2
|
export declare function validateNode(): ValidationResult;
|
|
3
3
|
export declare function validateRuby(): ValidationResult;
|
|
4
4
|
export declare function validateRails(): ValidationResult;
|
|
5
|
+
export declare function validateGit(): ValidationResult;
|
|
5
6
|
export declare function validatePackageManager(pm: 'npm' | 'pnpm'): ValidationResult;
|
|
6
7
|
export interface PrerequisiteResults {
|
|
7
8
|
allValid: boolean;
|
package/lib/validators.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.validateNode = validateNode;
|
|
4
4
|
exports.validateRuby = validateRuby;
|
|
5
5
|
exports.validateRails = validateRails;
|
|
6
|
+
exports.validateGit = validateGit;
|
|
6
7
|
exports.validatePackageManager = validatePackageManager;
|
|
7
8
|
exports.validateAll = validateAll;
|
|
8
9
|
const utils_js_1 = require("./utils.js");
|
|
@@ -85,6 +86,19 @@ function validateRails() {
|
|
|
85
86
|
}
|
|
86
87
|
return { valid: true, message: firstLine };
|
|
87
88
|
}
|
|
89
|
+
function validateGit() {
|
|
90
|
+
const gitVersion = (0, utils_js_1.getCommandVersion)('git');
|
|
91
|
+
if (!gitVersion) {
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
message: 'git is not installed or not found in PATH.\n\n' +
|
|
95
|
+
'create-react-on-rails-app now records the generated app as a step-by-step git history.\n' +
|
|
96
|
+
'Install git, then try again:\n' +
|
|
97
|
+
' https://git-scm.com/downloads',
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return { valid: true, message: gitVersion.split('\n')[0].trim() };
|
|
101
|
+
}
|
|
88
102
|
function validatePackageManager(pm) {
|
|
89
103
|
const version = (0, utils_js_1.getCommandVersion)(pm);
|
|
90
104
|
if (!version) {
|
|
@@ -100,6 +114,7 @@ function validateAll(packageManager) {
|
|
|
100
114
|
{ name: 'Node.js', result: validateNode() },
|
|
101
115
|
{ name: 'Ruby', result: validateRuby() },
|
|
102
116
|
{ name: 'Rails', result: validateRails() },
|
|
117
|
+
{ name: 'git', result: validateGit() },
|
|
103
118
|
{ name: 'Package Manager', result: validatePackageManager(packageManager) },
|
|
104
119
|
];
|
|
105
120
|
const allValid = results.every((r) => r.result.valid);
|
package/package.json
CHANGED