@everystack/cli 0.2.30 → 0.2.33
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 +1 -1
- package/src/cli/commands/console.ts +5 -5
- package/src/cli/commands/db.ts +9 -9
- package/src/cli/commands/update.ts +4 -26
- package/src/cli/config.ts +9 -0
- package/src/cli/discover.ts +8 -2
- package/src/env.js +31 -4
- package/src/env.ts +40 -4
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ import * as readline from 'node:readline';
|
|
|
15
15
|
import * as fs from 'node:fs';
|
|
16
16
|
import * as path from 'node:path';
|
|
17
17
|
import * as os from 'node:os';
|
|
18
|
-
import { resolveConfig, type CliConfig } from '../config.js';
|
|
18
|
+
import { resolveConfig, opsFunction, type CliConfig } from '../config.js';
|
|
19
19
|
import { invokeAction } from '../aws.js';
|
|
20
20
|
import { success, fail, info } from '../output.js';
|
|
21
21
|
import { formatResult } from '../utils/table.js';
|
|
@@ -189,7 +189,7 @@ export async function consoleCommand(flags: Record<string, string>): Promise<voi
|
|
|
189
189
|
process.exit(1);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
success(`Connected to ${stage} (${config.region}) — ${config
|
|
192
|
+
success(`Connected to ${stage} (${config.region}) — ${opsFunction(config)}`);
|
|
193
193
|
if (sandbox) {
|
|
194
194
|
info('Sandbox mode: all changes will be rolled back after each expression.');
|
|
195
195
|
}
|
|
@@ -219,7 +219,7 @@ export async function consoleCommand(flags: Record<string, string>): Promise<voi
|
|
|
219
219
|
if (cachedMeta) return cachedMeta;
|
|
220
220
|
cachedMeta = await invokeAction(
|
|
221
221
|
config.region,
|
|
222
|
-
config
|
|
222
|
+
opsFunction(config),
|
|
223
223
|
'console:meta',
|
|
224
224
|
{},
|
|
225
225
|
) as ConsoleMeta;
|
|
@@ -342,7 +342,7 @@ export async function consoleCommand(flags: Record<string, string>): Promise<voi
|
|
|
342
342
|
|
|
343
343
|
const loginResult: any = await invokeAction(
|
|
344
344
|
config.region,
|
|
345
|
-
config
|
|
345
|
+
opsFunction(config),
|
|
346
346
|
'console',
|
|
347
347
|
loginPayload,
|
|
348
348
|
);
|
|
@@ -423,7 +423,7 @@ export async function consoleCommand(flags: Record<string, string>): Promise<voi
|
|
|
423
423
|
|
|
424
424
|
const result: any = await invokeAction(
|
|
425
425
|
config.region,
|
|
426
|
-
config
|
|
426
|
+
opsFunction(config),
|
|
427
427
|
'console',
|
|
428
428
|
payload,
|
|
429
429
|
);
|
package/src/cli/commands/db.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* No HTTP endpoints, no shared secrets — IAM is the authorization layer.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { resolveConfig } from '../config.js';
|
|
8
|
+
import { resolveConfig, opsFunction } from '../config.js';
|
|
9
9
|
import { invokeAction } from '../aws.js';
|
|
10
10
|
import { step, success, fail, info } from '../output.js';
|
|
11
11
|
|
|
@@ -19,13 +19,13 @@ export async function dbMigrateCommand(flags: Record<string, string>): Promise<v
|
|
|
19
19
|
process.exit(1);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
info(`Region: ${config.region}, Function: ${config
|
|
22
|
+
info(`Region: ${config.region}, Function: ${opsFunction(config)}`);
|
|
23
23
|
step('Running migrations via Lambda invoke...');
|
|
24
24
|
|
|
25
25
|
try {
|
|
26
26
|
const result: any = await invokeAction(
|
|
27
27
|
config.region,
|
|
28
|
-
config
|
|
28
|
+
opsFunction(config),
|
|
29
29
|
'migrate',
|
|
30
30
|
{},
|
|
31
31
|
);
|
|
@@ -51,13 +51,13 @@ export async function dbSeedCommand(flags: Record<string, string>): Promise<void
|
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
info(`Region: ${config.region}, Function: ${config
|
|
54
|
+
info(`Region: ${config.region}, Function: ${opsFunction(config)}`);
|
|
55
55
|
step('Running seed via Lambda invoke...');
|
|
56
56
|
|
|
57
57
|
try {
|
|
58
58
|
const result: any = await invokeAction(
|
|
59
59
|
config.region,
|
|
60
|
-
config
|
|
60
|
+
opsFunction(config),
|
|
61
61
|
'seed',
|
|
62
62
|
{},
|
|
63
63
|
);
|
|
@@ -87,14 +87,14 @@ export async function dbResetCommand(flags: Record<string, string>): Promise<voi
|
|
|
87
87
|
process.exit(1);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
info(`Region: ${config.region}, Function: ${config
|
|
90
|
+
info(`Region: ${config.region}, Function: ${opsFunction(config)}`);
|
|
91
91
|
info('This will DROP every schema (auth, dist, logs, ops, public) and re-run migrations.');
|
|
92
92
|
step('Resetting database via Lambda invoke...');
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
95
|
const result: any = await invokeAction(
|
|
96
96
|
config.region,
|
|
97
|
-
config
|
|
97
|
+
opsFunction(config),
|
|
98
98
|
'db:reset',
|
|
99
99
|
{},
|
|
100
100
|
);
|
|
@@ -120,7 +120,7 @@ export async function dbPsqlCommand(flags: Record<string, string>): Promise<void
|
|
|
120
120
|
process.exit(1);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
-
info(`Region: ${config.region}, Function: ${config
|
|
123
|
+
info(`Region: ${config.region}, Function: ${opsFunction(config)}`);
|
|
124
124
|
|
|
125
125
|
// If -c flag is provided, execute SQL via Lambda and return results
|
|
126
126
|
if (flags.c) {
|
|
@@ -128,7 +128,7 @@ export async function dbPsqlCommand(flags: Record<string, string>): Promise<void
|
|
|
128
128
|
try {
|
|
129
129
|
const result: any = await invokeAction(
|
|
130
130
|
config.region,
|
|
131
|
-
config
|
|
131
|
+
opsFunction(config),
|
|
132
132
|
'db:query',
|
|
133
133
|
{ sql: flags.c },
|
|
134
134
|
);
|
|
@@ -6,7 +6,7 @@ import { pipeline } from 'node:stream/promises';
|
|
|
6
6
|
import { createWriteStream } from 'node:fs';
|
|
7
7
|
import { spawn } from 'node:child_process';
|
|
8
8
|
import { resolveConfig, type CliConfig } from '../config.js';
|
|
9
|
-
import { uploadToS3, headS3,
|
|
9
|
+
import { uploadToS3, headS3, invokeAction } from '../aws.js';
|
|
10
10
|
import { step, success, warn, fail, info } from '../output.js';
|
|
11
11
|
import { exportApp } from '../utils/export.js';
|
|
12
12
|
import { walkDirectory } from '../utils/walk.js';
|
|
@@ -486,7 +486,9 @@ export async function updateCommand(flags: UpdateFlags & Record<string, string>)
|
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
// Write manifest (no-cache so CDN always revalidates)
|
|
489
|
+
// Write new manifest (no-cache so CDN always revalidates).
|
|
490
|
+
// Old fingerprinted assets are intentionally kept — emails and other
|
|
491
|
+
// rendered content reference them forever. Storage cost is negligible.
|
|
490
492
|
const manifest = {
|
|
491
493
|
version: 1,
|
|
492
494
|
generatedAt: new Date().toISOString(),
|
|
@@ -501,30 +503,6 @@ export async function updateCommand(flags: UpdateFlags & Record<string, string>)
|
|
|
501
503
|
'no-cache',
|
|
502
504
|
);
|
|
503
505
|
|
|
504
|
-
// Prune stale fingerprinted files — scoped to configured prefixes only.
|
|
505
|
-
// Skip pruning entirely if any prefix is empty (would list the whole bucket).
|
|
506
|
-
const FINGERPRINT_RE = /-[0-9a-f]{8}\.[^.]+$/;
|
|
507
|
-
const prefixes = [...new Set(assetDirs.map(d => d.to))];
|
|
508
|
-
const allKeys: string[] = [];
|
|
509
|
-
const hasSafePrefix = prefixes.every(p => p.length > 0);
|
|
510
|
-
if (hasSafePrefix) {
|
|
511
|
-
for (const prefix of prefixes) {
|
|
512
|
-
const keys = await listS3(config.region, config.mediaBucket!, prefix);
|
|
513
|
-
allKeys.push(...keys);
|
|
514
|
-
}
|
|
515
|
-
} else {
|
|
516
|
-
warn('Skipping media prune: asset prefix is empty (would list entire bucket).');
|
|
517
|
-
}
|
|
518
|
-
const currentValues = new Set(Object.values(manifestFiles));
|
|
519
|
-
currentValues.add('media-manifest.json');
|
|
520
|
-
const staleKeys = allKeys.filter(key =>
|
|
521
|
-
FINGERPRINT_RE.test(key) && !currentValues.has(key)
|
|
522
|
-
);
|
|
523
|
-
if (staleKeys.length > 0) {
|
|
524
|
-
await deleteS3(config.region, config.mediaBucket!, staleKeys);
|
|
525
|
-
info(`Pruned ${staleKeys.length} stale media asset(s)`);
|
|
526
|
-
}
|
|
527
|
-
|
|
528
506
|
if (mediaUploaded > 0 || mediaSkipped > 0) {
|
|
529
507
|
success(`Media: ${mediaUploaded} new, ${mediaSkipped} unchanged (skipped)`);
|
|
530
508
|
}
|
package/src/cli/config.ts
CHANGED
|
@@ -15,6 +15,8 @@ export interface CliConfig {
|
|
|
15
15
|
baseUrl: string;
|
|
16
16
|
region: string;
|
|
17
17
|
apiFunctionName: string;
|
|
18
|
+
/** Dedicated operator function (db/console actions). Falls back to apiFunctionName. */
|
|
19
|
+
opsFunctionName?: string;
|
|
18
20
|
imageFunctionName?: string;
|
|
19
21
|
workerFunctionName?: string;
|
|
20
22
|
updatesBucket: string;
|
|
@@ -24,11 +26,17 @@ export interface CliConfig {
|
|
|
24
26
|
distributionId?: string;
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
/** Operator invoke target — dedicated ops function when deployed, else the API function. */
|
|
30
|
+
export function opsFunction(config: CliConfig): string {
|
|
31
|
+
return config.opsFunctionName ?? config.apiFunctionName;
|
|
32
|
+
}
|
|
33
|
+
|
|
27
34
|
interface SstOutputs {
|
|
28
35
|
routerUrl?: string;
|
|
29
36
|
apiUrl?: string;
|
|
30
37
|
imageUrl?: string;
|
|
31
38
|
apiFunctionName?: string;
|
|
39
|
+
opsFunctionName?: string;
|
|
32
40
|
imageFunctionName?: string;
|
|
33
41
|
workerFunctionName?: string;
|
|
34
42
|
updatesBucket?: string;
|
|
@@ -99,6 +107,7 @@ export async function resolveConfig(stage?: string): Promise<CliConfig> {
|
|
|
99
107
|
baseUrl: outputs.routerUrl,
|
|
100
108
|
region,
|
|
101
109
|
apiFunctionName: outputs.apiFunctionName,
|
|
110
|
+
opsFunctionName: outputs.opsFunctionName,
|
|
102
111
|
imageFunctionName: outputs.imageFunctionName,
|
|
103
112
|
workerFunctionName: outputs.workerFunctionName,
|
|
104
113
|
updatesBucket: outputs.updatesBucket,
|
package/src/cli/discover.ts
CHANGED
|
@@ -19,6 +19,7 @@ interface DiscoveredConfig {
|
|
|
19
19
|
baseUrl: string;
|
|
20
20
|
region: string;
|
|
21
21
|
apiFunctionName: string;
|
|
22
|
+
opsFunctionName?: string;
|
|
22
23
|
imageFunctionName?: string;
|
|
23
24
|
workerFunctionName?: string;
|
|
24
25
|
updatesBucket: string;
|
|
@@ -261,10 +262,11 @@ export async function discoverConfig(
|
|
|
261
262
|
const prefix = `${appName}-${stage}-`;
|
|
262
263
|
|
|
263
264
|
// Run CF function + Lambda + S3 discovery in parallel
|
|
264
|
-
const [cfResult, lambdaResult, bucketsResult, imageFn, workerFn] = await Promise.all([
|
|
265
|
+
const [cfResult, lambdaResult, bucketsResult, opsFn, imageFn, workerFn] = await Promise.all([
|
|
265
266
|
discoverCfFunction(region, prefix),
|
|
266
267
|
discoverLambda(region, prefix),
|
|
267
268
|
discoverBuckets(region, prefix),
|
|
269
|
+
discoverFunction(region, prefix, 'OpsFunction').catch(() => undefined),
|
|
268
270
|
discoverFunction(region, prefix, 'ImageFunction').catch(() => undefined),
|
|
269
271
|
discoverFunction(region, prefix, 'WorkerFunction')
|
|
270
272
|
.then(fn => fn || discoverFunction(region, prefix, 'JobsSubscriber'))
|
|
@@ -311,6 +313,7 @@ export async function discoverConfig(
|
|
|
311
313
|
baseUrl: lambdaResult.routerUrl || '',
|
|
312
314
|
region,
|
|
313
315
|
apiFunctionName: lambdaResult.functionName,
|
|
316
|
+
opsFunctionName: opsFn,
|
|
314
317
|
imageFunctionName: imageFn,
|
|
315
318
|
workerFunctionName: workerFn,
|
|
316
319
|
updatesBucket: bucketsResult.updatesBucket,
|
|
@@ -326,13 +329,14 @@ export async function discoverConfig(
|
|
|
326
329
|
// ---------------------------------------------------------------------------
|
|
327
330
|
|
|
328
331
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
329
|
-
const CACHE_VERSION =
|
|
332
|
+
const CACHE_VERSION = 3; // Bump when CachedOutputs schema changes to invalidate stale caches
|
|
330
333
|
|
|
331
334
|
interface CachedOutputs {
|
|
332
335
|
_cachedAt: number;
|
|
333
336
|
_version?: number;
|
|
334
337
|
routerUrl?: string;
|
|
335
338
|
apiFunctionName?: string;
|
|
339
|
+
opsFunctionName?: string;
|
|
336
340
|
imageFunctionName?: string;
|
|
337
341
|
workerFunctionName?: string;
|
|
338
342
|
updatesBucket?: string;
|
|
@@ -359,6 +363,7 @@ export async function getCachedConfig(stage: string): Promise<DiscoveredConfig |
|
|
|
359
363
|
baseUrl: cached.routerUrl || '',
|
|
360
364
|
region: process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1',
|
|
361
365
|
apiFunctionName: cached.apiFunctionName,
|
|
366
|
+
opsFunctionName: cached.opsFunctionName,
|
|
362
367
|
imageFunctionName: cached.imageFunctionName,
|
|
363
368
|
workerFunctionName: cached.workerFunctionName,
|
|
364
369
|
updatesBucket: cached.updatesBucket,
|
|
@@ -378,6 +383,7 @@ export async function setCachedConfig(stage: string, config: DiscoveredConfig):
|
|
|
378
383
|
_version: CACHE_VERSION,
|
|
379
384
|
routerUrl: config.baseUrl,
|
|
380
385
|
apiFunctionName: config.apiFunctionName,
|
|
386
|
+
opsFunctionName: config.opsFunctionName,
|
|
381
387
|
imageFunctionName: config.imageFunctionName,
|
|
382
388
|
workerFunctionName: config.workerFunctionName,
|
|
383
389
|
updatesBucket: config.updatesBucket,
|
package/src/env.js
CHANGED
|
@@ -120,14 +120,25 @@ function filterEnv(surface, config) {
|
|
|
120
120
|
* way to deliver env values to web client code is via EXPO_PUBLIC_* vars,
|
|
121
121
|
* which Metro inlines as literal strings at compile time.
|
|
122
122
|
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
123
|
+
* When `webKeys` is provided, only those keys get EXPO_PUBLIC_* vars.
|
|
124
|
+
* The inline path exists solely because web has no `extra` — so it must
|
|
125
|
+
* carry only web-tier values. Mobile-tier keys are delivered via `extra`
|
|
126
|
+
* on native and must never reach the Metro inline path: a mis-surfaced
|
|
127
|
+
* web export would otherwise bake them into public JS.
|
|
125
128
|
*/
|
|
126
|
-
function setExpoPublicVars(filtered) {
|
|
129
|
+
function setExpoPublicVars(filtered, webKeys) {
|
|
130
|
+
const allowed = webKeys ? new Set(webKeys) : null;
|
|
127
131
|
for (const [key, value] of Object.entries(filtered)) {
|
|
132
|
+
if (allowed && !allowed.has(key))
|
|
133
|
+
continue;
|
|
128
134
|
process.env[`EXPO_PUBLIC_${key}`] = value;
|
|
129
135
|
}
|
|
130
136
|
}
|
|
137
|
+
/** Stages where the permissive `local` tier set is acceptable. */
|
|
138
|
+
const NON_DEPLOYABLE_STAGES = new Set(['', 'development', 'dev', 'local', 'test']);
|
|
139
|
+
function isDeployableStage(stage) {
|
|
140
|
+
return !NON_DEPLOYABLE_STAGES.has((stage ?? '').toLowerCase());
|
|
141
|
+
}
|
|
131
142
|
/**
|
|
132
143
|
* Load and filter environment variables for the current build surface.
|
|
133
144
|
*
|
|
@@ -148,7 +159,23 @@ function setExpoPublicVars(filtered) {
|
|
|
148
159
|
function load(options) {
|
|
149
160
|
const config = loadEnvConfig(options?.path);
|
|
150
161
|
const surface = detectSurface();
|
|
162
|
+
// Fail closed: surface fell through to `local` (the most permissive tier
|
|
163
|
+
// set) while ENVIRONMENT names a deployable stage. Mixed signals mean a
|
|
164
|
+
// misconfigured build — refuse to bundle rather than risk shipping the
|
|
165
|
+
// mobile tier in a web export. EVERYSTACK_SURFACE=local is the explicit
|
|
166
|
+
// escape hatch for debugging.
|
|
167
|
+
if (surface === 'local' && isDeployableStage(process.env.ENVIRONMENT)) {
|
|
168
|
+
if (process.env.EVERYSTACK_SURFACE === 'local') {
|
|
169
|
+
console.warn(`[everystack/env] ENVIRONMENT=${process.env.ENVIRONMENT} but EVERYSTACK_SURFACE=local — `
|
|
170
|
+
+ 'bundling with the local tier set (mobile keys included). Debug builds only.');
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
throw new Error(`[everystack/env] Surface resolved to 'local' but ENVIRONMENT=${process.env.ENVIRONMENT}. `
|
|
174
|
+
+ "Deployable builds must run under 'everystack update' (EVERYSTACK_UPDATE) or EAS (EAS_BUILD). "
|
|
175
|
+
+ 'Refusing to bundle with the local tier set. Set EVERYSTACK_SURFACE=local to override for debugging.');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
151
178
|
const filtered = filterEnv(surface, config);
|
|
152
|
-
setExpoPublicVars(filtered);
|
|
179
|
+
setExpoPublicVars(filtered, config.web ?? []);
|
|
153
180
|
return { extra: filtered };
|
|
154
181
|
}
|
package/src/env.ts
CHANGED
|
@@ -148,15 +148,30 @@ export function filterEnv(surface: Surface, config: EnvConfig): Record<string, s
|
|
|
148
148
|
* way to deliver env values to web client code is via EXPO_PUBLIC_* vars,
|
|
149
149
|
* which Metro inlines as literal strings at compile time.
|
|
150
150
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
151
|
+
* When `webKeys` is provided, only those keys get EXPO_PUBLIC_* vars.
|
|
152
|
+
* The inline path exists solely because web has no `extra` — so it must
|
|
153
|
+
* carry only web-tier values. Mobile-tier keys are delivered via `extra`
|
|
154
|
+
* on native and must never reach the Metro inline path: a mis-surfaced
|
|
155
|
+
* web export would otherwise bake them into public JS.
|
|
153
156
|
*/
|
|
154
|
-
export function setExpoPublicVars(
|
|
157
|
+
export function setExpoPublicVars(
|
|
158
|
+
filtered: Record<string, string>,
|
|
159
|
+
webKeys?: string[]
|
|
160
|
+
): void {
|
|
161
|
+
const allowed = webKeys ? new Set(webKeys) : null;
|
|
155
162
|
for (const [key, value] of Object.entries(filtered)) {
|
|
163
|
+
if (allowed && !allowed.has(key)) continue;
|
|
156
164
|
process.env[`EXPO_PUBLIC_${key}`] = value;
|
|
157
165
|
}
|
|
158
166
|
}
|
|
159
167
|
|
|
168
|
+
/** Stages where the permissive `local` tier set is acceptable. */
|
|
169
|
+
const NON_DEPLOYABLE_STAGES = new Set(['', 'development', 'dev', 'local', 'test']);
|
|
170
|
+
|
|
171
|
+
function isDeployableStage(stage: string | undefined): boolean {
|
|
172
|
+
return !NON_DEPLOYABLE_STAGES.has((stage ?? '').toLowerCase());
|
|
173
|
+
}
|
|
174
|
+
|
|
160
175
|
/**
|
|
161
176
|
* Load and filter environment variables for the current build surface.
|
|
162
177
|
*
|
|
@@ -177,7 +192,28 @@ export function setExpoPublicVars(filtered: Record<string, string>): void {
|
|
|
177
192
|
export function load(options?: { path?: string }): { extra: Record<string, string> } {
|
|
178
193
|
const config = loadEnvConfig(options?.path);
|
|
179
194
|
const surface = detectSurface();
|
|
195
|
+
|
|
196
|
+
// Fail closed: surface fell through to `local` (the most permissive tier
|
|
197
|
+
// set) while ENVIRONMENT names a deployable stage. Mixed signals mean a
|
|
198
|
+
// misconfigured build — refuse to bundle rather than risk shipping the
|
|
199
|
+
// mobile tier in a web export. EVERYSTACK_SURFACE=local is the explicit
|
|
200
|
+
// escape hatch for debugging.
|
|
201
|
+
if (surface === 'local' && isDeployableStage(process.env.ENVIRONMENT)) {
|
|
202
|
+
if (process.env.EVERYSTACK_SURFACE === 'local') {
|
|
203
|
+
console.warn(
|
|
204
|
+
`[everystack/env] ENVIRONMENT=${process.env.ENVIRONMENT} but EVERYSTACK_SURFACE=local — `
|
|
205
|
+
+ 'bundling with the local tier set (mobile keys included). Debug builds only.'
|
|
206
|
+
);
|
|
207
|
+
} else {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`[everystack/env] Surface resolved to 'local' but ENVIRONMENT=${process.env.ENVIRONMENT}. `
|
|
210
|
+
+ "Deployable builds must run under 'everystack update' (EVERYSTACK_UPDATE) or EAS (EAS_BUILD). "
|
|
211
|
+
+ 'Refusing to bundle with the local tier set. Set EVERYSTACK_SURFACE=local to override for debugging.'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
180
216
|
const filtered = filterEnv(surface, config);
|
|
181
|
-
setExpoPublicVars(filtered);
|
|
217
|
+
setExpoPublicVars(filtered, config.web ?? []);
|
|
182
218
|
return { extra: filtered };
|
|
183
219
|
}
|