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,217 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const { readTemplate, ensureDir, relativeImportPath } = require('./shared/util.js');
|
|
8
|
+
const { genMessages, genRegex } = require('./shared/ts-messages-regex.js');
|
|
9
|
+
const { genSchemas } = require('./shared/yup-schemas.js');
|
|
10
|
+
const { genHook } = require('./shared/rhf-hook.js');
|
|
11
|
+
|
|
12
|
+
// ── ValidationError.tsx (React Native — Text + StyleSheet) ──────────────────────
|
|
13
|
+
|
|
14
|
+
function genValidationError(outDir) {
|
|
15
|
+
const content = [
|
|
16
|
+
`// Auto-generated by validation-engine-builder skill. DO NOT EDIT MANUALLY.`,
|
|
17
|
+
``,
|
|
18
|
+
`import React from 'react';`,
|
|
19
|
+
`import { Text, StyleSheet } from 'react-native';`,
|
|
20
|
+
``,
|
|
21
|
+
`interface Props {`,
|
|
22
|
+
` message?: string;`,
|
|
23
|
+
`}`,
|
|
24
|
+
``,
|
|
25
|
+
`/**`,
|
|
26
|
+
` * Displays a Yup validation error message below a TextInput.`,
|
|
27
|
+
` * Returns null when message is undefined (no layout space consumed).`,
|
|
28
|
+
` *`,
|
|
29
|
+
` * Usage:`,
|
|
30
|
+
` * <ValidationError message={errors.email?.message} />`,
|
|
31
|
+
` */`,
|
|
32
|
+
`export const ValidationError: React.FC<Props> = ({ message }) => {`,
|
|
33
|
+
` if (!message) return null;`,
|
|
34
|
+
` return <Text style={styles.error}>{message}</Text>;`,
|
|
35
|
+
`};`,
|
|
36
|
+
``,
|
|
37
|
+
`const styles = StyleSheet.create({`,
|
|
38
|
+
` error: {`,
|
|
39
|
+
` color: '#DC2626',`,
|
|
40
|
+
` fontSize: 12,`,
|
|
41
|
+
` marginTop: 4,`,
|
|
42
|
+
` marginLeft: 2,`,
|
|
43
|
+
` },`,
|
|
44
|
+
`});`,
|
|
45
|
+
].join('\n');
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(path.join(outDir, 'ValidationError.tsx'), content);
|
|
48
|
+
console.log(' ✅ ValidationError.tsx');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Screen scaffold ───────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
function getTextInputProps(fieldName, rules) {
|
|
54
|
+
const regex = rules.regex || '';
|
|
55
|
+
const props = [];
|
|
56
|
+
|
|
57
|
+
if (['password', 'confirmPassword', 'newPassword', 'currentPassword'].includes(fieldName)) {
|
|
58
|
+
props.push('secureTextEntry={true}');
|
|
59
|
+
}
|
|
60
|
+
if (regex === 'email' || fieldName === 'email') {
|
|
61
|
+
props.push('keyboardType="email-address"', 'autoCapitalize="none"');
|
|
62
|
+
} else if (regex === 'phone' || ['phone', 'phoneNumber', 'mobile'].includes(fieldName)) {
|
|
63
|
+
props.push('keyboardType="phone-pad"');
|
|
64
|
+
} else if (regex === 'otp' || fieldName === 'otp') {
|
|
65
|
+
props.push('keyboardType="number-pad"', 'maxLength={6}');
|
|
66
|
+
} else if (regex === 'numericOnly' || rules.type === 'number') {
|
|
67
|
+
props.push('keyboardType="numeric"');
|
|
68
|
+
} else if (regex === 'name' || ['name', 'fullName', 'firstName', 'lastName'].includes(fieldName)) {
|
|
69
|
+
props.push('autoCapitalize="words"');
|
|
70
|
+
} else if (regex === 'url' || fieldName === 'url' || fieldName === 'website') {
|
|
71
|
+
props.push('keyboardType="url"', 'autoCapitalize="none"');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return props.length > 0 ? '\n ' + props.join('\n ') : '';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function generateScreen(projectRoot, screenName, schemaKey, screenPath, schemasRaw) {
|
|
78
|
+
const fields = schemasRaw[schemaKey];
|
|
79
|
+
if (!fields) {
|
|
80
|
+
console.log(`\n ⚠️ Schema "${schemaKey}" not found in schemas.json.`);
|
|
81
|
+
console.log(` Available schemas: ${Object.keys(schemasRaw).join(', ')}`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const screenDir = path.join(projectRoot, screenPath, screenName);
|
|
86
|
+
ensureDir(screenDir);
|
|
87
|
+
|
|
88
|
+
const validationOutDir = path.join(projectRoot, 'src', 'validation');
|
|
89
|
+
const validationImportPath = relativeImportPath(screenDir, validationOutDir);
|
|
90
|
+
|
|
91
|
+
const controllerBlocks = Object.entries(fields).map(([fieldName, rules]) => {
|
|
92
|
+
const placeholder = rules.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
|
93
|
+
const extraProps = getTextInputProps(fieldName, rules);
|
|
94
|
+
|
|
95
|
+
return [
|
|
96
|
+
` <Controller`,
|
|
97
|
+
` control={control}`,
|
|
98
|
+
` name="${fieldName}"`,
|
|
99
|
+
` render={({ field: { onChange, onBlur, value } }) => (`,
|
|
100
|
+
` <View style={styles.fieldContainer}>`,
|
|
101
|
+
` <TextInput`,
|
|
102
|
+
` style={[styles.input, errors.${fieldName} && styles.inputError]}`,
|
|
103
|
+
` placeholder="${placeholder}"`,
|
|
104
|
+
` onChangeText={onChange}`,
|
|
105
|
+
` onBlur={onBlur}`,
|
|
106
|
+
` value={value ?? ''}${extraProps}`,
|
|
107
|
+
` />`,
|
|
108
|
+
` <ValidationError message={errors.${fieldName}?.message} />`,
|
|
109
|
+
` </View>`,
|
|
110
|
+
` )}`,
|
|
111
|
+
` />`,
|
|
112
|
+
].join('\n');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const content = [
|
|
116
|
+
`import React from 'react';`,
|
|
117
|
+
`import {`,
|
|
118
|
+
` View,`,
|
|
119
|
+
` TextInput,`,
|
|
120
|
+
` TouchableOpacity,`,
|
|
121
|
+
` Text,`,
|
|
122
|
+
` StyleSheet,`,
|
|
123
|
+
` ScrollView,`,
|
|
124
|
+
`} from 'react-native';`,
|
|
125
|
+
`import { Controller } from 'react-hook-form';`,
|
|
126
|
+
`import { useValidatedForm } from '${validationImportPath}/useValidatedForm';`,
|
|
127
|
+
`import { ValidationError } from '${validationImportPath}/ValidationError';`,
|
|
128
|
+
`import { schemas, FormData } from '${validationImportPath}/schemas';`,
|
|
129
|
+
``,
|
|
130
|
+
`const ${screenName} = () => {`,
|
|
131
|
+
` const {`,
|
|
132
|
+
` control,`,
|
|
133
|
+
` handleSubmit,`,
|
|
134
|
+
` formState: { errors },`,
|
|
135
|
+
` } = useValidatedForm(schemas.${schemaKey});`,
|
|
136
|
+
``,
|
|
137
|
+
` const onSubmit = (data: FormData<'${schemaKey}'>) => {`,
|
|
138
|
+
` console.log('${screenName} submit:', data);`,
|
|
139
|
+
` // TODO: Add your submit logic here`,
|
|
140
|
+
` };`,
|
|
141
|
+
``,
|
|
142
|
+
` return (`,
|
|
143
|
+
` <ScrollView contentContainerStyle={styles.container}>`,
|
|
144
|
+
...controllerBlocks,
|
|
145
|
+
` <TouchableOpacity style={styles.button} onPress={handleSubmit(onSubmit)}>`,
|
|
146
|
+
` <Text style={styles.buttonText}>Submit</Text>`,
|
|
147
|
+
` </TouchableOpacity>`,
|
|
148
|
+
` </ScrollView>`,
|
|
149
|
+
` );`,
|
|
150
|
+
`};`,
|
|
151
|
+
``,
|
|
152
|
+
`export default ${screenName};`,
|
|
153
|
+
``,
|
|
154
|
+
`const styles = StyleSheet.create({`,
|
|
155
|
+
` container: {`,
|
|
156
|
+
` flexGrow: 1,`,
|
|
157
|
+
` padding: 24,`,
|
|
158
|
+
` backgroundColor: '#FFFFFF',`,
|
|
159
|
+
` },`,
|
|
160
|
+
` fieldContainer: {`,
|
|
161
|
+
` marginBottom: 16,`,
|
|
162
|
+
` },`,
|
|
163
|
+
` input: {`,
|
|
164
|
+
` borderWidth: 1,`,
|
|
165
|
+
` borderColor: '#D1D5DB',`,
|
|
166
|
+
` borderRadius: 8,`,
|
|
167
|
+
` paddingHorizontal: 16,`,
|
|
168
|
+
` paddingVertical: 12,`,
|
|
169
|
+
` fontSize: 16,`,
|
|
170
|
+
` color: '#111827',`,
|
|
171
|
+
` backgroundColor: '#F9FAFB',`,
|
|
172
|
+
` },`,
|
|
173
|
+
` inputError: {`,
|
|
174
|
+
` borderColor: '#DC2626',`,
|
|
175
|
+
` },`,
|
|
176
|
+
` button: {`,
|
|
177
|
+
` backgroundColor: '#0EA5E9',`,
|
|
178
|
+
` borderRadius: 8,`,
|
|
179
|
+
` paddingVertical: 14,`,
|
|
180
|
+
` alignItems: 'center',`,
|
|
181
|
+
` marginTop: 8,`,
|
|
182
|
+
` },`,
|
|
183
|
+
` buttonText: {`,
|
|
184
|
+
` color: '#FFFFFF',`,
|
|
185
|
+
` fontSize: 16,`,
|
|
186
|
+
` fontWeight: '600',`,
|
|
187
|
+
` },`,
|
|
188
|
+
`});`,
|
|
189
|
+
].join('\n');
|
|
190
|
+
|
|
191
|
+
const outFile = path.join(screenDir, `${screenName}.tsx`);
|
|
192
|
+
fs.writeFileSync(outFile, content);
|
|
193
|
+
console.log(` ✅ ${screenName}.tsx → ${screenPath}/${screenName}/`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Main export ───────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
function generate(projectRoot) {
|
|
199
|
+
const outDir = path.join(projectRoot, 'src', 'validation');
|
|
200
|
+
ensureDir(outDir);
|
|
201
|
+
|
|
202
|
+
console.log('\n📁 Generating src/validation/ (React Native) ...');
|
|
203
|
+
|
|
204
|
+
const messages = readTemplate('messages.json');
|
|
205
|
+
const regexMap = readTemplate('regex.json');
|
|
206
|
+
const schemasRaw = readTemplate('schemas.json');
|
|
207
|
+
|
|
208
|
+
genMessages(messages, outDir);
|
|
209
|
+
genRegex(regexMap, outDir);
|
|
210
|
+
genSchemas(schemasRaw, outDir);
|
|
211
|
+
genHook(outDir);
|
|
212
|
+
genValidationError(outDir);
|
|
213
|
+
|
|
214
|
+
return schemasRaw;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = { generate, generateScreen };
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const { readTemplate, ensureDir, relativeImportPath } = require('./shared/util.js');
|
|
8
|
+
const { genMessages, genRegex } = require('./shared/ts-messages-regex.js');
|
|
9
|
+
const { genSchemas } = require('./shared/yup-schemas.js');
|
|
10
|
+
const { genHook } = require('./shared/rhf-hook.js');
|
|
11
|
+
|
|
12
|
+
// ── ValidationError.tsx (React web — plain <span>, no RN import) ────────────────
|
|
13
|
+
|
|
14
|
+
function genValidationError(outDir) {
|
|
15
|
+
const content = [
|
|
16
|
+
`// Auto-generated by validation-engine-builder skill. DO NOT EDIT MANUALLY.`,
|
|
17
|
+
``,
|
|
18
|
+
`import React from 'react';`,
|
|
19
|
+
``,
|
|
20
|
+
`interface Props {`,
|
|
21
|
+
` message?: string;`,
|
|
22
|
+
`}`,
|
|
23
|
+
``,
|
|
24
|
+
`const errorStyle: React.CSSProperties = {`,
|
|
25
|
+
` color: '#DC2626',`,
|
|
26
|
+
` fontSize: 12,`,
|
|
27
|
+
` marginTop: 4,`,
|
|
28
|
+
` marginLeft: 2,`,
|
|
29
|
+
` display: 'block',`,
|
|
30
|
+
`};`,
|
|
31
|
+
``,
|
|
32
|
+
`/**`,
|
|
33
|
+
` * Displays a Yup validation error message below an input.`,
|
|
34
|
+
` * Returns null when message is undefined (no layout space consumed).`,
|
|
35
|
+
` * Uses an inline style so it works with zero assumptions about the`,
|
|
36
|
+
` * project's CSS setup — replace with the project's own class/CSS module`,
|
|
37
|
+
` * if one already exists.`,
|
|
38
|
+
` *`,
|
|
39
|
+
` * Usage:`,
|
|
40
|
+
` * <ValidationError message={errors.email?.message} />`,
|
|
41
|
+
` */`,
|
|
42
|
+
`export const ValidationError: React.FC<Props> = ({ message }) => {`,
|
|
43
|
+
` if (!message) return null;`,
|
|
44
|
+
` return <span style={errorStyle}>{message}</span>;`,
|
|
45
|
+
`};`,
|
|
46
|
+
].join('\n');
|
|
47
|
+
|
|
48
|
+
fs.writeFileSync(path.join(outDir, 'ValidationError.tsx'), content);
|
|
49
|
+
console.log(' ✅ ValidationError.tsx');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ── Page scaffold ─────────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
function getInputProps(fieldName, rules) {
|
|
55
|
+
const regex = rules.regex || '';
|
|
56
|
+
const props = [];
|
|
57
|
+
|
|
58
|
+
if (['password', 'confirmPassword', 'newPassword', 'currentPassword'].includes(fieldName)) {
|
|
59
|
+
props.push(`type="password"`);
|
|
60
|
+
} else if (regex === 'email' || fieldName === 'email') {
|
|
61
|
+
props.push(`type="email"`, `autoComplete="email"`);
|
|
62
|
+
} else if (rules.type === 'number' || regex === 'numericOnly') {
|
|
63
|
+
props.push(`type="number"`);
|
|
64
|
+
} else if (regex === 'url' || fieldName === 'url' || fieldName === 'website') {
|
|
65
|
+
props.push(`type="url"`);
|
|
66
|
+
} else {
|
|
67
|
+
props.push(`type="text"`);
|
|
68
|
+
}
|
|
69
|
+
if (fieldName === 'otp') props.push(`maxLength={6}`);
|
|
70
|
+
|
|
71
|
+
return '\n ' + props.join('\n ');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function generateScreen(projectRoot, pageName, schemaKey, pagePath, schemasRaw) {
|
|
75
|
+
const fields = schemasRaw[schemaKey];
|
|
76
|
+
if (!fields) {
|
|
77
|
+
console.log(`\n ⚠️ Schema "${schemaKey}" not found in schemas.json.`);
|
|
78
|
+
console.log(` Available schemas: ${Object.keys(schemasRaw).join(', ')}`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const pageDir = path.join(projectRoot, pagePath, pageName);
|
|
83
|
+
ensureDir(pageDir);
|
|
84
|
+
|
|
85
|
+
const validationOutDir = path.join(projectRoot, 'src', 'validation');
|
|
86
|
+
const validationImportPath = relativeImportPath(pageDir, validationOutDir);
|
|
87
|
+
|
|
88
|
+
// Native <input> elements integrate with react-hook-form via register() —
|
|
89
|
+
// Controller is only needed for custom/controlled components, unlike RN's
|
|
90
|
+
// TextInput which requires Controller. Do not port the RN Controller
|
|
91
|
+
// pattern to plain HTML inputs.
|
|
92
|
+
const fieldBlocks = Object.entries(fields).map(([fieldName, rules]) => {
|
|
93
|
+
const label = rules.label || fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
|
|
94
|
+
const inputProps = getInputProps(fieldName, rules);
|
|
95
|
+
|
|
96
|
+
return [
|
|
97
|
+
` <div style={{ marginBottom: 16 }}>`,
|
|
98
|
+
` <label htmlFor="${fieldName}">${label}</label>`,
|
|
99
|
+
` <input`,
|
|
100
|
+
` id="${fieldName}"${inputProps}`,
|
|
101
|
+
` {...register('${fieldName}')}`,
|
|
102
|
+
` />`,
|
|
103
|
+
` <ValidationError message={errors.${fieldName}?.message} />`,
|
|
104
|
+
` </div>`,
|
|
105
|
+
].join('\n');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const content = [
|
|
109
|
+
`import React from 'react';`,
|
|
110
|
+
`import { useValidatedForm } from '${validationImportPath}/useValidatedForm';`,
|
|
111
|
+
`import { ValidationError } from '${validationImportPath}/ValidationError';`,
|
|
112
|
+
`import { schemas, FormData } from '${validationImportPath}/schemas';`,
|
|
113
|
+
``,
|
|
114
|
+
`const ${pageName} = () => {`,
|
|
115
|
+
` const {`,
|
|
116
|
+
` register,`,
|
|
117
|
+
` handleSubmit,`,
|
|
118
|
+
` formState: { errors },`,
|
|
119
|
+
` } = useValidatedForm(schemas.${schemaKey});`,
|
|
120
|
+
``,
|
|
121
|
+
` const onSubmit = (data: FormData<'${schemaKey}'>) => {`,
|
|
122
|
+
` console.log('${pageName} submit:', data);`,
|
|
123
|
+
` // TODO: Add your submit logic here`,
|
|
124
|
+
` };`,
|
|
125
|
+
``,
|
|
126
|
+
` return (`,
|
|
127
|
+
` <form onSubmit={handleSubmit(onSubmit)}>`,
|
|
128
|
+
...fieldBlocks,
|
|
129
|
+
` <button type="submit">Submit</button>`,
|
|
130
|
+
` </form>`,
|
|
131
|
+
` );`,
|
|
132
|
+
`};`,
|
|
133
|
+
``,
|
|
134
|
+
`export default ${pageName};`,
|
|
135
|
+
].join('\n');
|
|
136
|
+
|
|
137
|
+
const outFile = path.join(pageDir, `${pageName}.tsx`);
|
|
138
|
+
fs.writeFileSync(outFile, content);
|
|
139
|
+
console.log(` ✅ ${pageName}.tsx → ${pagePath}/${pageName}/`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── Main export ───────────────────────────────────────────────────────────────
|
|
143
|
+
|
|
144
|
+
function generate(projectRoot) {
|
|
145
|
+
const outDir = path.join(projectRoot, 'src', 'validation');
|
|
146
|
+
ensureDir(outDir);
|
|
147
|
+
|
|
148
|
+
console.log('\n📁 Generating src/validation/ (React web) ...');
|
|
149
|
+
|
|
150
|
+
const messages = readTemplate('messages.json');
|
|
151
|
+
const regexMap = readTemplate('regex.json');
|
|
152
|
+
const schemasRaw = readTemplate('schemas.json');
|
|
153
|
+
|
|
154
|
+
genMessages(messages, outDir);
|
|
155
|
+
genRegex(regexMap, outDir);
|
|
156
|
+
genSchemas(schemasRaw, outDir);
|
|
157
|
+
genHook(outDir);
|
|
158
|
+
genValidationError(outDir);
|
|
159
|
+
|
|
160
|
+
return schemasRaw;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = { generate, generateScreen };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* install-deps.js
|
|
6
|
+
* Installs react-hook-form, @hookform/resolvers, yup at versions compatible
|
|
7
|
+
* with the project — React Native / React only. Angular ships Reactive Forms
|
|
8
|
+
* with the framework and Flutter needs no package at all, so this is a no-op
|
|
9
|
+
* (with an explanatory message) on those two platforms.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node .github/skills/validation-engine-builder/scripts/install-deps.js
|
|
13
|
+
* node .github/skills/validation-engine-builder/scripts/install-deps.js --projectPath MyApp
|
|
14
|
+
* node .github/skills/validation-engine-builder/scripts/install-deps.js --platform react
|
|
15
|
+
* node .github/skills/validation-engine-builder/scripts/install-deps.js --dryRun
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const { detectPlatform } = require('./shared/detect-platform.js');
|
|
21
|
+
const { ensureDeps } = require('./shared/npm-deps.js');
|
|
22
|
+
|
|
23
|
+
function parseArgs(argv) {
|
|
24
|
+
const parsed = {};
|
|
25
|
+
for (let i = 0; i < argv.length; i++) {
|
|
26
|
+
const token = argv[i];
|
|
27
|
+
if (!token.startsWith('--')) continue;
|
|
28
|
+
const key = token.slice(2);
|
|
29
|
+
const next = argv[i + 1];
|
|
30
|
+
if (!next || next.startsWith('--')) { parsed[key] = true; continue; }
|
|
31
|
+
parsed[key] = next;
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
return parsed;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function main() {
|
|
38
|
+
const args = parseArgs(process.argv.slice(2));
|
|
39
|
+
const projectRoot = args.projectPath ? path.resolve(args.projectPath) : process.cwd();
|
|
40
|
+
const platform = args.platform || detectPlatform(projectRoot);
|
|
41
|
+
|
|
42
|
+
if (!platform) {
|
|
43
|
+
console.error('ERROR: Could not determine the target platform.');
|
|
44
|
+
console.error('Re-run with --platform <react-native|react|angular|flutter>.');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(`Platform: ${platform}${args.platform ? ' (explicit override)' : ' (detected)'}`);
|
|
49
|
+
|
|
50
|
+
ensureDeps(projectRoot, platform, { dryRun: !!args.dryRun });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Detects which of the 4 supported platforms the target project is.
|
|
6
|
+
* Mirrors the detection signals used by the clean-architecture-generator skill
|
|
7
|
+
* so both skills agree on what "React Native" / "React" / "Angular" / "Flutter"
|
|
8
|
+
* mean for the same repo.
|
|
9
|
+
*
|
|
10
|
+
* Returns one of: 'react-native' | 'react' | 'angular' | 'flutter' | null
|
|
11
|
+
* Returns null when no signal matches — callers MUST ask the user rather than
|
|
12
|
+
* silently defaulting to a platform.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
function readJsonSafe(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function detectPlatform(projectRoot) {
|
|
27
|
+
// 1. Flutter — pubspec.yaml with a flutter: SDK entry under dependencies
|
|
28
|
+
const pubspecPath = path.join(projectRoot, 'pubspec.yaml');
|
|
29
|
+
if (fs.existsSync(pubspecPath)) {
|
|
30
|
+
const pubspec = fs.readFileSync(pubspecPath, 'utf-8');
|
|
31
|
+
if (/\bflutter\s*:\s*\n\s*sdk\s*:\s*flutter/m.test(pubspec) || /^\s*flutter\s*:/m.test(pubspec)) {
|
|
32
|
+
return 'flutter';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 2. Angular — angular.json at the project root
|
|
37
|
+
if (fs.existsSync(path.join(projectRoot, 'angular.json'))) {
|
|
38
|
+
return 'angular';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 3. React Native / React — package.json dependency inspection
|
|
42
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
43
|
+
const pkg = readJsonSafe(pkgPath);
|
|
44
|
+
if (pkg) {
|
|
45
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
46
|
+
if (deps['react-native']) return 'react-native';
|
|
47
|
+
if (deps['react'] && deps['react-dom']) return 'react';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function detectReactSubVariant(projectRoot) {
|
|
54
|
+
const pkg = readJsonSafe(path.join(projectRoot, 'package.json'));
|
|
55
|
+
if (!pkg) return null;
|
|
56
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
57
|
+
if (deps['next']) return 'nextjs';
|
|
58
|
+
if (deps['react-scripts']) return 'cra';
|
|
59
|
+
if (deps['vite']) return 'vite';
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function detectAngularStandalone(projectRoot) {
|
|
64
|
+
return fs.existsSync(path.join(projectRoot, 'src', 'app', 'app.config.ts')) &&
|
|
65
|
+
!fs.existsSync(path.join(projectRoot, 'src', 'app', 'app.module.ts'));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { detectPlatform, detectReactSubVariant, detectAngularStandalone };
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* react-hook-form + @hookform/resolvers + yup — the dependency set this skill
|
|
6
|
+
* installs for React Native and React (web) only. Angular ships Reactive
|
|
7
|
+
* Forms (@angular/forms) as part of the framework — nothing to install.
|
|
8
|
+
* Flutter's TextFormField.validator needs no package at all. See
|
|
9
|
+
* ensureDeps() below for how each platform is handled.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { execSync } = require('child_process');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const REQUIRED_DEPS = ['react-hook-form', '@hookform/resolvers', 'yup'];
|
|
17
|
+
|
|
18
|
+
function readPkg(projectRoot) {
|
|
19
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
20
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
23
|
+
} catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getReactNativeVersion(projectRoot) {
|
|
29
|
+
const pkg = readPkg(projectRoot);
|
|
30
|
+
if (!pkg) return null;
|
|
31
|
+
const raw = pkg.dependencies?.['react-native'] || pkg.devDependencies?.['react-native'] || '';
|
|
32
|
+
const m = raw.match(/(\d+\.\d+)/);
|
|
33
|
+
return m ? parseFloat(m[1]) : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Version table verified against React Native compatibility only. For plain
|
|
38
|
+
* React (web), there is no RN version to gate on — this applies the same
|
|
39
|
+
* "current" tier as a reasonable default; if a long time has passed since
|
|
40
|
+
* this skill was last updated, verify against npm before installing rather
|
|
41
|
+
* than trusting these pins blindly.
|
|
42
|
+
*/
|
|
43
|
+
function getCompatibleVersions(rnVersion) {
|
|
44
|
+
if (rnVersion === null || rnVersion >= 0.80) {
|
|
45
|
+
return { 'react-hook-form': '^7.54.0', '@hookform/resolvers': '^3.9.0', 'yup': '^1.4.0' };
|
|
46
|
+
}
|
|
47
|
+
if (rnVersion >= 0.73) {
|
|
48
|
+
return { 'react-hook-form': '^7.51.0', '@hookform/resolvers': '^3.4.0', 'yup': '^1.3.0' };
|
|
49
|
+
}
|
|
50
|
+
return { 'react-hook-form': '^7.43.0', '@hookform/resolvers': '^3.1.0', 'yup': '^1.0.0' };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* platform: 'react-native' | 'react' | 'angular' | 'flutter'
|
|
55
|
+
* options: { skipInstall?: boolean, dryRun?: boolean }
|
|
56
|
+
*/
|
|
57
|
+
function ensureDeps(projectRoot, platform, options = {}) {
|
|
58
|
+
if (platform === 'angular') {
|
|
59
|
+
console.log(' Angular Reactive Forms (@angular/forms) ships with the framework — nothing to install.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (platform === 'flutter') {
|
|
63
|
+
console.log(' Flutter TextFormField.validator needs no pub package — nothing to install.');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (options.skipInstall) return;
|
|
68
|
+
|
|
69
|
+
const pkg = readPkg(projectRoot);
|
|
70
|
+
if (!pkg) {
|
|
71
|
+
console.log(' package.json not found — skipping install');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
76
|
+
const missing = REQUIRED_DEPS.filter((d) => !allDeps[d]);
|
|
77
|
+
|
|
78
|
+
if (missing.length === 0) {
|
|
79
|
+
console.log(` ${REQUIRED_DEPS.join(', ')} — already installed`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const rnVersion = platform === 'react-native' ? getReactNativeVersion(projectRoot) : null;
|
|
84
|
+
const versions = getCompatibleVersions(rnVersion);
|
|
85
|
+
const installStr = missing.map((d) => `${d}@${versions[d]}`).join(' ');
|
|
86
|
+
const cmd = `npm install ${installStr} --legacy-peer-deps`;
|
|
87
|
+
|
|
88
|
+
console.log(`\n Installing: ${installStr}`);
|
|
89
|
+
|
|
90
|
+
if (options.dryRun) {
|
|
91
|
+
console.log(' (dry run — not executing)');
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
execSync(cmd, { cwd: projectRoot, stdio: 'inherit' });
|
|
97
|
+
console.log(' Dependencies installed');
|
|
98
|
+
} catch {
|
|
99
|
+
console.log(` Auto-install failed. Run manually: ${cmd}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = { REQUIRED_DEPS, getReactNativeVersion, getCompatibleVersions, ensureDeps };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Emits useValidatedForm.ts — a typed react-hook-form wrapper. react-hook-form
|
|
6
|
+
* is a React library (not React-Native-specific), so this is shared verbatim
|
|
7
|
+
* by React Native and React (web). Angular and Flutter have no react-hook-form
|
|
8
|
+
* equivalent — Angular uses FormBuilder/FormGroup directly, Flutter uses
|
|
9
|
+
* GlobalKey<FormState>. Neither generates this file.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
function genHook(outDir) {
|
|
16
|
+
const content = [
|
|
17
|
+
`// Auto-generated by validation-engine-builder skill. DO NOT EDIT MANUALLY.`,
|
|
18
|
+
``,
|
|
19
|
+
`import { useForm } from 'react-hook-form';`,
|
|
20
|
+
`import { yupResolver } from '@hookform/resolvers/yup';`,
|
|
21
|
+
`import * as yup from 'yup';`,
|
|
22
|
+
``,
|
|
23
|
+
`/**`,
|
|
24
|
+
` * Typed form hook wrapping react-hook-form with Yup validation.`,
|
|
25
|
+
` *`,
|
|
26
|
+
` * Usage:`,
|
|
27
|
+
` * const { control, handleSubmit, formState: { errors } } =`,
|
|
28
|
+
` * useValidatedForm(schemas.login);`,
|
|
29
|
+
` */`,
|
|
30
|
+
`export function useValidatedForm<T extends yup.AnyObjectSchema>(schema: T) {`,
|
|
31
|
+
` return useForm<yup.InferType<T>>({`,
|
|
32
|
+
` resolver: yupResolver(schema) as any,`,
|
|
33
|
+
` mode: 'onBlur',`,
|
|
34
|
+
` reValidateMode: 'onChange',`,
|
|
35
|
+
` });`,
|
|
36
|
+
`}`,
|
|
37
|
+
].join('\n');
|
|
38
|
+
|
|
39
|
+
fs.writeFileSync(path.join(outDir, 'useValidatedForm.ts'), content);
|
|
40
|
+
console.log(' ✅ useValidatedForm.ts');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { genHook };
|