-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathenv.js
236 lines (198 loc) · 8.1 KB
/
env.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
* Env file to load and validate env variables
* Be cautious; this file should not be imported into your source folder. TODO: Add an eslint rule to prevent this.
* We split the env variables into two parts:
* 1. Client variables: These variables are used in the client-side code (src folder).
* 2. Build-time variables: These variables are used in the build process (app.config.ts file).
* Import this file into the `app.config.ts` file to use environment variables during the build process. The client variables can then be passed to the client-side using the extra field in the `app.config.ts` file.
* To access the client environment variables in your `src` folder, you can import them from `@env`. For example: `import Env from '@env'`.
*/
/**
* 1st part: Import packages and Load your env variables
* we use dotenv to load the correct variables from the .env file based on the APP_ENV variable (default is development)
* APP_ENV is passed as an inline variable while executing the command, for example: APP_ENV=staging pnpm build:android
*/
const z = require('zod');
const packageJSON = require('./package.json');
const path = require('path');
const APP_ENV =
/** @type {z.infer<typeof clientEnvSchema>['APP_ENV']} */
(process.env.APP_ENV) ?? 'development';
const isEASBuild = process.env.EAS_BUILD === 'true';
const LOCAL_BUILD_SCRIPT_PATTERNS = [
'--local',
'eas-cli-local-build-plugin',
'expo export',
];
const isLocalBuild = LOCAL_BUILD_SCRIPT_PATTERNS.some((pattern) =>
process.env.npm_lifecycle_script?.includes(pattern)
);
const EXPO_RUN_COMMANDS = ['expo start', 'expo run'];
const ENVIRONMENT_DEPENDANT_SCRIPTS = [
...EXPO_RUN_COMMANDS,
'expo prebuild',
'eas build',
'eas-cli-local-build-plugin',
'expo export',
];
const scriptIsEnvironmentDependant = ENVIRONMENT_DEPENDANT_SCRIPTS.some(
(script) => process.env.npm_lifecycle_script?.includes(script)
);
// Check if the environment file has to be validated for the current running script and build method
const isBuilding = isEASBuild || isLocalBuild;
const isRunning = EXPO_RUN_COMMANDS.some((script) =>
process.env.npm_lifecycle_script?.includes(script)
);
const shouldValidateEnv =
(isBuilding && scriptIsEnvironmentDependant) || isRunning;
const easEnvironmentFileVariable = `ENVIRONMENT_FILE_${APP_ENV.toUpperCase()}`;
const easEnvironmentFilePath = process.env[easEnvironmentFileVariable];
const localEnvironmentFilePath = path.resolve(__dirname, `.env.${APP_ENV}`);
const envPath = isEASBuild ? easEnvironmentFilePath : localEnvironmentFilePath;
require('dotenv').config({
path: envPath,
});
/**
* 2nd part: Define some static variables for the app
* Such as: bundle id, package name, app name.
*
* You can add them to the .env file but we think it's better to keep them here as as we use prefix to generate this values based on the APP_ENV
* for example: if the APP_ENV is staging, the bundle id will be com.template.staging
*/
// TODO: Replace these values with your own
const BUNDLE_ID = 'com.template'; // ios bundle id
const PACKAGE = 'com.template'; // android package name
const NAME = 'template'; // app name
const EXPO_ACCOUNT_OWNER = 'rsdevs'; // expo account owner
const EAS_PROJECT_ID = '72fdf440-59f1-493d-96e3-4afad8d7a045'; // eas project id
const SCHEME = 'template'; // app scheme
/**
* We declare a function withEnvSuffix that will add a suffix to the variable name based on the APP_ENV
* Add a suffix to variable env based on APP_ENV
* @param {string} name
* @returns {string}
*/
const withEnvSuffix = (name) =>
APP_ENV === 'production' ? name : `${name}.${APP_ENV}`;
/**
* 2nd part: Define your env variables schema
* we use zod to define our env variables schema
*
* we split the env variables into two parts:
* 1. client: These variables are used in the client-side code (`src` folder).
* 2. buildTime: These variables are used in the build process (app.config.ts file). You can think of them as server-side variables.
*
* Main rules:
* 1. If you need your variable on the client-side, you should add it to the client schema; otherwise, you should add it to the buildTime schema.
* 2. Whenever you want to add a new variable, you should add it to the correct schema based on the previous rule, then you should add it to the corresponding object (_clientEnv or _buildTimeEnv).
*
* Note: `z.string()` means that the variable exists and can be an empty string, but not `undefined`.
* If you want to make the variable required, you should use `z.string().min(1)` instead.
* Read more about zod here: https://zod.dev/?id=strings
*
*/
const parseString = (/** @type {string | undefined} */ value) =>
value === '' ? undefined : value;
const parseNumber = (/** @type {string | undefined} */ value) =>
value ? Number(value) : undefined;
const parseBoolean = (/** @type {string | undefined} */ value) =>
value ? value === 'true' : undefined;
const clientEnvSchema = z.object({
APP_ENV: z.enum(['development', 'production', 'qa', 'staging']),
NAME: z.string(),
SCHEME: z.string(),
BUNDLE_ID: z.string(),
PACKAGE: z.string(),
VERSION: z.string(),
// ADD YOUR CLIENT ENV VARS HERE
API_URL: z.string(),
VAR_NUMBER: z.number(),
VAR_BOOL: z.boolean(),
});
const buildTimeEnvSchema = z.object({
EXPO_ACCOUNT_OWNER: z.string(),
EAS_PROJECT_ID: z.string(),
// ADD YOUR BUILD TIME ENV VARS HERE
SECRET_KEY: z.string(),
});
/**
* @type {Partial<z.infer<typeof clientEnvSchema>>}
*/
const _clientEnv = {
APP_ENV,
NAME,
SCHEME,
BUNDLE_ID: withEnvSuffix(BUNDLE_ID),
PACKAGE: withEnvSuffix(PACKAGE),
VERSION: packageJSON.version,
// ADD YOUR ENV VARS HERE TOO
API_URL: parseString(process.env.API_URL),
VAR_NUMBER: parseNumber(process.env.VAR_NUMBER),
VAR_BOOL: parseBoolean(process.env.VAR_BOOL),
};
/**
* @type {Record<keyof z.infer<typeof buildTimeEnvSchema> , unknown>}
*/
const _buildTimeEnv = {
EXPO_ACCOUNT_OWNER,
EAS_PROJECT_ID,
// ADD YOUR ENV VARS HERE TOO
SECRET_KEY: parseString(process.env.SECRET_KEY),
};
/**
* 3rd part: Merge and Validate your env variables
* We use zod to validate our env variables based on the schema we defined above
* If the validation fails we throw an error and log the error to the console with a detailed message about missed variables
* If the validation passes we export the merged and parsed env variables to be used in the app.config.ts file as well as a ClientEnv object to be used in the client-side code
**/
const _wholeEnv = {
..._clientEnv,
..._buildTimeEnv,
};
const wholeEnvSchema = buildTimeEnvSchema.merge(clientEnvSchema);
/**
* @type {z.infer<typeof wholeEnvSchema>}
*/
let Env;
/**
* @type {z.infer<typeof clientEnvSchema>}
*/
let ClientEnv;
if (shouldValidateEnv) {
const parsedWholeEnv = wholeEnvSchema.safeParse(_wholeEnv);
if (parsedWholeEnv.success === false) {
const envFile = isEASBuild ? easEnvironmentFileVariable : `.env.${APP_ENV}`;
const messages = [
'❌ Invalid environment variables:',
parsedWholeEnv.error.flatten().fieldErrors,
`\n❌ Missing variables in \x1b[1m\x1b[4m\x1b[31m${envFile}\x1b[0m file. Make sure all required variables are defined in the \x1b[1m\x1b[4m\x1b[31m${envFile}\x1b[0m file.`,
];
if (isLocalBuild) {
messages.push(
`\n💡 Tip: If you recently updated the \x1b[1m\x1b[4m\x1b[31m${envFile}\x1b[0m file and the error still persists, try restarting the server with the -cc flag to clear the cache.`
);
}
if (isEASBuild) {
messages.push(
`\n☁️ For \x1b[1m\x1b[32mEAS Build\x1b[0m deployments, ensure the secret\x1b[1m\x1b[4m\x1b[31m${easEnvironmentFileVariable} \x1b[0m is defined in Project Secrets and has the proper environment file attached.`
);
}
console.error(...messages);
throw new Error(
'Invalid environment variables, Check terminal for more details '
);
}
Env = parsedWholeEnv.data;
ClientEnv = clientEnvSchema.parse(_clientEnv);
} else {
// Don't worry about TypeScript here; if we don't need to validate the env variables is because we aren't using them
//@ts-ignore
Env = _wholeEnv;
//@ts-ignore
ClientEnv = _clientEnv;
}
module.exports = {
Env,
ClientEnv,
withEnvSuffix,
};