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.
- package/.github/skills/boiler-project-generator/SKILL.md +103 -0
- package/.github/skills/boiler-project-generator/assets/templates/android/README.md +3 -0
- package/.github/skills/boiler-project-generator/assets/templates/angular/README.md +3 -0
- package/.github/skills/boiler-project-generator/assets/templates/flutter/README.md +3 -0
- package/.github/skills/boiler-project-generator/assets/templates/ios/README.md +3 -0
- package/.github/skills/boiler-project-generator/assets/templates/react/README.md +3 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/README.md +3 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/api-setup.ts +76 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/component-primary-button.tsx +29 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/env-config.env +4 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-auth-screen.tsx +45 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-dashboard-screen.tsx +45 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/feature-profile-screen.tsx +45 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/hook-use-app-boot.ts +11 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/navigation-setup.tsx +25 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/project-structure.md +24 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-hooks.ts +5 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-session-slice.ts +29 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/redux-store.ts +15 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/theme-index.ts +23 -0
- package/.github/skills/boiler-project-generator/assets/templates/react-native/utils-helpers.ts +7 -0
- package/.github/skills/boiler-project-generator/metadata.yaml +29 -0
- package/.github/skills/boiler-project-generator/references/android.md +19 -0
- package/.github/skills/boiler-project-generator/references/angular.md +14 -0
- package/.github/skills/boiler-project-generator/references/flutter.md +17 -0
- package/.github/skills/boiler-project-generator/references/ios.md +17 -0
- package/.github/skills/boiler-project-generator/references/react-native.md +103 -0
- package/.github/skills/boiler-project-generator/references/react.md +17 -0
- package/.github/skills/boiler-project-generator/scripts/angular.js +43 -0
- package/.github/skills/boiler-project-generator/scripts/generate-project.mjs +107 -0
- package/.github/skills/boiler-project-generator/scripts/init.js +105 -0
- package/.github/skills/boiler-project-generator/scripts/react-native.js +1041 -0
- package/.github/skills/boiler-project-generator/scripts/react.js +32 -0
- package/.github/skills/clean-architecture-generator/SKILL.md +235 -0
- package/.github/skills/clean-architecture-generator/references/architecture-core.md +126 -0
- package/.github/skills/clean-architecture-generator/references/platform-angular.md +374 -0
- package/.github/skills/clean-architecture-generator/references/platform-flutter.md +366 -0
- package/.github/skills/clean-architecture-generator/references/platform-react-native.md +590 -0
- package/.github/skills/clean-architecture-generator/references/platform-react.md +261 -0
- package/.github/skills/code-test-coverage-analyzer/SKILL.md +1358 -0
- package/.github/skills/code-test-coverage-analyzer/assets/config-questionnaire.md +118 -0
- package/.github/skills/code-test-coverage-analyzer/assets/conflict-resolution-protocol.md +99 -0
- package/.github/skills/code-test-coverage-analyzer/assets/coverage-calculation-rules.md +294 -0
- package/.github/skills/code-test-coverage-analyzer/assets/multi-strategy-search.md +145 -0
- package/.github/skills/code-test-coverage-analyzer/assets/output-file-contracts.md +651 -0
- package/.github/skills/code-test-coverage-analyzer/assets/status-definitions.md +273 -0
- package/.github/skills/code-test-coverage-analyzer/examples/example-invocation.md +208 -0
- package/.github/skills/code-test-coverage-analyzer/references/android-patterns.md +229 -0
- package/.github/skills/code-test-coverage-analyzer/references/angular-patterns.md +177 -0
- package/.github/skills/code-test-coverage-analyzer/references/backend-patterns.md +383 -0
- package/.github/skills/code-test-coverage-analyzer/references/flutter-patterns.md +254 -0
- package/.github/skills/code-test-coverage-analyzer/references/ios-patterns.md +228 -0
- package/.github/skills/code-test-coverage-analyzer/references/react-native-patterns.md +431 -0
- package/.github/skills/code-test-coverage-analyzer/references/react-patterns.md +206 -0
- package/.github/skills/code-test-coverage-analyzer/scripts/detect-platform.ps1 +261 -0
- package/.github/skills/code-test-coverage-analyzer/scripts/scan-repository.ps1 +242 -0
- package/.github/skills/code-test-coverage-analyzer/templates/brd-compliance-row.md +107 -0
- package/.github/skills/code-test-coverage-analyzer/templates/test-case-row.md +130 -0
- package/.github/skills/code-test-coverage-analyzer/templates/user-story-compliance-row.md +170 -0
- package/.github/skills/custom-font-integrator/SKILL.md +373 -0
- package/.github/skills/custom-font-integrator/assets/FontTestScreen.js +150 -0
- package/.github/skills/custom-font-integrator/references/example-info-plist.xml +21 -0
- package/.github/skills/custom-font-integrator/scripts/setup-fonts.sh +99 -0
- package/.github/skills/encryption-decryption-utility/Instructions.md +282 -0
- package/.github/skills/encryption-decryption-utility/README.md +222 -0
- package/.github/skills/encryption-decryption-utility/SKILL.md +645 -0
- package/.github/skills/encryption-decryption-utility/assets/encryption-config-template.json +13 -0
- package/.github/skills/encryption-decryption-utility/assets/platform-matrix.json +180 -0
- package/.github/skills/encryption-decryption-utility/metadata.yaml +51 -0
- package/.github/skills/encryption-decryption-utility/references/android-crypto.md +157 -0
- package/.github/skills/encryption-decryption-utility/references/angular-crypto.md +130 -0
- package/.github/skills/encryption-decryption-utility/references/approach-1-webcrypto.md +95 -0
- package/.github/skills/encryption-decryption-utility/references/approach-2-rsa-aes-cbc.md +111 -0
- package/.github/skills/encryption-decryption-utility/references/approach-3-aes-sharedkey.md +114 -0
- package/.github/skills/encryption-decryption-utility/references/architecture.md +233 -0
- package/.github/skills/encryption-decryption-utility/references/backend-alignment.md +506 -0
- package/.github/skills/encryption-decryption-utility/references/dependency-guidance.md +234 -0
- package/.github/skills/encryption-decryption-utility/references/ec-implementation.md +832 -0
- package/.github/skills/encryption-decryption-utility/references/error-handling-playbook.md +234 -0
- package/.github/skills/encryption-decryption-utility/references/flutter-crypto.md +105 -0
- package/.github/skills/encryption-decryption-utility/references/ios-crypto.md +338 -0
- package/.github/skills/encryption-decryption-utility/references/native-rsa-implementation.md +373 -0
- package/.github/skills/encryption-decryption-utility/references/node-crypto.md +86 -0
- package/.github/skills/encryption-decryption-utility/references/react-crypto.md +105 -0
- package/.github/skills/encryption-decryption-utility/references/react-native-crypto.md +1001 -0
- package/.github/skills/encryption-decryption-utility/references/react-web-crypto.md +668 -0
- package/.github/skills/encryption-decryption-utility/references/security-checklist.md +176 -0
- package/.github/skills/encryption-decryption-utility/references/web-crypto.md +74 -0
- package/.github/skills/encryption-decryption-utility/scripts/detect.js +128 -0
- package/.github/skills/encryption-decryption-utility/scripts/install.js +1713 -0
- package/.github/skills/encryption-decryption-utility/scripts/setup.js +800 -0
- package/.github/skills/encryption-decryption-utility/template.md +241 -0
- package/.github/skills/encryption-decryption-utility/templates/config-questionnaire.md +467 -0
- package/.github/skills/encryption-decryption-utility/templates/delivery-checklist.md +206 -0
- package/.github/skills/encryption-decryption-utility/templates/implementation-plan.md +304 -0
- package/.github/skills/feature-generator/SKILL.MD +2741 -0
- package/.github/skills/feature-generator/assets/fingerprint-schema.json +385 -0
- package/.github/skills/feature-generator/assets/spec-template.md +172 -0
- package/.github/skills/feature-generator/assets/transform-rules.json +82 -0
- package/.github/skills/feature-generator/references/api-envelope-patterns.md +327 -0
- package/.github/skills/feature-generator/references/screen-logic-patterns.md +399 -0
- package/.github/skills/feature-generator/references/state-library-patterns.md +464 -0
- package/.github/skills/figma-ui-mapper/README.md +34 -0
- package/.github/skills/figma-ui-mapper/SKILL.md +101 -0
- package/.github/skills/figma-ui-mapper/assets/templates/component-map-template.md +30 -0
- package/.github/skills/figma-ui-mapper/assets/templates/generation-report-template.md +49 -0
- package/.github/skills/figma-ui-mapper/metadata.yaml +22 -0
- package/.github/skills/figma-ui-mapper/references/android.md +11 -0
- package/.github/skills/figma-ui-mapper/references/angular.md +18 -0
- package/.github/skills/figma-ui-mapper/references/capacitor.md +18 -0
- package/.github/skills/figma-ui-mapper/references/common.md +23 -0
- package/.github/skills/figma-ui-mapper/references/expo.md +18 -0
- package/.github/skills/figma-ui-mapper/references/flutter.md +18 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/angular/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/capacitor/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/expo/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/flutter/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/ionic/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/kotlin-multiplatform/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/native-android/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/native-ios/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/next/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/nuxt/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/react/rules.md +19 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/react-native/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/svelte/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/frameworks/vue/rules.md +23 -0
- package/.github/skills/figma-ui-mapper/references/ionic.md +18 -0
- package/.github/skills/figma-ui-mapper/references/ios.md +11 -0
- package/.github/skills/figma-ui-mapper/references/kotlin-multiplatform.md +18 -0
- package/.github/skills/figma-ui-mapper/references/native-android.md +18 -0
- package/.github/skills/figma-ui-mapper/references/native-ios.md +18 -0
- package/.github/skills/figma-ui-mapper/references/next.md +18 -0
- package/.github/skills/figma-ui-mapper/references/nuxt.md +18 -0
- package/.github/skills/figma-ui-mapper/references/react-figma-guideline.md +21 -0
- package/.github/skills/figma-ui-mapper/references/react-native.md +18 -0
- package/.github/skills/figma-ui-mapper/references/react.md +18 -0
- package/.github/skills/figma-ui-mapper/references/script-usage.md +46 -0
- package/.github/skills/figma-ui-mapper/references/svelte.md +18 -0
- package/.github/skills/figma-ui-mapper/references/vue.md +18 -0
- package/.github/skills/figma-ui-mapper/references/workflow.md +74 -0
- package/.github/skills/figma-ui-mapper/scripts/generate-page-through-figma.js +635 -0
- package/.github/skills/figma-ui-mapper/scripts/generate-react-page-through-figma.js +4 -0
- package/.github/skills/frontend-task-breakdown/SKILL.md +734 -0
- package/.github/skills/md-file-converter/SKILL.md +291 -0
- package/.github/skills/md-file-converter/config/formats.json +114 -0
- package/.github/skills/md-file-converter/extractors/extract-docx.sh +120 -0
- package/.github/skills/md-file-converter/extractors/extract-eml.py +108 -0
- package/.github/skills/md-file-converter/extractors/extract-pdf.sh +97 -0
- package/.github/skills/md-file-converter/extractors/extract-pptx.py +143 -0
- package/.github/skills/md-file-converter/extractors/extract-xlsx.py +175 -0
- package/.github/skills/md-file-converter/scripts/check-tools.sh +47 -0
- package/.github/skills/md-file-converter/scripts/extract-archive.sh +83 -0
- package/.github/skills/md-file-converter/scripts/extract-ocr.sh +77 -0
- package/.github/skills/md-file-converter/scripts/get-metadata.sh +57 -0
- package/.github/skills/md-file-converter/templates/output.md +73 -0
- package/.github/skills/md-file-converter/templates/unsupported-object.md +6 -0
- package/.github/skills/mockAPI-contract-generator/SKILL.md +607 -0
- package/.github/skills/test-case-validator/CHANGELOG.md +155 -0
- package/.github/skills/test-case-validator/CONFIGURATION.md +130 -0
- package/.github/skills/test-case-validator/EXAMPLES.md +50 -0
- package/.github/skills/test-case-validator/QUALITY_SCORING.md +98 -0
- package/.github/skills/test-case-validator/README.md +111 -0
- package/.github/skills/test-case-validator/REPORT_TEMPLATE.md +116 -0
- package/.github/skills/test-case-validator/SKILL.md +209 -0
- package/.github/skills/test-case-validator/SYSTEM_PROMPT.md +127 -0
- package/.github/skills/test-case-validator/TRACEABILITY_ENGINE.md +152 -0
- package/.github/skills/test-case-validator/VALIDATION_RULES.md +411 -0
- package/.github/skills/test-case-validator/config/default.config.yaml +62 -0
- package/.github/skills/test-case-validator/docs/ARCHITECTURE.md +66 -0
- package/.github/skills/test-case-validator/docs/EXTENDING.md +85 -0
- package/.github/skills/test-case-validator/docs/FAQ.md +88 -0
- package/.github/skills/test-case-validator/docs/VERSIONING.md +55 -0
- package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-brd.md +43 -0
- package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-test-cases.csv +9 -0
- package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/input-user-story.md +29 -0
- package/.github/skills/test-case-validator/examples/example-1-manual-qa-ecommerce/output-report.md +303 -0
- package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/input-spec.md +43 -0
- package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/input-test-files-index.md +56 -0
- package/.github/skills/test-case-validator/examples/example-2-automated-frontend-suite/output-report.md +274 -0
- package/.github/skills/test-case-validator/prompts/00-applicability-resolution.md +61 -0
- package/.github/skills/test-case-validator/prompts/01-extraction-user-story.md +58 -0
- package/.github/skills/test-case-validator/prompts/02-extraction-brd.md +64 -0
- package/.github/skills/test-case-validator/prompts/03-extraction-test-cases.md +92 -0
- package/.github/skills/test-case-validator/prompts/04-traceability-matrix.md +41 -0
- package/.github/skills/test-case-validator/prompts/05-coverage-analysis.md +46 -0
- package/.github/skills/test-case-validator/prompts/06-missing-scenario-detection.md +32 -0
- package/.github/skills/test-case-validator/prompts/07-duplicate-detection.md +36 -0
- package/.github/skills/test-case-validator/prompts/08-risk-analysis.md +39 -0
- package/.github/skills/test-case-validator/prompts/09-gated-wiring-validation.md +53 -0
- package/.github/skills/test-case-validator/prompts/10-observation-report.md +56 -0
- package/.github/skills/test-case-validator/prompts/11-final-report-assembly.md +57 -0
- package/.github/skills/test-case-validator/templates/coverage-table.md +25 -0
- package/.github/skills/test-case-validator/templates/executive-summary.md +38 -0
- package/.github/skills/test-case-validator/templates/final-summary-block.md +87 -0
- package/.github/skills/test-case-validator/templates/observation-entry.md +41 -0
- package/.github/skills/test-case-validator/templates/rtm-row.md +25 -0
- package/.github/skills/validation-engine-builder/README.md +50 -0
- package/.github/skills/validation-engine-builder/SKILL.md +241 -0
- package/.github/skills/validation-engine-builder/references/core.md +158 -0
- package/.github/skills/validation-engine-builder/references/platform-angular.md +166 -0
- package/.github/skills/validation-engine-builder/references/platform-flutter.md +118 -0
- package/.github/skills/validation-engine-builder/references/platform-react-native.md +170 -0
- package/.github/skills/validation-engine-builder/references/platform-react.md +94 -0
- package/.github/skills/validation-engine-builder/scripts/apply-to-project.js +80 -0
- package/.github/skills/validation-engine-builder/scripts/generate-angular.js +271 -0
- package/.github/skills/validation-engine-builder/scripts/generate-flutter.js +298 -0
- package/.github/skills/validation-engine-builder/scripts/generate-react-native.js +217 -0
- package/.github/skills/validation-engine-builder/scripts/generate-react.js +163 -0
- package/.github/skills/validation-engine-builder/scripts/install-deps.js +53 -0
- package/.github/skills/validation-engine-builder/scripts/shared/detect-platform.js +68 -0
- package/.github/skills/validation-engine-builder/scripts/shared/npm-deps.js +103 -0
- package/.github/skills/validation-engine-builder/scripts/shared/rhf-hook.js +43 -0
- package/.github/skills/validation-engine-builder/scripts/shared/ts-messages-regex.js +62 -0
- package/.github/skills/validation-engine-builder/scripts/shared/util.js +44 -0
- package/.github/skills/validation-engine-builder/scripts/shared/yup-schemas.js +80 -0
- package/.github/skills/validation-engine-builder/template.md +73 -0
- package/.github/skills/validation-engine-builder/templates/messages.json +22 -0
- package/.github/skills/validation-engine-builder/templates/regex.json +16 -0
- package/.github/skills/validation-engine-builder/templates/schemas.json +48 -0
- package/bin/index.js +145 -0
- package/package.json +22 -0
- package/readme.md +0 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* setup.js - Encryption-Decryption Generator Skill
|
|
4
|
+
*
|
|
5
|
+
* Interactive wizard that collects platform/approach/key choices,
|
|
6
|
+
* validates against platform-matrix.json, runs detect.js, writes a
|
|
7
|
+
* resolved config, calls install.js, runs the project build, and prints
|
|
8
|
+
* a deterministic 6-field report.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node setup.js --project-path <path> [--help]
|
|
12
|
+
*
|
|
13
|
+
* Exit codes:
|
|
14
|
+
* 0 Setup complete, build pass
|
|
15
|
+
* 1 User-input validation failed
|
|
16
|
+
* 2 detect.js found existing encryption code (user must confirm override)
|
|
17
|
+
* 3 install.js write error
|
|
18
|
+
* 4 Build failed after max retries
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const readline = require('readline');
|
|
26
|
+
const { spawnSync } = require('child_process');
|
|
27
|
+
|
|
28
|
+
// ── React Native compatible version table ─────────────────────────────────────
|
|
29
|
+
//
|
|
30
|
+
// Entries are evaluated in order; the first match where RN major.minor >= minMajor.minMinor wins.
|
|
31
|
+
// Packages not in this table are installed without a pinned version (latest).
|
|
32
|
+
//
|
|
33
|
+
const VERSION_COMPAT = {
|
|
34
|
+
'react-native-quick-crypto': [
|
|
35
|
+
{ minMajor: 0, minMinor: 71, version: '^0.7.4' },
|
|
36
|
+
{ minMajor: 0, minMinor: 68, version: '^0.6.1' },
|
|
37
|
+
{ minMajor: 0, minMinor: 0, version: '^0.7.4' }, // fallback: latest stable
|
|
38
|
+
],
|
|
39
|
+
'@craftzdog/react-native-buffer': [
|
|
40
|
+
{ minMajor: 0, minMinor: 0, version: '^6.0.3' },
|
|
41
|
+
],
|
|
42
|
+
'react-native-keychain': [
|
|
43
|
+
{ minMajor: 0, minMinor: 73, version: '^9.0.0' }, // new arch support
|
|
44
|
+
{ minMajor: 0, minMinor: 60, version: '^8.2.0' },
|
|
45
|
+
{ minMajor: 0, minMinor: 0, version: '^8.2.0' },
|
|
46
|
+
],
|
|
47
|
+
'react-native-get-random-values': [
|
|
48
|
+
{ minMajor: 0, minMinor: 0, version: '^1.9.0' },
|
|
49
|
+
],
|
|
50
|
+
'jsencrypt': [
|
|
51
|
+
{ minMajor: 0, minMinor: 0, version: '^3.3.2' },
|
|
52
|
+
],
|
|
53
|
+
'crypto-js': [
|
|
54
|
+
{ minMajor: 0, minMinor: 0, version: '^4.2.0' },
|
|
55
|
+
],
|
|
56
|
+
'@types/crypto-js': [
|
|
57
|
+
{ minMajor: 0, minMinor: 0, version: '^4.2.2' },
|
|
58
|
+
],
|
|
59
|
+
'axios': [
|
|
60
|
+
{ minMajor: 0, minMinor: 0, version: '^1.7.2' },
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// ── Paths ─────────────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
const SKILL_ROOT = path.resolve(__dirname, '..');
|
|
67
|
+
const MATRIX_PATH = path.join(SKILL_ROOT, 'assets', 'platform-matrix.json');
|
|
68
|
+
const TEMPLATE_PATH = path.join(SKILL_ROOT, 'assets', 'encryption-config-template.json');
|
|
69
|
+
const DETECT_SCRIPT = path.join(__dirname, 'detect.js');
|
|
70
|
+
const INSTALL_SCRIPT = path.join(__dirname, 'install.js');
|
|
71
|
+
|
|
72
|
+
const MAX_BUILD_RETRIES = 3;
|
|
73
|
+
|
|
74
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
function loadJson(filePath, label) {
|
|
77
|
+
if (!fs.existsSync(filePath)) {
|
|
78
|
+
console.error('Error: ' + label + ' not found: ' + filePath);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('Error: ' + label + ' is not valid JSON: ' + e.message);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function prompt(rl, question) {
|
|
90
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function runSync(nodeScript, scriptArgs) {
|
|
94
|
+
const result = spawnSync(process.execPath, [nodeScript, ...scriptArgs], { encoding: 'utf8' });
|
|
95
|
+
return {
|
|
96
|
+
code: result.status,
|
|
97
|
+
stdout: result.stdout || '',
|
|
98
|
+
stderr: result.stderr || '',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── Package version helpers ───────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reads the project's package.json and extracts the react-native version.
|
|
106
|
+
* Returns { major, minor, patch, raw } or null if not found.
|
|
107
|
+
*/
|
|
108
|
+
function parseRnVersion(projectPath) {
|
|
109
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
110
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
111
|
+
try {
|
|
112
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
113
|
+
const raw = (pkg.dependencies && pkg.dependencies['react-native'])
|
|
114
|
+
|| (pkg.devDependencies && pkg.devDependencies['react-native'])
|
|
115
|
+
|| null;
|
|
116
|
+
if (!raw) return null;
|
|
117
|
+
const cleaned = raw.replace(/^[\^~>=<\s]+/, '');
|
|
118
|
+
const m = cleaned.match(/^(\d+)\.(\d+)\.?(\d*)/);
|
|
119
|
+
if (!m) return null;
|
|
120
|
+
return { major: parseInt(m[1], 10), minor: parseInt(m[2], 10), patch: parseInt(m[3] || '0', 10), raw };
|
|
121
|
+
} catch (e) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Given a React Native version object (or null) and an array of bare package names,
|
|
128
|
+
* returns an array of versioned install specs like "pkg@^x.y.z".
|
|
129
|
+
*/
|
|
130
|
+
function resolvePackageVersions(rnVersion, packageNames) {
|
|
131
|
+
return packageNames.map(name => {
|
|
132
|
+
const compat = VERSION_COMPAT[name];
|
|
133
|
+
if (!compat) return name; // no entry — install latest
|
|
134
|
+
if (!rnVersion) {
|
|
135
|
+
// No RN version detected — use the fallback (last entry)
|
|
136
|
+
return name + '@' + compat[compat.length - 1].version;
|
|
137
|
+
}
|
|
138
|
+
for (const entry of compat) {
|
|
139
|
+
const majorOk = rnVersion.major > entry.minMajor;
|
|
140
|
+
const minorOk = rnVersion.major === entry.minMajor && rnVersion.minor >= entry.minMinor;
|
|
141
|
+
if (majorOk || minorOk) {
|
|
142
|
+
return name + '@' + entry.version;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return name + '@' + compat[compat.length - 1].version; // fallback
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Returns the set of packages already present in the project's package.json.
|
|
151
|
+
*/
|
|
152
|
+
function getInstalledPackages(projectPath) {
|
|
153
|
+
const pkgPath = path.join(projectPath, 'package.json');
|
|
154
|
+
if (!fs.existsSync(pkgPath)) return {};
|
|
155
|
+
try {
|
|
156
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
157
|
+
return Object.assign({}, pkg.dependencies || {}, pkg.devDependencies || {});
|
|
158
|
+
} catch (e) {
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Extracts the bare package name from a versioned spec such as
|
|
165
|
+
* "react-native-quick-crypto@^0.7.4" → "react-native-quick-crypto"
|
|
166
|
+
* "@craftzdog/react-native-buffer@^6.0.3" → "@craftzdog/react-native-buffer"
|
|
167
|
+
*/
|
|
168
|
+
function extractPackageName(spec) {
|
|
169
|
+
if (spec.startsWith('@')) {
|
|
170
|
+
const atIdx = spec.indexOf('@', 1);
|
|
171
|
+
return atIdx === -1 ? spec : spec.slice(0, atIdx);
|
|
172
|
+
}
|
|
173
|
+
const atIdx = spec.indexOf('@');
|
|
174
|
+
return atIdx === -1 ? spec : spec.slice(0, atIdx);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Filters out packages that are already listed in the project's package.json,
|
|
179
|
+
* returning only packages that need to be installed.
|
|
180
|
+
*/
|
|
181
|
+
function filterNewPackages(projectPath, versionedSpecs) {
|
|
182
|
+
const installed = getInstalledPackages(projectPath);
|
|
183
|
+
return versionedSpecs.filter(spec => !installed[extractPackageName(spec)]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Detects whether the project uses yarn, pnpm, or npm by checking lock files.
|
|
188
|
+
*/
|
|
189
|
+
function detectPackageManager(projectPath) {
|
|
190
|
+
if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) return 'yarn';
|
|
191
|
+
if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm';
|
|
192
|
+
return 'npm';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Runs the detected package manager to install the given versioned specs.
|
|
197
|
+
* Logs what it's about to install. Returns true on success, false on failure.
|
|
198
|
+
*/
|
|
199
|
+
function installPackages(projectPath, versionedSpecs) {
|
|
200
|
+
if (!versionedSpecs || versionedSpecs.length === 0) return true;
|
|
201
|
+
const pm = detectPackageManager(projectPath);
|
|
202
|
+
const addCmd = pm === 'npm' ? 'install' : 'add';
|
|
203
|
+
const saveFlags = pm === 'npm' ? ['--save'] : [];
|
|
204
|
+
|
|
205
|
+
console.log('\nInstalling dependencies with ' + pm + ':');
|
|
206
|
+
versionedSpecs.forEach(s => console.log(' ' + s));
|
|
207
|
+
|
|
208
|
+
const result = spawnSync(pm, [addCmd, ...saveFlags, ...versionedSpecs], {
|
|
209
|
+
cwd: projectPath,
|
|
210
|
+
encoding: 'utf8',
|
|
211
|
+
shell: true,
|
|
212
|
+
stdio: 'inherit',
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (result.status !== 0) {
|
|
216
|
+
console.error('\nPackage install failed (exit ' + result.status + ').');
|
|
217
|
+
console.error('Run manually: ' + pm + ' ' + addCmd + ' ' + versionedSpecs.join(' '));
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
console.log('Packages installed successfully.');
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
const args = process.argv.slice(2);
|
|
227
|
+
|
|
228
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
229
|
+
console.log([
|
|
230
|
+
'Usage: node setup.js --project-path <path> [--help]',
|
|
231
|
+
'',
|
|
232
|
+
'Runs an interactive wizard to configure and install encryption.',
|
|
233
|
+
'',
|
|
234
|
+
'Options:',
|
|
235
|
+
' --project-path Absolute path to the project root (required)',
|
|
236
|
+
' --help Print this help and exit',
|
|
237
|
+
'',
|
|
238
|
+
'Exit codes:',
|
|
239
|
+
' 0 Setup complete, build passed',
|
|
240
|
+
' 1 Input validation failed',
|
|
241
|
+
' 2 Existing encryption detected (override not confirmed)',
|
|
242
|
+
' 3 Install write error',
|
|
243
|
+
' 4 Build failed after ' + MAX_BUILD_RETRIES + ' retries',
|
|
244
|
+
].join('\n'));
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const ppIndex = args.indexOf('--project-path');
|
|
249
|
+
if (ppIndex === -1 || !args[ppIndex + 1]) {
|
|
250
|
+
console.error('Error: --project-path is required.');
|
|
251
|
+
console.error('Run with --help for usage.');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const projectPath = path.resolve(args[ppIndex + 1]);
|
|
256
|
+
if (!fs.existsSync(projectPath)) {
|
|
257
|
+
console.error('Error: project path does not exist: ' + projectPath);
|
|
258
|
+
process.exit(1);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function main() {
|
|
262
|
+
const matrix = loadJson(MATRIX_PATH, 'platform-matrix.json');
|
|
263
|
+
const template = loadJson(TEMPLATE_PATH, 'encryption-config-template.json');
|
|
264
|
+
|
|
265
|
+
const rl = readline.createInterface({
|
|
266
|
+
input: process.stdin,
|
|
267
|
+
output: process.stdout,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const platforms = Object.keys(matrix.platforms);
|
|
271
|
+
|
|
272
|
+
// ── Step 1: Detect existing encryption ─────────────────────────────────────
|
|
273
|
+
console.log('\nScanning project for existing encryption code...');
|
|
274
|
+
const detect = runSync(DETECT_SCRIPT, ['--project-path', projectPath]);
|
|
275
|
+
let detectionMode = 'replace';
|
|
276
|
+
if (detect.code === 1) {
|
|
277
|
+
if (detect.stdout) process.stdout.write(detect.stdout.trim() + '\n');
|
|
278
|
+
console.log('\nExisting encryption logic detected. Choose how to proceed:');
|
|
279
|
+
console.log(' 1. Reuse existing logic (use existing service/interceptor, generate only missing parts)');
|
|
280
|
+
console.log(' 2. Replace with new implementation');
|
|
281
|
+
console.log(' 3. Refactor and centralize existing logic (scaffold common module, do not overwrite existing)');
|
|
282
|
+
console.log(' 4. Cancel setup');
|
|
283
|
+
let choice = '';
|
|
284
|
+
while (!['1', '2', '3', '4'].includes(choice)) {
|
|
285
|
+
choice = (await prompt(rl, '\nEnter choice [1/2/3/4]: ')).trim();
|
|
286
|
+
if (!['1', '2', '3', '4'].includes(choice)) {
|
|
287
|
+
console.log(' Invalid choice. Enter 1, 2, 3, or 4.');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (choice === '4') {
|
|
291
|
+
console.log('Setup cancelled by user.');
|
|
292
|
+
rl.close();
|
|
293
|
+
process.exit(2);
|
|
294
|
+
}
|
|
295
|
+
detectionMode = { '1': 'reuse', '2': 'replace', '3': 'refactor' }[choice];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── Step 2: Platform ────────────────────────────────────────────────────────
|
|
299
|
+
console.log('\nSupported platforms: ' + platforms.join(', '));
|
|
300
|
+
let platform = '';
|
|
301
|
+
while (!platform) {
|
|
302
|
+
const input = await prompt(rl, 'Select platform [' + platforms.join('/') + ']: ');
|
|
303
|
+
const trimmed = input.trim().toLowerCase();
|
|
304
|
+
if (platforms.includes(trimmed)) {
|
|
305
|
+
platform = trimmed;
|
|
306
|
+
} else {
|
|
307
|
+
console.log(' Invalid platform. Choose from: ' + platforms.join(', '));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const platformDef = matrix.platforms[platform];
|
|
312
|
+
const validApproaches = platformDef.approaches;
|
|
313
|
+
|
|
314
|
+
// ── Step 3: Approach ────────────────────────────────────────────────────────
|
|
315
|
+
console.log('\nSupported approaches for ' + platform + ': ' + validApproaches.join(', '));
|
|
316
|
+
console.log(' 1 = RSA-OAEP + AES-256-GCM (Web Crypto)');
|
|
317
|
+
console.log(' 2 = RSA + AES-256-CBC (jsencrypt + crypto-js)');
|
|
318
|
+
console.log(' 3 = AES-256-CBC shared key (crypto-js)');
|
|
319
|
+
if (platform === 'react-native') {
|
|
320
|
+
console.log(' 4 = Production JS: AES-256-GCM + RSA-OAEP (react-native-quick-crypto, per-request ephemeral keys)');
|
|
321
|
+
console.log(' 5 = Production Native: OS-level crypto (javax.crypto.Cipher / CryptoKit), same interceptor shape');
|
|
322
|
+
console.log(' 6 = EC (ECDH+AES-256-GCM): per-request forward secrecy via P-256 ECDH + HKDF-SHA256; per-request or session-init variants');
|
|
323
|
+
}
|
|
324
|
+
let approach = 0;
|
|
325
|
+
while (!approach) {
|
|
326
|
+
const input = await prompt(rl, 'Select approach [' + validApproaches.join('/') + ']: ');
|
|
327
|
+
const num = parseInt(input.trim(), 10);
|
|
328
|
+
if (validApproaches.includes(num)) {
|
|
329
|
+
approach = num;
|
|
330
|
+
} else {
|
|
331
|
+
console.log(' Invalid approach. Choose from: ' + validApproaches.join(', '));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const config = Object.assign({}, template, { platform, projectPath, approach });
|
|
336
|
+
|
|
337
|
+
// ── Step 4 (Production RN approaches 4 & 5) ─────────────────────────────────
|
|
338
|
+
if (platform === 'react-native' && (approach === 4 || approach === 5 || approach === 6)) {
|
|
339
|
+
console.log('\n-- Production React Native Setup --');
|
|
340
|
+
|
|
341
|
+
// ── Approach 6 (EC) — separate question flow ─────────────────────────────
|
|
342
|
+
if (approach === 6) {
|
|
343
|
+
const ecContractAnswer = await prompt(rl, 'EC contract type? (per-request/session-init) [per-request]: ');
|
|
344
|
+
config.ecContractType = ecContractAnswer.trim().toLowerCase() === 'session-init' ? 'session-init' : 'per-request';
|
|
345
|
+
|
|
346
|
+
const ecSourceAnswer = await prompt(rl, 'How is the EC public key provided? (api/env) [api]: ');
|
|
347
|
+
config.ecKeySource = ecSourceAnswer.trim().toLowerCase() === 'env' ? 'env' : 'api';
|
|
348
|
+
|
|
349
|
+
if (config.ecKeySource === 'api') {
|
|
350
|
+
const ecEndpointAnswer = await prompt(rl, 'EC key endpoint URL [<API_BASE_URL>/api/security/ec-public-key]: ');
|
|
351
|
+
config.ecKeyEndpoint = ecEndpointAnswer.trim() || '<API_BASE_URL>/api/security/ec-public-key';
|
|
352
|
+
const ecRetriesAnswer = await prompt(rl, 'Max retries for EC key fetch [3]: ');
|
|
353
|
+
const ecRetries = parseInt(ecRetriesAnswer.trim(), 10);
|
|
354
|
+
config.ecKeyRetries = (!isNaN(ecRetries) && ecRetries > 0) ? ecRetries : 3;
|
|
355
|
+
} else {
|
|
356
|
+
let ecPubKey = '';
|
|
357
|
+
while (!ecPubKey) {
|
|
358
|
+
const input = await prompt(rl, 'Enter EC public key (base64 of 65-byte uncompressed P-256 point): ');
|
|
359
|
+
const trimmed = input.trim();
|
|
360
|
+
if (!trimmed) { console.log(' Warning: no EC key provided.'); break; }
|
|
361
|
+
const decoded = Buffer.from(trimmed, 'base64');
|
|
362
|
+
if (decoded.length !== 65 || decoded[0] !== 0x04) {
|
|
363
|
+
console.log(' Invalid EC public key: must be 65-byte uncompressed P-256 point (base64).');
|
|
364
|
+
} else {
|
|
365
|
+
config.ecPublicKey = trimmed;
|
|
366
|
+
ecPubKey = trimmed;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (config.ecContractType === 'session-init') {
|
|
372
|
+
const initEndpointAnswer = await prompt(rl, 'Session init endpoint URL: ');
|
|
373
|
+
config.ecSessionInitEndpoint = initEndpointAnswer.trim();
|
|
374
|
+
const hkdfInfoAnswer = await prompt(rl, 'HKDF info string (backend-specific, must be confirmed — no default): ');
|
|
375
|
+
config.ecHkdfInfo = hkdfInfoAnswer.trim();
|
|
376
|
+
const nonceFormatAnswer = await prompt(rl, 'Nonce format/length (e.g. "12-char alphanumeric", "12 random bytes base64", etc. — no default): ');
|
|
377
|
+
config.ecNonceFormat = nonceFormatAnswer.trim();
|
|
378
|
+
const aadAnswer = await prompt(rl, 'Is AAD required for AES-GCM? (yes/no) [no]: ');
|
|
379
|
+
config.ecAadRequired = aadAnswer.trim().toLowerCase() === 'yes';
|
|
380
|
+
if (config.ecAadRequired) {
|
|
381
|
+
const aadShapeAnswer = await prompt(rl, 'AAD canonical JSON shape (e.g. {"api_ver":"v1","device_id":"...","key_id":"..."}): ');
|
|
382
|
+
config.ecAadShape = aadShapeAnswer.trim();
|
|
383
|
+
}
|
|
384
|
+
const respIvAnswer = await prompt(rl, 'Response IV source? (nonce-utf8 / nonce-base64 / iv-field / other) [nonce-utf8]: ');
|
|
385
|
+
config.ecResponseIvSource = respIvAnswer.trim() || 'nonce-utf8';
|
|
386
|
+
const createSessionAnswer = await prompt(rl, 'Token/session endpoint URL (CREATE_SESSION_ENDPOINT) — no default: ');
|
|
387
|
+
config.ecCreateSessionEndpoint = createSessionAnswer.trim();
|
|
388
|
+
const responseEncodingAnswer = await prompt(rl, 'Response payload encoding (standard-base64/base64url) [standard-base64]: ');
|
|
389
|
+
config.ecResponseEncoding = responseEncodingAnswer.trim().toLowerCase() === 'base64url' ? 'base64url' : 'standard-base64';
|
|
390
|
+
const deviceIdAnswer = await prompt(rl, 'DEVICE_ID_SOURCE — how is the stable device_id generated/retrieved? (no default): ');
|
|
391
|
+
config.ecDeviceIdSource = deviceIdAnswer.trim();
|
|
392
|
+
const encryptedAesAnswer = await prompt(rl, 'Does backend return encrypted_shared_aes_key? Should it be used or ignored? (used/ignored) [ignored]: ');
|
|
393
|
+
config.ecEncryptedAesKeyHandling = encryptedAesAnswer.trim().toLowerCase() === 'used' ? 'used' : 'ignored';
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// API client type
|
|
397
|
+
const ecApiClientAnswer = await prompt(rl, 'API client type? (axios/fetch/custom) [axios]: ');
|
|
398
|
+
config.apiClientType = ['fetch', 'custom'].includes(ecApiClientAnswer.trim().toLowerCase())
|
|
399
|
+
? ecApiClientAnswer.trim().toLowerCase() : 'axios';
|
|
400
|
+
|
|
401
|
+
const ecApiPathAnswer = await prompt(rl, 'API layer path [src/services/api.ts]: ');
|
|
402
|
+
config.apiLayerPath = ecApiPathAnswer.trim() || 'src/services/api.ts';
|
|
403
|
+
|
|
404
|
+
const ecLayerAnswer = await prompt(rl, 'Encryption layer? (rn/native) [rn]: ');
|
|
405
|
+
config.encryptionLayer = ecLayerAnswer.trim().toLowerCase() === 'native' ? 'native' : 'rn';
|
|
406
|
+
|
|
407
|
+
const ecHmacAnswer = await prompt(rl, 'Enable HMAC request signing? (yes/no) [no]: ');
|
|
408
|
+
config.hmac = { enabled: false };
|
|
409
|
+
if (ecHmacAnswer.trim().toLowerCase() === 'yes') {
|
|
410
|
+
config.hmac.enabled = true;
|
|
411
|
+
console.log(' HMAC enabled. Set the ENCRYPTION_HMAC_SECRET environment variable before running.');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const ecExcludeAnswer = await prompt(rl, 'Exclude any URL paths from encryption? (comma-separated, or leave blank): ');
|
|
415
|
+
if (ecExcludeAnswer.trim()) {
|
|
416
|
+
config.excludeUrls = ecExcludeAnswer.split(',').map(u => u.trim()).filter(Boolean);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
config.encryptionEnabled = true;
|
|
420
|
+
config.encryptionModel = 'ec';
|
|
421
|
+
|
|
422
|
+
rl.close();
|
|
423
|
+
|
|
424
|
+
const tmpConfig = path.join(projectPath, '.encryption-resolved-config.tmp.json');
|
|
425
|
+
try {
|
|
426
|
+
fs.writeFileSync(tmpConfig, JSON.stringify(config, null, 2), 'utf8');
|
|
427
|
+
} catch (e) {
|
|
428
|
+
console.error('Error: could not write temp config: ' + e.message);
|
|
429
|
+
process.exit(3);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
console.log('\nInstalling EC encryption files...');
|
|
433
|
+
const installEc = runSync(INSTALL_SCRIPT, ['--config-json', tmpConfig, '--detection-mode', detectionMode]);
|
|
434
|
+
if (installEc.stdout) process.stdout.write(installEc.stdout);
|
|
435
|
+
if (installEc.stderr) process.stderr.write(installEc.stderr);
|
|
436
|
+
try { fs.unlinkSync(tmpConfig); } catch {}
|
|
437
|
+
|
|
438
|
+
if (installEc.code !== 0) {
|
|
439
|
+
console.error('INSTALL_RESULT : ERROR');
|
|
440
|
+
process.exit(3);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const rnVersionEc = parseRnVersion(projectPath);
|
|
444
|
+
const approachDepsEc = (matrix.platforms[platform].dependencies || {})[String(approach)] || [];
|
|
445
|
+
const versionedDepsEc = resolvePackageVersions(rnVersionEc, approachDepsEc);
|
|
446
|
+
const newDepsEc = filterNewPackages(projectPath, versionedDepsEc);
|
|
447
|
+
let pkgOkEc = true;
|
|
448
|
+
if (newDepsEc.length > 0) {
|
|
449
|
+
pkgOkEc = installPackages(projectPath, newDepsEc);
|
|
450
|
+
} else {
|
|
451
|
+
console.log('\nAll required packages are already installed.');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
console.log([
|
|
455
|
+
'',
|
|
456
|
+
'=== SETUP REPORT ===',
|
|
457
|
+
'Platform : ' + platform,
|
|
458
|
+
'Approach : 6 (EC / ECDH+AES-256-GCM)',
|
|
459
|
+
'EC contract type : ' + config.ecContractType,
|
|
460
|
+
config.ecContractType === 'session-init' ? 'Session init endpoint : ' + (config.ecSessionInitEndpoint || '(not set)') : null,
|
|
461
|
+
config.ecContractType === 'session-init' ? 'Create session endpoint : ' + (config.ecCreateSessionEndpoint || '(not set)') : null,
|
|
462
|
+
config.ecContractType === 'session-init' ? 'Response encoding : ' + config.ecResponseEncoding : null,
|
|
463
|
+
config.ecContractType === 'session-init' ? 'Device ID source : ' + (config.ecDeviceIdSource || '(not set)') : null,
|
|
464
|
+
config.ecContractType === 'session-init' ? 'Enc. AES key handling : ' + config.ecEncryptedAesKeyHandling : null,
|
|
465
|
+
'Encryption layer : ' + config.encryptionLayer,
|
|
466
|
+
'HMAC enabled : ' + config.hmac.enabled,
|
|
467
|
+
'URL exclusions : ' + (config.excludeUrls || []).join(', '),
|
|
468
|
+
newDepsEc.length > 0
|
|
469
|
+
? 'Packages installed : ' + newDepsEc.join(', ')
|
|
470
|
+
: 'Packages installed : none (all already present)',
|
|
471
|
+
!pkgOkEc ? 'WARNING: Package install had errors — run manually' : null,
|
|
472
|
+
'====================',
|
|
473
|
+
].filter(l => l !== null).join('\n'));
|
|
474
|
+
|
|
475
|
+
process.exit(0);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// RSA key source (approaches 4 and 5 only)
|
|
479
|
+
const rsaSourceAnswer = await prompt(rl, 'How is the RSA public key provided? (api/env) [api]: ');
|
|
480
|
+
const rsaKeySource = rsaSourceAnswer.trim().toLowerCase() === 'env' ? 'env' : 'api';
|
|
481
|
+
config.rsaKeySource = rsaKeySource;
|
|
482
|
+
|
|
483
|
+
if (rsaKeySource === 'api') {
|
|
484
|
+
const endpointAnswer = await prompt(rl, 'RSA key endpoint URL [<API_BASE_URL>/api/security/public-key]: ');
|
|
485
|
+
config.rsaKeyEndpoint = endpointAnswer.trim() || '<API_BASE_URL>/api/security/public-key';
|
|
486
|
+
const retriesAnswer = await prompt(rl, 'Max retries for RSA key fetch [3]: ');
|
|
487
|
+
const retries = parseInt(retriesAnswer.trim(), 10);
|
|
488
|
+
config.rsaKeyRetries = (!isNaN(retries) && retries > 0) ? retries : 3;
|
|
489
|
+
} else {
|
|
490
|
+
// env mode: get PEM inline
|
|
491
|
+
let pubKey = '';
|
|
492
|
+
while (!pubKey) {
|
|
493
|
+
const input = await prompt(rl, 'Enter RSA public key PEM (must begin with -----BEGIN PUBLIC KEY-----): ');
|
|
494
|
+
const trimmed = input.trim();
|
|
495
|
+
if (!trimmed) {
|
|
496
|
+
console.log(' Warning: no public key provided — encryption will fail at runtime.');
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
if (!trimmed.includes('-----BEGIN') || !trimmed.includes('-----END')) {
|
|
500
|
+
console.log(' Invalid PEM format. Must contain -----BEGIN ... KEY----- / -----END ... KEY----- headers.');
|
|
501
|
+
} else {
|
|
502
|
+
config.publicKeyPem = trimmed;
|
|
503
|
+
pubKey = trimmed;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Approach 5 — additional native settings
|
|
509
|
+
if (approach === 5) {
|
|
510
|
+
const iosVersionAnswer = await prompt(rl, 'Minimum iOS deployment target [13]: ');
|
|
511
|
+
const iosV = parseInt(iosVersionAnswer.trim(), 10);
|
|
512
|
+
config.minIosVersion = (!isNaN(iosV) && iosV >= 13) ? iosV : 13;
|
|
513
|
+
|
|
514
|
+
if (!isNaN(iosV) && iosV < 13) {
|
|
515
|
+
console.log(' NOTE: iOS < 13 — CryptoKit unavailable. CommonCrypto fallback (CCCryptorGCM) will be used.');
|
|
516
|
+
console.log(' Deployment target raised to 13. Recommend targeting >= 13.0 for new projects.');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const androidPkgAnswer = await prompt(rl, 'Android Kotlin package for NativeCryptoModule [com.example.security]: ');
|
|
520
|
+
config.androidPackage = androidPkgAnswer.trim() || 'com.example.security';
|
|
521
|
+
|
|
522
|
+
// Warn about Android minSdkVersion
|
|
523
|
+
const androidGradle = path.join(projectPath, 'android/app/build.gradle');
|
|
524
|
+
if (fs.existsSync(androidGradle)) {
|
|
525
|
+
const gradleContent = fs.readFileSync(androidGradle, 'utf8');
|
|
526
|
+
const sdkMatch = gradleContent.match(/minSdkVersion\s*[=:]\s*(\d+)/);
|
|
527
|
+
if (sdkMatch && parseInt(sdkMatch[1], 10) < 23) {
|
|
528
|
+
console.log(' WARNING: minSdkVersion is ' + sdkMatch[1] + '. AES/GCM/NoPadding requires minSdkVersion >= 23.');
|
|
529
|
+
console.log(' Update android/app/build.gradle: minSdkVersion = 23');
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// API client type
|
|
535
|
+
const apiClientAnswer = await prompt(rl, 'API client type? (axios/fetch/custom) [axios]: ');
|
|
536
|
+
config.apiClientType = ['fetch', 'custom'].includes(apiClientAnswer.trim().toLowerCase())
|
|
537
|
+
? apiClientAnswer.trim().toLowerCase() : 'axios';
|
|
538
|
+
|
|
539
|
+
// API layer path
|
|
540
|
+
const apiPathAnswer = await prompt(rl, 'API layer path [src/services/api.ts]: ');
|
|
541
|
+
config.apiLayerPath = apiPathAnswer.trim() || 'src/services/api.ts';
|
|
542
|
+
|
|
543
|
+
// Encryption toggle policy
|
|
544
|
+
const toggleAnswer = await prompt(rl, 'Encryption toggle policy? (env-flag/always-on) [env-flag]: ');
|
|
545
|
+
config.encryptionToggle = toggleAnswer.trim().toLowerCase() === 'always-on' ? 'always-on' : 'env-flag';
|
|
546
|
+
|
|
547
|
+
// Keychain for tokens
|
|
548
|
+
const keychainAnswer = await prompt(rl, 'Use react-native-keychain for secure token storage? (yes/no) [yes]: ');
|
|
549
|
+
config.useKeychain = keychainAnswer.trim().toLowerCase() !== 'no';
|
|
550
|
+
|
|
551
|
+
// HMAC
|
|
552
|
+
const hmacAnswer = await prompt(rl, 'Enable HMAC request signing? (yes/no) [no]: ');
|
|
553
|
+
config.hmac = { enabled: false };
|
|
554
|
+
if (hmacAnswer.trim().toLowerCase() === 'yes') {
|
|
555
|
+
config.hmac.enabled = true;
|
|
556
|
+
console.log(' HMAC enabled. Set the HMAC_SECRET environment variable in your app before running.');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// URL exclusions
|
|
560
|
+
const excludeAnswer = await prompt(rl, 'Exclude any URL paths from encryption? (comma-separated, or leave blank): ');
|
|
561
|
+
if (excludeAnswer.trim()) {
|
|
562
|
+
config.excludeUrls = excludeAnswer.split(',').map(u => u.trim()).filter(Boolean);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
config.encryptionEnabled = true;
|
|
566
|
+
rl.close();
|
|
567
|
+
|
|
568
|
+
// Write temp config and call install.js
|
|
569
|
+
const tmpConfig = path.join(projectPath, '.encryption-resolved-config.tmp.json');
|
|
570
|
+
try {
|
|
571
|
+
const safeConfig = Object.assign({}, config, { hmac: { enabled: config.hmac.enabled } });
|
|
572
|
+
fs.writeFileSync(tmpConfig, JSON.stringify(safeConfig, null, 2), 'utf8');
|
|
573
|
+
} catch (e) {
|
|
574
|
+
console.error('Error: could not write temp config: ' + e.message);
|
|
575
|
+
process.exit(3);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
console.log('\nInstalling production encryption files...');
|
|
579
|
+
const install = runSync(INSTALL_SCRIPT, ['--config-json', tmpConfig, '--detection-mode', detectionMode]);
|
|
580
|
+
if (install.stdout) process.stdout.write(install.stdout);
|
|
581
|
+
if (install.stderr) process.stderr.write(install.stderr);
|
|
582
|
+
try { fs.unlinkSync(tmpConfig); } catch {}
|
|
583
|
+
|
|
584
|
+
if (install.code !== 0) {
|
|
585
|
+
console.error('INSTALL_RESULT : ERROR');
|
|
586
|
+
process.exit(3);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ── Detect RN version and install compatible packages ────────────────────
|
|
590
|
+
const rnVersionProd = parseRnVersion(projectPath);
|
|
591
|
+
if (rnVersionProd) {
|
|
592
|
+
console.log('\nDetected React Native version: ' + rnVersionProd.major + '.' + rnVersionProd.minor + '.' + rnVersionProd.patch + ' (from ' + rnVersionProd.raw + ')');
|
|
593
|
+
} else {
|
|
594
|
+
console.log('\nWARNING: Could not detect React Native version — installing latest compatible versions.');
|
|
595
|
+
}
|
|
596
|
+
const approachDeps = (matrix.platforms[platform].dependencies || {})[String(approach)] || [];
|
|
597
|
+
const versionedDepsProd = resolvePackageVersions(rnVersionProd, approachDeps);
|
|
598
|
+
const newDepsProd = filterNewPackages(projectPath, versionedDepsProd);
|
|
599
|
+
let pkgInstallOkProd = true;
|
|
600
|
+
if (newDepsProd.length > 0) {
|
|
601
|
+
pkgInstallOkProd = installPackages(projectPath, newDepsProd);
|
|
602
|
+
if (!pkgInstallOkProd) {
|
|
603
|
+
console.error('WARNING: Some packages could not be installed automatically.');
|
|
604
|
+
}
|
|
605
|
+
} else {
|
|
606
|
+
console.log('\nAll required packages are already installed.');
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
console.log([
|
|
610
|
+
'',
|
|
611
|
+
'=== SETUP REPORT ===',
|
|
612
|
+
'Platform : ' + platform,
|
|
613
|
+
'Approach : ' + approach + (approach === 4 ? ' (Production JS)' : ' (Production Native)'),
|
|
614
|
+
'RN version : ' + (rnVersionProd ? rnVersionProd.raw : 'unknown'),
|
|
615
|
+
'RSA key source : ' + config.rsaKeySource,
|
|
616
|
+
'RSA key endpoint : ' + (config.rsaKeyEndpoint || 'env'),
|
|
617
|
+
'API client type : ' + (config.apiClientType || 'axios'),
|
|
618
|
+
'Encryption toggle : ' + (config.encryptionToggle || 'env-flag'),
|
|
619
|
+
'HMAC enabled : ' + config.hmac.enabled,
|
|
620
|
+
'URL exclusions : ' + (config.excludeUrls || []).join(', '),
|
|
621
|
+
approach === 5 ? ('Min iOS version : ' + config.minIosVersion) : null,
|
|
622
|
+
approach === 5 ? ('Android package : ' + (config.androidPackage || 'com.example.security')) : null,
|
|
623
|
+
config.apiLayerPath && config.apiLayerPath !== 'src/services/api.ts'
|
|
624
|
+
? 'NOTE: Interceptor wired into src/api/axiosInstance.ts — integrate with your existing API layer at: ' + config.apiLayerPath
|
|
625
|
+
: null,
|
|
626
|
+
newDepsProd.length > 0
|
|
627
|
+
? 'Packages installed : ' + newDepsProd.join(', ')
|
|
628
|
+
: 'Packages installed : none (all already present)',
|
|
629
|
+
!pkgInstallOkProd ? 'WARNING: Package install had errors — run manually' : null,
|
|
630
|
+
'====================',
|
|
631
|
+
].filter(l => l !== null).join('\n'));
|
|
632
|
+
|
|
633
|
+
process.exit(0);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ── Step 4–6: Public/private/shared key prompts ─────────────────────────────
|
|
637
|
+
if (approach === 1 || approach === 2) {
|
|
638
|
+
let pubKey = '';
|
|
639
|
+
while (!pubKey) {
|
|
640
|
+
const input = await prompt(rl, 'Enter public key PEM (must begin with -----BEGIN PUBLIC KEY-----): ');
|
|
641
|
+
const trimmed = input.trim();
|
|
642
|
+
if (!trimmed) {
|
|
643
|
+
console.log(' Warning: no public key provided — encryption will fail at runtime.');
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
if (!trimmed.includes('-----BEGIN') || !trimmed.includes('-----END')) {
|
|
647
|
+
console.log(' Invalid PEM format. Must contain -----BEGIN ... KEY----- / -----END ... KEY----- headers.');
|
|
648
|
+
} else {
|
|
649
|
+
config.publicKeyPem = trimmed;
|
|
650
|
+
pubKey = trimmed;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (approach === 1 || approach === 2) {
|
|
656
|
+
const decryptResponse = await prompt(rl, 'Decrypt responses too? (yes/no) [no]: ');
|
|
657
|
+
if (decryptResponse.trim().toLowerCase() === 'yes') {
|
|
658
|
+
const privKey = await prompt(rl, 'Enter private key PEM (paste on one line, or leave blank to skip): ');
|
|
659
|
+
if (privKey.trim()) config.privateKeyPem = privKey.trim();
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (approach === 3) {
|
|
664
|
+
let sharedKey = '';
|
|
665
|
+
while (!sharedKey) {
|
|
666
|
+
const input = await prompt(rl, 'Enter shared secret key (min 32 characters): ');
|
|
667
|
+
const trimmed = input.trim();
|
|
668
|
+
if (!trimmed) {
|
|
669
|
+
console.log(' Warning: no shared key provided — encryption will fail at runtime.');
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
if (trimmed.length < 32) {
|
|
673
|
+
console.log(' Error: shared key must be at least 32 characters. Got ' + trimmed.length + '.');
|
|
674
|
+
} else {
|
|
675
|
+
config.sharedKey = trimmed;
|
|
676
|
+
sharedKey = trimmed;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// ── Step 7: HMAC ────────────────────────────────────────────────────────────
|
|
682
|
+
const hmacAnswer = await prompt(rl, 'Enable HMAC signing? (yes/no) [no]: ');
|
|
683
|
+
config.hmac = { enabled: false };
|
|
684
|
+
if (hmacAnswer.trim().toLowerCase() === 'yes') {
|
|
685
|
+
config.hmac.enabled = true;
|
|
686
|
+
// HMAC secret is intentionally NOT stored in config or temp file.
|
|
687
|
+
// Set it as env var ENCRYPTION_HMAC_SECRET before running the app.
|
|
688
|
+
console.log(' HMAC enabled. Set the ENCRYPTION_HMAC_SECRET environment variable in your app before running.');
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// ── Step 8: URL exclusions ──────────────────────────────────────────────────
|
|
692
|
+
const excludeAnswer = await prompt(rl, 'Exclude any URL prefixes from encryption? (comma-separated, or leave blank): ');
|
|
693
|
+
if (excludeAnswer.trim()) {
|
|
694
|
+
config.excludeUrls = excludeAnswer.split(',').map(u => u.trim()).filter(Boolean);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
config.encryptionEnabled = true;
|
|
698
|
+
rl.close();
|
|
699
|
+
|
|
700
|
+
// ── Write temp config and call install.js ───────────────────────────────────
|
|
701
|
+
const tmpConfig = path.join(projectPath, '.encryption-resolved-config.tmp.json');
|
|
702
|
+
try {
|
|
703
|
+
// HMAC secret is never written to disk; generated files read it from ENCRYPTION_HMAC_SECRET env var
|
|
704
|
+
const safeConfig = Object.assign({}, config, { hmac: { enabled: config.hmac.enabled } });
|
|
705
|
+
fs.writeFileSync(tmpConfig, JSON.stringify(safeConfig, null, 2), 'utf8');
|
|
706
|
+
} catch (e) {
|
|
707
|
+
console.error('Error: could not write temp config: ' + e.message);
|
|
708
|
+
process.exit(3);
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
console.log('\nInstalling encryption files...');
|
|
712
|
+
const install = runSync(INSTALL_SCRIPT, ['--config-json', tmpConfig, '--detection-mode', detectionMode]);
|
|
713
|
+
if (install.stdout) process.stdout.write(install.stdout);
|
|
714
|
+
if (install.stderr) process.stderr.write(install.stderr);
|
|
715
|
+
|
|
716
|
+
try { fs.unlinkSync(tmpConfig); } catch {}
|
|
717
|
+
|
|
718
|
+
if (install.code !== 0) {
|
|
719
|
+
console.error('INSTALL_RESULT : ERROR');
|
|
720
|
+
process.exit(3);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// ── Detect RN/project version and install compatible packages ───────────────
|
|
724
|
+
const rnVersion = parseRnVersion(projectPath);
|
|
725
|
+
if (platform === 'react-native') {
|
|
726
|
+
if (rnVersion) {
|
|
727
|
+
console.log('\nDetected React Native version: ' + rnVersion.major + '.' + rnVersion.minor + '.' + rnVersion.patch + ' (from ' + rnVersion.raw + ')');
|
|
728
|
+
} else {
|
|
729
|
+
console.log('\nWARNING: Could not detect React Native version — installing latest compatible versions.');
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const approachDepsRegular = (platformDef.dependencies || {})[String(approach)] || [];
|
|
733
|
+
if (approachDepsRegular.length > 0) {
|
|
734
|
+
const versionedDepsRegular = resolvePackageVersions(rnVersion, approachDepsRegular);
|
|
735
|
+
const newDepsRegular = filterNewPackages(projectPath, versionedDepsRegular);
|
|
736
|
+
if (newDepsRegular.length > 0) {
|
|
737
|
+
const pkgOk = installPackages(projectPath, newDepsRegular);
|
|
738
|
+
if (!pkgOk) {
|
|
739
|
+
console.error('WARNING: Some packages could not be installed. The build may fail.');
|
|
740
|
+
}
|
|
741
|
+
} else {
|
|
742
|
+
console.log('\nAll required packages are already installed.');
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ── Build with retries ──────────────────────────────────────────────────────
|
|
747
|
+
const buildCmd = platformDef.buildCommand;
|
|
748
|
+
let buildResult = null;
|
|
749
|
+
let buildAttempts = 0;
|
|
750
|
+
let buildPassed = false;
|
|
751
|
+
|
|
752
|
+
if (buildCmd) {
|
|
753
|
+
console.log('\nRunning build: ' + buildCmd);
|
|
754
|
+
const [cmd, ...cmdArgs] = buildCmd.split(' ');
|
|
755
|
+
while (buildAttempts < MAX_BUILD_RETRIES) {
|
|
756
|
+
buildAttempts++;
|
|
757
|
+
const build = spawnSync(cmd, cmdArgs, {
|
|
758
|
+
cwd: projectPath,
|
|
759
|
+
encoding: 'utf8',
|
|
760
|
+
shell: true,
|
|
761
|
+
});
|
|
762
|
+
buildResult = build;
|
|
763
|
+
if (build.status === 0) {
|
|
764
|
+
buildPassed = true;
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
console.log(' Build attempt ' + buildAttempts + '/' + MAX_BUILD_RETRIES + ' failed.');
|
|
768
|
+
}
|
|
769
|
+
} else {
|
|
770
|
+
buildPassed = true;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// ── Report ──────────────────────────────────────────────────────────────────
|
|
774
|
+
const filesWritten = (install.stdout.match(/^(WROTE|PATCHED)\s+/mg) || []).length;
|
|
775
|
+
const buildErrors = (!buildPassed && buildResult) ? (buildResult.stderr || '').split('\n').slice(0, 5).join('\n') : '';
|
|
776
|
+
|
|
777
|
+
console.log([
|
|
778
|
+
'',
|
|
779
|
+
'=== SETUP REPORT ===',
|
|
780
|
+
'Platform : ' + platform,
|
|
781
|
+
'Approach : ' + approach,
|
|
782
|
+
'HMAC enabled : ' + config.hmac.enabled,
|
|
783
|
+
'URL exclusions : ' + (config.excludeUrls || []).join(', '),
|
|
784
|
+
'Files written/patched : ' + filesWritten,
|
|
785
|
+
'Build status : ' + (buildPassed ? 'PASS' : 'FAIL'),
|
|
786
|
+
buildErrors ? ('Build errors (first 5 lines):\n' + buildErrors) : null,
|
|
787
|
+
'====================',
|
|
788
|
+
].filter(l => l !== null).join('\n'));
|
|
789
|
+
|
|
790
|
+
if (!buildPassed) {
|
|
791
|
+
process.exit(4);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
process.exit(0);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
main().catch(e => {
|
|
798
|
+
console.error('Unexpected error: ' + e.message);
|
|
799
|
+
process.exit(1);
|
|
800
|
+
});
|