@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 +54 -52
- package/dist/{chunk-PYXH4J77.js → chunk-3RDUQUJW.js} +1 -27
- package/dist/{chunk-VOL7YISA.js → chunk-6O5TB4L4.js} +2 -2
- package/dist/{chunk-QFP5R25M.js → chunk-W6O77AAZ.js} +2 -20
- package/dist/cli.js +182 -219
- package/dist/eas-integrations-5FVP7DI6.js +4 -0
- package/dist/{pkg-manager-PU7UPAID.js → pkg-manager-DOOC6W2C.js} +1 -1
- package/dist/proc-QPMIGTW6.js +2 -0
- package/package.json +1 -1
- package/dist/eas-integrations-2QVR45NE.js +0 -4
- package/dist/proc-TEOPRZZ2.js +0 -2
package/README.md
CHANGED
|
@@ -3,69 +3,74 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@ramonclaudio/vexpo)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
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
|
|
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-
|
|
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
|
|
24
|
-
vexpo doctor --json
|
|
25
|
-
vexpo doctor --strict
|
|
26
|
-
|
|
27
|
-
vexpo accounts
|
|
28
|
-
vexpo rebrand
|
|
29
|
-
vexpo review-account
|
|
30
|
-
vexpo convex
|
|
31
|
-
vexpo better-auth
|
|
32
|
-
vexpo resend
|
|
33
|
-
vexpo env push .env.local + .env.prod
|
|
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
|
|
40
|
-
vexpo apple
|
|
41
|
-
vexpo apple
|
|
42
|
-
vexpo apple
|
|
43
|
-
vexpo apple jwt
|
|
44
|
-
vexpo apple
|
|
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
|
|
48
|
+
## App Store Connect
|
|
48
49
|
|
|
49
|
-
TestFlight
|
|
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
|
|
53
|
-
vexpo testflight groups create <name>
|
|
54
|
-
vexpo testflight groups view <id>
|
|
55
|
-
vexpo testflight groups delete <id>
|
|
56
|
-
vexpo testflight testers list
|
|
57
|
-
vexpo testflight invite <email>
|
|
58
|
-
vexpo testflight whats-new <buildId> <text>
|
|
59
|
-
|
|
60
|
-
vexpo asc:privacy show [file]
|
|
61
|
-
vexpo asc:privacy lint <file>
|
|
62
|
-
vexpo asc:accessibility show
|
|
63
|
-
vexpo asc:accessibility lint <file>
|
|
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
|
-
|
|
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`
|
|
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
|
-
|
|
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
|
|
104
|
-
- App bundle IDs report `platform: "UNIVERSAL"` for newer accounts.
|
|
105
|
-
- Relationship endpoints reject `limit`.
|
|
106
|
-
- `filter[platform]=SERVICES` returns 400. `doctor`
|
|
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,
|
|
42
|
+
export { currentRuntime, currentRuntimeVersion, detectPackageManager, dlx, installCmdFor };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn as spawn$1
|
|
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,
|
|
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-
|
|
4
|
-
import { dlx, detectPackageManager, installCmdFor } from './chunk-
|
|
5
|
-
import { run, spawn } from './chunk-
|
|
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.
|
|
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-
|
|
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
|
-
|
|
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
|
|
1238
|
-
if (
|
|
1239
|
-
|
|
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?.
|
|
1874
|
-
return paginatedList("/v1/
|
|
1865
|
+
if (filter?.bundleId) query["filter[bundleId]"] = filter.bundleId;
|
|
1866
|
+
return paginatedList("/v1/apps", query);
|
|
1875
1867
|
},
|
|
1876
|
-
async
|
|
1877
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
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 (
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
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
|
-
|
|
5339
|
-
|
|
5316
|
+
} catch (err) {
|
|
5317
|
+
bad(err instanceof Error ? err.message : String(err));
|
|
5340
5318
|
}
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
const
|
|
5346
|
-
|
|
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("
|
|
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 (
|
|
5329
|
+
`pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (production, preview)`
|
|
5357
5330
|
);
|
|
5358
5331
|
} else {
|
|
5359
|
-
nop(
|
|
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("
|
|
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-
|
|
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
|
-
|
|
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
|
|
5598
|
-
const
|
|
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", {
|
|
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
|
-
|
|
5624
|
-
|
|
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:
|
|
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,
|
|
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-
|
|
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
|
|
6363
|
-
|
|
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",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { currentRuntime, currentRuntimeVersion, detectPackageManager, dlx,
|
|
2
|
+
export { currentRuntime, currentRuntimeVersion, detectPackageManager, dlx, installCmdFor } from './chunk-3RDUQUJW.js';
|
package/package.json
CHANGED
package/dist/proc-TEOPRZZ2.js
DELETED