@codyswann/lisa 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +867 -0
- package/all/copy-overwrite/.claude/README.md +205 -0
- package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
- package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
- package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
- package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
- package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
- package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
- package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
- package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
- package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
- package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
- package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
- package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
- package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
- package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
- package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
- package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
- package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
- package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
- package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
- package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
- package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
- package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
- package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
- package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
- package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
- package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
- package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
- package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
- package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
- package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
- package/all/copy-overwrite/.claude/hooks/README.md +301 -0
- package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
- package/all/copy-overwrite/.claude/settings.json +41 -0
- package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
- package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
- package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
- package/all/copy-overwrite/CLAUDE.md +77 -0
- package/all/copy-overwrite/HUMAN.md +17 -0
- package/all/copy-overwrite/specs/.keep +0 -0
- package/all/create-only/PROJECT_RULES.md +0 -0
- package/cdk/merge/package.json +20 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +107 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/prompts.d.ts +45 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +58 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/config.d.ts +73 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +36 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/lisa.d.ts +81 -0
- package/dist/core/lisa.d.ts.map +1 -0
- package/dist/core/lisa.js +459 -0
- package/dist/core/lisa.js.map +1 -0
- package/dist/core/manifest.d.ts +58 -0
- package/dist/core/manifest.d.ts.map +1 -0
- package/dist/core/manifest.js +104 -0
- package/dist/core/manifest.js.map +1 -0
- package/dist/detection/detector.interface.d.ts +15 -0
- package/dist/detection/detector.interface.d.ts.map +1 -0
- package/dist/detection/detector.interface.js +2 -0
- package/dist/detection/detector.interface.js.map +1 -0
- package/dist/detection/detectors/cdk.d.ts +10 -0
- package/dist/detection/detectors/cdk.d.ts.map +1 -0
- package/dist/detection/detectors/cdk.js +34 -0
- package/dist/detection/detectors/cdk.js.map +1 -0
- package/dist/detection/detectors/expo.d.ts +10 -0
- package/dist/detection/detectors/expo.d.ts.map +1 -0
- package/dist/detection/detectors/expo.js +30 -0
- package/dist/detection/detectors/expo.js.map +1 -0
- package/dist/detection/detectors/nestjs.d.ts +10 -0
- package/dist/detection/detectors/nestjs.d.ts.map +1 -0
- package/dist/detection/detectors/nestjs.js +34 -0
- package/dist/detection/detectors/nestjs.js.map +1 -0
- package/dist/detection/detectors/npm-package.d.ts +13 -0
- package/dist/detection/detectors/npm-package.d.ts.map +1 -0
- package/dist/detection/detectors/npm-package.js +30 -0
- package/dist/detection/detectors/npm-package.js.map +1 -0
- package/dist/detection/detectors/typescript.d.ts +10 -0
- package/dist/detection/detectors/typescript.d.ts.map +1 -0
- package/dist/detection/detectors/typescript.js +25 -0
- package/dist/detection/detectors/typescript.js.map +1 -0
- package/dist/detection/index.d.ts +24 -0
- package/dist/detection/index.d.ts.map +1 -0
- package/dist/detection/index.js +57 -0
- package/dist/detection/index.js.map +1 -0
- package/dist/errors/index.d.ts +69 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +110 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/console-logger.d.ts +12 -0
- package/dist/logging/console-logger.d.ts.map +1 -0
- package/dist/logging/console-logger.js +22 -0
- package/dist/logging/console-logger.js.map +1 -0
- package/dist/logging/index.d.ts +4 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.interface.d.ts +20 -0
- package/dist/logging/logger.interface.d.ts.map +1 -0
- package/dist/logging/logger.interface.js +2 -0
- package/dist/logging/logger.interface.js.map +1 -0
- package/dist/logging/silent-logger.d.ts +12 -0
- package/dist/logging/silent-logger.d.ts.map +1 -0
- package/dist/logging/silent-logger.js +21 -0
- package/dist/logging/silent-logger.js.map +1 -0
- package/dist/strategies/copy-contents.d.ts +14 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -0
- package/dist/strategies/copy-contents.js +69 -0
- package/dist/strategies/copy-contents.js.map +1 -0
- package/dist/strategies/copy-overwrite.d.ts +14 -0
- package/dist/strategies/copy-overwrite.d.ts.map +1 -0
- package/dist/strategies/copy-overwrite.js +47 -0
- package/dist/strategies/copy-overwrite.js.map +1 -0
- package/dist/strategies/create-only.d.ts +13 -0
- package/dist/strategies/create-only.d.ts.map +1 -0
- package/dist/strategies/create-only.js +30 -0
- package/dist/strategies/create-only.js.map +1 -0
- package/dist/strategies/index.d.ts +31 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +52 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/merge.d.ts +13 -0
- package/dist/strategies/merge.d.ts.map +1 -0
- package/dist/strategies/merge.js +60 -0
- package/dist/strategies/merge.js.map +1 -0
- package/dist/strategies/strategy.interface.d.ts +31 -0
- package/dist/strategies/strategy.interface.d.ts.map +1 -0
- package/dist/strategies/strategy.interface.js +2 -0
- package/dist/strategies/strategy.interface.js.map +1 -0
- package/dist/transaction/backup.d.ts +38 -0
- package/dist/transaction/backup.d.ts.map +1 -0
- package/dist/transaction/backup.js +97 -0
- package/dist/transaction/backup.js.map +1 -0
- package/dist/transaction/index.d.ts +4 -0
- package/dist/transaction/index.d.ts.map +1 -0
- package/dist/transaction/index.js +3 -0
- package/dist/transaction/index.js.map +1 -0
- package/dist/transaction/transaction.d.ts +34 -0
- package/dist/transaction/transaction.d.ts.map +1 -0
- package/dist/transaction/transaction.js +68 -0
- package/dist/transaction/transaction.js.map +1 -0
- package/dist/utils/file-operations.d.ts +29 -0
- package/dist/utils/file-operations.d.ts.map +1 -0
- package/dist/utils/file-operations.js +84 -0
- package/dist/utils/file-operations.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/json-utils.d.ts +22 -0
- package/dist/utils/json-utils.d.ts.map +1 -0
- package/dist/utils/json-utils.js +57 -0
- package/dist/utils/json-utils.js.map +1 -0
- package/dist/utils/path-utils.d.ts +21 -0
- package/dist/utils/path-utils.d.ts.map +1 -0
- package/dist/utils/path-utils.js +35 -0
- package/dist/utils/path-utils.js.map +1 -0
- package/eslint-plugin-code-organization/README.md +149 -0
- package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/eslint-plugin-code-organization/index.js +23 -0
- package/eslint-plugin-code-organization/package.json +10 -0
- package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
- package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
- package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
- package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
- package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
- package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
- package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
- package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
- package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
- package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
- package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
- package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
- package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
- package/expo/copy-overwrite/.easignore.extra +2 -0
- package/expo/copy-overwrite/.mcp.json +33 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
- package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
- package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
- package/expo/copy-overwrite/eslint.config.mjs +560 -0
- package/expo/copy-overwrite/lighthouserc.js +194 -0
- package/expo/create-only/lighthouserc-config.json +28 -0
- package/expo/merge/package.json +132 -0
- package/lisa.sh +35 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
- package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
- package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
- package/nestjs/merge/package.json +75 -0
- package/package.json +124 -0
- package/typescript/copy-contents/.husky/commit-msg +91 -0
- package/typescript/copy-contents/.husky/pre-commit +96 -0
- package/typescript/copy-contents/.husky/pre-push +211 -0
- package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
- package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
- package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
- package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
- package/typescript/copy-overwrite/.github/README.md +455 -0
- package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
- package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
- package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
- package/typescript/copy-overwrite/.github/k6/README.md +386 -0
- package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
- package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
- package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
- package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
- package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
- package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
- package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
- package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
- package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
- package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
- package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
- package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
- package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
- package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
- package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
- package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
- package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
- package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
- package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
- package/typescript/copy-overwrite/.gitleaksignore +28 -0
- package/typescript/copy-overwrite/.nvmrc +1 -0
- package/typescript/copy-overwrite/.prettierignore +23 -0
- package/typescript/copy-overwrite/.prettierrc.json +22 -0
- package/typescript/copy-overwrite/.versionrc +42 -0
- package/typescript/copy-overwrite/.yamllint +20 -0
- package/typescript/copy-overwrite/commitlint.config.js +11 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
- package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
- package/typescript/copy-overwrite/eslint.config.mjs +390 -0
- package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
- package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
- package/typescript/github-rulesets/base.json +106 -0
- package/typescript/merge/.claude/settings.json +28 -0
- package/typescript/merge/package.json +71 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: expo-env-config
|
|
3
|
+
description: This skill should be used when creating, modifying, or accessing environment variables in this Expo/React Native codebase. It enforces type-safe, validated environment configuration using Zod schemas. Use this skill when adding new environment variables, setting up env validation, or writing code that reads from process.env.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Expo Environment Configuration
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
This skill enforces type-safe, validated environment variable management for Expo/React Native using Zod schemas. Environment variables are validated at build time and provide full TypeScript inference, regardless of their source (`.env` files, EAS Build secrets, CI/CD pipelines, or command-line exports).
|
|
11
|
+
|
|
12
|
+
## Why This Pattern?
|
|
13
|
+
|
|
14
|
+
Unlike NestJS's `@nestjs/config`, Expo has no official type-safe env solution. The Zod validation pattern provides:
|
|
15
|
+
|
|
16
|
+
1. **Type safety** - Full TypeScript inference via `z.infer<typeof schema>`
|
|
17
|
+
2. **Build-time validation** - Fails fast with clear error messages before deployment
|
|
18
|
+
3. **Source agnostic** - Works with `.env`, EAS secrets, CI variables
|
|
19
|
+
4. **Testing support** - Easy mocking via module aliases
|
|
20
|
+
|
|
21
|
+
## Core Pattern
|
|
22
|
+
|
|
23
|
+
### Environment Schema (`src/lib/env.ts`)
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Environment variable schema with Zod validation.
|
|
30
|
+
* Variables are validated at module load time.
|
|
31
|
+
*/
|
|
32
|
+
const envSchema = z.object({
|
|
33
|
+
// Required variables
|
|
34
|
+
EXPO_PUBLIC_API_URL: z.string().url(),
|
|
35
|
+
EXPO_PUBLIC_APP_ENV: z.enum(["development", "staging", "production"]),
|
|
36
|
+
|
|
37
|
+
// Optional variables with defaults
|
|
38
|
+
EXPO_PUBLIC_SENTRY_DSN: z.string().optional(),
|
|
39
|
+
EXPO_PUBLIC_FEATURE_FLAG: z
|
|
40
|
+
.string()
|
|
41
|
+
.transform(v => v === "true")
|
|
42
|
+
.default("false"),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validated environment configuration.
|
|
47
|
+
* Throws at module load if validation fails.
|
|
48
|
+
*/
|
|
49
|
+
export const env = envSchema.parse(process.env);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Type-safe environment configuration.
|
|
53
|
+
*/
|
|
54
|
+
export type Env = z.infer<typeof envSchema>;
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Usage in Components/Hooks
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { env } from "@/lib/env";
|
|
61
|
+
|
|
62
|
+
// Full autocomplete and type safety
|
|
63
|
+
const apiUrl = env.EXPO_PUBLIC_API_URL; // string
|
|
64
|
+
const isDev = env.EXPO_PUBLIC_APP_ENV === "development"; // boolean
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Build-Time Validation (`app.config.ts`)
|
|
68
|
+
|
|
69
|
+
For variables needed during the build process, validate in `app.config.ts`:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
// app.config.ts
|
|
73
|
+
const { z } = require("zod");
|
|
74
|
+
|
|
75
|
+
const buildEnvSchema = z.object({
|
|
76
|
+
EXPO_PUBLIC_API_URL: z.string().url(),
|
|
77
|
+
EXPO_PUBLIC_APP_ENV: z.enum(["development", "staging", "production"]),
|
|
78
|
+
// Build-only secrets (not exposed to client)
|
|
79
|
+
SENTRY_AUTH_TOKEN: z.string().optional(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Throws during `eas build` if invalid
|
|
83
|
+
const env = buildEnvSchema.parse(process.env);
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
name: "MyApp",
|
|
87
|
+
slug: "my-app",
|
|
88
|
+
extra: {
|
|
89
|
+
apiUrl: env.EXPO_PUBLIC_API_URL,
|
|
90
|
+
appEnv: env.EXPO_PUBLIC_APP_ENV,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Variable Sources
|
|
96
|
+
|
|
97
|
+
Environment variables arrive in `process.env` from multiple sources:
|
|
98
|
+
|
|
99
|
+
| Source | When Available | How Set |
|
|
100
|
+
|--------|----------------|---------|
|
|
101
|
+
| `.env.local` | Local dev | Expo CLI auto-loads |
|
|
102
|
+
| `.env.development` | Local dev | Copied to `.env.local` via npm script |
|
|
103
|
+
| `eas.json` env | EAS Build | `build.production.env` section |
|
|
104
|
+
| EAS Secrets | EAS Build | `eas secret:create` |
|
|
105
|
+
| CI Variables | CI builds | GitHub Actions / GitLab CI settings |
|
|
106
|
+
|
|
107
|
+
The Zod pattern validates `process.env` directly - it doesn't care how variables got there.
|
|
108
|
+
|
|
109
|
+
## Testing Pattern
|
|
110
|
+
|
|
111
|
+
### Jest Setup (`jest.setup.ts`)
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Mock the env module for all tests
|
|
115
|
+
jest.mock("@/lib/env", () => ({
|
|
116
|
+
env: {
|
|
117
|
+
EXPO_PUBLIC_API_URL: "https://test.example.com",
|
|
118
|
+
EXPO_PUBLIC_APP_ENV: "development",
|
|
119
|
+
EXPO_PUBLIC_SENTRY_DSN: undefined,
|
|
120
|
+
EXPO_PUBLIC_FEATURE_FLAG: false,
|
|
121
|
+
},
|
|
122
|
+
}));
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Override in Specific Tests
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { env } from "@/lib/env";
|
|
129
|
+
|
|
130
|
+
jest.mock("@/lib/env");
|
|
131
|
+
|
|
132
|
+
describe("ProductionFeature", () => {
|
|
133
|
+
beforeEach(() => {
|
|
134
|
+
(env as jest.Mocked<typeof env>).EXPO_PUBLIC_APP_ENV = "production";
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should behave differently in production", () => {
|
|
138
|
+
// Test production-specific behavior
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## ESLint Enforcement
|
|
144
|
+
|
|
145
|
+
This pattern is enforced by ESLint's `no-restricted-syntax` rule in `eslint.config.mjs`:
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
"no-restricted-syntax": [
|
|
149
|
+
"error",
|
|
150
|
+
{
|
|
151
|
+
selector: "MemberExpression[object.name='process'][property.name='env']",
|
|
152
|
+
message: "Direct process.env access is forbidden. Import { env } from '@/lib/env' instead.",
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Exceptions** (files allowed to use `process.env`):
|
|
158
|
+
- `lib/env.ts` - The env validation module itself
|
|
159
|
+
- `app.config.ts` - Expo build config
|
|
160
|
+
- `codegen.ts` - GraphQL codegen config
|
|
161
|
+
- `playwright.config.ts` - E2E test config
|
|
162
|
+
- `lighthouserc.js` - Lighthouse CI config
|
|
163
|
+
|
|
164
|
+
## Core Rules
|
|
165
|
+
|
|
166
|
+
### 1. Always Prefix with EXPO_PUBLIC_
|
|
167
|
+
|
|
168
|
+
Variables without this prefix are not available in client code:
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// CORRECT - available in client
|
|
172
|
+
EXPO_PUBLIC_API_URL=https://api.example.com
|
|
173
|
+
|
|
174
|
+
// INCORRECT - only available at build time
|
|
175
|
+
API_URL=https://api.example.com
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 2. Never Access process.env Directly
|
|
179
|
+
|
|
180
|
+
Always use the validated `env` object:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// CORRECT - type-safe, validated
|
|
184
|
+
import { env } from "@/lib/env";
|
|
185
|
+
const url = env.EXPO_PUBLIC_API_URL;
|
|
186
|
+
|
|
187
|
+
// INCORRECT - untyped, unvalidated
|
|
188
|
+
const url = process.env.EXPO_PUBLIC_API_URL;
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 3. Validate Early, Fail Fast
|
|
192
|
+
|
|
193
|
+
Validation happens at module load. If a required variable is missing, the app fails immediately with a clear error rather than at runtime.
|
|
194
|
+
|
|
195
|
+
### 4. Use Transforms for Non-String Types
|
|
196
|
+
|
|
197
|
+
Environment variables are always strings. Use Zod transforms:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const envSchema = z.object({
|
|
201
|
+
// Boolean from string
|
|
202
|
+
EXPO_PUBLIC_DEBUG: z
|
|
203
|
+
.string()
|
|
204
|
+
.transform(v => v === "true")
|
|
205
|
+
.default("false"),
|
|
206
|
+
|
|
207
|
+
// Number from string
|
|
208
|
+
EXPO_PUBLIC_TIMEOUT_MS: z
|
|
209
|
+
.string()
|
|
210
|
+
.transform(v => parseInt(v, 10))
|
|
211
|
+
.default("5000"),
|
|
212
|
+
|
|
213
|
+
// Array from comma-separated string
|
|
214
|
+
EXPO_PUBLIC_ALLOWED_HOSTS: z
|
|
215
|
+
.string()
|
|
216
|
+
.transform(v => v.split(",").map(s => s.trim()))
|
|
217
|
+
.default(""),
|
|
218
|
+
});
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### 5. Separate Client vs Build-Only Variables
|
|
222
|
+
|
|
223
|
+
Keep sensitive build-time variables out of the client schema:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
// Client variables (embedded in JS bundle)
|
|
227
|
+
const clientSchema = z.object({
|
|
228
|
+
EXPO_PUBLIC_API_URL: z.string().url(),
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Build-only variables (NOT in bundle)
|
|
232
|
+
const buildSchema = z.object({
|
|
233
|
+
SENTRY_AUTH_TOKEN: z.string(),
|
|
234
|
+
EAS_PROJECT_ID: z.string(),
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## File Organization
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
src/
|
|
242
|
+
lib/
|
|
243
|
+
env.ts # Main env schema and exports
|
|
244
|
+
app.config.ts # Build-time validation (if needed)
|
|
245
|
+
.env.localhost # Local development (git-ignored)
|
|
246
|
+
.env.development # Development environment
|
|
247
|
+
.env.staging # Staging environment
|
|
248
|
+
.env.production # Production environment
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Detailed Reference
|
|
252
|
+
|
|
253
|
+
For comprehensive patterns, transforms, and testing examples:
|
|
254
|
+
|
|
255
|
+
- **[references/validation-patterns.md](references/validation-patterns.md)** - Advanced Zod schemas, transforms, and refinements
|
|
256
|
+
|
|
257
|
+
## Anti-Patterns to Avoid
|
|
258
|
+
|
|
259
|
+
### Never use process.env directly in components
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
// WRONG - untyped, could be undefined
|
|
263
|
+
const Component = () => {
|
|
264
|
+
const url = process.env.EXPO_PUBLIC_API_URL;
|
|
265
|
+
// url is string | undefined, no validation
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// CORRECT - validated and typed
|
|
269
|
+
import { env } from "@/lib/env";
|
|
270
|
+
const Component = () => {
|
|
271
|
+
const url = env.EXPO_PUBLIC_API_URL;
|
|
272
|
+
// url is string, guaranteed to be valid URL
|
|
273
|
+
};
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Never skip validation for "simple" variables
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
// WRONG - skipping validation
|
|
280
|
+
export const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3000";
|
|
281
|
+
|
|
282
|
+
// CORRECT - always validate
|
|
283
|
+
const envSchema = z.object({
|
|
284
|
+
EXPO_PUBLIC_API_URL: z.string().url().default("http://localhost:3000"),
|
|
285
|
+
});
|
|
286
|
+
export const { EXPO_PUBLIC_API_URL: API_URL } = envSchema.parse(process.env);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Never store secrets in EXPO_PUBLIC_ variables
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// WRONG - secrets exposed in client bundle
|
|
293
|
+
EXPO_PUBLIC_API_SECRET=super-secret-key
|
|
294
|
+
|
|
295
|
+
// CORRECT - secrets only at build time, passed securely
|
|
296
|
+
SENTRY_AUTH_TOKEN=secret # Build-only, not in bundle
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Validation Checklist
|
|
300
|
+
|
|
301
|
+
When adding or modifying environment variables:
|
|
302
|
+
|
|
303
|
+
- [ ] Variable is prefixed with `EXPO_PUBLIC_` (if needed in client code)
|
|
304
|
+
- [ ] Variable is added to the Zod schema in `src/lib/env.ts`
|
|
305
|
+
- [ ] Appropriate Zod type/transform is used (url, enum, boolean transform, etc.)
|
|
306
|
+
- [ ] Default value provided for optional variables
|
|
307
|
+
- [ ] Jest mock updated in `jest.setup.ts`
|
|
308
|
+
- [ ] Variable documented in `.env.example` or `.env.development`
|
|
309
|
+
- [ ] Sensitive values use EAS Secrets, not `.env` files
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
# Environment Validation Patterns
|
|
2
|
+
|
|
3
|
+
Advanced Zod patterns for environment variable validation in Expo/React Native.
|
|
4
|
+
|
|
5
|
+
## Complete Schema Example
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Transforms a string "true"/"false" to boolean.
|
|
12
|
+
*/
|
|
13
|
+
const booleanString = z
|
|
14
|
+
.string()
|
|
15
|
+
.transform(v => v.toLowerCase() === "true")
|
|
16
|
+
.default("false");
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Transforms a string to number with validation.
|
|
20
|
+
*/
|
|
21
|
+
const numberString = (defaultValue: number) =>
|
|
22
|
+
z
|
|
23
|
+
.string()
|
|
24
|
+
.transform(v => {
|
|
25
|
+
const parsed = parseInt(v, 10);
|
|
26
|
+
if (isNaN(parsed)) {
|
|
27
|
+
throw new Error(`Invalid number: ${v}`);
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
})
|
|
31
|
+
.default(String(defaultValue));
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Transforms comma-separated string to array.
|
|
35
|
+
*/
|
|
36
|
+
const arrayString = z
|
|
37
|
+
.string()
|
|
38
|
+
.transform(v => (v ? v.split(",").map(s => s.trim()) : []))
|
|
39
|
+
.default("");
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Complete environment schema.
|
|
43
|
+
*/
|
|
44
|
+
const envSchema = z.object({
|
|
45
|
+
// Required strings
|
|
46
|
+
EXPO_PUBLIC_API_URL: z.string().url(),
|
|
47
|
+
EXPO_PUBLIC_APP_ENV: z.enum(["development", "staging", "production"]),
|
|
48
|
+
|
|
49
|
+
// Optional strings
|
|
50
|
+
EXPO_PUBLIC_SENTRY_DSN: z.string().url().optional(),
|
|
51
|
+
EXPO_PUBLIC_ANALYTICS_ID: z.string().optional(),
|
|
52
|
+
|
|
53
|
+
// Booleans (from string)
|
|
54
|
+
EXPO_PUBLIC_DEBUG_MODE: booleanString,
|
|
55
|
+
EXPO_PUBLIC_FEATURE_NEW_UI: booleanString,
|
|
56
|
+
|
|
57
|
+
// Numbers (from string)
|
|
58
|
+
EXPO_PUBLIC_API_TIMEOUT_MS: numberString(5000),
|
|
59
|
+
EXPO_PUBLIC_MAX_RETRIES: numberString(3),
|
|
60
|
+
|
|
61
|
+
// Arrays (from comma-separated string)
|
|
62
|
+
EXPO_PUBLIC_ALLOWED_ORIGINS: arrayString,
|
|
63
|
+
|
|
64
|
+
// Conditional/derived
|
|
65
|
+
EXPO_PUBLIC_LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export const env = envSchema.parse(process.env);
|
|
69
|
+
export type Env = z.infer<typeof envSchema>;
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Common Transforms
|
|
73
|
+
|
|
74
|
+
### Boolean from String
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Handles "true", "TRUE", "True", etc.
|
|
78
|
+
const booleanEnv = z
|
|
79
|
+
.string()
|
|
80
|
+
.transform(v => v.toLowerCase() === "true")
|
|
81
|
+
.default("false");
|
|
82
|
+
|
|
83
|
+
// Usage
|
|
84
|
+
EXPO_PUBLIC_FEATURE_FLAG: booleanEnv,
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Number from String
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// With validation
|
|
91
|
+
const numberEnv = z.string().transform((v, ctx) => {
|
|
92
|
+
const parsed = parseInt(v, 10);
|
|
93
|
+
if (isNaN(parsed)) {
|
|
94
|
+
ctx.addIssue({
|
|
95
|
+
code: z.ZodIssueCode.custom,
|
|
96
|
+
message: "Must be a valid number",
|
|
97
|
+
});
|
|
98
|
+
return z.NEVER;
|
|
99
|
+
}
|
|
100
|
+
return parsed;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// With default
|
|
104
|
+
const numberWithDefault = (def: number) =>
|
|
105
|
+
z
|
|
106
|
+
.string()
|
|
107
|
+
.optional()
|
|
108
|
+
.transform(v => (v ? parseInt(v, 10) : def));
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Array from Comma-Separated String
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
const arrayEnv = z
|
|
115
|
+
.string()
|
|
116
|
+
.transform(v =>
|
|
117
|
+
v
|
|
118
|
+
.split(",")
|
|
119
|
+
.map(s => s.trim())
|
|
120
|
+
.filter(Boolean)
|
|
121
|
+
)
|
|
122
|
+
.default("");
|
|
123
|
+
|
|
124
|
+
// Example: "host1.com, host2.com" -> ["host1.com", "host2.com"]
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### URL with Protocol Validation
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
const httpUrl = z.string().url().refine(
|
|
131
|
+
url => url.startsWith("https://") || url.startsWith("http://"),
|
|
132
|
+
{ message: "Must be an HTTP(S) URL" }
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// HTTPS only in production
|
|
136
|
+
const secureUrl = z.string().url().refine(
|
|
137
|
+
url => {
|
|
138
|
+
if (process.env.EXPO_PUBLIC_APP_ENV === "production") {
|
|
139
|
+
return url.startsWith("https://");
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
},
|
|
143
|
+
{ message: "Production URLs must use HTTPS" }
|
|
144
|
+
);
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Environment-Specific Defaults
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
const getDefaultApiUrl = () => {
|
|
151
|
+
switch (process.env.EXPO_PUBLIC_APP_ENV) {
|
|
152
|
+
case "production":
|
|
153
|
+
return "https://api.example.com";
|
|
154
|
+
case "staging":
|
|
155
|
+
return "https://staging-api.example.com";
|
|
156
|
+
default:
|
|
157
|
+
return "http://localhost:3000";
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const envSchema = z.object({
|
|
162
|
+
EXPO_PUBLIC_API_URL: z.string().url().default(getDefaultApiUrl()),
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Refinements and Cross-Field Validation
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const envSchema = z
|
|
170
|
+
.object({
|
|
171
|
+
EXPO_PUBLIC_APP_ENV: z.enum(["development", "staging", "production"]),
|
|
172
|
+
EXPO_PUBLIC_DEBUG_MODE: booleanString,
|
|
173
|
+
EXPO_PUBLIC_SENTRY_DSN: z.string().url().optional(),
|
|
174
|
+
})
|
|
175
|
+
.refine(
|
|
176
|
+
data => {
|
|
177
|
+
// Sentry DSN required in production
|
|
178
|
+
if (data.EXPO_PUBLIC_APP_ENV === "production") {
|
|
179
|
+
return !!data.EXPO_PUBLIC_SENTRY_DSN;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
message: "EXPO_PUBLIC_SENTRY_DSN is required in production",
|
|
185
|
+
path: ["EXPO_PUBLIC_SENTRY_DSN"],
|
|
186
|
+
}
|
|
187
|
+
)
|
|
188
|
+
.refine(
|
|
189
|
+
data => {
|
|
190
|
+
// Debug mode forbidden in production
|
|
191
|
+
if (data.EXPO_PUBLIC_APP_ENV === "production") {
|
|
192
|
+
return !data.EXPO_PUBLIC_DEBUG_MODE;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
message: "Debug mode cannot be enabled in production",
|
|
198
|
+
path: ["EXPO_PUBLIC_DEBUG_MODE"],
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Error Handling
|
|
204
|
+
|
|
205
|
+
### Custom Error Messages
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
const envSchema = z.object({
|
|
209
|
+
EXPO_PUBLIC_API_URL: z
|
|
210
|
+
.string({
|
|
211
|
+
required_error: "API URL is required. Set EXPO_PUBLIC_API_URL in your .env file.",
|
|
212
|
+
})
|
|
213
|
+
.url({
|
|
214
|
+
message: "API URL must be a valid URL (e.g., https://api.example.com)",
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Graceful Error Formatting
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
const parseEnv = () => {
|
|
223
|
+
const result = envSchema.safeParse(process.env);
|
|
224
|
+
|
|
225
|
+
if (!result.success) {
|
|
226
|
+
const formatted = result.error.issues
|
|
227
|
+
.map(issue => ` - ${issue.path.join(".")}: ${issue.message}`)
|
|
228
|
+
.join("\n");
|
|
229
|
+
|
|
230
|
+
throw new Error(`Environment validation failed:\n${formatted}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return result.data;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export const env = parseEnv();
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## Testing Patterns
|
|
240
|
+
|
|
241
|
+
### Complete Mock Setup
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
// jest.setup.ts
|
|
245
|
+
const mockEnv = {
|
|
246
|
+
EXPO_PUBLIC_API_URL: "https://test.example.com",
|
|
247
|
+
EXPO_PUBLIC_APP_ENV: "development" as const,
|
|
248
|
+
EXPO_PUBLIC_DEBUG_MODE: false,
|
|
249
|
+
EXPO_PUBLIC_FEATURE_NEW_UI: true,
|
|
250
|
+
EXPO_PUBLIC_API_TIMEOUT_MS: 5000,
|
|
251
|
+
EXPO_PUBLIC_MAX_RETRIES: 3,
|
|
252
|
+
EXPO_PUBLIC_ALLOWED_ORIGINS: ["localhost"],
|
|
253
|
+
EXPO_PUBLIC_LOG_LEVEL: "info" as const,
|
|
254
|
+
EXPO_PUBLIC_SENTRY_DSN: undefined,
|
|
255
|
+
EXPO_PUBLIC_ANALYTICS_ID: undefined,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
jest.mock("@/lib/env", () => ({
|
|
259
|
+
env: mockEnv,
|
|
260
|
+
}));
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Override for Specific Tests
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
// In test file
|
|
267
|
+
import { env } from "@/lib/env";
|
|
268
|
+
|
|
269
|
+
jest.mock("@/lib/env");
|
|
270
|
+
|
|
271
|
+
const mockEnv = env as jest.Mocked<typeof env>;
|
|
272
|
+
|
|
273
|
+
describe("Feature with production behavior", () => {
|
|
274
|
+
beforeEach(() => {
|
|
275
|
+
mockEnv.EXPO_PUBLIC_APP_ENV = "production";
|
|
276
|
+
mockEnv.EXPO_PUBLIC_DEBUG_MODE = false;
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
afterEach(() => {
|
|
280
|
+
mockEnv.EXPO_PUBLIC_APP_ENV = "development";
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should disable debug features in production", () => {
|
|
284
|
+
// Test production-specific behavior
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Testing Validation Itself
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
// env.test.ts
|
|
293
|
+
describe("Environment Schema Validation", () => {
|
|
294
|
+
const originalEnv = process.env;
|
|
295
|
+
|
|
296
|
+
beforeEach(() => {
|
|
297
|
+
jest.resetModules();
|
|
298
|
+
process.env = { ...originalEnv };
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
afterAll(() => {
|
|
302
|
+
process.env = originalEnv;
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("should throw for missing required variables", () => {
|
|
306
|
+
delete process.env.EXPO_PUBLIC_API_URL;
|
|
307
|
+
|
|
308
|
+
expect(() => {
|
|
309
|
+
require("@/lib/env");
|
|
310
|
+
}).toThrow(/EXPO_PUBLIC_API_URL/);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("should throw for invalid URL format", () => {
|
|
314
|
+
process.env.EXPO_PUBLIC_API_URL = "not-a-url";
|
|
315
|
+
|
|
316
|
+
expect(() => {
|
|
317
|
+
require("@/lib/env");
|
|
318
|
+
}).toThrow(/url/i);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should use defaults for optional variables", () => {
|
|
322
|
+
process.env.EXPO_PUBLIC_API_URL = "https://api.example.com";
|
|
323
|
+
process.env.EXPO_PUBLIC_APP_ENV = "development";
|
|
324
|
+
|
|
325
|
+
const { env } = require("@/lib/env");
|
|
326
|
+
|
|
327
|
+
expect(env.EXPO_PUBLIC_DEBUG_MODE).toBe(false);
|
|
328
|
+
expect(env.EXPO_PUBLIC_API_TIMEOUT_MS).toBe(5000);
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## EAS Build Integration
|
|
334
|
+
|
|
335
|
+
### eas.json Configuration
|
|
336
|
+
|
|
337
|
+
```json
|
|
338
|
+
{
|
|
339
|
+
"build": {
|
|
340
|
+
"development": {
|
|
341
|
+
"env": {
|
|
342
|
+
"EXPO_PUBLIC_APP_ENV": "development",
|
|
343
|
+
"EXPO_PUBLIC_API_URL": "https://dev-api.example.com"
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
"staging": {
|
|
347
|
+
"env": {
|
|
348
|
+
"EXPO_PUBLIC_APP_ENV": "staging",
|
|
349
|
+
"EXPO_PUBLIC_API_URL": "https://staging-api.example.com"
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
"production": {
|
|
353
|
+
"env": {
|
|
354
|
+
"EXPO_PUBLIC_APP_ENV": "production",
|
|
355
|
+
"EXPO_PUBLIC_API_URL": "https://api.example.com"
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### EAS Secrets for Sensitive Values
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
# Set secrets via CLI (not in eas.json)
|
|
366
|
+
eas secret:create --name SENTRY_AUTH_TOKEN --value "your-token"
|
|
367
|
+
eas secret:create --name GOOGLE_SERVICES_JSON --type file --value ./google-services.json
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## app.config.ts Build-Time Validation
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
// app.config.ts
|
|
374
|
+
const { z } = require("zod");
|
|
375
|
+
|
|
376
|
+
// Validate at build time
|
|
377
|
+
const buildSchema = z.object({
|
|
378
|
+
EXPO_PUBLIC_API_URL: z.string().url(),
|
|
379
|
+
EXPO_PUBLIC_APP_ENV: z.enum(["development", "staging", "production"]),
|
|
380
|
+
// Build-only (not exposed to client)
|
|
381
|
+
SENTRY_AUTH_TOKEN: z.string().optional(),
|
|
382
|
+
EAS_PROJECT_ID: z.string().optional(),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const result = buildSchema.safeParse(process.env);
|
|
386
|
+
|
|
387
|
+
if (!result.success) {
|
|
388
|
+
console.error("Build environment validation failed:");
|
|
389
|
+
result.error.issues.forEach(issue => {
|
|
390
|
+
console.error(` - ${issue.path.join(".")}: ${issue.message}`);
|
|
391
|
+
});
|
|
392
|
+
throw new Error("Invalid build environment");
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const env = result.data;
|
|
396
|
+
|
|
397
|
+
module.exports = {
|
|
398
|
+
name: "MyApp",
|
|
399
|
+
slug: "my-app",
|
|
400
|
+
version: "1.0.0",
|
|
401
|
+
extra: {
|
|
402
|
+
eas: {
|
|
403
|
+
projectId: env.EAS_PROJECT_ID,
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
hooks: {
|
|
407
|
+
postPublish: [
|
|
408
|
+
{
|
|
409
|
+
file: "sentry-expo/upload-sourcemaps",
|
|
410
|
+
config: {
|
|
411
|
+
authToken: env.SENTRY_AUTH_TOKEN,
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
};
|
|
417
|
+
```
|