devendra-skill-package 1.0.2

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.
Files changed (223) hide show
  1. package/.github/skills/boiler-project-generator/SKILL.md +103 -0
  2. package/.github/skills/boiler-project-generator/assets/templates/android/README.md +3 -0
  3. package/.github/skills/boiler-project-generator/assets/templates/angular/README.md +3 -0
  4. package/.github/skills/boiler-project-generator/assets/templates/flutter/README.md +3 -0
  5. package/.github/skills/boiler-project-generator/assets/templates/ios/README.md +3 -0
  6. package/.github/skills/boiler-project-generator/assets/templates/react/README.md +3 -0
  7. package/.github/skills/boiler-project-generator/assets/templates/react-native/README.md +3 -0
  8. package/.github/skills/boiler-project-generator/assets/templates/react-native/api-setup.ts +76 -0
  9. package/.github/skills/boiler-project-generator/assets/templates/react-native/component-primary-button.tsx +29 -0
  10. package/.github/skills/boiler-project-generator/assets/templates/react-native/env-config.env +4 -0
  11. package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-auth-screen.tsx +45 -0
  12. package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-dashboard-screen.tsx +45 -0
  13. package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-profile-screen.tsx +45 -0
  14. package/.github/skills/boiler-project-generator/assets/templates/react-native/hook-use-app-boot.ts +11 -0
  15. package/.github/skills/boiler-project-generator/assets/templates/react-native/navigation-setup.tsx +25 -0
  16. package/.github/skills/boiler-project-generator/assets/templates/react-native/project-structure.md +24 -0
  17. package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-hooks.ts +5 -0
  18. package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-session-slice.ts +29 -0
  19. package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-store.ts +15 -0
  20. package/.github/skills/boiler-project-generator/assets/templates/react-native/theme-index.ts +23 -0
  21. package/.github/skills/boiler-project-generator/assets/templates/react-native/utils-helpers.ts +7 -0
  22. package/.github/skills/boiler-project-generator/metadata.yaml +29 -0
  23. package/.github/skills/boiler-project-generator/references/android.md +19 -0
  24. package/.github/skills/boiler-project-generator/references/angular.md +14 -0
  25. package/.github/skills/boiler-project-generator/references/flutter.md +17 -0
  26. package/.github/skills/boiler-project-generator/references/ios.md +17 -0
  27. package/.github/skills/boiler-project-generator/references/react-native.md +103 -0
  28. package/.github/skills/boiler-project-generator/references/react.md +17 -0
  29. package/.github/skills/boiler-project-generator/scripts/angular.js +43 -0
  30. package/.github/skills/boiler-project-generator/scripts/generate-project.mjs +107 -0
  31. package/.github/skills/boiler-project-generator/scripts/init.js +105 -0
  32. package/.github/skills/boiler-project-generator/scripts/react-native.js +1041 -0
  33. package/.github/skills/boiler-project-generator/scripts/react.js +32 -0
  34. package/.github/skills/clean-architecture-generator/SKILL.md +235 -0
  35. package/.github/skills/clean-architecture-generator/references/architecture-core.md +126 -0
  36. package/.github/skills/clean-architecture-generator/references/platform-angular.md +374 -0
  37. package/.github/skills/clean-architecture-generator/references/platform-flutter.md +366 -0
  38. package/.github/skills/clean-architecture-generator/references/platform-react-native.md +590 -0
  39. package/.github/skills/clean-architecture-generator/references/platform-react.md +261 -0
  40. package/.github/skills/code-test-coverage-analyzer/SKILL.md +1358 -0
  41. package/.github/skills/code-test-coverage-analyzer/assets/config-questionnaire.md +118 -0
  42. package/.github/skills/code-test-coverage-analyzer/assets/conflict-resolution-protocol.md +99 -0
  43. package/.github/skills/code-test-coverage-analyzer/assets/coverage-calculation-rules.md +294 -0
  44. package/.github/skills/code-test-coverage-analyzer/assets/multi-strategy-search.md +145 -0
  45. package/.github/skills/code-test-coverage-analyzer/assets/output-file-contracts.md +651 -0
  46. package/.github/skills/code-test-coverage-analyzer/assets/status-definitions.md +273 -0
  47. package/.github/skills/code-test-coverage-analyzer/examples/example-invocation.md +208 -0
  48. package/.github/skills/code-test-coverage-analyzer/references/android-patterns.md +229 -0
  49. package/.github/skills/code-test-coverage-analyzer/references/angular-patterns.md +177 -0
  50. package/.github/skills/code-test-coverage-analyzer/references/backend-patterns.md +383 -0
  51. package/.github/skills/code-test-coverage-analyzer/references/flutter-patterns.md +254 -0
  52. package/.github/skills/code-test-coverage-analyzer/references/ios-patterns.md +228 -0
  53. package/.github/skills/code-test-coverage-analyzer/references/react-native-patterns.md +431 -0
  54. package/.github/skills/code-test-coverage-analyzer/references/react-patterns.md +206 -0
  55. package/.github/skills/code-test-coverage-analyzer/scripts/detect-platform.ps1 +261 -0
  56. package/.github/skills/code-test-coverage-analyzer/scripts/scan-repository.ps1 +242 -0
  57. package/.github/skills/code-test-coverage-analyzer/templates/brd-compliance-row.md +107 -0
  58. package/.github/skills/code-test-coverage-analyzer/templates/test-case-row.md +130 -0
  59. package/.github/skills/code-test-coverage-analyzer/templates/user-story-compliance-row.md +170 -0
  60. package/.github/skills/custom-font-integrator/SKILL.md +373 -0
  61. package/.github/skills/custom-font-integrator/assets/FontTestScreen.js +150 -0
  62. package/.github/skills/custom-font-integrator/references/example-info-plist.xml +21 -0
  63. package/.github/skills/custom-font-integrator/scripts/setup-fonts.sh +99 -0
  64. package/.github/skills/encryption-decryption-utility/Instructions.md +282 -0
  65. package/.github/skills/encryption-decryption-utility/README.md +222 -0
  66. package/.github/skills/encryption-decryption-utility/SKILL.md +645 -0
  67. package/.github/skills/encryption-decryption-utility/assets/encryption-config-template.json +13 -0
  68. package/.github/skills/encryption-decryption-utility/assets/platform-matrix.json +180 -0
  69. package/.github/skills/encryption-decryption-utility/metadata.yaml +51 -0
  70. package/.github/skills/encryption-decryption-utility/references/android-crypto.md +157 -0
  71. package/.github/skills/encryption-decryption-utility/references/angular-crypto.md +130 -0
  72. package/.github/skills/encryption-decryption-utility/references/approach-1-webcrypto.md +95 -0
  73. package/.github/skills/encryption-decryption-utility/references/approach-2-rsa-aes-cbc.md +111 -0
  74. package/.github/skills/encryption-decryption-utility/references/approach-3-aes-sharedkey.md +114 -0
  75. package/.github/skills/encryption-decryption-utility/references/architecture.md +233 -0
  76. package/.github/skills/encryption-decryption-utility/references/backend-alignment.md +506 -0
  77. package/.github/skills/encryption-decryption-utility/references/dependency-guidance.md +234 -0
  78. package/.github/skills/encryption-decryption-utility/references/ec-implementation.md +832 -0
  79. package/.github/skills/encryption-decryption-utility/references/error-handling-playbook.md +234 -0
  80. package/.github/skills/encryption-decryption-utility/references/flutter-crypto.md +105 -0
  81. package/.github/skills/encryption-decryption-utility/references/ios-crypto.md +338 -0
  82. package/.github/skills/encryption-decryption-utility/references/native-rsa-implementation.md +373 -0
  83. package/.github/skills/encryption-decryption-utility/references/node-crypto.md +86 -0
  84. package/.github/skills/encryption-decryption-utility/references/react-crypto.md +105 -0
  85. package/.github/skills/encryption-decryption-utility/references/react-native-crypto.md +1001 -0
  86. package/.github/skills/encryption-decryption-utility/references/react-web-crypto.md +668 -0
  87. package/.github/skills/encryption-decryption-utility/references/security-checklist.md +176 -0
  88. package/.github/skills/encryption-decryption-utility/references/web-crypto.md +74 -0
  89. package/.github/skills/encryption-decryption-utility/scripts/detect.js +128 -0
  90. package/.github/skills/encryption-decryption-utility/scripts/install.js +1713 -0
  91. package/.github/skills/encryption-decryption-utility/scripts/setup.js +800 -0
  92. package/.github/skills/encryption-decryption-utility/template.md +241 -0
  93. package/.github/skills/encryption-decryption-utility/templates/config-questionnaire.md +467 -0
  94. package/.github/skills/encryption-decryption-utility/templates/delivery-checklist.md +206 -0
  95. package/.github/skills/encryption-decryption-utility/templates/implementation-plan.md +304 -0
  96. package/.github/skills/feature-generator/SKILL.MD +2741 -0
  97. package/.github/skills/feature-generator/assets/fingerprint-schema.json +385 -0
  98. package/.github/skills/feature-generator/assets/spec-template.md +172 -0
  99. package/.github/skills/feature-generator/assets/transform-rules.json +82 -0
  100. package/.github/skills/feature-generator/references/api-envelope-patterns.md +327 -0
  101. package/.github/skills/feature-generator/references/screen-logic-patterns.md +399 -0
  102. package/.github/skills/feature-generator/references/state-library-patterns.md +464 -0
  103. package/.github/skills/figma-ui-mapper/README.md +34 -0
  104. package/.github/skills/figma-ui-mapper/SKILL.md +101 -0
  105. package/.github/skills/figma-ui-mapper/assets/templates/component-map-template.md +30 -0
  106. package/.github/skills/figma-ui-mapper/assets/templates/generation-report-template.md +49 -0
  107. package/.github/skills/figma-ui-mapper/metadata.yaml +22 -0
  108. package/.github/skills/figma-ui-mapper/references/android.md +11 -0
  109. package/.github/skills/figma-ui-mapper/references/angular.md +18 -0
  110. package/.github/skills/figma-ui-mapper/references/capacitor.md +18 -0
  111. package/.github/skills/figma-ui-mapper/references/common.md +23 -0
  112. package/.github/skills/figma-ui-mapper/references/expo.md +18 -0
  113. package/.github/skills/figma-ui-mapper/references/flutter.md +18 -0
  114. package/.github/skills/figma-ui-mapper/references/frameworks/angular/rules.md +23 -0
  115. package/.github/skills/figma-ui-mapper/references/frameworks/capacitor/rules.md +23 -0
  116. package/.github/skills/figma-ui-mapper/references/frameworks/expo/rules.md +23 -0
  117. package/.github/skills/figma-ui-mapper/references/frameworks/flutter/rules.md +23 -0
  118. package/.github/skills/figma-ui-mapper/references/frameworks/ionic/rules.md +23 -0
  119. package/.github/skills/figma-ui-mapper/references/frameworks/kotlin-multiplatform/rules.md +23 -0
  120. package/.github/skills/figma-ui-mapper/references/frameworks/native-android/rules.md +23 -0
  121. package/.github/skills/figma-ui-mapper/references/frameworks/native-ios/rules.md +23 -0
  122. package/.github/skills/figma-ui-mapper/references/frameworks/next/rules.md +23 -0
  123. package/.github/skills/figma-ui-mapper/references/frameworks/nuxt/rules.md +23 -0
  124. package/.github/skills/figma-ui-mapper/references/frameworks/react/rules.md +19 -0
  125. package/.github/skills/figma-ui-mapper/references/frameworks/react-native/rules.md +23 -0
  126. package/.github/skills/figma-ui-mapper/references/frameworks/svelte/rules.md +23 -0
  127. package/.github/skills/figma-ui-mapper/references/frameworks/vue/rules.md +23 -0
  128. package/.github/skills/figma-ui-mapper/references/ionic.md +18 -0
  129. package/.github/skills/figma-ui-mapper/references/ios.md +11 -0
  130. package/.github/skills/figma-ui-mapper/references/kotlin-multiplatform.md +18 -0
  131. package/.github/skills/figma-ui-mapper/references/native-android.md +18 -0
  132. package/.github/skills/figma-ui-mapper/references/native-ios.md +18 -0
  133. package/.github/skills/figma-ui-mapper/references/next.md +18 -0
  134. package/.github/skills/figma-ui-mapper/references/nuxt.md +18 -0
  135. package/.github/skills/figma-ui-mapper/references/react-figma-guideline.md +21 -0
  136. package/.github/skills/figma-ui-mapper/references/react-native.md +18 -0
  137. package/.github/skills/figma-ui-mapper/references/react.md +18 -0
  138. package/.github/skills/figma-ui-mapper/references/script-usage.md +46 -0
  139. package/.github/skills/figma-ui-mapper/references/svelte.md +18 -0
  140. package/.github/skills/figma-ui-mapper/references/vue.md +18 -0
  141. package/.github/skills/figma-ui-mapper/references/workflow.md +74 -0
  142. package/.github/skills/figma-ui-mapper/scripts/generate-page-through-figma.js +635 -0
  143. package/.github/skills/figma-ui-mapper/scripts/generate-react-page-through-figma.js +4 -0
  144. package/.github/skills/frontend-task-breakdown/SKILL.md +734 -0
  145. package/.github/skills/md-file-converter/SKILL.md +291 -0
  146. package/.github/skills/md-file-converter/config/formats.json +114 -0
  147. package/.github/skills/md-file-converter/extractors/extract-docx.sh +120 -0
  148. package/.github/skills/md-file-converter/extractors/extract-eml.py +108 -0
  149. package/.github/skills/md-file-converter/extractors/extract-pdf.sh +97 -0
  150. package/.github/skills/md-file-converter/extractors/extract-pptx.py +143 -0
  151. package/.github/skills/md-file-converter/extractors/extract-xlsx.py +175 -0
  152. package/.github/skills/md-file-converter/scripts/check-tools.sh +47 -0
  153. package/.github/skills/md-file-converter/scripts/extract-archive.sh +83 -0
  154. package/.github/skills/md-file-converter/scripts/extract-ocr.sh +77 -0
  155. package/.github/skills/md-file-converter/scripts/get-metadata.sh +57 -0
  156. package/.github/skills/md-file-converter/templates/output.md +73 -0
  157. package/.github/skills/md-file-converter/templates/unsupported-object.md +6 -0
  158. package/.github/skills/mockAPI-contract-generator/SKILL.md +607 -0
  159. package/.github/skills/test-case-validator/CHANGELOG.md +155 -0
  160. package/.github/skills/test-case-validator/CONFIGURATION.md +130 -0
  161. package/.github/skills/test-case-validator/EXAMPLES.md +50 -0
  162. package/.github/skills/test-case-validator/QUALITY_SCORING.md +98 -0
  163. package/.github/skills/test-case-validator/README.md +111 -0
  164. package/.github/skills/test-case-validator/REPORT_TEMPLATE.md +116 -0
  165. package/.github/skills/test-case-validator/SKILL.md +209 -0
  166. package/.github/skills/test-case-validator/SYSTEM_PROMPT.md +127 -0
  167. package/.github/skills/test-case-validator/TRACEABILITY_ENGINE.md +152 -0
  168. package/.github/skills/test-case-validator/VALIDATION_RULES.md +411 -0
  169. package/.github/skills/test-case-validator/config/default.config.yaml +62 -0
  170. package/.github/skills/test-case-validator/docs/ARCHITECTURE.md +66 -0
  171. package/.github/skills/test-case-validator/docs/EXTENDING.md +85 -0
  172. package/.github/skills/test-case-validator/docs/FAQ.md +88 -0
  173. package/.github/skills/test-case-validator/docs/VERSIONING.md +55 -0
  174. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-brd.md +43 -0
  175. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-test-cases.csv +9 -0
  176. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-user-story.md +29 -0
  177. package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/output-report.md +303 -0
  178. package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/input-spec.md +43 -0
  179. package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/input-test-files-index.md +56 -0
  180. package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/output-report.md +274 -0
  181. package/.github/skills/test-case-validator/prompts/00-applicability-resolution.md +61 -0
  182. package/.github/skills/test-case-validator/prompts/01-extraction-user-story.md +58 -0
  183. package/.github/skills/test-case-validator/prompts/02-extraction-brd.md +64 -0
  184. package/.github/skills/test-case-validator/prompts/03-extraction-test-cases.md +92 -0
  185. package/.github/skills/test-case-validator/prompts/04-traceability-matrix.md +41 -0
  186. package/.github/skills/test-case-validator/prompts/05-coverage-analysis.md +46 -0
  187. package/.github/skills/test-case-validator/prompts/06-missing-scenario-detection.md +32 -0
  188. package/.github/skills/test-case-validator/prompts/07-duplicate-detection.md +36 -0
  189. package/.github/skills/test-case-validator/prompts/08-risk-analysis.md +39 -0
  190. package/.github/skills/test-case-validator/prompts/09-gated-wiring-validation.md +53 -0
  191. package/.github/skills/test-case-validator/prompts/10-observation-report.md +56 -0
  192. package/.github/skills/test-case-validator/prompts/11-final-report-assembly.md +57 -0
  193. package/.github/skills/test-case-validator/templates/coverage-table.md +25 -0
  194. package/.github/skills/test-case-validator/templates/executive-summary.md +38 -0
  195. package/.github/skills/test-case-validator/templates/final-summary-block.md +87 -0
  196. package/.github/skills/test-case-validator/templates/observation-entry.md +41 -0
  197. package/.github/skills/test-case-validator/templates/rtm-row.md +25 -0
  198. package/.github/skills/validation-engine-builder/README.md +50 -0
  199. package/.github/skills/validation-engine-builder/SKILL.md +241 -0
  200. package/.github/skills/validation-engine-builder/references/core.md +158 -0
  201. package/.github/skills/validation-engine-builder/references/platform-angular.md +166 -0
  202. package/.github/skills/validation-engine-builder/references/platform-flutter.md +118 -0
  203. package/.github/skills/validation-engine-builder/references/platform-react-native.md +170 -0
  204. package/.github/skills/validation-engine-builder/references/platform-react.md +94 -0
  205. package/.github/skills/validation-engine-builder/scripts/apply-to-project.js +80 -0
  206. package/.github/skills/validation-engine-builder/scripts/generate-angular.js +271 -0
  207. package/.github/skills/validation-engine-builder/scripts/generate-flutter.js +298 -0
  208. package/.github/skills/validation-engine-builder/scripts/generate-react-native.js +217 -0
  209. package/.github/skills/validation-engine-builder/scripts/generate-react.js +163 -0
  210. package/.github/skills/validation-engine-builder/scripts/install-deps.js +53 -0
  211. package/.github/skills/validation-engine-builder/scripts/shared/detect-platform.js +68 -0
  212. package/.github/skills/validation-engine-builder/scripts/shared/npm-deps.js +103 -0
  213. package/.github/skills/validation-engine-builder/scripts/shared/rhf-hook.js +43 -0
  214. package/.github/skills/validation-engine-builder/scripts/shared/ts-messages-regex.js +62 -0
  215. package/.github/skills/validation-engine-builder/scripts/shared/util.js +44 -0
  216. package/.github/skills/validation-engine-builder/scripts/shared/yup-schemas.js +80 -0
  217. package/.github/skills/validation-engine-builder/template.md +73 -0
  218. package/.github/skills/validation-engine-builder/templates/messages.json +22 -0
  219. package/.github/skills/validation-engine-builder/templates/regex.json +16 -0
  220. package/.github/skills/validation-engine-builder/templates/schemas.json +48 -0
  221. package/bin/index.js +145 -0
  222. package/package.json +22 -0
  223. package/readme.md +0 -0
