@ramonclaudio/vexpo 0.1.3 → 0.1.5
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/LICENSE +21 -0
- package/README.md +58 -55
- package/dist/{chunk-PYXH4J77.js → chunk-3RDUQUJW.js} +1 -27
- package/dist/{chunk-QFP5R25M.js → chunk-5BTLX335.js} +7 -21
- package/dist/{chunk-VOL7YISA.js → chunk-BRSFTWP2.js} +2 -2
- package/dist/cli.js +210 -234
- package/dist/eas-integrations-ZULIUD4T.js +4 -0
- package/dist/{pkg-manager-PU7UPAID.js → pkg-manager-DOOC6W2C.js} +1 -1
- package/dist/proc-L3ORJMPB.js +2 -0
- package/package.json +1 -1
- package/dist/eas-integrations-2QVR45NE.js +0 -4
- package/dist/proc-TEOPRZZ2.js +0 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ramon Claudio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -3,73 +3,79 @@
|
|
|
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
|
|
8
|
+
Scaffolded by [`create-vexpo`](https://www.npmjs.com/package/@ramonclaudio/create-vexpo) into your devDependencies. Run it with `npx vexpo`.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img src="https://raw.githubusercontent.com/ramonclaudio/vexpo/main/docs/assets/demo-doctor.gif" width="720" alt="vexpo doctor auth-checking every credential against the live services">
|
|
12
|
+
</p>
|
|
13
13
|
|
|
14
14
|
## Setup
|
|
15
15
|
|
|
16
16
|
```
|
|
17
|
-
vexpo lite Convex + Better Auth only, simulator-ready (60s)
|
|
18
|
-
vexpo lite --new same + Convex signup walkthrough for first-
|
|
17
|
+
vexpo lite Convex + Better Auth only, simulator-ready (~60s)
|
|
18
|
+
vexpo lite --new same + Convex signup walkthrough for first-timers
|
|
19
19
|
vexpo full full provisioning (Convex + Better Auth + Resend + Apple + EAS init + rebrand)
|
|
20
20
|
vexpo full --new same + walks Apple/Convex/Expo/Resend signups
|
|
21
21
|
vexpo full --skip-rebrand full setup, skip the rebrand wizard
|
|
22
22
|
|
|
23
|
-
vexpo doctor
|
|
24
|
-
vexpo doctor --json
|
|
25
|
-
vexpo doctor --strict
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
vexpo
|
|
29
|
-
vexpo
|
|
30
|
-
vexpo
|
|
31
|
-
vexpo
|
|
32
|
-
vexpo
|
|
33
|
-
vexpo
|
|
34
|
-
vexpo env
|
|
35
|
-
vexpo
|
|
36
|
-
vexpo
|
|
37
|
-
vexpo
|
|
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
|
+
vexpo doctor --redact mask identifying values (screenshots, issue reports)
|
|
27
|
+
|
|
28
|
+
vexpo accounts walk Apple/Expo/Convex/Resend signups (standalone)
|
|
29
|
+
vexpo rebrand replace template defaults with your identity
|
|
30
|
+
vexpo review-account seed the App Review demo account on Convex
|
|
31
|
+
vexpo convex provision or connect a Convex deployment
|
|
32
|
+
vexpo better-auth set SITE_URL, BETTER_AUTH_SECRET, APP_NAME on Convex
|
|
33
|
+
vexpo resend provision Resend sending key + webhook, write to Convex env
|
|
34
|
+
vexpo env push push .env.local + .env.prod to Convex + EAS env
|
|
35
|
+
vexpo env convex-key sync Convex deploy key + selector to EAS (post-migration fix)
|
|
36
|
+
vexpo adopt finish a project created by `eas integrations:convex:connect`
|
|
37
|
+
vexpo convex:migrate copy server-side Convex env from another deployment
|
|
38
|
+
vexpo asc:connect link the EAS project to its ASC app (wraps `eas integrations:asc:connect`)
|
|
38
39
|
```
|
|
39
40
|
|
|
40
41
|
## Apple
|
|
41
42
|
|
|
42
43
|
```
|
|
43
|
-
vexpo apple asc-key
|
|
44
|
-
vexpo apple
|
|
45
|
-
vexpo apple
|
|
46
|
-
vexpo apple
|
|
47
|
-
vexpo apple jwt
|
|
48
|
-
vexpo apple
|
|
44
|
+
vexpo apple asc-key validate an ASC API key against /v1/apps
|
|
45
|
+
vexpo apple asc-key --revalidate re-check the cached key without re-prompting
|
|
46
|
+
vexpo apple credentials wrap `eas credentials:configure-build` with the cached ASC key
|
|
47
|
+
vexpo apple services-id detect SIWA Services ID + attach APPLE_ID_AUTH capability
|
|
48
|
+
vexpo apple jwt sign the SIWA ES256 client_secret JWT (180-day expiry)
|
|
49
|
+
vexpo apple jwt --rotate re-sign the JWT only
|
|
50
|
+
vexpo apple eas-rotation-secrets push the 5 EAS production secrets the JWT cron needs
|
|
49
51
|
```
|
|
50
52
|
|
|
51
|
-
## App Store Connect
|
|
53
|
+
## App Store Connect
|
|
52
54
|
|
|
53
|
-
TestFlight
|
|
55
|
+
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
56
|
|
|
55
57
|
```
|
|
56
|
-
vexpo testflight groups list
|
|
57
|
-
vexpo testflight groups create <name>
|
|
58
|
-
vexpo testflight groups view <id>
|
|
59
|
-
vexpo testflight groups delete <id>
|
|
60
|
-
vexpo testflight testers list
|
|
61
|
-
vexpo testflight invite <email>
|
|
62
|
-
vexpo testflight whats-new <buildId> <text>
|
|
63
|
-
|
|
64
|
-
vexpo asc:privacy show [file]
|
|
65
|
-
vexpo asc:privacy lint <file>
|
|
66
|
-
vexpo asc:accessibility show
|
|
67
|
-
vexpo asc:accessibility lint <file>
|
|
58
|
+
vexpo testflight groups list list beta groups
|
|
59
|
+
vexpo testflight groups create <name> create a beta group
|
|
60
|
+
vexpo testflight groups view <id> view a beta group + its testers
|
|
61
|
+
vexpo testflight groups delete <id> delete a beta group
|
|
62
|
+
vexpo testflight testers list list beta testers
|
|
63
|
+
vexpo testflight invite <email> add a tester + send a TestFlight invite
|
|
64
|
+
vexpo testflight whats-new <buildId> <text> set the "What's new" notes
|
|
65
|
+
|
|
66
|
+
vexpo asc:privacy show [file] show the declared privacy.config.json
|
|
67
|
+
vexpo asc:privacy lint <file> validate privacy.config.json against Apple's enums
|
|
68
|
+
vexpo asc:accessibility show fetch the app's accessibility declarations
|
|
69
|
+
vexpo asc:accessibility lint <file> validate accessibility.config.json against Apple's enums
|
|
68
70
|
```
|
|
69
71
|
|
|
72
|
+
## Design rule: don't reinvent EAS
|
|
73
|
+
|
|
74
|
+
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>`.
|
|
75
|
+
|
|
70
76
|
## What `vexpo` doesn't wrap
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
Reach for `eas` directly for the canonical platform surface.
|
|
73
79
|
|
|
74
80
|
```bash
|
|
75
81
|
npx eas init # EAS project init
|
|
@@ -89,26 +95,23 @@ npx eas env [...] # env:push, env:pull, env:get, env:delete, env:
|
|
|
89
95
|
npx eas integrations:asc [...] # status, connect, disconnect
|
|
90
96
|
```
|
|
91
97
|
|
|
92
|
-
`vexpo full`
|
|
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.
|
|
98
|
+
`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
99
|
|
|
96
100
|
## Architecture
|
|
97
101
|
|
|
98
|
-
|
|
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+.
|
|
102
|
+
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
103
|
|
|
103
104
|
## Apple ASC API workarounds
|
|
104
105
|
|
|
105
|
-
Apple changed several ASC API behaviors after the initial CLI release. The CLI handles each one
|
|
106
|
+
Apple changed several ASC API behaviors after the initial CLI release. The CLI handles each one.
|
|
106
107
|
|
|
107
|
-
- `POST /v1/bundleIds` rejects `platform: "SERVICES"`. `services-id` walks
|
|
108
|
-
- App bundle IDs report `platform: "UNIVERSAL"` for newer accounts.
|
|
109
|
-
- Relationship endpoints reject `limit`.
|
|
110
|
-
- `filter[platform]=SERVICES` returns 400. `doctor`
|
|
108
|
+
- `POST /v1/bundleIds` rejects `platform: "SERVICES"`. `services-id` walks you through manual creation in the developer portal, then re-polls.
|
|
109
|
+
- App bundle IDs report `platform: "UNIVERSAL"` for newer accounts. The App ID lookup matches any non-SERVICES platform.
|
|
110
|
+
- Relationship endpoints reject `limit`. The capability list fetches without pagination.
|
|
111
|
+
- `filter[platform]=SERVICES` returns 400. `doctor` filters by identifier alone.
|
|
111
112
|
|
|
112
113
|
## Repo
|
|
113
114
|
|
|
114
115
|
[github.com/ramonclaudio/vexpo](https://github.com/ramonclaudio/vexpo)
|
|
116
|
+
|
|
117
|
+
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 = [];
|
|
@@ -56,7 +38,11 @@ async function run(argv, opts = {}) {
|
|
|
56
38
|
stdin: opts.stdin ?? "ignore",
|
|
57
39
|
stdout: "pipe",
|
|
58
40
|
stderr: "pipe",
|
|
59
|
-
|
|
41
|
+
// run() exists to PARSE output. A FORCE_COLOR=1 in the caller's shell
|
|
42
|
+
// (CI, recordings) makes child CLIs wrap fields in ANSI codes and every
|
|
43
|
+
// regex parser downstream silently misses. Force color off; an explicit
|
|
44
|
+
// opts.env can still override.
|
|
45
|
+
env: { FORCE_COLOR: "0", NO_COLOR: "1", ...opts.env },
|
|
60
46
|
cwd: opts.cwd
|
|
61
47
|
});
|
|
62
48
|
const [code, stdout, stderr] = await Promise.all([
|
|
@@ -67,4 +53,4 @@ async function run(argv, opts = {}) {
|
|
|
67
53
|
return { code, stdout, stderr };
|
|
68
54
|
}
|
|
69
55
|
|
|
70
|
-
export { run, spawn,
|
|
56
|
+
export { run, spawn, streamText };
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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-BRSFTWP2.js';
|
|
4
|
+
import { dlx, detectPackageManager, installCmdFor } from './chunk-3RDUQUJW.js';
|
|
5
|
+
import { run, spawn } from './chunk-5BTLX335.js';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
|
-
import { readFile, access,
|
|
7
|
+
import { readFile, access, unlink, stat, mkdir, writeFile, rename } from 'fs/promises';
|
|
8
8
|
import { createInterface } from 'readline/promises';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import { join } from 'path';
|
|
@@ -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.5"};
|
|
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-L3ORJMPB.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
|
-
|
|
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
1863
|
async list(filter) {
|
|
1883
1864
|
const query = {};
|
|
1884
|
-
if (filter?.
|
|
1885
|
-
return paginatedList("/v1/
|
|
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;
|
|
1865
|
+
if (filter?.bundleId) query["filter[bundleId]"] = filter.bundleId;
|
|
1866
|
+
return paginatedList("/v1/apps", query);
|
|
1899
1867
|
},
|
|
1900
|
-
async
|
|
1901
|
-
await request("
|
|
1902
|
-
}
|
|
1903
|
-
},
|
|
1904
|
-
profiles: {
|
|
1905
|
-
async list(filter) {
|
|
1906
|
-
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);
|
|
1910
|
-
},
|
|
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"];
|
|
@@ -2415,6 +2385,23 @@ async function loadAscFromState2() {
|
|
|
2415
2385
|
if (!existsSync(p8Path)) return null;
|
|
2416
2386
|
return { issuerId, keyId, p8Path };
|
|
2417
2387
|
}
|
|
2388
|
+
async function syncAscAppIdToEasJson(ascAppId) {
|
|
2389
|
+
if (!ascAppId || !existsSync("eas.json")) return;
|
|
2390
|
+
try {
|
|
2391
|
+
const before = await readFile("eas.json", "utf8");
|
|
2392
|
+
const after = withAscAppId(before, ascAppId);
|
|
2393
|
+
if (after !== before) {
|
|
2394
|
+
await writeFile("eas.json", after);
|
|
2395
|
+
ok(`wrote ascAppId ${BOLD}${ascAppId}${RESET} to eas.json submit profiles`);
|
|
2396
|
+
note("commit this in your fork: non-interactive `eas submit` (CI) needs it");
|
|
2397
|
+
} else {
|
|
2398
|
+
nop("eas.json submit profiles already carry ascAppId");
|
|
2399
|
+
}
|
|
2400
|
+
} catch (err) {
|
|
2401
|
+
yep(`couldn't write ascAppId to eas.json: ${err instanceof Error ? err.message : err}`);
|
|
2402
|
+
note("non-interactive submit will need `ascAppId` set manually in eas.json");
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2418
2405
|
async function runAscConnect(opts = {}) {
|
|
2419
2406
|
section("ASC connect");
|
|
2420
2407
|
if (!opts.force) {
|
|
@@ -2429,6 +2416,7 @@ async function runAscConnect(opts = {}) {
|
|
|
2429
2416
|
bundleId: status.appStoreConnectApp.bundleIdentifier,
|
|
2430
2417
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2431
2418
|
});
|
|
2419
|
+
await syncAscAppIdToEasJson(status.appStoreConnectApp.ascAppIdentifier);
|
|
2432
2420
|
return 0;
|
|
2433
2421
|
}
|
|
2434
2422
|
} catch {
|
|
@@ -2449,6 +2437,15 @@ async function runAscConnect(opts = {}) {
|
|
|
2449
2437
|
return 1;
|
|
2450
2438
|
}
|
|
2451
2439
|
ok(`bundle id: ${BOLD}${bundleId}${RESET}`);
|
|
2440
|
+
if (await ascAppExists(bundleId) === "defer") {
|
|
2441
|
+
yep("no App Store Connect app record for this bundle id yet, NOT connected");
|
|
2442
|
+
note("the ASC app record only appears after the first `eas submit`. run:");
|
|
2443
|
+
note(
|
|
2444
|
+
` ${BOLD}npx eas build -p ios --profile production --auto-submit-with-profile testflight${RESET}`
|
|
2445
|
+
);
|
|
2446
|
+
note("then re-run `npx vexpo asc:connect` to finish the EAS\u2194ASC link");
|
|
2447
|
+
return 0;
|
|
2448
|
+
}
|
|
2452
2449
|
if (!process.stdin.isTTY) {
|
|
2453
2450
|
bad("ASC connect needs a TTY: eas integrations:asc:connect can't run headless");
|
|
2454
2451
|
note("run `vexpo asc:connect` in an interactive terminal to finish the EAS\u2194ASC link");
|
|
@@ -2485,55 +2482,15 @@ async function runAscConnect(opts = {}) {
|
|
|
2485
2482
|
connectedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2486
2483
|
});
|
|
2487
2484
|
if (existsSync("eas.json")) {
|
|
2485
|
+
let postStatus = null;
|
|
2488
2486
|
try {
|
|
2489
|
-
|
|
2490
|
-
if (ascAppId) {
|
|
2491
|
-
const before = await readFile("eas.json", "utf8");
|
|
2492
|
-
const after = withAscAppId(before, ascAppId);
|
|
2493
|
-
if (after !== before) {
|
|
2494
|
-
await writeFile("eas.json", after);
|
|
2495
|
-
ok(`wrote ascAppId ${BOLD}${ascAppId}${RESET} to eas.json submit profiles`);
|
|
2496
|
-
note("commit this in your fork: non-interactive `eas submit` (CI) needs it");
|
|
2497
|
-
} else {
|
|
2498
|
-
nop("eas.json submit profiles already carry ascAppId");
|
|
2499
|
-
}
|
|
2500
|
-
}
|
|
2501
|
-
} catch (err) {
|
|
2502
|
-
yep(`couldn't write ascAppId to eas.json: ${err instanceof Error ? err.message : err}`);
|
|
2503
|
-
note("non-interactive submit will need `ascAppId` set manually in eas.json");
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
return 0;
|
|
2507
|
-
}
|
|
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;
|
|
2487
|
+
postStatus = await ascStatus();
|
|
2533
2488
|
} catch {
|
|
2489
|
+
postStatus = null;
|
|
2534
2490
|
}
|
|
2491
|
+
await syncAscAppIdToEasJson(postStatus?.appStoreConnectApp?.ascAppIdentifier);
|
|
2535
2492
|
}
|
|
2536
|
-
return
|
|
2493
|
+
return 0;
|
|
2537
2494
|
}
|
|
2538
2495
|
|
|
2539
2496
|
// src/lib/asc-accessibility.ts
|
|
@@ -3167,6 +3124,9 @@ var ROUTING = {
|
|
|
3167
3124
|
RESEND_TEST_MODE: {
|
|
3168
3125
|
routes: (c) => [{ type: "convex", key: "RESEND_TEST_MODE", channel: c }]
|
|
3169
3126
|
},
|
|
3127
|
+
REQUIRE_EMAIL_VERIFICATION: {
|
|
3128
|
+
routes: (c) => [{ type: "convex", key: "REQUIRE_EMAIL_VERIFICATION", channel: c }]
|
|
3129
|
+
},
|
|
3170
3130
|
APP_BUNDLE_ID: { routes: (c) => [{ type: "convex", key: "APP_BUNDLE_ID", channel: c }] },
|
|
3171
3131
|
APPLE_CLIENT_ID: { routes: (c) => [{ type: "convex", key: "APPLE_CLIENT_ID", channel: c }] },
|
|
3172
3132
|
APPLE_CLIENT_SECRET: {
|
|
@@ -3934,7 +3894,7 @@ async function verifyEas(ctx) {
|
|
|
3934
3894
|
"eas",
|
|
3935
3895
|
"asc-submit-id",
|
|
3936
3896
|
`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"
|
|
3897
|
+
"run `vexpo asc:connect` to write it; non-interactive `eas submit` (CI) fails without it"
|
|
3938
3898
|
)
|
|
3939
3899
|
);
|
|
3940
3900
|
} else if (existsSync("eas.json")) {
|
|
@@ -4078,7 +4038,7 @@ async function readContext(channel) {
|
|
|
4078
4038
|
() => /* @__PURE__ */ new Map()
|
|
4079
4039
|
) : Promise.resolve(/* @__PURE__ */ new Map()),
|
|
4080
4040
|
readAppConfigFacts(),
|
|
4081
|
-
|
|
4041
|
+
loadAscCreds2()
|
|
4082
4042
|
]
|
|
4083
4043
|
);
|
|
4084
4044
|
return {
|
|
@@ -4111,21 +4071,6 @@ async function readAppConfigFacts() {
|
|
|
4111
4071
|
return {};
|
|
4112
4072
|
}
|
|
4113
4073
|
}
|
|
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
4074
|
async function verifyAll(ctx) {
|
|
4130
4075
|
const [files, convex, resend, apple2, eas] = await Promise.all([
|
|
4131
4076
|
Promise.resolve(verifyFiles(ctx)),
|
|
@@ -4170,10 +4115,25 @@ function icon(severity) {
|
|
|
4170
4115
|
return `${DIM}-${RESET}`;
|
|
4171
4116
|
}
|
|
4172
4117
|
}
|
|
4118
|
+
var REDACTIONS = [
|
|
4119
|
+
[/https?:\/\/[a-z0-9-]+\.convex\.(cloud|site)[^\s]*/g, "https://<deployment>.convex.$1"],
|
|
4120
|
+
[/\b[a-z]+-[a-z]+-\d{3}\b/g, "<deployment>"],
|
|
4121
|
+
[/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi, "<project-id>"],
|
|
4122
|
+
[/\b[\w.+-]+@[\w-]+\.[\w.]+\b/g, "<email>"],
|
|
4123
|
+
[/\b(?:[a-z0-9-]+\.){1,}[a-z]{2,}\b(?= verified)/g, "<domain>"],
|
|
4124
|
+
[/\b(?:com|io|dev|app|net|org)(?:\.[a-z0-9-]+){2,}\b/gi, "<bundle-id>"],
|
|
4125
|
+
[/\b[A-Z0-9]{10}\b/g, "<id>"],
|
|
4126
|
+
[/(@)[\w-]+(\/)/g, "$1<owner>$2"]
|
|
4127
|
+
];
|
|
4128
|
+
function redactValue(text) {
|
|
4129
|
+
let out = text;
|
|
4130
|
+
for (const [re, sub] of REDACTIONS) out = out.replace(re, sub);
|
|
4131
|
+
return out;
|
|
4132
|
+
}
|
|
4173
4133
|
function categoryHeader(c) {
|
|
4174
4134
|
return c.charAt(0).toUpperCase() + c.slice(1);
|
|
4175
4135
|
}
|
|
4176
|
-
function printResults(checks) {
|
|
4136
|
+
function printResults(checks, redact) {
|
|
4177
4137
|
const byCategory = /* @__PURE__ */ new Map();
|
|
4178
4138
|
for (const c of checks) {
|
|
4179
4139
|
if (!byCategory.has(c.category)) byCategory.set(c.category, []);
|
|
@@ -4186,8 +4146,9 @@ function printResults(checks) {
|
|
|
4186
4146
|
section(categoryHeader(cat));
|
|
4187
4147
|
const w = Math.max(...items.map((c) => c.name.length));
|
|
4188
4148
|
for (const c of items) {
|
|
4189
|
-
|
|
4190
|
-
|
|
4149
|
+
const message = redact ? redactValue(c.message) : c.message;
|
|
4150
|
+
line(` ${icon(c.severity)} ${BOLD}${c.name.padEnd(w)}${RESET} ${message}`);
|
|
4151
|
+
if (c.details) line(` ${DIM}${redact ? redactValue(c.details) : c.details}${RESET}`);
|
|
4191
4152
|
}
|
|
4192
4153
|
}
|
|
4193
4154
|
}
|
|
@@ -4222,7 +4183,7 @@ async function runDoctor(options2) {
|
|
|
4222
4183
|
process.stdout.write(JSON.stringify({ channel, summary, checks }, null, 2) + "\n");
|
|
4223
4184
|
} else {
|
|
4224
4185
|
section(`Verify (${channel})`);
|
|
4225
|
-
printResults(checks);
|
|
4186
|
+
printResults(checks, options2.redact === true);
|
|
4226
4187
|
line();
|
|
4227
4188
|
const parts = [
|
|
4228
4189
|
`${GREEN}${summary.ok} ok${RESET}`,
|
|
@@ -4606,7 +4567,6 @@ async function runEnvPush(options2) {
|
|
|
4606
4567
|
return 2;
|
|
4607
4568
|
}
|
|
4608
4569
|
ok("nothing to do. all source values match destinations");
|
|
4609
|
-
await recordStep("accounts", { mode: "lite", verifiedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4610
4570
|
return 0;
|
|
4611
4571
|
}
|
|
4612
4572
|
const prodConvexWrites = entries.some(
|
|
@@ -4651,12 +4611,6 @@ async function runEnvPush(options2) {
|
|
|
4651
4611
|
return 1;
|
|
4652
4612
|
}
|
|
4653
4613
|
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
4614
|
if (!options2.noVerify) {
|
|
4661
4615
|
const haveProd = sources.some((s) => s.channel === "prod");
|
|
4662
4616
|
const verifyChannels = haveProd ? ["dev", "prod"] : ["dev"];
|
|
@@ -4716,19 +4670,21 @@ function bundleSlug(value) {
|
|
|
4716
4670
|
return value.toLowerCase().normalize("NFKD").replace(/[̀-ͯ]/g, "").replace(/[^a-z0-9]+/g, "").slice(0, 32);
|
|
4717
4671
|
}
|
|
4718
4672
|
async function promptInputs(overrides) {
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4673
|
+
const interactive = process.stdin.isTTY === true;
|
|
4674
|
+
if (interactive) {
|
|
4675
|
+
line();
|
|
4676
|
+
note(
|
|
4677
|
+
`${DIM}4 prompts. Everything else is derived. Override any with flags or edit later.${RESET}`
|
|
4678
|
+
);
|
|
4679
|
+
line();
|
|
4680
|
+
}
|
|
4681
|
+
const appName2 = overrides.appName ?? (interactive ? (await ask(` ${BOLD}App name${RESET} ${DIM}(e.g. Foobar)${RESET} > `)).trim() : "");
|
|
4726
4682
|
if (!appName2) fail("app name required");
|
|
4727
4683
|
const defaultPkg = slug(appName2);
|
|
4728
4684
|
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();
|
|
4685
|
+
const bundleId = overrides.bundleId ?? (interactive ? (await ask(` ${BOLD}Bundle ID${RESET} ${DIM}[${bundleHint}]${RESET} > `)).trim() || bundleHint : bundleHint);
|
|
4686
|
+
const ownerName = overrides.ownerName ?? (interactive ? (await ask(` ${BOLD}Your name${RESET} > `)).trim() || "Owner" : "Owner");
|
|
4687
|
+
const reviewEmail = overrides.reviewEmail ?? (interactive ? (await ask(` ${BOLD}Apple review contact email${RESET} > `)).trim() : "");
|
|
4732
4688
|
if (!reviewEmail) fail("review email required");
|
|
4733
4689
|
const packageName = overrides.packageName ?? defaultPkg;
|
|
4734
4690
|
const scheme2 = overrides.scheme ?? bundleSlug(packageName);
|
|
@@ -4759,6 +4715,20 @@ async function promptInputs(overrides) {
|
|
|
4759
4715
|
expoOwner
|
|
4760
4716
|
};
|
|
4761
4717
|
}
|
|
4718
|
+
async function syncBundleId(bundleId) {
|
|
4719
|
+
const env2 = await readAll();
|
|
4720
|
+
const current = env2.get("EXPO_PUBLIC_APP_BUNDLE_ID");
|
|
4721
|
+
if (current === bundleId) return;
|
|
4722
|
+
if (current !== void 0) await removeLines(["EXPO_PUBLIC_APP_BUNDLE_ID"]);
|
|
4723
|
+
await ensureLine("EXPO_PUBLIC_APP_BUNDLE_ID", bundleId);
|
|
4724
|
+
ok(`wrote EXPO_PUBLIC_APP_BUNDLE_ID=${bundleId} to .env.local`);
|
|
4725
|
+
if (env2.has("CONVEX_DEPLOYMENT")) {
|
|
4726
|
+
await envSet("APP_BUNDLE_ID", bundleId);
|
|
4727
|
+
ok(`Convex env: APP_BUNDLE_ID=${bundleId}`);
|
|
4728
|
+
} else {
|
|
4729
|
+
note("no Convex deployment yet; the next `vexpo convex` run carries APP_BUNDLE_ID");
|
|
4730
|
+
}
|
|
4731
|
+
}
|
|
4762
4732
|
async function backup(files, stamp) {
|
|
4763
4733
|
const dir = `.rebrand-backup/${stamp}`;
|
|
4764
4734
|
await mkdir(dir, { recursive: true });
|
|
@@ -4960,6 +4930,7 @@ async function runRebrand(options2) {
|
|
|
4960
4930
|
await rewriteAppJson();
|
|
4961
4931
|
await rewritePackageJson(inputs);
|
|
4962
4932
|
await rewriteStoreConfig(inputs);
|
|
4933
|
+
await syncBundleId(inputs.bundleId);
|
|
4963
4934
|
if (inputs.expoOwner) {
|
|
4964
4935
|
await ensureLine("EXPO_PUBLIC_EXPO_OWNER", inputs.expoOwner);
|
|
4965
4936
|
ok(`wrote EXPO_PUBLIC_EXPO_OWNER=${inputs.expoOwner} to .env.local`);
|
|
@@ -5337,67 +5308,63 @@ async function runEas(options2) {
|
|
|
5337
5308
|
ok(`signed in as ${BOLD}${who}${RESET}`);
|
|
5338
5309
|
}
|
|
5339
5310
|
let projectId = await resolveProjectId();
|
|
5340
|
-
if (
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5311
|
+
if (projectId) {
|
|
5312
|
+
ok(`EAS project linked: ${projectId}`);
|
|
5313
|
+
} else {
|
|
5314
|
+
const result = await init();
|
|
5315
|
+
if (!result.ok) {
|
|
5316
|
+
bad("eas init failed");
|
|
5317
|
+
return 1;
|
|
5318
|
+
}
|
|
5319
|
+
projectId = result.projectId ?? null;
|
|
5320
|
+
ok(`EAS project created: ${projectId}`);
|
|
5321
|
+
}
|
|
5322
|
+
const channels = ["development", "preview", "production"];
|
|
5323
|
+
const createdChannels = await ensureChannels(channels);
|
|
5324
|
+
if (createdChannels.length > 0) ok(`channels created: ${createdChannels.join(", ")}`);
|
|
5325
|
+
else nop(`channels already exist (${channels.join(", ")})`);
|
|
5326
|
+
const branches = ["development", "preview", "production"];
|
|
5327
|
+
const createdBranches = await ensureBranches(branches);
|
|
5328
|
+
if (createdBranches.length > 0) ok(`branches created: ${createdBranches.join(", ")}`);
|
|
5329
|
+
else nop(`branches already exist (${branches.join(", ")})`);
|
|
5330
|
+
if (await fileExists7(".env.local")) {
|
|
5331
|
+
try {
|
|
5332
|
+
const pushed = await pushEasRoutedKeys(".env.local", ["development"]);
|
|
5333
|
+
if (pushed.length > 0) {
|
|
5334
|
+
ok(
|
|
5335
|
+
`pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (development)`
|
|
5336
|
+
);
|
|
5337
|
+
} else {
|
|
5338
|
+
nop(".env.local has no EAS-routed keys yet (run `vexpo convex` first)");
|
|
5348
5339
|
}
|
|
5349
|
-
|
|
5350
|
-
|
|
5340
|
+
} catch (err) {
|
|
5341
|
+
bad(err instanceof Error ? err.message : String(err));
|
|
5351
5342
|
}
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
const
|
|
5357
|
-
|
|
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")) {
|
|
5343
|
+
} else {
|
|
5344
|
+
nop(".env.local missing. skipping development env push (run `vexpo convex` first)");
|
|
5345
|
+
}
|
|
5346
|
+
if (options2.withProd) {
|
|
5347
|
+
const prodFile = await fileExists7(".env.prod") ? ".env.prod" : await fileExists7(".env.production") ? ".env.production" : null;
|
|
5348
|
+
if (prodFile) {
|
|
5363
5349
|
try {
|
|
5364
|
-
const pushed = await pushEasRoutedKeys("
|
|
5350
|
+
const pushed = await pushEasRoutedKeys(prodFile, ["production", "preview"]);
|
|
5365
5351
|
if (pushed.length > 0) {
|
|
5366
5352
|
ok(
|
|
5367
|
-
`pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (
|
|
5353
|
+
`pushed ${pushed.length} EXPO_PUBLIC_* var${pushed.length === 1 ? "" : "s"} \u2192 EAS env (production, preview)`
|
|
5368
5354
|
);
|
|
5369
5355
|
} else {
|
|
5370
|
-
nop(
|
|
5356
|
+
nop(`${prodFile} has no EAS-routed keys`);
|
|
5371
5357
|
}
|
|
5372
5358
|
} catch (err) {
|
|
5373
5359
|
bad(err instanceof Error ? err.message : String(err));
|
|
5374
5360
|
}
|
|
5375
5361
|
} else {
|
|
5376
|
-
nop("
|
|
5362
|
+
nop("--with-prod set but no .env.prod or .env.production found");
|
|
5377
5363
|
}
|
|
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
5364
|
}
|
|
5365
|
+
note(
|
|
5366
|
+
`server-side secrets route to Convex, not EAS. run ${BOLD}vexpo env push${RESET} to sync those`
|
|
5367
|
+
);
|
|
5401
5368
|
if (projectId) {
|
|
5402
5369
|
await recordStep("eas", {
|
|
5403
5370
|
projectId,
|
|
@@ -5556,7 +5523,7 @@ async function liveCheckEas() {
|
|
|
5556
5523
|
}
|
|
5557
5524
|
async function liveCheckAscLink() {
|
|
5558
5525
|
try {
|
|
5559
|
-
const { ascStatus: ascStatus2 } = await import('./eas-integrations-
|
|
5526
|
+
const { ascStatus: ascStatus2 } = await import('./eas-integrations-ZULIUD4T.js');
|
|
5560
5527
|
const status = await ascStatus2();
|
|
5561
5528
|
return status.status === "connected";
|
|
5562
5529
|
} catch {
|
|
@@ -5575,16 +5542,20 @@ async function liveCheckRotationSecrets() {
|
|
|
5575
5542
|
"CONVEX_DEPLOY_KEY"
|
|
5576
5543
|
].every((k) => eas.has(k));
|
|
5577
5544
|
}
|
|
5545
|
+
var LOCAL_ENV_LITE_CORE = [
|
|
5546
|
+
"CONVEX_DEPLOYMENT",
|
|
5547
|
+
"EXPO_PUBLIC_CONVEX_URL",
|
|
5548
|
+
"EXPO_PUBLIC_CONVEX_SITE_URL",
|
|
5549
|
+
"EXPO_PUBLIC_SITE_URL",
|
|
5550
|
+
"EXPO_PUBLIC_APP_BUNDLE_ID"
|
|
5551
|
+
];
|
|
5552
|
+
var LOCAL_ENV_TEAM_ID = "EXPO_PUBLIC_APPLE_TEAM_ID";
|
|
5553
|
+
function classifyLocalEnv(env2) {
|
|
5554
|
+
if (!LOCAL_ENV_LITE_CORE.every((k) => env2.has(k))) return "missing";
|
|
5555
|
+
return env2.has(LOCAL_ENV_TEAM_ID) ? "ok" : "partial";
|
|
5556
|
+
}
|
|
5578
5557
|
async function liveCheckLocalEnv() {
|
|
5579
|
-
|
|
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));
|
|
5558
|
+
return classifyLocalEnv(await readAll());
|
|
5588
5559
|
}
|
|
5589
5560
|
async function stepPrerequisites() {
|
|
5590
5561
|
section("Prerequisites");
|
|
@@ -5605,12 +5576,17 @@ async function stepPrerequisites() {
|
|
|
5605
5576
|
async function stepProbe() {
|
|
5606
5577
|
section("Probe");
|
|
5607
5578
|
const installOk = await nodeModulesPresent();
|
|
5608
|
-
const
|
|
5609
|
-
const
|
|
5579
|
+
const localEnvState = await liveCheckLocalEnv();
|
|
5580
|
+
const convexLive = localEnvState !== "missing";
|
|
5581
|
+
const convex = convexLive ? await envMap() : /* @__PURE__ */ new Map();
|
|
5610
5582
|
const rows = /* @__PURE__ */ new Map();
|
|
5611
5583
|
rows.set("accounts", await shouldRun("accounts", async () => true));
|
|
5612
5584
|
rows.set("rebrand", await shouldRun("rebrand", async () => false));
|
|
5613
|
-
rows.set("convex", {
|
|
5585
|
+
rows.set("convex", {
|
|
5586
|
+
step: "convex",
|
|
5587
|
+
label: "convex",
|
|
5588
|
+
status: convexLive ? "live" : "missing"
|
|
5589
|
+
});
|
|
5614
5590
|
rows.set("better-auth", await shouldRun("better-auth", () => liveCheckBetterAuth(convex)));
|
|
5615
5591
|
rows.set("resend", await shouldRun("resend", () => liveCheckResend(convex)));
|
|
5616
5592
|
rows.set("asc-key", await shouldRun("asc-key", async () => false));
|
|
@@ -5631,9 +5607,8 @@ async function stepProbe() {
|
|
|
5631
5607
|
line(
|
|
5632
5608
|
` ${BOLD}${"node_modules".padEnd(w)}${RESET} ${installOk ? `${GREEN}ok${RESET}` : `${RED}missing${RESET}`}`
|
|
5633
5609
|
);
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
);
|
|
5610
|
+
const localEnvMark = localEnvState === "ok" ? `${GREEN}ok${RESET}` : localEnvState === "partial" ? `${YELLOW}partial (lite)${RESET}` : `${RED}missing${RESET}`;
|
|
5611
|
+
line(` ${BOLD}${".env.local".padEnd(w)}${RESET} ${localEnvMark}`);
|
|
5637
5612
|
for (const [key, row] of rows) {
|
|
5638
5613
|
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
5614
|
line(` ${BOLD}${label.padEnd(w)}${RESET} ${mark(row.status)}`);
|
|
@@ -5643,7 +5618,7 @@ async function stepProbe() {
|
|
|
5643
5618
|
);
|
|
5644
5619
|
const needs = /* @__PURE__ */ new Map();
|
|
5645
5620
|
for (const [k, row] of rows) needs.set(k, row.status === "missing");
|
|
5646
|
-
return { rows, needs, install: !installOk, localEnv:
|
|
5621
|
+
return { rows, needs, install: !installOk, localEnv: localEnvState };
|
|
5647
5622
|
}
|
|
5648
5623
|
async function describePhase(step, probe) {
|
|
5649
5624
|
const status = probe.rows.get(step)?.status;
|
|
@@ -6096,13 +6071,13 @@ async function runStep(name, state) {
|
|
|
6096
6071
|
}
|
|
6097
6072
|
if (state) completed.push(state);
|
|
6098
6073
|
}
|
|
6099
|
-
async function maybeRunStep(name, prompt, state) {
|
|
6074
|
+
async function maybeRunStep(name, prompt, state, defaultYes = true) {
|
|
6100
6075
|
if (!process.stdin.isTTY) {
|
|
6101
6076
|
nop(`non-TTY: skipping ${name} (run \`${name}\` later)`);
|
|
6102
6077
|
if (state) skipped.push(state);
|
|
6103
6078
|
return;
|
|
6104
6079
|
}
|
|
6105
|
-
if (!await askYesNo(prompt,
|
|
6080
|
+
if (!await askYesNo(prompt, defaultYes)) {
|
|
6106
6081
|
nop(`skipped ${name} (run \`${name}\` later)`);
|
|
6107
6082
|
if (state) skipped.push(state);
|
|
6108
6083
|
return;
|
|
@@ -6124,7 +6099,7 @@ function printShipNextSteps() {
|
|
|
6124
6099
|
}
|
|
6125
6100
|
async function stepExpoDoctor() {
|
|
6126
6101
|
section("expo-doctor");
|
|
6127
|
-
const { dlx: dlx2 } = await import('./pkg-manager-
|
|
6102
|
+
const { dlx: dlx2 } = await import('./pkg-manager-DOOC6W2C.js');
|
|
6128
6103
|
const { code, stdout, stderr } = await run([dlx2(), "expo-doctor"]);
|
|
6129
6104
|
if (stdout.trim()) process.stderr.write(stdout);
|
|
6130
6105
|
if (stderr.trim()) process.stderr.write(stderr);
|
|
@@ -6370,8 +6345,9 @@ async function runSetup(opts) {
|
|
|
6370
6345
|
nop("vexpo apple services-id cached");
|
|
6371
6346
|
}
|
|
6372
6347
|
const status = probe.rows.get("apple-sign-in")?.status;
|
|
6373
|
-
const
|
|
6374
|
-
|
|
6348
|
+
const healthy = status === "live" || status === "cached";
|
|
6349
|
+
const prompt = healthy ? "Apple Sign In is configured, rotate the JWT now?" : "Sign the Apple Sign In JWT now?";
|
|
6350
|
+
await maybeRunStep("vexpo apple jwt", prompt, "apple-sign-in", !healthy);
|
|
6375
6351
|
if (options.force || probe.needs.get("apple-eas-rotation-secrets")) {
|
|
6376
6352
|
await maybeRunStep(
|
|
6377
6353
|
"vexpo apple eas-rotation-secrets",
|
|
@@ -6488,7 +6464,7 @@ program.command("rebrand").description("Replace template defaults with your fork
|
|
|
6488
6464
|
program.command("review-account").description("Seed the App Review demo account on Convex.").option("--email <email>", "override demo email").option("--password <password>", "override demo password").option("--name <name>", "override demo display name", "App Review").option("--username <username>", "optional username").action((options2) => exitWith(runReviewAccount(options2)));
|
|
6489
6465
|
program.command("doctor").description(
|
|
6490
6466
|
"Cross-source drift detection. Auth-checks every credential, confirms IDs match across `.env.local`, Convex env, EAS env, `app.config.ts`. No eas-cli equivalent."
|
|
6491
|
-
).option("--channel <channel>", "dev | prod", "dev").option("--json", "machine-readable output", false).option("--strict", "exit non-zero on any warn", false).action((options2) => {
|
|
6467
|
+
).option("--channel <channel>", "dev | prod", "dev").option("--json", "machine-readable output", false).option("--strict", "exit non-zero on any warn", false).option("--redact", "mask identifying values (for screenshots and issue reports)", false).action((options2) => {
|
|
6492
6468
|
exitWith(runDoctor(options2));
|
|
6493
6469
|
});
|
|
6494
6470
|
program.command("adopt").description(
|
|
@@ -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