@ramonclaudio/vexpo 0.1.3 → 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,73 +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)
34
- vexpo env convex-key Sync Convex deploy key + selector to EAS env (post-migration fix)
35
- vexpo adopt Finish a project created by `eas integrations:convex:connect`
36
- vexpo convex:migrate Copy server-side Convex env from another deployment
37
- vexpo asc:connect Re-link the EAS project to its ASC app (standalone, wraps `eas integrations:asc:connect`)
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`)
38
34
  ```
39
35
 
40
36
  ## Apple
41
37
 
42
38
  ```
43
- vexpo apple asc-key Validate ASC API key against /v1/apps
44
- vexpo apple credentials Wraps `eas credentials:configure-build` with cached ASC env vars (skips Apple Developer login prompt)
45
- vexpo apple services-id Detect SIWA Services ID + attach APPLE_ID_AUTH capability
46
- vexpo apple jwt Sign SIWA ES256 client_secret JWT (180-day expiry)
47
- vexpo apple jwt --rotate Re-sign the JWT only
48
- 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
49
46
  ```
50
47
 
51
- ## App Store Connect (the last mile to a first ship)
48
+ ## App Store Connect
52
49
 
53
- 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.
54
51
 
55
52
  ```
56
- vexpo testflight groups list List beta groups
57
- vexpo testflight groups create <name> Create a beta group
58
- vexpo testflight groups view <id> View a beta group + its testers
59
- vexpo testflight groups delete <id> Delete a beta group
60
- vexpo testflight testers list List beta testers
61
- vexpo testflight invite <email> Add a tester + send invite
62
- vexpo testflight whats-new <buildId> <text> Set "What's new" notes
63
-
64
- vexpo asc:privacy show [file] Show the declared privacy nutrition label
65
- vexpo asc:privacy lint <file> Validate privacy.config.json against Apple's enums
66
- vexpo asc:accessibility show Fetch the app's accessibility declarations
67
- 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
68
65
  ```
69
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
+
70
71
  ## What `vexpo` doesn't wrap
71
72
 
72
- 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.
73
74
 
74
75
  ```bash
75
76
  npx eas init # EAS project init
@@ -89,26 +90,23 @@ npx eas env [...] # env:push, env:pull, env:get, env:delete, env:
89
90
  npx eas integrations:asc [...] # status, connect, disconnect
90
91
  ```
91
92
 
92
- `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. Only the ASC link step is also exposed standalone as `vexpo asc:connect` (re-link without re-running `full`); the rest are setup-only. 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.
93
-
94
- 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`.
95
94
 
96
95
  ## Architecture
97
96
 
98
- - Commander-based command tree in `src/cli.ts`.
99
- - One file per top-level command in `src/commands/`. Each exports `run<Name>(options)` returning a numeric exit code.
100
- - `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.
101
- - 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+.
102
98
 
103
99
  ## Apple ASC API workarounds
104
100
 
105
- 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.
106
102
 
107
- - `POST /v1/bundleIds` rejects `platform: "SERVICES"`. `services-id` walks the user through manual creation in the developer portal, then re-polls.
108
- - App bundle IDs report `platform: "UNIVERSAL"` for newer accounts. `findOrCreateBundleId` matches any non-SERVICES platform when looking up an App ID.
109
- - Relationship endpoints reject `limit`. `bundleIdCapabilities.list` fetches without pagination.
110
- - `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.
111
107
 
112
108
  ## Repo
113
109
 
114
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.3"};
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
  }
@@ -1228,7 +1228,7 @@ async function ensureIdentity(localEnv) {
1228
1228
  ok(`wrote EXPO_PUBLIC_APP_BUNDLE_ID=${bundleId}`);
1229
1229
  }
1230
1230
  } else {
1231
- nop(`EXPO_PUBLIC_APP_BUNDLE_ID already set (${bundleId})`);
1231
+ ok(`EXPO_PUBLIC_APP_BUNDLE_ID=${bundleId} (from .env.local); syncing to Convex`);
1232
1232
  }