@@ -0,0 +1,1041 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const {spawn} = require('node:child_process');
6
+ const readline = require('node:readline/promises');
7
+
8
+ const skillRoot = path.resolve(__dirname, '..');
9
+ const templateDir = path.join(skillRoot, 'assets', 'templates', 'react-native');
10
+
11
+ const defaultConfig = {
12
+ reactNativeVersion: 'latest',
13
+ useTypeScript: true,
14
+ enableNavigation: false,
15
+ enableRedux: false,
16
+ enableEnv: false,
17
+ uatApiBaseUrl: 'https://api-uat.example.com',
18
+ stagingApiBaseUrl: 'https://api-staging.example.com',
19
+ prodApiBaseUrl: 'https://api.example.com',
20
+ verificationTarget: 'none',
21
+ };
22
+
23
+ function isWindows() {
24
+ return process.platform === 'win32';
25
+ }
26
+
27
+ function resolveCommand(command) {
28
+ if (!isWindows()) {
29
+ return command;
30
+ }
31
+
32
+ if (command === 'npm' || command === 'npx') {
33
+ return `${command}.cmd`;
34
+ }
35
+
36
+ return command;
37
+ }
38
+
39
+ function runCommand(command, args, options = {}) {
40
+ const display = [command, ...args].join(' ');
41
+ console.log(`\n$ ${display}`);
42
+
43
+ const resolvedCommand = resolveCommand(command);
44
+ return new Promise((resolve, reject) => {
45
+ const child = spawn(resolvedCommand, args, {
46
+ cwd: options.cwd || process.cwd(),
47
+ shell: isWindows() && resolvedCommand.endsWith('.cmd'),
48
+ env: process.env,
49
+ });
50
+
51
+ let output = '';
52
+
53
+ child.stdout.on('data', chunk => {
54
+ const text = chunk.toString();
55
+ output += text;
56
+ process.stdout.write(text);
57
+ });
58
+
59
+ child.stderr.on('data', chunk => {
60
+ const text = chunk.toString();
61
+ output += text;
62
+ process.stderr.write(text);
63
+ });
64
+
65
+ child.on('error', error => {
66
+ reject(new Error(`Failed to run ${display}: ${error.message}`));
67
+ });
68
+
69
+ child.on('close', status => {
70
+ if (status !== 0 && !options.allowFailure) {
71
+ reject(new Error(`Command failed: ${display}\n${output.trim()}`.trim()));
72
+ return;
73
+ }
74
+
75
+ resolve({
76
+ status,
77
+ output,
78
+ });
79
+ });
80
+ });
81
+ }
82
+
83
+ function parseArgs(argv) {
84
+ const parsed = {};
85
+
86
+ for (let index = 0; index < argv.length; index += 1) {
87
+ const token = argv[index];
88
+
89
+ if (!token.startsWith('--')) {
90
+ continue;
91
+ }
92
+
93
+ const key = token.slice(2);
94
+ const next = argv[index + 1];
95
+
96
+ if (!next || next.startsWith('--')) {
97
+ parsed[key] = 'true';
98
+ continue;
99
+ }
100
+
101
+ parsed[key] = next;
102
+ index += 1;
103
+ }
104
+
105
+ return parsed;
106
+ }
107
+
108
+ function showHelp() {
109
+ console.log(`
110
+ React Native Boilerplate Generator (boiler-project-generator)
111
+
112
+ Usage:
113
+ node react-native.js [options]
114
+ node react-native.js <projectName> [language]
115
+
116
+ The project is created under the current working directory.
117
+
118
+ Options:
119
+ --project <PascalCase> Project name (alias for --projectName)
120
+ --projectName <PascalCase> Project name
121
+ --language <javascript|typescript> Language preference
122
+ --reactNativeVersion <latest|x.y.z>
123
+ --useTypeScript <yes|no>
124
+ --enableNavigation <yes|no>
125
+ --enableRedux <yes|no>
126
+ --enableEnv <yes|no>
127
+ --uatApiBaseUrl <url>
128
+ --stagingApiBaseUrl <url>
129
+ --prodApiBaseUrl <url>
130
+ --verificationTarget <none|android|ios>
131
+ --help
132
+
133
+ If options are omitted, the script prompts for them one-by-one.
134
+ `);
135
+ }
136
+
137
+ function isValidProjectName(value) {
138
+ return /^[A-Z][A-Za-z0-9]*$/.test(value);
139
+ }
140
+
141
+ function isValidReactNativeVersion(value) {
142
+ return value === 'latest' || /^\d+\.\d+\.\d+$/.test(value);
143
+ }
144
+
145
+ function isValidUrl(value) {
146
+ try {
147
+ const parsed = new URL(value);
148
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:';
149
+ } catch {
150
+ return false;
151
+ }
152
+ }
153
+
154
+ function parseBoolean(value, label) {
155
+ const normalized = String(value).trim().toLowerCase();
156
+
157
+ if (['y', 'yes', 'true'].includes(normalized)) {
158
+ return true;
159
+ }
160
+
161
+ if (['n', 'no', 'false'].includes(normalized)) {
162
+ return false;
163
+ }
164
+
165
+ throw new Error(`${label} must be yes or no.`);
166
+ }
167
+
168
+ function validateVerificationTarget(value) {
169
+ const normalized = String(value).trim().toLowerCase();
170
+
171
+ if (['none', 'android', 'ios'].includes(normalized)) {
172
+ return normalized;
173
+ }
174
+
175
+ throw new Error('verificationTarget must be one of: none, android, ios.');
176
+ }
177
+
178
+ async function askQuestion(reader, label, defaultValue, validator, parser = value => value) {
179
+ while (true) {
180
+ const suffix = defaultValue === undefined ? '' : ` [${defaultValue}]`;
181
+ const answer = (await reader.question(`${label}${suffix}: `)).trim();
182
+ const value = answer || defaultValue;
183
+
184
+ try {
185
+ validator(value);
186
+ return parser(value);
187
+ } catch (error) {
188
+ console.log(error.message);
189
+ }
190
+ }
191
+ }
192
+
193
+ async function collectConfig(rawArgs) {
194
+ const args = {...rawArgs};
195
+
196
+ if (args.help === 'true') {
197
+ showHelp();
198
+ process.exit(0);
199
+ }
200
+
201
+ // Support legacy positional args: react-native.js <projectName> <language>
202
+ const positionalProject = process.argv[2] && !process.argv[2].startsWith('--') ? process.argv[2] : null;
203
+ const positionalLang = process.argv[3] && !process.argv[3].startsWith('--') ? process.argv[3] : null;
204
+
205
+ if (!args.projectName && !args.project && positionalProject) {
206
+ args.projectName = positionalProject;
207
+ }
208
+ if (!args.projectName && args.project) {
209
+ args.projectName = args.project;
210
+ }
211
+
212
+ // Map --language to --useTypeScript if useTypeScript not explicitly set
213
+ if (args.useTypeScript === undefined) {
214
+ const lang = args.language || positionalLang;
215
+ if (lang === 'typescript') {
216
+ args.useTypeScript = 'yes';
217
+ } else if (lang === 'javascript') {
218
+ args.useTypeScript = 'no';
219
+ }
220
+ }
221
+
222
+ const reader = readline.createInterface({
223
+ input: process.stdin,
224
+ output: process.stdout,
225
+ });
226
+
227
+ try {
228
+ const projectName = args.projectName
229
+ ? (() => {
230
+ if (!isValidProjectName(args.projectName)) {
231
+ throw new Error('projectName must be PascalCase and alphanumeric.');
232
+ }
233
+ return args.projectName;
234
+ })()
235
+ : await askQuestion(
236
+ reader,
237
+ 'Project name',
238
+ undefined,
239
+ value => {
240
+ if (!isValidProjectName(value)) {
241
+ throw new Error('Project name must be PascalCase and alphanumeric.');
242
+ }
243
+ },
244
+ );
245
+
246
+ const reactNativeVersion = args.reactNativeVersion
247
+ ? (() => {
248
+ if (!isValidReactNativeVersion(args.reactNativeVersion)) {
249
+ throw new Error('reactNativeVersion must be latest or a semver value like 0.79.2.');
250
+ }
251
+ return args.reactNativeVersion;
252
+ })()
253
+ : await askQuestion(
254
+ reader,
255
+ 'React Native version',
256
+ defaultConfig.reactNativeVersion,
257
+ value => {
258
+ if (!isValidReactNativeVersion(value)) {
259
+ throw new Error('React Native version must be latest or a semver value like 0.79.2.');
260
+ }
261
+ },
262
+ );
263
+
264
+ const useTypeScript = args.useTypeScript !== undefined
265
+ ? parseBoolean(args.useTypeScript, 'useTypeScript')
266
+ : await askQuestion(
267
+ reader,
268
+ 'Use TypeScript? (yes/no)',
269
+ 'yes',
270
+ value => parseBoolean(value, 'useTypeScript'),
271
+ value => parseBoolean(value, 'useTypeScript'),
272
+ );
273
+
274
+ const enableNavigation = args.enableNavigation !== undefined
275
+ ? parseBoolean(args.enableNavigation, 'enableNavigation')
276
+ : await askQuestion(
277
+ reader,
278
+ 'Enable Navigation? (yes/no)',
279
+ 'no',
280
+ value => parseBoolean(value, 'enableNavigation'),
281
+ value => parseBoolean(value, 'enableNavigation'),
282
+ );
283
+
284
+ const enableRedux = args.enableRedux !== undefined
285
+ ? parseBoolean(args.enableRedux, 'enableRedux')
286
+ : await askQuestion(
287
+ reader,
288
+ 'Enable Redux Toolkit? (yes/no)',
289
+ 'no',
290
+ value => parseBoolean(value, 'enableRedux'),
291
+ value => parseBoolean(value, 'enableRedux'),
292
+ );
293
+
294
+ const enableEnv = args.enableEnv !== undefined
295
+ ? parseBoolean(args.enableEnv, 'enableEnv')
296
+ : await askQuestion(
297
+ reader,
298
+ 'Enable environment config? (yes/no)',
299
+ 'no',
300
+ value => parseBoolean(value, 'enableEnv'),
301
+ value => parseBoolean(value, 'enableEnv'),
302
+ );
303
+
304
+ const uatApiBaseUrl = enableEnv || args.uatApiBaseUrl
305
+ ? args.uatApiBaseUrl && isValidUrl(args.uatApiBaseUrl)
306
+ ? args.uatApiBaseUrl
307
+ : await askQuestion(
308
+ reader,
309
+ 'UAT API base URL',
310
+ defaultConfig.uatApiBaseUrl,
311
+ value => {
312
+ if (!isValidUrl(value)) {
313
+ throw new Error('UAT API base URL must be a valid http or https URL.');
314
+ }
315
+ },
316
+ )
317
+ : defaultConfig.uatApiBaseUrl;
318
+
319
+ const stagingApiBaseUrl = enableEnv || args.stagingApiBaseUrl
320
+ ? args.stagingApiBaseUrl && isValidUrl(args.stagingApiBaseUrl)
321
+ ? args.stagingApiBaseUrl
322
+ : await askQuestion(
323
+ reader,
324
+ 'Staging API base URL',
325
+ defaultConfig.stagingApiBaseUrl,
326
+ value => {
327
+ if (!isValidUrl(value)) {
328
+ throw new Error('Staging API base URL must be a valid http or https URL.');
329
+ }
330
+ },
331
+ )
332
+ : defaultConfig.stagingApiBaseUrl;
333
+
334
+ const prodApiBaseUrl = enableEnv || args.prodApiBaseUrl
335
+ ? args.prodApiBaseUrl && isValidUrl(args.prodApiBaseUrl)
336
+ ? args.prodApiBaseUrl
337
+ : await askQuestion(
338
+ reader,
339
+ 'Production API base URL',
340
+ defaultConfig.prodApiBaseUrl,
341
+ value => {
342
+ if (!isValidUrl(value)) {
343
+ throw new Error('Production API base URL must be a valid http or https URL.');
344
+ }
345
+ },
346
+ )
347
+ : defaultConfig.prodApiBaseUrl;
348
+
349
+ const verificationTarget = args.verificationTarget !== undefined
350
+ ? validateVerificationTarget(args.verificationTarget)
351
+ : await askQuestion(
352
+ reader,
353
+ 'Verification target (none/android/ios)',
354
+ 'none',
355
+ value => validateVerificationTarget(value),
356
+ value => validateVerificationTarget(value),
357
+ );
358
+
359
+ return {
360
+ projectName,
361
+ reactNativeVersion,
362
+ useTypeScript,
363
+ enableNavigation,
364
+ enableRedux,
365
+ enableEnv,
366
+ uatApiBaseUrl,
367
+ stagingApiBaseUrl,
368
+ prodApiBaseUrl,
369
+ verificationTarget,
370
+ };
371
+ } finally {
372
+ reader.close();
373
+ }
374
+ }
375
+
376
+ function readText(filePath) {
377
+ return fs.readFileSync(filePath, 'utf8');
378
+ }
379
+
380
+ function writeText(filePath, content, summary) {
381
+ fs.mkdirSync(path.dirname(filePath), {recursive: true});
382
+ const relativePath = path.relative(summary.projectDir, filePath).replace(/\\/g, '/');
383
+
384
+ if (fs.existsSync(filePath)) {
385
+ summary.updatedFiles.add(relativePath);
386
+ } else {
387
+ summary.createdFiles.add(relativePath);
388
+ }
389
+
390
+ fs.writeFileSync(filePath, content, 'utf8');
391
+ }
392
+
393
+ function ensureTsconfig(projectDir, summary) {
394
+ const tsconfigPath = path.join(projectDir, 'tsconfig.json');
395
+ let tsconfig = {
396
+ extends: '@react-native/typescript-config/tsconfig.json',
397
+ compilerOptions: {},
398
+ };
399
+
400
+ if (fs.existsSync(tsconfigPath)) {
401
+ tsconfig = JSON.parse(readText(tsconfigPath));
402
+ }
403
+
404
+ tsconfig.compilerOptions = tsconfig.compilerOptions || {};
405
+ tsconfig.compilerOptions.jsx = 'react-native';
406
+
407
+ writeText(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}\n`, summary);
408
+ }
409
+
410
+ async function installPackages(projectDir, packages, summary, options = {}) {
411
+ if (!packages.length) {
412
+ return;
413
+ }
414
+
415
+ const command = ['install'];
416
+
417
+ if (options.dev) {
418
+ command.push('--save-dev');
419
+ }
420
+
421
+ command.push(...packages);
422
+
423
+ try {
424
+ await runCommand('npm', command, {cwd: projectDir});
425
+ } catch (error) {
426
+ if (!/ERESOLVE|Invalid Version|peer dep/i.test(error.message)) {
427
+ throw error;
428
+ }
429
+
430
+ // Step 1: Try unpinned versions to auto-resolve compatible ones
431
+ const unpinned = packages.map(pkg => pkg.replace(/@\d[\d.]*(-[\w.]+)?$/, ''));
432
+ const hasVersionDiff = unpinned.some((u, i) => u !== packages[i]);
433
+
434
+ if (hasVersionDiff) {
435
+ console.log('\nVersion conflict detected. Retrying with unpinned versions...');
436
+ const retryCmd = ['install', ...(options.dev ? ['--save-dev'] : []), ...unpinned];
437
+ try {
438
+ await runCommand('npm', retryCmd, {cwd: projectDir});
439
+ summary.warnings.add(`Installed with unpinned versions due to conflict: ${packages.join(', ')}`);
440
+ unpinned.forEach(pkg => summary.installedPackages.add(pkg));
441
+ return;
442
+ } catch {
443
+ // Fall through to clean reinstall
444
+ }
445
+ }
446
+
447
+ // Step 2: Clean reinstall then retry original versions
448
+ console.log('\nClean reinstalling all dependencies...');
449
+ fs.rmSync(path.join(projectDir, 'node_modules'), {recursive: true, force: true});
450
+ fs.rmSync(path.join(projectDir, 'package-lock.json'), {force: true});
451
+ await runCommand('npm', ['install'], {cwd: projectDir});
452
+ await runCommand('npm', command, {cwd: projectDir});
453
+ }
454
+
455
+ packages.forEach(pkg => summary.installedPackages.add(pkg));
456
+ }
457
+
458
+ function getNavigationPackages(reactNativeVersion) {
459
+ if (/^0\.79\./.test(reactNativeVersion)) {
460
+ return [
461
+ '@react-navigation/native@6.1.18',
462
+ '@react-navigation/native-stack@6.11.0',
463
+ 'react-native-screens@4.4.0',
464
+ 'react-native-safe-area-context@4.14.0',
465
+ 'react-native-gesture-handler@2.20.2',
466
+ 'react-native-reanimated@3.16.1',
467
+ ];
468
+ }
469
+
470
+ return [
471
+ '@react-navigation/native',
472
+ '@react-navigation/native-stack',
473
+ 'react-native-screens',
474
+ 'react-native-safe-area-context',
475
+ 'react-native-gesture-handler',
476
+ 'react-native-reanimated@4',
477
+ 'react-native-worklets',
478
+ ];
479
+ }
480
+
481
+ function hasDirectDependency(projectDir, packageName) {
482
+ const packageJsonPath = path.join(projectDir, 'package.json');
483
+ const packageJson = JSON.parse(readText(packageJsonPath));
484
+
485
+ return Boolean(
486
+ packageJson.dependencies?.[packageName] ||
487
+ packageJson.devDependencies?.[packageName],
488
+ );
489
+ }
490
+
491
+ function isWorkletsRelatedFailure(error) {
492
+ return /react-native-worklets|worklets|react-native-reanimated/i.test(error.message);
493
+ }
494
+
495
+ async function ensureWorkletsDependency(projectDir, summary) {
496
+ if (hasDirectDependency(projectDir, 'react-native-worklets')) {
497
+ return false;
498
+ }
499
+
500
+ summary.warnings.add(
501
+ 'Adding react-native-worklets explicitly because the current navigation/reanimated setup requires it.',
502
+ );
503
+ await installPackages(projectDir, ['react-native-worklets'], summary);
504
+ return true;
505
+ }
506
+
507
+ function ensureReanimatedPlugin(projectDir, summary) {
508
+ const babelConfigPath = path.join(projectDir, 'babel.config.js');
509
+ const plugin = "'react-native-reanimated/plugin'";
510
+ let content = fs.existsSync(babelConfigPath)
511
+ ? readText(babelConfigPath)
512
+ : "module.exports = {presets: ['module:@react-native/babel-preset']};\n";
513
+
514
+ if (content.includes(plugin)) {
515
+ content = content.replace(/plugins:\s*\[(.*?)\]/s, (match, inner) => {
516
+ const items = inner
517
+ .split(',')
518
+ .map(item => item.trim())
519
+ .filter(Boolean)
520
+ .filter(item => item !== plugin);
521
+ items.push(plugin);
522
+ return `plugins: [${items.join(', ')}]`;
523
+ });
524
+ } else if (/module\.exports\s*=\s*\{[\s\S]*\}/.test(content)) {
525
+ if (/plugins:\s*\[.*?\]/s.test(content)) {
526
+ content = content.replace(/plugins:\s*\[(.*?)\]/s, (match, inner) => {
527
+ const items = inner
528
+ .split(',')
529
+ .map(item => item.trim())
530
+ .filter(Boolean);
531
+ items.push(plugin);
532
+ return `plugins: [${items.join(', ')}]`;
533
+ });
534
+ } else {
535
+ content = content.replace(/module\.exports\s*=\s*\{/, "module.exports = {\n plugins: ['react-native-reanimated/plugin'],");
536
+ }
537
+ }
538
+
539
+ writeText(babelConfigPath, content.endsWith('\n') ? content : `${content}\n`, summary);
540
+ }
541
+
542
+ function ensureGestureHandlerImport(projectDir, summary) {
543
+ const entryPath = path.join(projectDir, 'index.js');
544
+ let content = readText(entryPath);
545
+
546
+ if (!content.includes("import 'react-native-gesture-handler';")) {
547
+ content = `import 'react-native-gesture-handler';\n${content}`;
548
+ writeText(entryPath, content, summary);
549
+ }
550
+ }
551
+
552
+ function resolveInstalledRnVersion(projectDir) {
553
+ const pkgPath = path.join(projectDir, 'node_modules', 'react-native', 'package.json');
554
+ if (!fs.existsSync(pkgPath)) {
555
+ return null;
556
+ }
557
+ return JSON.parse(readText(pkgPath)).version;
558
+ }
559
+
560
+ async function verifyDependencyCompatibility(projectDir, summary) {
561
+ const result = await runCommand('npm', ['ls', '--depth=0'], {
562
+ cwd: projectDir,
563
+ allowFailure: true,
564
+ });
565
+
566
+ if (result.status !== 0 && result.output) {
567
+ const issues = result.output.split('\n')
568
+ .filter(line => /peer dep|ERESOLVE|invalid|missing peer/i.test(line))
569
+ .map(line => line.trim())
570
+ .filter(Boolean);
571
+
572
+ if (issues.length > 0) {
573
+ issues.forEach(issue => summary.warnings.add(`Dependency: ${issue}`));
574
+ console.log(`\nFound ${issues.length} compatibility issue(s). Attempting clean reinstall...`);
575
+ fs.rmSync(path.join(projectDir, 'node_modules'), {recursive: true, force: true});
576
+ fs.rmSync(path.join(projectDir, 'package-lock.json'), {force: true});
577
+ await runCommand('npm', ['install'], {cwd: projectDir});
578
+
579
+ const recheck = await runCommand('npm', ['ls', '--depth=0'], {
580
+ cwd: projectDir,
581
+ allowFailure: true,
582
+ });
583
+ if (recheck.status === 0) {
584
+ console.log('Compatibility issues resolved after reinstall.');
585
+ } else {
586
+ summary.warnings.add('Some dependency issues persist. Run npm ls manually to review.');
587
+ }
588
+ } else {
589
+ console.log('All dependencies are compatible.');
590
+ }
591
+ } else {
592
+ console.log('All dependencies are compatible.');
593
+ }
594
+ }
595
+
596
+ function seedEnvironmentFiles(projectDir, config, summary) {
597
+ const template = readText(path.join(templateDir, 'env-config.env'));
598
+ const files = [
599
+ {name: '.env', env: 'prod', url: config.prodApiBaseUrl},
600
+ {name: '.env.example', env: 'prod', url: config.prodApiBaseUrl},
601
+ {name: '.env.uat', env: 'uat', url: config.uatApiBaseUrl},
602
+ {name: '.env.staging', env: 'staging', url: config.stagingApiBaseUrl},
603
+ {name: '.env.prod', env: 'prod', url: config.prodApiBaseUrl},
604
+ ];
605
+
606
+ files.forEach(file => {
607
+ const content = template
608
+ .replace('__APP_ENV__', file.env)
609
+ .replace('__API_BASE_URL__', file.url);
610
+ writeText(path.join(projectDir, file.name), `${content}\n`, summary);
611
+ });
612
+ }
613
+
614
+ function createSrcStructure(projectDir) {
615
+ [
616
+ 'src/assets/image',
617
+ 'src/features/auth',
618
+ 'src/features/dashboard',
619
+ 'src/features/profile',
620
+ 'src/components',
621
+ 'src/services',
622
+ 'src/navigation',
623
+ 'src/hooks',
624
+ 'src/utils',
625
+ 'src/theme',
626
+ 'src/store',
627
+ ].forEach(dir => {
628
+ fs.mkdirSync(path.join(projectDir, dir), {recursive: true});
629
+ });
630
+ }
631
+
632
+ function writeTemplate(projectDir, templateName, destination, summary) {
633
+ const content = readText(path.join(templateDir, templateName));
634
+ writeText(path.join(projectDir, destination), `${content.trimEnd()}\n`, summary);
635
+ }
636
+
637
+ function applyTemplates(projectDir, config, summary) {
638
+ createSrcStructure(projectDir);
639
+
640
+ writeTemplate(projectDir, 'api-setup.ts', 'src/services/api.ts', summary);
641
+ writeTemplate(projectDir, 'feature-auth-screen.tsx', 'src/features/auth/AuthScreen.tsx', summary);
642
+ writeTemplate(projectDir, 'feature-dashboard-screen.tsx', 'src/features/dashboard/DashboardScreen.tsx', summary);
643
+ writeTemplate(projectDir, 'feature-profile-screen.tsx', 'src/features/profile/ProfileScreen.tsx', summary);
644
+ writeTemplate(projectDir, 'component-primary-button.tsx', 'src/components/PrimaryButton.tsx', summary);
645
+ writeTemplate(projectDir, 'hook-use-app-boot.ts', 'src/hooks/useAppBoot.ts', summary);
646
+ writeTemplate(projectDir, 'utils-helpers.ts', 'src/utils/helpers.ts', summary);
647
+ writeTemplate(projectDir, 'theme-index.ts', 'src/theme/index.ts', summary);
648
+
649
+ if (config.enableNavigation) {
650
+ writeTemplate(projectDir, 'navigation-setup.tsx', 'src/navigation/RootNavigator.tsx', summary);
651
+ }
652
+
653
+ if (config.enableRedux) {
654
+ writeTemplate(projectDir, 'redux-store.ts', 'src/store/store.ts', summary);
655
+ writeTemplate(projectDir, 'redux-hooks.ts', 'src/store/hooks.ts', summary);
656
+ writeTemplate(projectDir, 'redux-session-slice.ts', 'src/features/auth/sessionSlice.ts', summary);
657
+ }
658
+ }
659
+
660
+ function buildAppContent(config, projectDir) {
661
+ const prefersTsx = fs.existsSync(path.join(projectDir, 'App.tsx')) || config.useTypeScript;
662
+ const returnType = prefersTsx ? ': React.JSX.Element' : '';
663
+ const lines = ["import React from 'react';"];
664
+
665
+ if (config.enableNavigation) {
666
+ lines.push("import {NavigationContainer} from '@react-navigation/native';");
667
+ }
668
+
669
+ if (config.enableRedux) {
670
+ lines.push("import {Provider} from 'react-redux';");
671
+ }
672
+
673
+ if (config.enableNavigation) {
674
+ lines.push("import RootNavigator from './src/navigation/RootNavigator';");
675
+ } else {
676
+ lines.push("import DashboardScreen from './src/features/dashboard/DashboardScreen';");
677
+ }
678
+
679
+ if (config.enableRedux) {
680
+ lines.push("import {store} from './src/store/store';");
681
+ }
682
+
683
+ lines.push('');
684
+ lines.push(`function App()${returnType} {`);
685
+
686
+ let body = config.enableNavigation
687
+ ? ['<NavigationContainer>', ' <RootNavigator />', '</NavigationContainer>']
688
+ : ['<DashboardScreen />'];
689
+
690
+ if (config.enableRedux) {
691
+ body = ['<Provider store={store}>', ...body.map(line => ` ${line}`), '</Provider>'];
692
+ }
693
+
694
+ lines.push(' return (');
695
+ body.forEach(line => lines.push(` ${line}`));
696
+ lines.push(' );');
697
+ lines.push('}');
698
+ lines.push('');
699
+ lines.push('export default App;');
700
+ lines.push('');
701
+
702
+ return lines.join('\n');
703
+ }
704
+
705
+ function writeAppEntrypoint(projectDir, config, summary) {
706
+ const appTsPath = path.join(projectDir, 'App.tsx');
707
+ const appJsPath = path.join(projectDir, 'App.js');
708
+ const target = fs.existsSync(appTsPath) || config.useTypeScript ? appTsPath : appJsPath;
709
+ const content = buildAppContent(config, projectDir);
710
+
711
+ writeText(target, content, summary);
712
+
713
+ if (!config.useTypeScript && fs.existsSync(appTsPath) && target === appJsPath) {
714
+ fs.rmSync(appTsPath, {force: true});
715
+ }
716
+ }
717
+
718
+ function buildAppTestContent(config) {
719
+ const lines = [
720
+ '/**',
721
+ ' * @format',
722
+ ' */',
723
+ '',
724
+ "import React from 'react';",
725
+ "import ReactTestRenderer from 'react-test-renderer';",
726
+ ];
727
+
728
+ if (config.enableNavigation) {
729
+ lines.push('');
730
+ lines.push("jest.mock('@react-navigation/native', () => ({");
731
+ lines.push(' NavigationContainer: ({children}: {children: React.ReactNode}) => children,');
732
+ lines.push('}));');
733
+ lines.push('');
734
+ lines.push("jest.mock('@react-navigation/native-stack', () => ({");
735
+ lines.push(' createNativeStackNavigator: () => ({');
736
+ lines.push(' Navigator: ({children}: {children: React.ReactNode}) => children,');
737
+ lines.push(' Screen: ({component: Component}: {component: React.ComponentType}) => <Component />,');
738
+ lines.push(' }),');
739
+ lines.push('}));');
740
+ }
741
+
742
+ if (config.enableRedux) {
743
+ lines.push('');
744
+ lines.push("jest.mock('react-redux', () => ({");
745
+ lines.push(' Provider: ({children}: {children: React.ReactNode}) => children,');
746
+ lines.push(' useDispatch: jest.fn(),');
747
+ lines.push(' useSelector: jest.fn(),');
748
+ lines.push('}));');
749
+ lines.push('');
750
+ lines.push("jest.mock('../src/store/store', () => ({");
751
+ lines.push(' store: {');
752
+ lines.push(' getState: () => ({}),');
753
+ lines.push(' dispatch: jest.fn(),');
754
+ lines.push(' subscribe: () => jest.fn(),');
755
+ lines.push(' },');
756
+ lines.push('}));');
757
+ }
758
+
759
+ lines.push('');
760
+ lines.push("import App from '../App';");
761
+ lines.push('');
762
+ lines.push("test('renders correctly', async () => {");
763
+ lines.push(' await ReactTestRenderer.act(() => {');
764
+ lines.push(' ReactTestRenderer.create(<App />);');
765
+ lines.push(' });');
766
+ lines.push('});');
767
+ lines.push('');
768
+
769
+ return lines.join('\n');
770
+ }
771
+
772
+ function writeAppTest(projectDir, config, summary) {
773
+ writeText(
774
+ path.join(projectDir, '__tests__', 'App.test.tsx'),
775
+ buildAppTestContent(config),
776
+ summary,
777
+ );
778
+ }
779
+
780
+ function updatePackageScripts(projectDir, config, summary) {
781
+ const packageJsonPath = path.join(projectDir, 'package.json');
782
+ const packageJson = JSON.parse(readText(packageJsonPath));
783
+ packageJson.scripts = packageJson.scripts || {};
784
+
785
+ if (config.enableEnv) {
786
+ Object.assign(packageJson.scripts, {
787
+ 'start:uat': 'cross-env ENVFILE=.env.uat react-native start',
788
+ 'start:staging': 'cross-env ENVFILE=.env.staging react-native start',
789
+ 'start:prod': 'cross-env ENVFILE=.env.prod react-native start',
790
+ 'android:uat': 'cross-env ENVFILE=.env.uat react-native run-android',
791
+ 'android:staging': 'cross-env ENVFILE=.env.staging react-native run-android',
792
+ 'android:prod': 'cross-env ENVFILE=.env.prod react-native run-android',
793
+ 'ios:uat': 'cross-env ENVFILE=.env.uat react-native run-ios',
794
+ 'ios:staging': 'cross-env ENVFILE=.env.staging react-native run-ios',
795
+ 'ios:prod': 'cross-env ENVFILE=.env.prod react-native run-ios',
796
+ 'apk:uat': 'cross-env ENVFILE=.env.uat react-native build-android --mode=release',
797
+ 'apk:staging': 'cross-env ENVFILE=.env.staging react-native build-android --mode=release',
798
+ 'apk:prod': 'cross-env ENVFILE=.env.prod react-native build-android --mode=release',
799
+ });
800
+ }
801
+
802
+ writeText(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, summary);
803
+ }
804
+
805
+ async function runPreflightChecks(config, summary) {
806
+ const nodeMajor = Number(process.versions.node.split('.')[0]);
807
+ summary.runtime.nodeMajor = nodeMajor;
808
+ summary.runtime.cliVersion = nodeMajor >= 20 ? 'latest' : '18.0.0';
809
+
810
+ if (config.verificationTarget === 'android') {
811
+ const javaCheck = await runCommand('java', ['-version'], {allowFailure: true});
812
+ if (javaCheck.status !== 0) {
813
+ summary.warnings.add('Java was not detected during Android preflight.');
814
+ }
815
+
816
+ if (!process.env.ANDROID_HOME && !process.env.ANDROID_SDK_ROOT) {
817
+ summary.warnings.add('ANDROID_HOME or ANDROID_SDK_ROOT is not set.');
818
+ }
819
+ }
820
+
821
+ if (config.verificationTarget === 'ios') {
822
+ if (process.platform !== 'darwin') {
823
+ summary.warnings.add('iOS verification was requested on a non-macOS system.');
824
+ return;
825
+ }
826
+
827
+ const xcodeCheck = await runCommand('xcodebuild', ['-version'], {allowFailure: true});
828
+ const podCheck = await runCommand('pod', ['--version'], {allowFailure: true});
829
+
830
+ if (xcodeCheck.status !== 0) {
831
+ summary.warnings.add('xcodebuild was not detected during iOS preflight.');
832
+ }
833
+
834
+ if (podCheck.status !== 0) {
835
+ summary.warnings.add('CocoaPods was not detected during iOS preflight.');
836
+ }
837
+ }
838
+ }
839
+
840
+ async function verifyProject(projectDir, config, summary) {
841
+ const checks = [
842
+ {name: 'lint', command: 'npm', args: ['run', 'lint']},
843
+ ];
844
+
845
+ if (fs.existsSync(path.join(projectDir, 'tsconfig.json'))) {
846
+ checks.push({name: 'typecheck', command: 'npx', args: ['tsc', '--noEmit']});
847
+ }
848
+
849
+ checks.push({name: 'tests', command: 'npm', args: ['test', '--', '--watch=false']});
850
+
851
+ if (config.verificationTarget === 'android') {
852
+ checks.push({name: 'android', command: 'npm', args: ['run', 'android']});
853
+ }
854
+
855
+ if (config.verificationTarget === 'ios' && process.platform === 'darwin') {
856
+ checks.push({name: 'ios', command: 'npm', args: ['run', 'ios']});
857
+ }
858
+
859
+ for (const check of checks) {
860
+ try {
861
+ await runCommand(check.command, check.args, {cwd: projectDir});
862
+ } catch (error) {
863
+ const canRetryAndroidWithWorklets =
864
+ check.name === 'android' &&
865
+ config.enableNavigation &&
866
+ isWorkletsRelatedFailure(error);
867
+
868
+ if (!canRetryAndroidWithWorklets) {
869
+ throw error;
870
+ }
871
+
872
+ const addedWorklets = await ensureWorkletsDependency(projectDir, summary);
873
+
874
+ if (!addedWorklets) {
875
+ throw error;
876
+ }
877
+
878
+ await runCommand(check.command, check.args, {cwd: projectDir});
879
+ }
880
+
881
+ summary.verificationResults.set(check.name, 'passed');
882
+ }
883
+ }
884
+
885
+ async function maybeRunPodInstall(projectDir, config, summary) {
886
+ if (process.platform !== 'darwin') {
887
+ if (config.enableNavigation || config.enableEnv) {
888
+ summary.warnings.add('Skipping pod install because the current system is not macOS.');
889
+ }
890
+ return;
891
+ }
892
+
893
+ if (config.enableNavigation || config.enableEnv) {
894
+ await runCommand('pod', ['install'], {cwd: path.join(projectDir, 'ios')});
895
+ }
896
+ }
897
+
898
+ async function initializeProject(config, summary) {
899
+ const targetDir = path.join(process.cwd(), config.projectName);
900
+ summary.projectDir = targetDir;
901
+
902
+ if (fs.existsSync(targetDir)) {
903
+ const packageJsonPath = path.join(targetDir, 'package.json');
904
+
905
+ if (!fs.existsSync(packageJsonPath)) {
906
+ throw new Error(`Target directory already exists and is not a React Native project: ${targetDir}`);
907
+ }
908
+
909
+ summary.warnings.add(
910
+ `Target directory already existed. Reusing ${targetDir} and continuing from the existing scaffold.`,
911
+ );
912
+ } else {
913
+ console.log('\nInitializing React Native app. This can take several minutes on first run.');
914
+ await runCommand(
915
+ 'npx',
916
+ [
917
+ '--yes',
918
+ `@react-native-community/cli@${summary.runtime.cliVersion}`,
919
+ 'init',
920
+ config.projectName,
921
+ '--version',
922
+ config.reactNativeVersion,
923
+ ],
924
+ {cwd: process.cwd()},
925
+ );
926
+ }
927
+
928
+ if (!config.useTypeScript) {
929
+ summary.warnings.add(
930
+ 'JavaScript-only mode is not fully specialized; the generator keeps the default React Native TypeScript-capable scaffold.',
931
+ );
932
+ }
933
+
934
+ ensureTsconfig(targetDir, summary);
935
+
936
+ const resolvedRnVersion = resolveInstalledRnVersion(targetDir) || config.reactNativeVersion;
937
+ console.log(`\nResolved React Native version: ${resolvedRnVersion}`);
938
+
939
+ console.log('\n[1/6] Installing core packages (axios, react-native-config)...');
940
+ await installPackages(targetDir, ['axios', 'react-native-config'], summary);
941
+
942
+ if (config.enableNavigation) {
943
+ console.log('\n[2/6] Installing navigation packages...');
944
+ await installPackages(targetDir, getNavigationPackages(resolvedRnVersion), summary);
945
+ ensureReanimatedPlugin(targetDir, summary);
946
+ ensureGestureHandlerImport(targetDir, summary);
947
+ } else {
948
+ console.log('\n[2/6] Navigation skipped.');
949
+ }
950
+
951
+ if (config.enableRedux) {
952
+ console.log('\n[3/6] Installing Redux Toolkit...');
953
+ await installPackages(targetDir, ['@reduxjs/toolkit', 'react-redux'], summary);
954
+ } else {
955
+ console.log('\n[3/6] Redux skipped.');
956
+ }
957
+
958
+ if (config.enableEnv) {
959
+ console.log('\n[4/6] Installing environment config...');
960
+ await installPackages(
961
+ targetDir,
962
+ [summary.runtime.nodeMajor >= 20 ? 'cross-env@10' : 'cross-env@7.0.3'],
963
+ summary,
964
+ {dev: true},
965
+ );
966
+ seedEnvironmentFiles(targetDir, config, summary);
967
+ } else {
968
+ console.log('\n[4/6] Environment config skipped.');
969
+ }
970
+
971
+ console.log('\n[5/6] Applying templates and wiring entry point...');
972
+ applyTemplates(targetDir, config, summary);
973
+ writeAppEntrypoint(targetDir, config, summary);
974
+ writeAppTest(targetDir, config, summary);
975
+ updatePackageScripts(targetDir, config, summary);
976
+ await maybeRunPodInstall(targetDir, config, summary);
977
+
978
+ console.log('\n[6/6] Verifying dependency compatibility...');
979
+ await verifyDependencyCompatibility(targetDir, summary);
980
+ }
981
+
982
+ function printSummary(summary) {
983
+ console.log('\nCreated files:');
984
+ [...summary.createdFiles].sort().forEach(file => console.log(`- ${file}`));
985
+
986
+ console.log('\nUpdated files:');
987
+ [...summary.updatedFiles].sort().forEach(file => console.log(`- ${file}`));
988
+
989
+ console.log('\nInstalled packages:');
990
+ [...summary.installedPackages].sort().forEach(pkg => console.log(`- ${pkg}`));
991
+
992
+ console.log('\nVerification results:');
993
+ if (!summary.verificationResults.size) {
994
+ console.log('- verification skipped');
995
+ } else {
996
+ [...summary.verificationResults.entries()].forEach(([name, status]) => {
997
+ console.log(`- ${name}: ${status}`);
998
+ });
999
+ }
1000
+
1001
+ console.log('\nWarnings:');
1002
+ if (!summary.warnings.size) {
1003
+ console.log('- none');
1004
+ } else {
1005
+ [...summary.warnings].forEach(warning => console.log(`- ${warning}`));
1006
+ }
1007
+
1008
+ console.log('\nNext command:');
1009
+ if (summary.projectDir) {
1010
+ console.log(`- cd ${summary.projectDir}`);
1011
+ console.log('- npm run start');
1012
+ } else {
1013
+ console.log('- not available');
1014
+ }
1015
+ }
1016
+
1017
+ async function main() {
1018
+ const config = await collectConfig(parseArgs(process.argv.slice(2)));
1019
+ const summary = {
1020
+ projectDir: '',
1021
+ createdFiles: new Set(),
1022
+ updatedFiles: new Set(),
1023
+ installedPackages: new Set(),
1024
+ verificationResults: new Map(),
1025
+ warnings: new Set(),
1026
+ runtime: {
1027
+ nodeMajor: 0,
1028
+ cliVersion: '',
1029
+ },
1030
+ };
1031
+
1032
+ await runPreflightChecks(config, summary);
1033
+ await initializeProject(config, summary);
1034
+ await verifyProject(summary.projectDir, config, summary);
1035
+ printSummary(summary);
1036
+ }
1037
+
1038
+ main().catch(error => {
1039
+ console.error(`\nGenerator failed: ${error.message}`);
1040
+ process.exitCode = 1;
1041
+ });