@everystack/cli 0.2.21 → 0.2.22
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/package.json +3 -2
- package/src/env.js +154 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@everystack/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.22",
|
|
4
4
|
"description": "CLI and OTA updates for Expo apps on everystack",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"publishConfig": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"./env": {
|
|
35
35
|
"types": "./src/env.ts",
|
|
36
|
-
"default": "./src/env.
|
|
36
|
+
"default": "./src/env.js"
|
|
37
37
|
},
|
|
38
38
|
"./sst": {
|
|
39
39
|
"types": "./src/sst.ts",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"scripts": {
|
|
47
47
|
"test": "jest",
|
|
48
48
|
"build": "tsc --build",
|
|
49
|
+
"prepublishOnly": "tsc --module commonjs --moduleResolution node --target ES2022 --esModuleInterop --skipLibCheck --declaration false --outDir src src/env.ts",
|
|
49
50
|
"lint": "tsc --noEmit"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
package/src/env.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @everystack/cli/env — Deny-by-default environment variable filtering.
|
|
4
|
+
*
|
|
5
|
+
* One import in app.config.js. Auto-discovers env.config.js.
|
|
6
|
+
* Detects build surface, filters secrets by tier, sets EXPO_PUBLIC_* vars.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const { extra } = require('@everystack/cli/env').load();
|
|
10
|
+
* module.exports = { expo: { extra } };
|
|
11
|
+
*
|
|
12
|
+
* With explicit config path:
|
|
13
|
+
* const { extra } = require('@everystack/cli/env').load({ path: './env.config' });
|
|
14
|
+
*
|
|
15
|
+
* Multiple stages. Four buckets. One gate. One file.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.detectSurface = detectSurface;
|
|
24
|
+
exports.loadEnvConfig = loadEnvConfig;
|
|
25
|
+
exports.filterEnv = filterEnv;
|
|
26
|
+
exports.setExpoPublicVars = setExpoPublicVars;
|
|
27
|
+
exports.load = load;
|
|
28
|
+
const path_1 = __importDefault(require("path"));
|
|
29
|
+
const fs_1 = __importDefault(require("fs"));
|
|
30
|
+
/**
|
|
31
|
+
* Detect the current build surface from environment signals.
|
|
32
|
+
*
|
|
33
|
+
* EAS_BUILD / EAS_BUILD_PLATFORM -> native (EAS native build)
|
|
34
|
+
* EAS_UPDATE -> native (EAS OTA update)
|
|
35
|
+
* EVERYSTACK_UPDATE -> web (everystack update)
|
|
36
|
+
* none of the above -> local (pnpm dev)
|
|
37
|
+
*/
|
|
38
|
+
function detectSurface() {
|
|
39
|
+
if (process.env.EAS_BUILD || process.env.EAS_BUILD_PLATFORM || process.env.EAS_UPDATE) {
|
|
40
|
+
return 'native';
|
|
41
|
+
}
|
|
42
|
+
if (process.env.EVERYSTACK_UPDATE) {
|
|
43
|
+
return 'web';
|
|
44
|
+
}
|
|
45
|
+
return 'local';
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Load env config from a file path or auto-discover env.config.js in cwd.
|
|
49
|
+
*/
|
|
50
|
+
function loadEnvConfig(configPath) {
|
|
51
|
+
const resolved = configPath
|
|
52
|
+
? path_1.default.resolve(configPath)
|
|
53
|
+
: findEnvConfig();
|
|
54
|
+
// Synchronous require — same pattern used throughout the CLI package.
|
|
55
|
+
// Works in Metro (CJS interop), tsx (SST), and Node CJS.
|
|
56
|
+
delete require.cache?.[resolved];
|
|
57
|
+
const mod = require(resolved);
|
|
58
|
+
return mod.default || mod;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Auto-discover env.config.js in the current working directory.
|
|
62
|
+
*/
|
|
63
|
+
function findEnvConfig() {
|
|
64
|
+
const name = 'env.config.js';
|
|
65
|
+
const fullPath = path_1.default.resolve(process.cwd(), name);
|
|
66
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
67
|
+
return fullPath;
|
|
68
|
+
}
|
|
69
|
+
throw new Error('No env.config.js found. Create one:\n\n'
|
|
70
|
+
+ ' module.exports = {\n'
|
|
71
|
+
+ ' server: [],\n'
|
|
72
|
+
+ ' build: [],\n'
|
|
73
|
+
+ ' mobile: [],\n'
|
|
74
|
+
+ ' web: [],\n'
|
|
75
|
+
+ ' };\n');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Filter process.env through tier allowlists based on the detected surface.
|
|
79
|
+
*
|
|
80
|
+
* Deny-by-default: undeclared keys are dropped.
|
|
81
|
+
* Server and build tiers are NEVER passed through, even if duplicated in other tiers.
|
|
82
|
+
*
|
|
83
|
+
* Surface behavior:
|
|
84
|
+
* local -> web + mobile tiers pass
|
|
85
|
+
* native -> web + mobile tiers pass
|
|
86
|
+
* web -> web tier only
|
|
87
|
+
*/
|
|
88
|
+
function filterEnv(surface, config) {
|
|
89
|
+
// Build the allowlist: web tier always passes
|
|
90
|
+
const allowed = new Set(config.web || []);
|
|
91
|
+
// Mobile tier: passes on native and local, blocked on web
|
|
92
|
+
if (surface !== 'web') {
|
|
93
|
+
for (const k of (config.mobile || [])) {
|
|
94
|
+
allowed.add(k);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// server and build tiers are NEVER added to the allowlist.
|
|
98
|
+
// Actively remove them in case a key appears in multiple tiers.
|
|
99
|
+
const blocked = new Set([
|
|
100
|
+
...(config.server || []),
|
|
101
|
+
...(config.build || []),
|
|
102
|
+
]);
|
|
103
|
+
for (const k of blocked) {
|
|
104
|
+
allowed.delete(k);
|
|
105
|
+
}
|
|
106
|
+
// Collect values for allowed keys from process.env
|
|
107
|
+
const result = {};
|
|
108
|
+
for (const key of allowed) {
|
|
109
|
+
if (process.env[key] !== undefined) {
|
|
110
|
+
result[key] = process.env[key];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Set EXPO_PUBLIC_* process.env vars for web delivery.
|
|
117
|
+
*
|
|
118
|
+
* Constants.expoConfig.extra does NOT work on web — Metro never sets
|
|
119
|
+
* APP_MANIFEST, so Constants.expoConfig is null in the browser. The only
|
|
120
|
+
* way to deliver env values to web client code is via EXPO_PUBLIC_* vars,
|
|
121
|
+
* which Metro inlines as literal strings at compile time.
|
|
122
|
+
*
|
|
123
|
+
* Sets EXPO_PUBLIC_* for ALL filtered keys so consumers can use either
|
|
124
|
+
* delivery path (extra on native, EXPO_PUBLIC_* on web).
|
|
125
|
+
*/
|
|
126
|
+
function setExpoPublicVars(filtered) {
|
|
127
|
+
for (const [key, value] of Object.entries(filtered)) {
|
|
128
|
+
process.env[`EXPO_PUBLIC_${key}`] = value;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Load and filter environment variables for the current build surface.
|
|
133
|
+
*
|
|
134
|
+
* Auto-discovers env.config.js in the current working directory,
|
|
135
|
+
* or accepts an explicit path. Detects the build surface, filters
|
|
136
|
+
* process.env through the tier allowlists, sets EXPO_PUBLIC_* vars,
|
|
137
|
+
* and returns the filtered object for expo.extra.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* // app.config.js — auto-discover env.config.js
|
|
141
|
+
* const { extra } = require('@everystack/cli/env').load();
|
|
142
|
+
* module.exports = { expo: { extra } };
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* // app.config.js — explicit path
|
|
146
|
+
* const { extra } = require('@everystack/cli/env').load({ path: './env.config' });
|
|
147
|
+
*/
|
|
148
|
+
function load(options) {
|
|
149
|
+
const config = loadEnvConfig(options?.path);
|
|
150
|
+
const surface = detectSurface();
|
|
151
|
+
const filtered = filterEnv(surface, config);
|
|
152
|
+
setExpoPublicVars(filtered);
|
|
153
|
+
return { extra: filtered };
|
|
154
|
+
}
|