1233
1233
  if (!haveTeam) {
1234
1234
  if (!process.stdin.isTTY) {
@@ -1829,13 +1829,6 @@ function makeAscClient(creds) {
1829
1829
  };
1830
1830
  const res = await request("POST", "/v1/bundleIds", body);
1831
1831
  return res.data;
1832
- },
1833
- async get(id) {
1834
- const res = await request("GET", `/v1/bundleIds/${id}`);
1835
- return res.data;
1836
- },
1837
- async delete(id) {
1838
- await request("DELETE", `/v1/bundleIds/${id}`);
1839
1832
  }
1840
1833
  },
1841
1834
  bundleIdCapabilities: {
@@ -1864,80 +1857,17 @@ function makeAscClient(creds) {
1864
1857
  body
1865
1858
  );
1866
1859
  return res.data;
1867
- },
1868
- async delete(id) {
1869
- await request("DELETE", `/v1/bundleIdCapabilities/${id}`);
1870
1860
  }
1871
1861
  },
1872
1862
  apps: {
1873
- async list() {
1874
- return paginatedList("/v1/apps");
1875
- },
1876
- async get(id) {
1877
- const res = await request("GET", `/v1/apps/${id}`);
1878
- return res.data;
1879
- }
1880
- },
1881
- certificates: {
1882
- async list(filter) {
1883
- const query = {};
1884
- if (filter?.certificateType) query["filter[certificateType]"] = filter.certificateType;
1885
- return paginatedList("/v1/certificates", query);
1886
- },
1887
- async create(args) {
1888
- const body = {
1889
- data: {
1890
- type: "certificates",
1891
- attributes: {
1892
- csrContent: args.csrContent,
1893
- certificateType: args.certificateType
1894
- }
1895
- }
1896
- };
1897
- const res = await request("POST", "/v1/certificates", body);
1898
- return res.data;
1899
- },
1900
- async delete(id) {
1901
- await request("DELETE", `/v1/certificates/${id}`);
1902
- }
1903
- },
1904
- profiles: {
1905
1863
  async list(filter) {
1906
1864
  const query = {};
1907
- if (filter?.profileType) query["filter[profileType]"] = filter.profileType;
1908
- if (filter?.name) query["filter[name]"] = filter.name;
1909
- return paginatedList("/v1/profiles", query);
1865
+ if (filter?.bundleId) query["filter[bundleId]"] = filter.bundleId;
1866
+ return paginatedList("/v1/apps", query);
1910
1867
  },
1911
- async create(args) {
1912
- const body = {
1913
- data: {
1914
- type: "profiles",
1915
- attributes: { name: args.name, profileType: args.profileType },
1916
- relationships: {
1917
- bundleId: { data: { type: "bundleIds", id: args.bundleIdResourceId } },
1918
- certificates: {
1919
- data: args.certificateIds.map((id) => ({ type: "certificates", id }))
1920
- },
1921
- ...args.deviceIds && args.deviceIds.length > 0 ? {
1922
- devices: {
1923
- data: args.deviceIds.map((id) => ({ type: "devices", id }))
1924
- }
1925
- } : {}
1926
- }
1927
- }
1928
- };
1929
- const res = await request("POST", "/v1/profiles", body);
1868
+ async get(id) {
1869
+ const res = await request("GET", `/v1/apps/${id}`);
1930
1870
  return res.data;
1931
- },
1932
- async delete(id) {
1933
- await request("DELETE", `/v1/profiles/${id}`);
1934
- }
1935
- },
1936
- devices: {
1937
- async list(filter) {
1938
- const query = {};
1939
- if (filter?.status) query["filter[status]"] = filter.status;
1940
- return paginatedList("/v1/devices", query);
1941
1871
  }
1942
1872
  }
1943
1873
  };
@@ -2367,6 +2297,36 @@ async function runServicesId(options2) {
2367
2297
  return 1;
2368
2298
  }
2369
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
+ }
2370
2330
 
2371
2331
  // src/lib/eas-submit.ts
2372
2332
  function isObject(value) {
@@ -2402,6 +2362,16 @@ function withAscAppId(easJson, ascAppId) {
2402
2362
  }
2403
2363
 
2404
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
+ }
2405
2375
  async function loadAscFromState2() {
2406
2376
  const state = await load();
2407
2377
  const rec = state.steps["asc-key"];
@@ -2449,6 +2419,15 @@ async function runAscConnect(opts = {}) {
2449
2419
  return 1;
2450
2420
  }
2451
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
+ }
2452
2431
  if (!process.stdin.isTTY) {
2453
2432
  bad("ASC connect needs a TTY: eas integrations:asc:connect can't run headless");
2454
2433
  note("run `vexpo asc:connect` in an interactive terminal to finish the EAS\u2194ASC link");
@@ -2505,36 +2484,6 @@ async function runAscConnect(opts = {}) {
2505
2484
  }
2506
2485
  return 0;
2507
2486
  }
