@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.
Files changed (2) hide show
  1. package/package.json +3 -2
  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.21",
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.ts"
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
+ }