@ramonclaudio/vexpo 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -3,69 +3,74 @@
3
3
  [![npm](https://img.shields.io/npm/v/@ramonclaudio/vexpo)](https://www.npmjs.com/package/@ramonclaudio/vexpo)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- Operational CLI for [vexpo](https://github.com/ramonclaudio/vexpo) projects: Expo + Convex + Better Auth + Resend, end-to-end iOS. Provisions the stack, validates every credential, keeps env values in sync across `.env.local` / Convex env / EAS env, and covers the last App Store Connect mile to a first ship: TestFlight delivery plus the privacy and accessibility labels Apple requires before review.
6
+ The setup CLI for [vexpo](https://github.com/ramonclaudio/vexpo) projects (Expo + Convex + Better Auth + Resend, end-to-end iOS). It creates or links your Convex deployment, signs and rotates the Apple keys (the P8 dance), and keeps your env in sync everywhere. It covers the App Store Connect last mile to a first ship. EAS does the heavy lifting (builds, updates, submission), vexpo covers the setup around it.
7
7
 
8
- Scaffolded by [`create-vexpo`](https://www.npmjs.com/package/@ramonclaudio/create-vexpo) into your project's devDependencies. Invoke via `npx vexpo`.
9
-
10
- ## Design rule: don't reinvent EAS
11
-
12
- If `eas <subcommand>` is the canonical answer, the recipe is `npx eas <subcommand>`, not `vexpo`. This CLI only surfaces what `eas-cli` doesn't do: setup orchestration, cross-source drift detection, Apple SIWA work, and the last App Store Connect mile to a first ship. Scope test for any command: does it help an empty directory become a first shipped, authenticated, backed iOS app? Post-launch ops (customer reviews, IAP sandbox testing, release management) are out, that's `eas` and App Store Connect once you have users. Wrapping every `eas` command would add no value over `eas-cli` itself, expand the maintenance surface, and signal a lack of trust in the platform.
8
+ Scaffolded by [`create-vexpo`](https://www.npmjs.com/package/@ramonclaudio/create-vexpo) into your devDependencies. Run it with `npx vexpo`.
13
9
 
14
10
  ## Setup
15
11
 
16
12
  ```
17
- vexpo lite Convex + Better Auth only, simulator-ready (60s)
18
- vexpo lite --new same + Convex signup walkthrough for first-time users
13
+ vexpo lite Convex + Better Auth only, simulator-ready (~60s)
14
+ vexpo lite --new same + Convex signup walkthrough for first-timers
19
15
  vexpo full full provisioning (Convex + Better Auth + Resend + Apple + EAS init + rebrand)
20
16
  vexpo full --new same + walks Apple/Convex/Expo/Resend signups
21
17
  vexpo full --skip-rebrand full setup, skip the rebrand wizard
22
18
 
23
- vexpo doctor Cross-source drift detection
24
- vexpo doctor --json Machine-readable output
25
- vexpo doctor --strict Exit non-zero on any warn
26
-
27
- vexpo accounts Walk Apple/Expo/Convex/Resend signups (standalone)
28
- vexpo rebrand Replace template defaults with your identity
29
- vexpo review-account Seed the App Review demo account on Convex
30
- vexpo convex Provision Convex deployment + write .env.local
31
- vexpo better-auth Set BETTER_AUTH_SECRET, SITE_URL, APP_NAME on Convex
32
- vexpo resend Provision Resend sending key + webhook, flip REQUIRE_EMAIL_VERIFICATION=true
33
- vexpo env push .env.local + .env.prod Convex + EAS (one pass)
19
+ vexpo doctor cross-source drift detection
20
+ vexpo doctor --json machine-readable output
21
+ vexpo doctor --strict exit non-zero on any warn
22
+
23
+ vexpo accounts walk Apple/Expo/Convex/Resend signups (standalone)
24
+ vexpo rebrand replace template defaults with your identity
25
+ vexpo review-account seed the App Review demo account on Convex
26
+ vexpo convex provision or connect a Convex deployment
27
+ vexpo better-auth set SITE_URL, BETTER_AUTH_SECRET, APP_NAME on Convex
28
+ vexpo resend provision Resend sending key + webhook, write to Convex env
29
+ vexpo env push push .env.local + .env.prod to Convex + EAS env
30
+ vexpo env convex-key sync Convex deploy key + selector to EAS (post-migration fix)
31
+ vexpo adopt finish a project created by `eas integrations:convex:connect`
32
+ vexpo convex:migrate copy server-side Convex env from another deployment
33
+ vexpo asc:connect link the EAS project to its ASC app (wraps `eas integrations:asc:connect`)
34
34
  ```
35
35
 
36
36
  ## Apple
37
37
 
38
38
  ```
39
- vexpo apple asc-key Validate ASC API key against /v1/apps
40
- vexpo apple credentials Wraps `eas credentials:configure-build` with cached ASC env vars (skips Apple Developer login prompt)
41
- vexpo apple services-id Detect SIWA Services ID + attach APPLE_ID_AUTH capability
42
- vexpo apple jwt Sign SIWA ES256 client_secret JWT (180-day expiry)
43
- vexpo apple jwt --rotate Re-sign the JWT only
44
- vexpo apple eas-rotation-secrets Push the 5 EAS production secrets the JWT cron needs
39
+ vexpo apple asc-key validate an ASC API key against /v1/apps
40
+ vexpo apple asc-key --revalidate re-check the cached key without re-prompting
41
+ vexpo apple credentials wrap `eas credentials:configure-build` with the cached ASC key
42
+ vexpo apple services-id detect SIWA Services ID + attach APPLE_ID_AUTH capability
43
+ vexpo apple jwt sign the SIWA ES256 client_secret JWT (180-day expiry)
44
+ vexpo apple jwt --rotate re-sign the JWT only
45
+ vexpo apple eas-rotation-secrets push the 5 EAS production secrets the JWT cron needs
45
46
  ```
46
47
 
47
- ## App Store Connect (the last mile to a first ship)
48
+ ## App Store Connect
48
49
 
49
- TestFlight delivery, plus the privacy and accessibility labels Apple requires before a submission clears review. `eas-cli` hands a build to TestFlight and stops there.
50
+ Picks up after `eas submit` hands a build to TestFlight: groups, testers, release notes, plus the privacy and accessibility labels Apple requires before review.
50
51
 
51
52
  ```
52
- vexpo testflight groups list List beta groups
53
- vexpo testflight groups create <name> Create a beta group
54
- vexpo testflight groups view <id> View a beta group + its testers
55
- vexpo testflight groups delete <id> Delete a beta group
56
- vexpo testflight testers list List beta testers
57
- vexpo testflight invite <email> Add a tester + send invite
58
- vexpo testflight whats-new <buildId> <text> Set "What's new" notes
59
-
60
- vexpo asc:privacy show [file] Show the declared privacy nutrition label
61
- vexpo asc:privacy lint <file> Validate privacy.config.json against Apple's enums
62
- vexpo asc:accessibility show Fetch the app's accessibility declarations
63
- vexpo asc:accessibility lint <file> Validate accessibility.config.json against Apple's enums
53
+ vexpo testflight groups list list beta groups
54
+ vexpo testflight groups create <name> create a beta group
55
+ vexpo testflight groups view <id> view a beta group + its testers
56
+ vexpo testflight groups delete <id> delete a beta group
57
+ vexpo testflight testers list list beta testers
58
+ vexpo testflight invite <email> add a tester + send a TestFlight invite
59
+ vexpo testflight whats-new <buildId> <text> set the "What's new" notes
60
+
61
+ vexpo asc:privacy show [file] show the declared privacy.config.json
62
+ vexpo asc:privacy lint <file> validate privacy.config.json against Apple's enums
63
+ vexpo asc:accessibility show fetch the app's accessibility declarations
64
+ vexpo asc:accessibility lint <file> validate accessibility.config.json against Apple's enums
64
65
  ```
65
66
 
67
+ ## Design rule: don't reinvent EAS
68
+
69
+ vexpo only covers what `eas-cli` doesn't: setup orchestration, cross-source drift detection, Apple SIWA work, and the last App Store Connect mile to a first ship. If `eas <subcommand>` is the canonical answer, run `npx eas <subcommand>`.
70
+
66
71
  ## What `vexpo` doesn't wrap
67
72
 
68
- For canonical EAS surface, use `eas` directly. Wrapping these would add no value over `eas-cli` itself.
73
+ Reach for `eas` directly for the canonical platform surface.
69
74
 
70
75
  ```bash
71
76
  npx eas init # EAS project init
@@ -85,26 +90,23 @@ npx eas env [...] # env:push, env:pull, env:get, env:delete, env:
85
90
  npx eas integrations:asc [...] # status, connect, disconnect
86
91
  ```
87
92
 
88
- `vexpo full` orchestrates `eas init`, `eas env:push`, `eas credentials -p ios` (via `eas credentials:configure-build`), and `eas integrations:asc:connect` internally as setup steps, none are exposed as standalone `vexpo` commands. The ASC API key flows through to both wizards via `EXPO_ASC_API_KEY_PATH` / `EXPO_ASC_KEY_ID` / `EXPO_ASC_ISSUER_ID` env vars pre-set from the cached `asc-key` state. These env vars set `AppStoreApi.defaultAuthenticationMode = API_KEY` inside eas-cli, so when the wizard reaches the Apple auth step during ASC key generation, it uses our cached key instead of prompting for Apple ID + password. The manual paste step doesn't auto-fill — the wizard skips it by auto-generating the key instead.
89
-
90
- Earlier vexpo versions passed `--api-key-id <apple-key-id>` to `integrations:asc:connect`. That flag matches against EAS's uploaded key resources, not Apple-side identifiers, so it failed with `No App Store Connect API key found with Apple key identifier ...` whenever the key hadn't been uploaded to EAS yet (the common case on fresh projects). The current orchestration drops the flag and relies on the env vars + the wizard's "Create new or use existing" prompt instead.
93
+ `vexpo full` drives `eas init`, `eas env:push`, `eas credentials`, and the ASC link internally using the cached ASC key. Only the ASC link is also standalone, as `vexpo asc:connect`.
91
94
 
92
95
  ## Architecture
93
96
 
94
- - Commander-based command tree in `src/cli.ts`.
95
- - One file per top-level command in `src/commands/`. Each exports `run<Name>(options)` returning a numeric exit code.
96
- - `src/lib/eas-cli.ts` is the shared shell-out helper: `easJson<T>(argv)` parses `--json --non-interactive` output, `easSpawn(argv)` forwards stdio for interactive commands, `easText(argv)` returns raw streams.
97
- - Built with tsup → single ESM bundle in `dist/`. Node 20+.
97
+ Commander tree in `src/cli.ts`, one file per top-level command in `src/commands/`. `src/lib/eas-cli.ts` shells out to `eas-cli`. Built with tsup to ESM, a `cli.js` entry plus shared chunks. Node 20+.
98
98
 
99
99
  ## Apple ASC API workarounds
100
100
 
101
- Apple changed several ASC API behaviors after the initial CLI release. The CLI handles each one:
101
+ Apple changed several ASC API behaviors after the initial CLI release. The CLI handles each one.
102
102
 
103
- - `POST /v1/bundleIds` rejects `platform: "SERVICES"`. `services-id` walks the user through manual creation in the developer portal, then re-polls.
104
- - App bundle IDs report `platform: "UNIVERSAL"` for newer accounts. `findOrCreateBundleId` matches any non-SERVICES platform when looking up an App ID.
105
- - Relationship endpoints reject `limit`. `bundleIdCapabilities.list` fetches without pagination.
106
- - `filter[platform]=SERVICES` returns 400. `doctor`'s `services-id-exists` check filters by identifier alone.
103
+ - `POST /v1/bundleIds` rejects `platform: "SERVICES"`. `services-id` walks you through manual creation in the developer portal, then re-polls.
104
+ - App bundle IDs report `platform: "UNIVERSAL"` for newer accounts. The App ID lookup matches any non-SERVICES platform.
105
+ - Relationship endpoints reject `limit`. The capability list fetches without pagination.
106
+ - `filter[platform]=SERVICES` returns 400. `doctor` filters by identifier alone.
107
107
 
108
108
  ## Repo
109
109
 
110
110
  [github.com/ramonclaudio/vexpo](https://github.com/ramonclaudio/vexpo)
111
+
112
+ Working on the CLI itself? See [CONTRIBUTING.md](https://github.com/ramonclaudio/vexpo/blob/main/CONTRIBUTING.md).
@@ -19,19 +19,6 @@ async function detectPackageManager() {
19
19
  function dlx() {
20
20
  return process.versions.bun ? "bunx" : "npx";
21
21
  }
22
- function dlxFor(pm) {
23
- switch (pm) {
24
- case "bun":
25
- return "bunx";
26
- case "pnpm":
27
- return "pnpm dlx";
28
- case "yarn":
29
- return "yarn dlx";
30
- case "npm":
31
- default:
32
- return "npx";
33
- }
34
- }
35
22
  function installCmdFor(pm) {
36
23
  switch (pm) {
37
24
  case "bun":
@@ -45,19 +32,6 @@ function installCmdFor(pm) {
45
32
  return "npm install";
46
33
  }
47
34
  }
48
- function runCmdFor(pm) {
49
- switch (pm) {
50
- case "bun":
51
- return "bun run";
52
- case "pnpm":
53
- return "pnpm run";
54
- case "yarn":
55
- return "yarn";
56
- case "npm":
57
- default:
58
- return "npm run";
59
- }
60
- }
61
35
  function currentRuntime() {
62
36
  return process.versions.bun ? "bun" : "node";
63
37
  }
@@ -65,4 +39,4 @@ function currentRuntimeVersion() {
65
39
  return process.versions.bun ?? process.versions.node ?? "?";
66
40
  }
67
41
 
68
- export { currentRuntime, currentRuntimeVersion, detectPackageManager, dlx, dlxFor, installCmdFor, runCmdFor };
42
+ export { currentRuntime, currentRuntimeVersion, detectPackageManager, dlx, installCmdFor };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { dlx } from './chunk-PYXH4J77.js';
3
- import { run } from './chunk-QFP5R25M.js';
2
+ import { dlx } from './chunk-3RDUQUJW.js';
3
+ import { run } from './chunk-W6O77AAZ.js';
4
4
 
5
5
  // src/lib/eas-cli.ts
6
6
  function compact(argv) {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { spawn as spawn$1, spawnSync as spawnSync$1 } from 'child_process';
2
+ import { spawn as spawn$1 } from 'child_process';
3
3
 
4
4
  function spawn(argv, opts = {}) {
5
5
  const stdio = opts.stdio ?? [
@@ -25,24 +25,6 @@ function spawn(argv, opts = {}) {
25
25
  kill: (signal) => proc.kill(signal)
26
26
  };
27
27
  }
28
- function spawnSync(argv, opts = {}) {
29
- const stdio = opts.stdio ?? [
30
- opts.stdin ?? "inherit",
31
- opts.stdout ?? "pipe",
32
- opts.stderr ?? "pipe"
33
- ];
34
- const result = spawnSync$1(argv[0], argv.slice(1), {
35
- stdio,
36
- env: opts.env ? { ...process.env, ...opts.env } : process.env,
37
- cwd: opts.cwd,
38
- encoding: "utf8"
39
- });
40
- return {
41
- code: result.status ?? (result.signal ? 1 : 0),
42
- stdout: typeof result.stdout === "string" ? result.stdout : "",
43
- stderr: typeof result.stderr === "string" ? result.stderr : ""
44
- };
45
- }
46
28
  async function streamText(stream) {
47
29
  if (!stream) return "";
48
30
  const chunks = [];
@@ -67,4 +49,4 @@ async function run(argv, opts = {}) {
67
49
  return { code, stdout, stderr };
68
50
  }
69
51
 
70
- export { run, spawn, spawnSync, streamText };
52
+ export { run, spawn, streamText };
package/dist/cli.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { ensureLine, readOne, readAll, removeLines, ENV_FILE } from './chunk-3TT4CDAJ.js';
3
- import { ascStatus } from './chunk-VOL7YISA.js';
4
- import { dlx, detectPackageManager, installCmdFor } from './chunk-PYXH4J77.js';
5
- import { run, spawn } from './chunk-QFP5R25M.js';
3
+ import { ascStatus } from './chunk-6O5TB4L4.js';
4
+ import { dlx, detectPackageManager, installCmdFor } from './chunk-3RDUQUJW.js';
5
+ import { run, spawn } from './chunk-W6O77AAZ.js';
6
6
  import { Command } from 'commander';
7
7
  import { readFile, access, writeFile, unlink, stat, mkdir, rename } from 'fs/promises';
8
8
  import { createInterface } from 'readline/promises';
@@ -13,7 +13,7 @@ import { createSign } from 'crypto';
13
13
 
14
14
  // package.json
15
15
  var package_default = {
16
- version: "0.1.2"};
16
+ version: "0.1.4"};
17
17
  function deploymentName(value) {
18
18
  if (!value) return void 0;
19
19
  const m = /^(?:dev|prod|preview):(.+)$/.exec(value);
@@ -470,7 +470,7 @@ async function askYesNo(question, defaultYes) {
470
470
  return raw === "y" || raw === "yes";
471
471
  }
472
472
  async function openUrlExternal(url) {
473
- const { spawn: spawn2 } = await import('./proc-TEOPRZZ2.js');
473
+ const { spawn: spawn2 } = await import('./proc-QPMIGTW6.js');
474
474
  const cmd = process.platform === "darwin" ? ["open", url] : process.platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
475
475
  spawn2(cmd, { stdin: "ignore", stdout: "ignore", stderr: "ignore" });
476
476
  }
@@ -1090,6 +1090,12 @@ function describeDeployment(d) {
1090
1090
  // src/commands/convex.ts
1091
1091
  var BUNDLE_ID_RE = /^[A-Za-z0-9.-]+$/;
1092
1092
  var TEAM_ID_RE = /^[A-Z0-9]{10}$/;
1093
+ function resolveTeamIdInput(raw, fromConfig) {
1094
+ const value = raw.trim().toUpperCase() || (fromConfig ?? "");
1095
+ if (!value) return { kind: "skip" };
1096
+ if (!TEAM_ID_RE.test(value)) return { kind: "invalid", value };
1097
+ return { kind: "ok", value };
1098
+ }
1093
1099
  function planConvexDev(options2, needsProvisioning, projectName) {
1094
1100
  const devArgs = ["convex", "dev", "--once", "--tail-logs", "disable"];
1095
1101
  if (needsProvisioning) {
@@ -1222,7 +1228,7 @@ async function ensureIdentity(localEnv) {
1222
1228
  ok(`wrote EXPO_PUBLIC_APP_BUNDLE_ID=${bundleId}`);
1223
1229
  }
1224
1230
  } else {
1225
- nop(`EXPO_PUBLIC_APP_BUNDLE_ID already set (${bundleId})`);
1231
+ ok(`EXPO_PUBLIC_APP_BUNDLE_ID=${bundleId} (from .env.local); syncing to Convex`);
1226
1232
  }
1227
1233
  if (!haveTeam) {
1228
1234
  if (!process.stdin.isTTY) {
@@ -1234,13 +1240,18 @@ async function ensureIdentity(localEnv) {
1234
1240
  ` Apple Team id ${DIM}(10-char alphanumeric, find at developer.apple.com/account)${RESET}${cachedHint}
1235
1241
  ${DIM}> ${RESET}`
1236
1242
  )).trim().toUpperCase();
1237
- const value = raw || (fromConfig ?? "");
1238
- if (!TEAM_ID_RE.test(value)) {
1239
- throw new Error(`invalid Apple Team id '${value}' (must be 10 uppercase alphanumeric)`);
1243
+ const resolved = resolveTeamIdInput(raw, fromConfig);
1244
+ if (resolved.kind === "skip") {
1245
+ yep("EXPO_PUBLIC_APPLE_TEAM_ID not set (optional for lite; `vexpo full` asks again)");
1246
+ } else if (resolved.kind === "invalid") {
1247
+ throw new Error(
1248
+ `invalid Apple Team id '${resolved.value}' (must be 10 uppercase alphanumeric)`
1249
+ );
1250
+ } else {
1251
+ teamId = resolved.value;
1252
+ await ensureLine("EXPO_PUBLIC_APPLE_TEAM_ID", teamId);
1253
+ ok(`wrote EXPO_PUBLIC_APPLE_TEAM_ID=${teamId}`);
1240
1254
  }
1241
- teamId = value;
1242
- await ensureLine("EXPO_PUBLIC_APPLE_TEAM_ID", teamId);
1243
- ok(`wrote EXPO_PUBLIC_APPLE_TEAM_ID=${teamId}`);
1244
1255
  }
1245
1256
  } else {
1246
1257
  nop(`EXPO_PUBLIC_APPLE_TEAM_ID already set (${teamId})`);
@@ -1818,13 +1829,6 @@ function makeAscClient(creds) {
1818
1829
  };
1819
1830
  const res = await request("POST", "/v1/bundleIds", body);
1820
1831
  return res.data;
1821
- },
1822
- async get(id) {
1823
- const res = await request("GET", `/v1/bundleIds/${id}`);
1824
- return res.data;
1825
- },
1826
- async delete(id) {
1827
- await request("DELETE", `/v1/bundleIds/${id}`);
1828
1832
  }
1829
1833
  },
1830
1834
  bundleIdCapabilities: {
@@ -1853,80 +1857,17 @@ function makeAscClient(creds) {
1853
1857
  body
1854
1858
  );
1855
1859
  return res.data;
1856
- },
1857
- async delete(id) {
1858
- await request("DELETE", `/v1/bundleIdCapabilities/${id}`);
1859
1860
  }
1860
1861
  },
1861
1862
  apps: {
1862
- async list() {
1863
- return paginatedList("/v1/apps");
1864
- },
1865
- async get(id) {
1866
- const res = await request("GET", `/v1/apps/${id}`);
1867
- return res.data;
1868
- }
1869
- },
1870
- certificates: {
1871
1863
  async list(filter) {
1872
1864
  const query = {};
1873
- if (filter?.certificateType) query["filter[certificateType]"] = filter.certificateType;
1874
- return paginatedList("/v1/certificates", query);
1865
+ if (filter?.bundleId) query["filter[bundleId]"] = filter.bundleId;
1866
+ return paginatedList("/v1/apps", query);
1875
1867
  },
1876
- async create(args) {
1877
- const body = {
1878
- data: {
1879
- type: "certificates",
1880
- attributes: {
1881
- csrContent: args.csrContent,
1882
- certificateType: args.certificateType
1883
- }
1884
- }
1885
- };
1886
- const res = await request("POST", "/v1/certificates", body);
1887
- return res.data;
1888
- },
1889
- async delete(id) {
1890
- await request("DELETE", `/v1/certificates/${id}`);
1891
- }
1892
- },
1893
- profiles: {
1894
- async list(filter) {
1895
- const query = {};
1896
- if (filter?.profileType) query["filter[profileType]"] = filter.profileType;
1897
- if (filter?.name) query["filter[name]"] = filter.name;
1898
- return paginatedList("/v1/profiles", query);
1899
- },
1900
- async create(args) {
1901
- const body = {
1902
- data: {
1903
- type: "profiles",
1904
- attributes: { name: args.name, profileType: args.profileType },
1905
- relationships: {
1906
- bundleId: { data: { type: "bundleIds", id: args.bundleIdResourceId } },
1907
- certificates: {
1908
- data: args.certificateIds.map((id) => ({ type: "certificates", id }))
1909
- },
1910
- ...args.deviceIds && args.deviceIds.length > 0 ? {
1911
- devices: {
1912
- data: args.deviceIds.map((id) => ({ type: "devices", id }))
1913
- }
1914
- } : {}
1915
- }
1916
- }
1917
- };
1918
- const res = await request("POST", "/v1/profiles", body);
1868
+ async get(id) {
1869
+ const res = await request("GET", `/v1/apps/${id}`);
1919
1870
  return res.data;
1920
- },
1921
- async delete(id) {
1922
- await request("DELETE", `/v1/profiles/${id}`);
1923
- }
1924
- },
1925
- devices: {
1926
- async list(filter) {
1927
- const query = {};
1928
- if (filter?.status) query["filter[status]"] = filter.status;
1929
- return paginatedList("/v1/devices", query);
1930
1871
  }
1931
1872
  }
1932
1873
  };
@@ -2356,6 +2297,36 @@ async function runServicesId(options2) {
2356
2297
  return 1;
2357
2298
  }
2358
2299
  }
2300
+ async function loadAscCreds2() {
2301
+ const state = await load();
2302
+ const rec = state.steps["asc-key"];
2303
+ if (!rec?.outputs) return null;
2304
+ const out = rec.outputs;
2305
+ const issuerId = out.issuerId;
2306
+ const keyId = out.keyId;
2307
+ const rawPath = out.p8Path;
2308
+ if (!issuerId || !keyId || !rawPath) return null;
2309
+ const p8Path = expandTilde(rawPath);
2310
+ if (!existsSync(p8Path)) return null;
2311
+ return { issuerId, keyId, privateKey: { path: p8Path } };
2312
+ }
2313
+ async function ascBootstrap() {
2314
+ const creds = await loadAscCreds2();
2315
+ if (!creds) {
2316
+ throw new Error("no cached ASC creds. run `vexpo apple asc-key` first");
2317
+ }
2318
+ const client = makeAscClient(creds);
2319
+ const bundleId = await readOne("EXPO_PUBLIC_APP_BUNDLE_ID") ?? await readOne("APP_BUNDLE_ID") ?? void 0;
2320
+ let ascAppId;
2321
+ if (bundleId) {
2322
+ try {
2323
+ const apps = await client.paginatedList("/v1/apps", { "filter[bundleId]": bundleId }, 5);
2324
+ ascAppId = apps[0]?.id;
2325
+ } catch {
2326
+ }
2327
+ }
2328
+ return { client, bundleId, ascAppId, creds };
2329
+ }
2359
2330
 
2360
2331
  // src/lib/eas-submit.ts
2361
2332
  function isObject(value) {
@@ -2391,6 +2362,16 @@ function withAscAppId(easJson, ascAppId) {
2391
2362
  }
2392
2363
 
2393
2364
  // src/commands/asc.ts
2365
+ async function ascAppExists(bundleId) {
2366
+ const creds = await loadAscCreds2();
2367
+ if (!creds) return "unknown";
2368
+ try {
2369
+ const apps = await makeAscClient(creds).apps.list({ bundleId });
2370
+ return apps.length > 0 ? "proceed" : "defer";
2371
+ } catch {
2372
+ return "unknown";
2373
+ }
2374
+ }
2394
2375
  async function loadAscFromState2() {
2395
2376
  const state = await load();
2396
2377
  const rec = state.steps["asc-key"];
@@ -2438,6 +2419,15 @@ async function runAscConnect(opts = {}) {
2438
2419
  return 1;
2439
2420
  }
2440
2421
  ok(`bundle id: ${BOLD}${bundleId}${RESET}`);
2422
+ if (await ascAppExists(bundleId) === "defer") {
2423
+ yep("no App Store Connect app record for this bundle id yet, NOT connected");
2424
+ note("the ASC app record only appears after the first `eas submit`. run:");
2425
+ note(
2426
+ ` ${BOLD}npx eas build -p ios --profile production --auto-submit-with-profile testflight${RESET}`
2427
+ );
2428
+ note("then re-run `npx vexpo asc:connect` to finish the EAS\u2194ASC link");
2429
+ return 0;
2430
+ }
2441
2431
  if (!process.stdin.isTTY) {
2442
2432
  bad("ASC connect needs a TTY: eas integrations:asc:connect can't run headless");
2443
2433
  note("run `vexpo asc:connect` in an interactive terminal to finish the EAS\u2194ASC link");
@@ -2494,36 +2484,6 @@ async function runAscConnect(opts = {}) {
2494
2484
  }
2495
2485
  return 0;
2496
2486
  }
2497
- async function loadAscCreds2() {
2498
- const state = await load();
2499
- const rec = state.steps["asc-key"];
2500
- if (!rec?.outputs) return null;
2501
- const out = rec.outputs;
2502
- const issuerId = out.issuerId;
2503
- const keyId = out.keyId;
2504
- const rawPath = out.p8Path;
2505
- if (!issuerId || !keyId || !rawPath) return null;
2506
- const p8Path = expandTilde(rawPath);
2507
- if (!existsSync(p8Path)) return null;
2508
- return { issuerId, keyId, privateKey: { path: p8Path } };
2509
- }
2510
- async function ascBootstrap() {
2511
- const creds = await loadAscCreds2();
2512
- if (!creds) {
2513
- throw new Error("no cached ASC creds. run `vexpo apple asc-key` first");
2514
- }
2515
- const client = makeAscClient(creds);
2516
- const bundleId = await readOne("EXPO_PUBLIC_APP_BUNDLE_ID") ?? await readOne("APP_BUNDLE_ID") ?? void 0;
2517
- let ascAppId;
2518
- if (bundleId) {
2519
- try {
2520
- const apps = await client.paginatedList("/v1/apps", { "filter[bundleId]": bundleId }, 5);
2521
- ascAppId = apps[0]?.id;
2522
- } catch {
2523
- }
2524
- }
2525
- return { client, bundleId, ascAppId, creds };
2526
- }
2527
2487
 
2528
2488
  // src/lib/asc-accessibility.ts
2529
2489
  var ACCESSIBILITY_FEATURES = [
@@ -3156,6 +3116,9 @@ var ROUTING = {
3156
3116
  RESEND_TEST_MODE: {
3157
3117
  routes: (c) => [{ type: "convex", key: "RESEND_TEST_MODE", channel: c }]
3158
3118
  },
3119
+ REQUIRE_EMAIL_VERIFICATION: {
3120
+ routes: (c) => [{ type: "convex", key: "REQUIRE_EMAIL_VERIFICATION", channel: c }]
3121
+ },
3159
3122
  APP_BUNDLE_ID: { routes: (c) => [{ type: "convex", key: "APP_BUNDLE_ID", channel: c }] },
3160
3123
  APPLE_CLIENT_ID: { routes: (c) => [{ type: "convex", key: "APPLE_CLIENT_ID", channel: c }] },
3161
3124
  APPLE_CLIENT_SECRET: {
@@ -3923,7 +3886,7 @@ async function verifyEas(ctx) {
3923
3886
  "eas",
3924
3887
  "asc-submit-id",
3925
3888
  `submit profile${missing.length === 1 ? "" : "s"} ${missing.join(", ")} missing ascAppId`,
3926
- "run `vexpo asc` to write it; non-interactive `eas submit` (CI) fails without it"
3889
+ "run `vexpo asc:connect` to write it; non-interactive `eas submit` (CI) fails without it"
3927
3890
  )
3928
3891
  );
3929
3892
  } else if (existsSync("eas.json")) {
@@ -4067,7 +4030,7 @@ async function readContext(channel) {
4067
4030
  () => /* @__PURE__ */ new Map()
4068
4031
  ) : Promise.resolve(/* @__PURE__ */ new Map()),
4069
4032
  readAppConfigFacts(),
4070
- loadAscCreds3()
4033
+ loadAscCreds2()
4071
4034
  ]
4072
4035
  );
4073
4036
  return {
@@ -4100,21 +4063,6 @@ async function readAppConfigFacts() {
4100
4063
  return {};
4101
4064
  }
4102
4065
  }
4103
- async function loadAscCreds3() {
4104
- try {
4105
- const state = await load();
4106
- const rec = state.steps["asc-key"];
4107
- if (!rec?.outputs) return null;
4108
- const out = rec.outputs;
4109
- const issuerId = out.issuerId;
4110
- const keyId = out.keyId;
4111
- const p8Path = out.p8Path;
4112
- if (!issuerId || !keyId || !p8Path) return null;
4113
- return { issuerId, keyId, privateKey: { path: p8Path } };
4114
- } catch {
4115
- return null;
4116
- }
4117
- }
4118
4066
  async function verifyAll(ctx) {
4119
4067
  const [files, convex, resend, apple2, eas] = await Promise.all([
4120
4068
  Promise.resolve(verifyFiles(ctx)),
@@ -4595,7 +4543,6 @@ async function runEnvPush(options2) {
4595
4543
  return 2;
4596
4544
  }
4597
4545
  ok("nothing to do. all source values match destinations");
4598
- await recordStep("accounts", { mode: "lite", verifiedAt: (/* @__PURE__ */ new Date()).toISOString() });
4599
4546
  return 0;
4600
4547
  }
4601
4548
  const prodConvexWrites = entries.some(
@@ -4640,12 +4587,6 @@ async function runEnvPush(options2) {
4640
4587
  return 1;
4641
4588
  }
4642
4589
  ok(`${appliedTotal} value${appliedTotal === 1 ? "" : "s"} synced`);
4643
- await recordStep("accounts", {
4644
- mode: "lite",
4645
- syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
4646
- sources: sources.map((s) => ({ path: s.path, channel: s.channel, keys: s.entries.size })),
4647
- applied: appliedTotal
4648
- });
4649
4590
  if (!options2.noVerify) {
4650
4591
  const haveProd = sources.some((s) => s.channel === "prod");
4651
4592
  const verifyChannels = haveProd ? ["dev", "prod"] : ["dev"];
@@ -4705,19 +4646,21 @@ function bundleSlug(value) {
4705
4646
  return value.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "").slice(0, 32);
4706
4647
  }
4707
4648
  async function promptInputs(overrides) {
4708
- if (!process.stdin.isTTY) fail("rebrand wizard needs a TTY");
4709
- line();
4710
- note(
4711
- `${DIM}4 prompts. Everything else is derived. Override any with flags or edit later.${RESET}`
4712
- );
4713
- line();
4714
- const appName2 = overrides.appName ?? (await ask(` ${BOLD}App name${RESET} ${DIM}(e.g. Foobar)${RESET} > `)).trim();
4649
+ const interactive = process.stdin.isTTY === true;
4650
+ if (interactive) {
4651
+ line();
4652
+ note(
4653
+ `${DIM}4 prompts. Everything else is derived. Override any with flags or edit later.${RESET}`
4654
+ );
4655
+ line();
4656
+ }
4657
+ const appName2 = overrides.appName ?? (interactive ? (await ask(` ${BOLD}App name${RESET} ${DIM}(e.g. Foobar)${RESET} > `)).trim() : "");
4715
4658
  if (!appName2) fail("app name required");
4716
4659
  const defaultPkg = slug(appName2);
4717
4660
  const bundleHint = `com.${slug(appName2).replace(/-/g, "")}.${bundleSlug(defaultPkg)}`;
4718
- const bundleId = overrides.bundleId ?? ((await ask(` ${BOLD}Bundle ID${RESET} ${DIM}[${bundleHint}]${RESET} > `)).trim() || bundleHint);
4719
- const ownerName = overrides.ownerName ?? ((await ask(` ${BOLD}Your name${RESET} > `)).trim() || "Owner");
4720
- const reviewEmail = overrides.reviewEmail ?? (await ask(` ${BOLD}Apple review contact email${RESET} > `)).trim();
4661
+ const bundleId = overrides.bundleId ?? (interactive ? (await ask(` ${BOLD}Bundle ID${RESET} ${DIM}[${bundleHint}]${RESET} > `)).trim() || bundleHint : bundleHint);
4662
+ const ownerName = overrides.ownerName ?? (interactive ? (await ask(` ${BOLD}Your name${RESET} > `)).trim() || "Owner" : "Owner");
4663
+ const reviewEmail = overrides.reviewEmail ?? (interactive ? (await ask(` ${BOLD}Apple review contact email${RESET} > `)).trim() : "");
4721
4664
  if (!reviewEmail) fail("review email required");
4722
4665
  const packageName = overrides.packageName ?? defaultPkg;
4723
4666
  const scheme2 = overrides.scheme ?? bundleSlug(packageName);
@@ -4748,6 +4691,20 @@ async function promptInputs(overrides) {
4748
4691
  expoOwner
4749
4692
  };
4750
4693
  }
4694
+ async function syncBundleId(bundleId) {
4695
+ const env2 = await readAll();
4696
+ const current = env2.get("EXPO_PUBLIC_APP_BUNDLE_ID");
4697
+ if (current === bundleId) return;
4698
+ if (current !== void 0) await removeLines(["EXPO_PUBLIC_APP_BUNDLE_ID"]);
4699
+ await ensureLine("EXPO_PUBLIC_APP_BUNDLE_ID", bundleId);
4700
+ ok(`wrote EXPO_PUBLIC_APP_BUNDLE_ID=${bundleId} to .env.local`);
4701
+ if (env2.has("CONVEX_DEPLOYMENT")) {
4702
+ await envSet("APP_BUNDLE_ID", bundleId);
4703
+ ok(`Convex env: APP_BUNDLE_ID=${bundleId}`);
4704
+ } else {
4705
+ note("no Convex deployment yet; the next `vexpo convex` run carries APP_BUNDLE_ID");
4706
+ }
4707
+ }
4751
4708
  async function backup(files, stamp) {
4752
4709
  const dir = `.rebrand-backup/${stamp}`;
4753
4710
  await mkdir(dir, { recursive: true });
@@ -4949,6 +4906,7 @@ async function runRebrand(options2) {
4949
4906
  await rewriteAppJson();
4950
4907
  await rewritePackageJson(inputs);
4951
4908
  await rewriteStoreConfig(inputs);
4909
+ await syncBundleId(inputs.bundleId);
4952
4910
  if (inputs.expoOwner) {
4953
4911
  await ensureLine("EXPO_PUBLIC_EXPO_OWNER", inputs.expoOwner);
4954
4912
  ok(`wrote EXPO_PUBLIC_EXPO_OWNER=${inputs.expoOwner} to .env.local`);
@@ -5326,67 +5284,63 @@ async function runEas(options2) {
5326
5284
  ok(`signed in as ${BOLD}${who}${RESET}`);
5327
5285
  }
5328
5286
  let projectId = await resolveProjectId();
5329
- if (!options2.skipInit) {
5330
- if (projectId) {
5331
- ok(`EAS project linked: ${projectId}`);
5332
- } else {
5333
- const result = await init();
5334
- if (!result.ok) {
5335
- bad("eas init failed");
5336
- return 1;
5287
+ if (projectId) {
5288
+ ok(`EAS project linked: ${projectId}`);
5289
+ } else {
5290
+ const result = await init();
5291
+ if (!result.ok) {
5292
+ bad("eas init failed");
5293
+ return 1;
5294
+ }
5295
+ projectId = result.projectId ?? null;
5296
+ ok(`EAS project created: ${projectId}`);
5297
+ }
5298
+ const channels = ["development", "preview", "production"];
5299
+ const createdChannels = await ensureChannels(channels);
5300
+ if (createdChannels.length > 0) ok(`channels created: ${createdChannels.join(", ")}`);
5301
+ else nop(`channels already exist (${channels.join(", ")})`);
5302
+ const branches = ["development", "preview", "production"];
5303
+ const createdBranches = await ensureBranches(branches);
5304
+ if (createdBranches.length > 0) ok(`branches created: ${createdBranches.join(", ")}`);
5305
+ else nop(`branches already exist (${branches.join(", ")})`);
5306
+ if (await fileExists7(".env.local")) {
5307
+ try {
5308
+ const pushed = await pushEasRoutedKeys(".env.local", ["development"]);
5309
+ if (pushed.length > 0) {
5310
+ ok(
5311
+ `pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (development)`
5312
+ );
5313
+ } else {
5314
+ nop(".env.local has no EAS-routed keys yet (run `vexpo convex` first)");
5337
5315
  }
5338
- projectId = result.projectId ?? null;
5339
- ok(`EAS project created: ${projectId}`);
5316
+ } catch (err) {
5317
+ bad(err instanceof Error ? err.message : String(err));
5340
5318
  }
5341
- const channels = ["development", "preview", "production"];
5342
- const createdChannels = await ensureChannels(channels);
5343
- if (createdChannels.length > 0) ok(`channels created: ${createdChannels.join(", ")}`);
5344
- else nop(`channels already exist (${channels.join(", ")})`);
5345
- const branches = ["development", "preview", "production"];
5346
- const createdBranches = await ensureBranches(branches);
5347
- if (createdBranches.length > 0) ok(`branches created: ${createdBranches.join(", ")}`);
5348
- else nop(`branches already exist (${branches.join(", ")})`);
5349
- }
5350
- if (!options2.skipEnv) {
5351
- if (await fileExists7(".env.local")) {
5319
+ } else {
5320
+ nop(".env.local missing. skipping development env push (run `vexpo convex` first)");
5321
+ }
5322
+ if (options2.withProd) {
5323
+ const prodFile = await fileExists7(".env.prod") ? ".env.prod" : await fileExists7(".env.production") ? ".env.production" : null;
5324
+ if (prodFile) {
5352
5325
  try {
5353
- const pushed = await pushEasRoutedKeys(".env.local", ["development"]);
5326
+ const pushed = await pushEasRoutedKeys(prodFile, ["production", "preview"]);
5354
5327
  if (pushed.length > 0) {
5355
5328
  ok(
5356
- `pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (development)`
5329
+ `pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (production, preview)`
5357
5330
  );
5358
5331
  } else {
5359
- nop(".env.local has no EAS-routed keys yet (run `vexpo convex` first)");
5332
+ nop(`${prodFile} has no EAS-routed keys`);
5360
5333
  }
5361
5334
  } catch (err) {
5362
5335
  bad(err instanceof Error ? err.message : String(err));
5363
5336
  }
5364
5337
  } else {
5365
- nop(".env.local missing. skipping development env push (run `vexpo convex` first)");
5366
- }
5367
- if (options2.withProd) {
5368
- const prodFile = await fileExists7(".env.prod") ? ".env.prod" : await fileExists7(".env.production") ? ".env.production" : null;
5369
- if (prodFile) {
5370
- try {
5371
- const pushed = await pushEasRoutedKeys(prodFile, ["production", "preview"]);
5372
- if (pushed.length > 0) {
5373
- ok(
5374
- `pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (production, preview)`
5375
- );
5376
- } else {
5377
- nop(`${prodFile} has no EAS-routed keys`);
5378
- }
5379
- } catch (err) {
5380
- bad(err instanceof Error ? err.message : String(err));
5381
- }
5382
- } else {
5383
- nop("--with-prod set but no .env.prod or .env.production found");
5384
- }
5338
+ nop("--with-prod set but no .env.prod or .env.production found");
5385
5339
  }
5386
- note(
5387
- `server-side secrets route to Convex, not EAS. run ${BOLD}vexpo env push${RESET} to sync those`
5388
- );
5389
5340
  }
5341
+ note(
5342
+ `server-side secrets route to Convex, not EAS. run ${BOLD}vexpo env push${RESET} to sync those`
5343
+ );
5390
5344
  if (projectId) {
5391
5345
  await recordStep("eas", {
5392
5346
  projectId,
@@ -5545,7 +5499,7 @@ async function liveCheckEas() {
5545
5499
  }
5546
5500
  async function liveCheckAscLink() {
5547
5501
  try {
5548
- const { ascStatus: ascStatus2 } = await import('./eas-integrations-2QVR45NE.js');
5502
+ const { ascStatus: ascStatus2 } = await import('./eas-integrations-5FVP7DI6.js');
5549
5503
  const status = await ascStatus2();
5550
5504
  return status.status === "connected";
5551
5505
  } catch {
@@ -5564,16 +5518,20 @@ async function liveCheckRotationSecrets() {
5564
5518
  "CONVEX_DEPLOY_KEY"
5565
5519
  ].every((k) => eas.has(k));
5566
5520
  }
5521
+ var LOCAL_ENV_LITE_CORE = [
5522
+ "CONVEX_DEPLOYMENT",
5523
+ "EXPO_PUBLIC_CONVEX_URL",
5524
+ "EXPO_PUBLIC_CONVEX_SITE_URL",
5525
+ "EXPO_PUBLIC_SITE_URL",
5526
+ "EXPO_PUBLIC_APP_BUNDLE_ID"
5527
+ ];
5528
+ var LOCAL_ENV_TEAM_ID = "EXPO_PUBLIC_APPLE_TEAM_ID";
5529
+ function classifyLocalEnv(env2) {
5530
+ if (!LOCAL_ENV_LITE_CORE.every((k) => env2.has(k))) return "missing";
5531
+ return env2.has(LOCAL_ENV_TEAM_ID) ? "ok" : "partial";
5532
+ }
5567
5533
  async function liveCheckLocalEnv() {
5568
- const env2 = await readAll();
5569
- return [
5570
- "CONVEX_DEPLOYMENT",
5571
- "EXPO_PUBLIC_CONVEX_URL",
5572
- "EXPO_PUBLIC_CONVEX_SITE_URL",
5573
- "EXPO_PUBLIC_SITE_URL",
5574
- "EXPO_PUBLIC_APP_BUNDLE_ID",
5575
- "EXPO_PUBLIC_APPLE_TEAM_ID"
5576
- ].every((k) => env2.has(k));
5534
+ return classifyLocalEnv(await readAll());
5577
5535
  }
5578
5536
  async function stepPrerequisites() {
5579
5537
  section("Prerequisites");
@@ -5594,12 +5552,17 @@ async function stepPrerequisites() {
5594
5552
  async function stepProbe() {
5595
5553
  section("Probe");
5596
5554
  const installOk = await nodeModulesPresent();
5597
- const localOk = await liveCheckLocalEnv();
5598
- const convex = localOk ? await envMap() : /* @__PURE__ */ new Map();
5555
+ const localEnvState = await liveCheckLocalEnv();
5556
+ const convexLive = localEnvState !== "missing";
5557
+ const convex = convexLive ? await envMap() : /* @__PURE__ */ new Map();
5599
5558
  const rows = /* @__PURE__ */ new Map();
5600
5559
  rows.set("accounts", await shouldRun("accounts", async () => true));
5601
5560
  rows.set("rebrand", await shouldRun("rebrand", async () => false));
5602
- rows.set("convex", { step: "convex", label: "convex", status: localOk ? "live" : "missing" });
5561
+ rows.set("convex", {
5562
+ step: "convex",
5563
+ label: "convex",
5564
+ status: convexLive ? "live" : "missing"
5565
+ });
5603
5566
  rows.set("better-auth", await shouldRun("better-auth", () => liveCheckBetterAuth(convex)));
5604
5567
  rows.set("resend", await shouldRun("resend", () => liveCheckResend(convex)));
5605
5568
  rows.set("asc-key", await shouldRun("asc-key", async () => false));
@@ -5620,9 +5583,8 @@ async function stepProbe() {
5620
5583
  line(
5621
5584
  ` ${BOLD}${"node_modules".padEnd(w)}${RESET} ${installOk ? `${GREEN}ok${RESET}` : `${RED}missing${RESET}`}`
5622
5585
  );
5623
- line(
5624
- ` ${BOLD}${".env.local".padEnd(w)}${RESET} ${localOk ? `${GREEN}ok${RESET}` : `${RED}missing${RESET}`}`
5625
- );
5586
+ const localEnvMark = localEnvState === "ok" ? `${GREEN}ok${RESET}` : localEnvState === "partial" ? `${YELLOW}partial (lite)${RESET}` : `${RED}missing${RESET}`;
5587
+ line(` ${BOLD}${".env.local".padEnd(w)}${RESET} ${localEnvMark}`);
5626
5588
  for (const [key, row] of rows) {
5627
5589
  const label = key === "convex" ? "Convex / .env.local" : key === "better-auth" ? "Better Auth" : key === "resend" ? "Resend" : key === "asc-key" ? "ASC API key" : key === "apple-services-id" ? "Sign In Services ID" : key === "apple-sign-in" ? "Sign In JWT" : key === "apple-credentials" ? "EAS iOS credentials" : key === "apple-asc-link" ? "EAS \u2194 ASC link" : key === "apple-eas-rotation-secrets" ? "EAS rotation secrets" : key === "eas" ? "EAS project + env" : key === "rebrand" ? "Rebrand" : key === "accounts" ? "Accounts" : key;
5628
5590
  line(` ${BOLD}${label.padEnd(w)}${RESET} ${mark(row.status)}`);
@@ -5632,7 +5594,7 @@ async function stepProbe() {
5632
5594
  );
5633
5595
  const needs = /* @__PURE__ */ new Map();
5634
5596
  for (const [k, row] of rows) needs.set(k, row.status === "missing");
5635
- return { rows, needs, install: !installOk, localEnv: localOk };
5597
+ return { rows, needs, install: !installOk, localEnv: localEnvState };
5636
5598
  }
5637
5599
  async function describePhase(step, probe) {
5638
5600
  const status = probe.rows.get(step)?.status;
@@ -6085,13 +6047,13 @@ async function runStep(name, state) {
6085
6047
  }
6086
6048
  if (state) completed.push(state);
6087
6049
  }
6088
- async function maybeRunStep(name, prompt, state) {
6050
+ async function maybeRunStep(name, prompt, state, defaultYes = true) {
6089
6051
  if (!process.stdin.isTTY) {
6090
6052
  nop(`non-TTY: skipping ${name} (run \`${name}\` later)`);
6091
6053
  if (state) skipped.push(state);
6092
6054
  return;
6093
6055
  }
6094
- if (!await askYesNo(prompt, true)) {
6056
+ if (!await askYesNo(prompt, defaultYes)) {
6095
6057
  nop(`skipped ${name} (run \`${name}\` later)`);
6096
6058
  if (state) skipped.push(state);
6097
6059
  return;
@@ -6113,7 +6075,7 @@ function printShipNextSteps() {
6113
6075
  }
6114
6076
  async function stepExpoDoctor() {
6115
6077
  section("expo-doctor");
6116
- const { dlx: dlx2 } = await import('./pkg-manager-PU7UPAID.js');
6078
+ const { dlx: dlx2 } = await import('./pkg-manager-DOOC6W2C.js');
6117
6079
  const { code, stdout, stderr } = await run([dlx2(), "expo-doctor"]);
6118
6080
  if (stdout.trim()) process.stderr.write(stdout);
6119
6081
  if (stderr.trim()) process.stderr.write(stderr);
@@ -6359,8 +6321,9 @@ async function runSetup(opts) {
6359
6321
  nop("vexpo apple services-id cached");
6360
6322
  }
6361
6323
  const status = probe.rows.get("apple-sign-in")?.status;
6362
- const prompt = status === "live" || status === "cached" ? "Apple Sign In is configured, rotate the JWT now?" : "Sign the Apple Sign In JWT now?";
6363
- await maybeRunStep("vexpo apple jwt", prompt, "apple-sign-in");
6324
+ const healthy = status === "live" || status === "cached";
6325
+ const prompt = healthy ? "Apple Sign In is configured, rotate the JWT now?" : "Sign the Apple Sign In JWT now?";
6326
+ await maybeRunStep("vexpo apple jwt", prompt, "apple-sign-in", !healthy);
6364
6327
  if (options.force || probe.needs.get("apple-eas-rotation-secrets")) {
6365
6328
  await maybeRunStep(
6366
6329
  "vexpo apple eas-rotation-secrets",
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ export { ascStatus } from './chunk-6O5TB4L4.js';
3
+ import './chunk-3RDUQUJW.js';
4
+ import './chunk-W6O77AAZ.js';
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export { currentRuntime, currentRuntimeVersion, detectPackageManager, dlx, dlxFor, installCmdFor, runCmdFor } from './chunk-PYXH4J77.js';
2
+ export { currentRuntime, currentRuntimeVersion, detectPackageManager, dlx, installCmdFor } from './chunk-3RDUQUJW.js';
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export { run, spawn, streamText } from './chunk-W6O77AAZ.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramonclaudio/vexpo",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Operational CLI for vexpo projects: setup orchestration, drift detection, env sync, Apple JWT signing, ASC API integration.",
5
5
  "keywords": [
6
6
  "apple-sign-in",
@@ -1,4 +0,0 @@
1
- #!/usr/bin/env node
2
- export { ascStatus } from './chunk-VOL7YISA.js';
3
- import './chunk-PYXH4J77.js';
4
- import './chunk-QFP5R25M.js';
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export { run, spawn, spawnSync, streamText } from './chunk-QFP5R25M.js';