2508
- async function loadAscCreds2() {
2509
- const state = await load();
2510
- const rec = state.steps["asc-key"];
2511
- if (!rec?.outputs) return null;
2512
- const out = rec.outputs;
2513
- const issuerId = out.issuerId;
2514
- const keyId = out.keyId;
2515
- const rawPath = out.p8Path;
2516
- if (!issuerId || !keyId || !rawPath) return null;
2517
- const p8Path = expandTilde(rawPath);
2518
- if (!existsSync(p8Path)) return null;
2519
- return { issuerId, keyId, privateKey: { path: p8Path } };
2520
- }
2521
- async function ascBootstrap() {
2522
- const creds = await loadAscCreds2();
2523
- if (!creds) {
2524
- throw new Error("no cached ASC creds. run `vexpo apple asc-key` first");
2525
- }
2526
- const client = makeAscClient(creds);
2527
- const bundleId = await readOne("EXPO_PUBLIC_APP_BUNDLE_ID") ?? await readOne("APP_BUNDLE_ID") ?? void 0;
2528
- let ascAppId;
2529
- if (bundleId) {
2530
- try {
2531
- const apps = await client.paginatedList("/v1/apps", { "filter[bundleId]": bundleId }, 5);
2532
- ascAppId = apps[0]?.id;
2533
- } catch {
2534
- }
2535
- }
2536
- return { client, bundleId, ascAppId, creds };
2537
- }
2538
2487
 
2539
2488
  // src/lib/asc-accessibility.ts
2540
2489
  var ACCESSIBILITY_FEATURES = [
@@ -3167,6 +3116,9 @@ var ROUTING = {
3167
3116
  RESEND_TEST_MODE: {
3168
3117
  routes: (c) => [{ type: "convex", key: "RESEND_TEST_MODE", channel: c }]
3169
3118
  },
3119
+ REQUIRE_EMAIL_VERIFICATION: {
3120
+ routes: (c) => [{ type: "convex", key: "REQUIRE_EMAIL_VERIFICATION", channel: c }]
3121
+ },
3170
3122
  APP_BUNDLE_ID: { routes: (c) => [{ type: "convex", key: "APP_BUNDLE_ID", channel: c }] },
3171
3123
  APPLE_CLIENT_ID: { routes: (c) => [{ type: "convex", key: "APPLE_CLIENT_ID", channel: c }] },
3172
3124
  APPLE_CLIENT_SECRET: {
@@ -3934,7 +3886,7 @@ async function verifyEas(ctx) {
3934
3886
  "eas",
3935
3887
  "asc-submit-id",
3936
3888
  `submit profile${missing.length === 1 ? "" : "s"} ${missing.join(", ")} missing ascAppId`,
3937
- "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"
3938
3890
  )
3939
3891
  );
3940
3892
  } else if (existsSync("eas.json")) {
@@ -4078,7 +4030,7 @@ async function readContext(channel) {
4078
4030
  () => /* @__PURE__ */ new Map()
4079
4031
  ) : Promise.resolve(/* @__PURE__ */ new Map()),
4080
4032
  readAppConfigFacts(),
4081
- loadAscCreds3()
4033
+ loadAscCreds2()
4082
4034
  ]
4083
4035
  );
4084
4036
  return {
@@ -4111,21 +4063,6 @@ async function readAppConfigFacts() {
4111
4063
  return {};
4112
4064
  }
4113
4065
  }
