@everystack/cli 0.2.1 → 0.2.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@everystack/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "CLI and OTA updates for Expo apps on everystack",
5
5
  "license": "AGPL-3.0-only",
6
6
  "publishConfig": {
@@ -5,7 +5,7 @@ import zlib from 'node:zlib';
5
5
  import { pipeline } from 'node:stream/promises';
6
6
  import { createWriteStream } from 'node:fs';
7
7
  import { spawn } from 'node:child_process';
8
- import { resolveConfig } from '../config.js';
8
+ import { resolveConfig, type CliConfig } from '../config.js';
9
9
  import { uploadToS3, invokeAction } from '../aws.js';
10
10
  import { step, success, warn, fail, info } from '../output.js';
11
11
  import { exportApp, isDistStale } from '../utils/export.js';
@@ -20,6 +20,32 @@ export interface UpdateFlags {
20
20
  }
21
21
 
22
22
  export async function updateCommand(flags: UpdateFlags & Record<string, string>): Promise<void> {
23
+ // Propagate --stage to ENVIRONMENT before app.config.js evaluation.
24
+ // Without this, the manifest snapshots whatever the local shell has
25
+ // (often unset → 'development'), baking wrong API URLs and feature
26
+ // flags into the OTA update. Only set if not already supplied.
27
+ if (flags.stage) {
28
+ process.env.ENVIRONMENT ||= flags.stage;
29
+ }
30
+
31
+ // When --stage is provided, resolve deployed config early so HOST_URL
32
+ // is correct before app.config.js evaluation and expo export.
33
+ // loadSstOutputs() reads .sst/outputs.json (last deploy, any stage)
34
+ // which may be from a different stage entirely.
35
+ let resolvedConfig: CliConfig | undefined;
36
+ if (flags.stage) {
37
+ try {
38
+ step('Resolving deployed config...');
39
+ resolvedConfig = await resolveConfig(flags.stage);
40
+ if (resolvedConfig.baseUrl) {
41
+ process.env.HOST_URL ||= resolvedConfig.baseUrl;
42
+ }
43
+ success(`Region: ${resolvedConfig.region}, Function: ${resolvedConfig.apiFunctionName}`);
44
+ } catch {
45
+ // Stage may not be deployed yet — HOST_URL falls back to loadSstOutputs
46
+ }
47
+ }
48
+
23
49
  const branch = flags.branch;
24
50
  const channel = flags.channel || branch || flags.stage || 'production';
25
51
  const message = flags.message || '';
@@ -82,15 +108,19 @@ export async function updateCommand(flags: UpdateFlags & Record<string, string>)
82
108
  // Mobile platforms — direct S3 upload + IAM-authed Lambda invoke (mirrors web path).
83
109
  // No HTTP publish endpoint, no shared bearer token, no WAF body-size limits.
84
110
  if (mobilePlatforms.length > 0) {
85
- step('Resolving deployed config...');
86
- let config;
87
- try {
88
- config = await resolveConfig(flags.stage);
89
- } catch (err: any) {
90
- fail(err.message);
91
- process.exit(1);
111
+ let config: CliConfig;
112
+ if (resolvedConfig) {
113
+ config = resolvedConfig;
114
+ } else {
115
+ step('Resolving deployed config...');
116
+ try {
117
+ config = await resolveConfig(flags.stage);
118
+ } catch (err: any) {
119
+ fail(err.message);
120
+ process.exit(1);
121
+ }
122
+ success(`Region: ${config.region}, Function: ${config.apiFunctionName}`);
92
123
  }
93
- success(`Region: ${config.region}, Function: ${config.apiFunctionName}`);
94
124
 
95
125
  for (const platform of mobilePlatforms) {
96
126
  const platformMetadata = metadata?.fileMetadata?.[platform];
@@ -197,16 +227,20 @@ export async function updateCommand(flags: UpdateFlags & Record<string, string>)
197
227
  if (!(await fileExists(routesJson))) {
198
228
  warn('Skipping web (no server export found — ensure app.json has "output": "server")');
199
229
  } else {
200
- // Resolve SST config for direct AWS access
201
- step('Resolving deployed config...');
202
- let config;
203
- try {
204
- config = await resolveConfig(flags.stage);
205
- } catch (err: any) {
206
- fail(err.message);
207
- process.exit(1);
230
+ // Resolve SST config for direct AWS access (reuse early resolution if available)
231
+ let config: CliConfig;
232
+ if (resolvedConfig) {
233
+ config = resolvedConfig;
234
+ } else {
235
+ step('Resolving deployed config...');
236
+ try {
237
+ config = await resolveConfig(flags.stage);
238
+ } catch (err: any) {
239
+ fail(err.message);
240
+ process.exit(1);
241
+ }
242
+ success(`Region: ${config.region}, Function: ${config.apiFunctionName}`);
208
243
  }
209
- success(`Region: ${config.region}, Function: ${config.apiFunctionName}`);
210
244
 
211
245
  // 1. Create tar+brotli archive of server bundle
212
246
  step('Creating server bundle archive...');
package/src/cli/index.ts CHANGED
@@ -38,10 +38,29 @@ function parseFlags(args: string[]): Record<string, string> {
38
38
  * Auto-detect HOST_URL from SST outputs.
39
39
  * SST writes .sst/outputs.json after every deploy with { routerUrl, apiUrl, ... }.
40
40
  * If HOST_URL isn't already set, use the routerUrl from the last deploy.
41
+ *
42
+ * When a stage is specified, checks the stage-specific cached config first.
43
+ * .sst/outputs.json reflects the last deploy (any stage) and may be wrong
44
+ * when publishing to a different stage.
41
45
  */
42
- async function loadSstOutputs(): Promise<void> {
46
+ async function loadSstOutputs(stage?: string): Promise<void> {
43
47
  if (process.env.HOST_URL) return;
44
48
 
49
+ // When --stage is provided, try stage-specific cache first
50
+ if (stage) {
51
+ const stagePath = path.resolve('.sst', `outputs.${stage}.json`);
52
+ try {
53
+ const raw = await fs.readFile(stagePath, 'utf8');
54
+ const cached = JSON.parse(raw);
55
+ if (cached.routerUrl) {
56
+ process.env.HOST_URL = cached.routerUrl;
57
+ return;
58
+ }
59
+ } catch {
60
+ // No cached config for this stage — fall through to default
61
+ }
62
+ }
63
+
45
64
  const outputsPath = path.resolve('.sst', 'outputs.json');
46
65
  try {
47
66
  const raw = await fs.readFile(outputsPath, 'utf8');
@@ -63,7 +82,7 @@ async function main() {
63
82
  const flags = parseFlags(args.slice(1));
64
83
 
65
84
  // Auto-detect deployed URL from SST outputs
66
- await loadSstOutputs();
85
+ await loadSstOutputs(flags.stage);
67
86
 
68
87
  switch (command) {
69
88
  case 'update':