4114
- async function loadAscCreds3() {
4115
- try {
4116
- const state = await load();
4117
- const rec = state.steps["asc-key"];
4118
- if (!rec?.outputs) return null;
4119
- const out = rec.outputs;
4120
- const issuerId = out.issuerId;
4121
- const keyId = out.keyId;
4122
- const p8Path = out.p8Path;
4123
- if (!issuerId || !keyId || !p8Path) return null;
4124
- return { issuerId, keyId, privateKey: { path: p8Path } };
4125
- } catch {
4126
- return null;
4127
- }
4128
- }
4129
4066
  async function verifyAll(ctx) {
4130
4067
  const [files, convex, resend, apple2, eas] = await Promise.all([
4131
4068
  Promise.resolve(verifyFiles(ctx)),
@@ -4606,7 +4543,6 @@ async function runEnvPush(options2) {
4606
4543
  return 2;
4607
4544
  }
4608
4545
  ok("nothing to do. all source values match destinations");
4609
- await recordStep("accounts", { mode: "lite", verifiedAt: (/* @__PURE__ */ new Date()).toISOString() });
4610
4546
  return 0;
4611
4547
  }
4612
4548
  const prodConvexWrites = entries.some(
@@ -4651,12 +4587,6 @@ async function runEnvPush(options2) {
4651
4587
  return 1;
4652
4588
  }
4653
4589
  ok(`${appliedTotal} value${appliedTotal === 1 ? "" : "s"} synced`);
4654
- await recordStep("accounts", {
4655
- mode: "lite",
4656
- syncedAt: (/* @__PURE__ */ new Date()).toISOString(),
4657
- sources: sources.map((s) => ({ path: s.path, channel: s.channel, keys: s.entries.size })),
4658
- applied: appliedTotal
4659
- });
4660
4590
  if (!options2.noVerify) {
4661
4591
  const haveProd = sources.some((s) => s.channel === "prod");
4662
4592
  const verifyChannels = haveProd ? ["dev", "prod"] : ["dev"];
@@ -4716,19 +4646,21 @@ function bundleSlug(value) {
4716
4646
  return value.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "").slice(0, 32);
4717
4647
  }
4718
4648
  async function promptInputs(overrides) {
4719
- if (!process.stdin.isTTY) fail("rebrand wizard needs a TTY");
4720
- line();
4721
- note(
4722
- `${DIM}4 prompts. Everything else is derived. Override any with flags or edit later.${RESET}`
4723
- );
4724
- line();
4725
- 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() : "");
4726
4658
  if (!appName2) fail("app name required");
4727
4659
  const defaultPkg = slug(appName2);
4728
4660
  const bundleHint = `com.${slug(appName2).replace(/-/g, "")}.${bundleSlug(defaultPkg)}`;
4729
- const bundleId = overrides.bundleId ?? ((await ask(` ${BOLD}Bundle ID${RESET} ${DIM}[${bundleHint}]${RESET} > `)).trim() || bundleHint);
4730
- const ownerName = overrides.ownerName ?? ((await ask(` ${BOLD}Your name${RESET} > `)).trim() || "Owner");
4731
- 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() : "");
4732
4664
  if (!reviewEmail) fail("review email required");
4733
4665
  const packageName = overrides.packageName ?? defaultPkg;
4734
4666
  const scheme2 = overrides.scheme ?? bundleSlug(packageName);
@@ -4759,6 +4691,20 @@ async function promptInputs(overrides) {
4759
4691
  expoOwner
4760
4692
  };
4761
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
+ }
4762
4708
  async function backup(files, stamp) {
4763
4709
  const dir = `.rebrand-backup/${stamp}`;
4764
4710
  await mkdir(dir, { recursive: true });
@@ -4960,6 +4906,7 @@ async function runRebrand(options2) {
4960
4906
  await rewriteAppJson();
4961
4907
  await rewritePackageJson(inputs);
4962
4908
  await rewriteStoreConfig(inputs);
4909
+ await syncBundleId(inputs.bundleId);
4963
4910
  if (inputs.expoOwner) {
4964
4911
  await ensureLine("EXPO_PUBLIC_EXPO_OWNER", inputs.expoOwner);
4965
4912
  ok(`wrote EXPO_PUBLIC_EXPO_OWNER=${inputs.expoOwner} to .env.local`);
@@ -5337,67 +5284,63 @@ async function runEas(options2) {
5337
5284
  ok(`signed in as ${BOLD}${who}${RESET}`);
5338
5285
  }
5339
5286
  let projectId = await resolveProjectId();
5340
- if (!options2.skipInit) {
5341
- if (projectId) {
5342
- ok(`EAS project linked: ${projectId}`);
5343
- } else {
5344
- const result = await init();
5345
- if (!result.ok) {
5346
- bad("eas init failed");
5347
- 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)");
5348
5315
  }
5349
- projectId = result.projectId ?? null;
5350
- ok(`EAS project created: ${projectId}`);
5316
+ } catch (err) {
5317
+ bad(err instanceof Error ? err.message : String(err));
5351
5318
  }
5352
- const channels = ["development", "preview", "production"];
5353
- const createdChannels = await ensureChannels(channels);
5354
- if (createdChannels.length > 0) ok(`channels created: ${createdChannels.join(", ")}`);
5355
- else nop(`channels already exist (${channels.join(", ")})`);
5356
- const branches = ["development", "preview", "production"];
5357
- const createdBranches = await ensureBranches(branches);
5358
- if (createdBranches.length > 0) ok(`branches created: ${createdBranches.join(", ")}`);
5359
- else nop(`branches already exist (${branches.join(", ")})`);
5360
- }
5361
- if (!options2.skipEnv) {
5362
- 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) {
5363
5325
  try {
5364
- const pushed = await pushEasRoutedKeys(".env.local", ["development"]);
5326
+ const pushed = await pushEasRoutedKeys(prodFile, ["production", "preview"]);
5365
5327
  if (pushed.length > 0) {
5366
5328
  ok(
5367
- `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)`
5368
5330
  );
5369
5331
  } else {
5370
- nop(".env.local has no EAS-routed keys yet (run `vexpo convex` first)");
5332
+ nop(`${prodFile} has no EAS-routed keys`);
5371
5333
  }
5372
5334
  } catch (err) {
5373
5335
  bad(err instanceof Error ? err.message : String(err));
5374
5336
  }
5375
5337
  } else {
5376
- nop(".env.local missing. skipping development env push (run `vexpo convex` first)");
5338
+ nop("--with-prod set but no .env.prod or .env.production found");
5377
5339
  }
5378
- if (options2.withProd) {
5379
- const prodFile = await fileExists7(".env.prod") ? ".env.prod" : await fileExists7(".env.production") ? ".env.production" : null;
5380
- if (prodFile) {
5381
- try {
5382
- const pushed = await pushEasRoutedKeys(prodFile, ["production", "preview"]);
5383
- if (pushed.length > 0) {
5384
- ok(
5385
- `pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (production, preview)`
5386
- );
5387
- } else {
5388
- nop(`${prodFile} has no EAS-routed keys`);
5389
- }
5390
- } catch (err) {
5391
- bad(err instanceof Error ? err.message : String(err));
5392
- }
5393
- } else {
5394
- nop("--with-prod set but no .env.prod or .env.production found");
5395
- }
5396
- }
5397
- note(
5398
- `server-side secrets route to Convex, not EAS. run ${BOLD}vexpo env push${RESET} to sync those`
5399
- );
5400
5340
  }
5341
+ note(
5342
+ `server-side secrets route to Convex, not EAS. run ${BOLD}vexpo env push${RESET} to sync those`
5343
+ );
5401
5344
  if (projectId) {
5402
5345
  await recordStep("eas", {
5403
5346
  projectId,
@@ -5556,7 +5499,7 @@ async function liveCheckEas() {
5556
5499
  }
5557
5500
  async function liveCheckAscLink() {
5558
5501
  try {
5559
- const { ascStatus: ascStatus2 } = await import('./eas-integrations-2QVR45NE.js');
5502
+ const { ascStatus: ascStatus2 } = await import('./eas-integrations-5FVP7DI6.js');
5560
5503
  const status = await ascStatus2();
5561
5504
  return status.status === "connected";
5562
5505
  } catch {
@@ -5575,16 +5518,20 @@ async function liveCheckRotationSecrets() {
5575
5518
  "CONVEX_DEPLOY_KEY"
5576
5519
  ].every((k) => eas.has(k));
5577
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
+ }
5578
5533
  async function liveCheckLocalEnv() {
5579
- const env2 = await readAll();
5580
- return [
5581
- "CONVEX_DEPLOYMENT",
5582
- "EXPO_PUBLIC_CONVEX_URL",
5583
- "EXPO_PUBLIC_CONVEX_SITE_URL",
5584
- "EXPO_PUBLIC_SITE_URL",
5585
- "EXPO_PUBLIC_APP_BUNDLE_ID",
5586
- "EXPO_PUBLIC_APPLE_TEAM_ID"
5587
- ].every((k) => env2.has(k));
5534
+ return classifyLocalEnv(await readAll());
5588
5535
  }
5589
5536
  async function stepPrerequisites() {
5590
5537
  section("Prerequisites");
@@ -5605,12 +5552,17 @@ async function stepPrerequisites() {
5605
5552
  async function stepProbe() {
5606
5553
  section("Probe");
5607
5554
  const installOk = await nodeModulesPresent();
5608
- const localOk = await liveCheckLocalEnv();
5609
- 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();
5610
5558
  const rows = /* @__PURE__ */ new Map();
5611
5559
  rows.set("accounts", await shouldRun("accounts", async () => true));
5612
5560
  rows.set("rebrand", await shouldRun("rebrand", async () => false));
5613
- 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
+ });
5614
5566
  rows.set("better-auth", await shouldRun("better-auth", () => liveCheckBetterAuth(convex)));
5615
5567
  rows.set("resend", await shouldRun("resend", () => liveCheckResend(convex)));
5616
5568
  rows.set("asc-key", await shouldRun("asc-key", async () => false));
@@ -5631,9 +5583,8 @@ async function stepProbe() {
5631
5583
  line(
5632
5584
  ` ${BOLD}${"node_modules".padEnd(w)}${RESET} ${installOk ? `${GREEN}ok${RESET}` : `${RED}missing${RESET}`}`
5633
5585
  );
5634
- line(
5635
- ` ${BOLD}${".env.local".padEnd(w)}${RESET} ${localOk ? `${GREEN}ok${RESET}` : `${RED}missing${RESET}`}`
5636
- );
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}`);
5637
5588
  for (const [key, row] of rows) {
5638
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;
5639
5590
  line(` ${BOLD}${label.padEnd(w)}${RESET} ${mark(row.status)}`);
@@ -5643,7 +5594,7 @@ async function stepProbe() {
5643
5594
  );
5644
5595
  const needs = /* @__PURE__ */ new Map();
5645
5596
  for (const [k, row] of rows) needs.set(k, row.status === "missing");
5646
- return { rows, needs, install: !installOk, localEnv: localOk };
5597
+ return { rows, needs, install: !installOk, localEnv: localEnvState };
5647
5598
  }
5648
5599
  async function describePhase(step, probe) {
5649
5600
  const status = probe.rows.get(step)?.status;
@@ -6096,13 +6047,13 @@ async function runStep(name, state) {
6096
6047
  }
6097
6048
  if (state) completed.push(state);
6098
6049
  }
6099
- async function maybeRunStep(name, prompt, state) {
6050
+ async function maybeRunStep(name, prompt, state, defaultYes = true) {
6100
6051
  if (!process.stdin.isTTY) {
6101
6052
  nop(`non-TTY: skipping ${name} (run \`${name}\` later)`);
6102
6053
  if (state) skipped.push(state);
6103
6054
  return;
6104
6055
  }
6105
- if (!await askYesNo(prompt, true)) {
6056
+ if (!await askYesNo(prompt, defaultYes)) {
6106
6057
  nop(`skipped ${name} (run \`${name}\` later)`);
6107
6058
  if (state) skipped.push(state);
6108
6059
  return;
@@ -6124,7 +6075,7 @@ function printShipNextSteps() {
6124
6075
  }
6125
6076
  async function stepExpoDoctor() {
6126
6077
  section("expo-doctor");
6127
- const { dlx: dlx2 } = await import('./pkg-manager-PU7UPAID.js');
6078
+ const { dlx: dlx2 } = await import('./pkg-manager-DOOC6W2C.js');
6128
6079
  const { code, stdout, stderr } = await run([dlx2(), "expo-doctor"]);
6129
6080
  if (stdout.trim()) process.stderr.write(stdout);
6130
6081
  if (stderr.trim()) process.stderr.write(stderr);
@@ -6370,8 +6321,9 @@ async function runSetup(opts) {
6370
6321
  nop("vexpo apple services-id cached");
6371
6322
  }
6372
6323
  const status = probe.rows.get("apple-sign-in")?.status;
6373
- const prompt = status === "live" || status === "cached" ? "Apple Sign In is configured, rotate the JWT now?" : "Sign the Apple Sign In JWT now?";
6374
- 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);
6375
6327
  if (options.force || probe.needs.get("apple-eas-rotation-secrets")) {
6376
6328
  await maybeRunStep(
6377
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.3",
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';