@ship-et/buildatlas-cli 0.12.0 → 0.14.0
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/dist/build-source.d.ts +57 -0
- package/dist/build-source.d.ts.map +1 -0
- package/dist/build-source.js +84 -0
- package/dist/build-source.js.map +1 -0
- package/dist/build-source.test.d.ts +2 -0
- package/dist/build-source.test.d.ts.map +1 -0
- package/dist/build-source.test.js +86 -0
- package/dist/build-source.test.js.map +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +8 -0
- package/dist/client.js.map +1 -1
- package/dist/client.test.js +31 -0
- package/dist/client.test.js.map +1 -1
- package/dist/commands/build.d.ts.map +1 -1
- package/dist/commands/build.js +53 -27
- package/dist/commands/build.js.map +1 -1
- package/dist/commands/credentials-wizard.d.ts +1 -1
- package/dist/commands/credentials-wizard.d.ts.map +1 -1
- package/dist/commands/credentials-wizard.js +20 -0
- package/dist/commands/credentials-wizard.js.map +1 -1
- package/dist/commands/credentials.d.ts.map +1 -1
- package/dist/commands/credentials.js +11 -3
- package/dist/commands/credentials.js.map +1 -1
- package/dist/commands/migrate/auth.d.ts +9 -0
- package/dist/commands/migrate/auth.d.ts.map +1 -0
- package/dist/commands/migrate/auth.js +8 -0
- package/dist/commands/migrate/auth.js.map +1 -0
- package/dist/commands/migrate/build-numbers.d.ts +12 -0
- package/dist/commands/migrate/build-numbers.d.ts.map +1 -0
- package/dist/commands/migrate/build-numbers.js +34 -0
- package/dist/commands/migrate/build-numbers.js.map +1 -0
- package/dist/commands/migrate/credentials.d.ts +24 -0
- package/dist/commands/migrate/credentials.d.ts.map +1 -0
- package/dist/commands/migrate/credentials.js +78 -0
- package/dist/commands/migrate/credentials.js.map +1 -0
- package/dist/commands/migrate/eas-client.d.ts +88 -0
- package/dist/commands/migrate/eas-client.d.ts.map +1 -0
- package/dist/commands/migrate/eas-client.js +186 -0
- package/dist/commands/migrate/eas-client.js.map +1 -0
- package/dist/commands/migrate/expo-session.d.ts +10 -0
- package/dist/commands/migrate/expo-session.d.ts.map +1 -0
- package/dist/commands/migrate/expo-session.js +51 -0
- package/dist/commands/migrate/expo-session.js.map +1 -0
- package/dist/commands/migrate/index.d.ts +3 -0
- package/dist/commands/migrate/index.d.ts.map +1 -0
- package/dist/commands/migrate/index.js +117 -0
- package/dist/commands/migrate/index.js.map +1 -0
- package/dist/commands/migrate/link-project.d.ts +13 -0
- package/dist/commands/migrate/link-project.d.ts.map +1 -0
- package/dist/commands/migrate/link-project.js +74 -0
- package/dist/commands/migrate/link-project.js.map +1 -0
- package/dist/commands/migrate/retry.d.ts +8 -0
- package/dist/commands/migrate/retry.d.ts.map +1 -0
- package/dist/commands/migrate/retry.js +25 -0
- package/dist/commands/migrate/retry.js.map +1 -0
- package/dist/config.d.ts +2 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +12 -3
- package/dist/config.js.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/profiles.test.js +10 -0
- package/dist/profiles.test.js.map +1 -1
- package/dist/project-hint.d.ts.map +1 -1
- package/dist/project-hint.js +9 -5
- package/dist/project-hint.js.map +1 -1
- package/dist/spinner.d.ts +8 -0
- package/dist/spinner.d.ts.map +1 -0
- package/dist/spinner.js +35 -0
- package/dist/spinner.js.map +1 -0
- package/dist/tarball.d.ts.map +1 -1
- package/dist/tarball.js +39 -0
- package/dist/tarball.js.map +1 -1
- package/dist/tarball.test.js +54 -0
- package/dist/tarball.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Listing an Expo account's EAS projects.
|
|
3
|
+
*
|
|
4
|
+
* `meActor` and `app.byId` are proven elsewhere in this repo — BuildAtlas's own
|
|
5
|
+
* server (`packages/shared/src/expo-auth.ts`) uses both to validate a token and
|
|
6
|
+
* check project ownership. `account.byName(...).apps` is not proven anywhere in
|
|
7
|
+
* this repo; it matches the pattern eas-cli itself uses, but if Expo's schema
|
|
8
|
+
* has drifted this throws the raw GraphQL error rather than a wrong-looking
|
|
9
|
+
* empty list.
|
|
10
|
+
*/
|
|
11
|
+
const EXPO_GRAPHQL_URL = 'https://api.expo.dev/graphql';
|
|
12
|
+
async function expoGraphQL(auth, query, variables) {
|
|
13
|
+
let res;
|
|
14
|
+
try {
|
|
15
|
+
res = await fetch(EXPO_GRAPHQL_URL, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
...(auth.kind === 'token'
|
|
19
|
+
? { authorization: `Bearer ${auth.token}` }
|
|
20
|
+
: { 'expo-session': auth.sessionSecret }),
|
|
21
|
+
'content-type': 'application/json',
|
|
22
|
+
},
|
|
23
|
+
body: JSON.stringify({ query, variables }),
|
|
24
|
+
signal: AbortSignal.timeout(15_000),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
throw new Error(`Could not reach api.expo.dev: ${err.message}`);
|
|
29
|
+
}
|
|
30
|
+
if (res.status === 401 || res.status === 403) {
|
|
31
|
+
throw new Error(auth.kind === 'token' ? 'Expo rejected this token.' : 'Expo rejected this session.');
|
|
32
|
+
}
|
|
33
|
+
const body = (await res.json().catch(() => undefined));
|
|
34
|
+
const error = body?.errors?.[0];
|
|
35
|
+
if (error)
|
|
36
|
+
throw new Error(`Expo API: ${error.message ?? 'unknown error'}`);
|
|
37
|
+
if (!body?.data)
|
|
38
|
+
throw new Error(`Expo API returned no data (HTTP ${res.status}).`);
|
|
39
|
+
return body.data;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The last version Expo submitted to a store for this app, per platform — mirrors
|
|
43
|
+
* eas-cli's own `AppVersionQuery.latestVersionAsync` query (`latestAppVersionByPlatformAndApplicationIdentifier`).
|
|
44
|
+
* `applicationIdentifier` is the bundle id (iOS) or package name (Android); Expo
|
|
45
|
+
* tracks versions per identifier since an app can rebrand its bundle id over time.
|
|
46
|
+
*/
|
|
47
|
+
export async function getLatestBuildVersion(auth, appId, platform, applicationIdentifier) {
|
|
48
|
+
const { app } = await expoGraphQL(auth, `query LatestAppVersion($appId: String!, $platform: AppPlatform!, $applicationIdentifier: String!) {
|
|
49
|
+
app {
|
|
50
|
+
byId(appId: $appId) {
|
|
51
|
+
id
|
|
52
|
+
latestAppVersionByPlatformAndApplicationIdentifier(
|
|
53
|
+
platform: $platform
|
|
54
|
+
applicationIdentifier: $applicationIdentifier
|
|
55
|
+
) {
|
|
56
|
+
id
|
|
57
|
+
buildVersion
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}`, { appId, platform: platform === 'ios' ? 'IOS' : 'ANDROID', applicationIdentifier });
|
|
62
|
+
const raw = app.byId.latestAppVersionByPlatformAndApplicationIdentifier?.buildVersion;
|
|
63
|
+
if (!raw)
|
|
64
|
+
return undefined;
|
|
65
|
+
const parsed = Number.parseInt(raw, 10);
|
|
66
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The bundle id / package name EAS already has on file for this app, read off its
|
|
70
|
+
* credentials (`appleAppIdentifier.bundleIdentifier`, `androidAppCredentials.applicationIdentifier`)
|
|
71
|
+
* rather than a local app.json — so migrate works from any directory, not just
|
|
72
|
+
* the project's own checkout. Empty when the app has never had credentials
|
|
73
|
+
* generated on EAS for that platform.
|
|
74
|
+
*/
|
|
75
|
+
export async function getAppIdentifiers(auth, appId) {
|
|
76
|
+
const { app } = await expoGraphQL(auth, `query AppIdentifiers($appId: String!) {
|
|
77
|
+
app {
|
|
78
|
+
byId(appId: $appId) {
|
|
79
|
+
id
|
|
80
|
+
iosAppCredentials { appleAppIdentifier { bundleIdentifier } }
|
|
81
|
+
androidAppCredentials { applicationIdentifier }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}`, { appId });
|
|
85
|
+
return {
|
|
86
|
+
bundleIdentifier: app.byId.iosAppCredentials[0]?.appleAppIdentifier.bundleIdentifier,
|
|
87
|
+
androidPackage: app.byId.androidAppCredentials.find((c) => c.applicationIdentifier)?.applicationIdentifier ?? undefined,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The actual signing material EAS holds for this app — a distribution cert
|
|
92
|
+
* (+ its provisioning profile) for iOS, a keystore for Android, and an APNs
|
|
93
|
+
* push key if one was ever generated for push notifications — in the same
|
|
94
|
+
* base64 shape `buildatlas credentials add` already expects (`ios_dist_cert`,
|
|
95
|
+
* `ios_provisioning_profile`, `android_keystore`, `ios_push_key`), so it can be
|
|
96
|
+
* forwarded as-is.
|
|
97
|
+
*
|
|
98
|
+
* Picks the App Store build credentials for iOS (ad-hoc/enterprise/development
|
|
99
|
+
* profiles are for internal testing, not what a migrated app should ship with)
|
|
100
|
+
* and whichever Android keystore is marked default. Omits a platform, or the
|
|
101
|
+
* push key, entirely rather than guessing when EAS has never generated one.
|
|
102
|
+
*
|
|
103
|
+
* The push key is unrelated to whether OneSignal is used: OneSignal does not
|
|
104
|
+
* read from EAS at all — it needs the same .p8 uploaded to its own dashboard,
|
|
105
|
+
* which this only pulls into BuildAtlas's vault for safekeeping and for the
|
|
106
|
+
* worker to warn about when a project looks push-capable but has none stored.
|
|
107
|
+
*/
|
|
108
|
+
export async function getEasCredentials(auth, appId) {
|
|
109
|
+
const { app } = await expoGraphQL(auth, `query AppCredentials($appId: String!) {
|
|
110
|
+
app {
|
|
111
|
+
byId(appId: $appId) {
|
|
112
|
+
id
|
|
113
|
+
iosAppCredentials {
|
|
114
|
+
pushKey { keyIdentifier keyP8 }
|
|
115
|
+
iosAppBuildCredentialsList {
|
|
116
|
+
iosDistributionType
|
|
117
|
+
distributionCertificate { certificateP12 certificatePassword }
|
|
118
|
+
provisioningProfile { provisioningProfile }
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
androidAppCredentials {
|
|
122
|
+
androidAppBuildCredentialsList {
|
|
123
|
+
isDefault
|
|
124
|
+
androidKeystore { keystore keystorePassword keyAlias keyPassword }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}`, { appId });
|
|
130
|
+
const iosBuildCredentials = app.byId.iosAppCredentials.flatMap((c) => c.iosAppBuildCredentialsList);
|
|
131
|
+
const iosEntry = iosBuildCredentials.find((c) => c.iosDistributionType === 'APP_STORE' && c.distributionCertificate?.certificateP12) ??
|
|
132
|
+
iosBuildCredentials.find((c) => c.distributionCertificate?.certificateP12);
|
|
133
|
+
const androidBuildCredentials = app.byId.androidAppCredentials.flatMap((c) => c.androidAppBuildCredentialsList);
|
|
134
|
+
const androidEntry = androidBuildCredentials.find((c) => c.isDefault && c.androidKeystore) ??
|
|
135
|
+
androidBuildCredentials.find((c) => c.androidKeystore);
|
|
136
|
+
const pushKeyEntry = app.byId.iosAppCredentials.find((c) => c.pushKey)?.pushKey;
|
|
137
|
+
return {
|
|
138
|
+
ios: iosEntry?.distributionCertificate?.certificateP12
|
|
139
|
+
? {
|
|
140
|
+
p12Base64: iosEntry.distributionCertificate.certificateP12,
|
|
141
|
+
password: iosEntry.distributionCertificate.certificatePassword ?? undefined,
|
|
142
|
+
profileBase64: iosEntry.provisioningProfile?.provisioningProfile ?? undefined,
|
|
143
|
+
}
|
|
144
|
+
: undefined,
|
|
145
|
+
android: androidEntry?.androidKeystore
|
|
146
|
+
? {
|
|
147
|
+
keystoreBase64: androidEntry.androidKeystore.keystore,
|
|
148
|
+
keystorePassword: androidEntry.androidKeystore.keystorePassword,
|
|
149
|
+
keyAlias: androidEntry.androidKeystore.keyAlias,
|
|
150
|
+
keyPassword: androidEntry.androidKeystore.keyPassword ?? undefined,
|
|
151
|
+
}
|
|
152
|
+
: undefined,
|
|
153
|
+
iosPushKey: pushKeyEntry
|
|
154
|
+
? { keyId: pushKeyEntry.keyIdentifier, p8Base64: pushKeyEntry.keyP8 }
|
|
155
|
+
: undefined,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** Every app in every account this token or session can see. */
|
|
159
|
+
export async function listExpoApps(auth) {
|
|
160
|
+
const { meActor } = await expoGraphQL(auth, `{ meActor { accounts { id name } } }`);
|
|
161
|
+
if (!meActor)
|
|
162
|
+
throw new Error('Expo did not recognise this token.');
|
|
163
|
+
if (meActor.accounts.length === 0)
|
|
164
|
+
return [];
|
|
165
|
+
const PAGE_SIZE = 50;
|
|
166
|
+
const apps = [];
|
|
167
|
+
for (const account of meActor.accounts) {
|
|
168
|
+
for (let offset = 0;; offset += PAGE_SIZE) {
|
|
169
|
+
const { account: acc } = await expoGraphQL(auth, `query AppsForAccount($accountName: String!, $limit: Int!, $offset: Int!) {
|
|
170
|
+
account {
|
|
171
|
+
byName(accountName: $accountName) {
|
|
172
|
+
apps(limit: $limit, offset: $offset) { id name slug fullName }
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}`, { accountName: account.name, limit: PAGE_SIZE, offset });
|
|
176
|
+
const page = acc.byName?.apps ?? [];
|
|
177
|
+
for (const app of page) {
|
|
178
|
+
apps.push({ ...app, accountName: account.name });
|
|
179
|
+
}
|
|
180
|
+
if (page.length < PAGE_SIZE)
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return apps;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=eas-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eas-client.js","sourceRoot":"","sources":["../../../src/commands/migrate/eas-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An existing EAS/Expo session, or runs `eas login` interactively and reads
|
|
3
|
+
* the session it leaves behind. Lets this script skip the "paste a personal
|
|
4
|
+
* access token" step for anyone who already has `eas-cli` set up.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getExpoSession(): Promise<{
|
|
7
|
+
sessionSecret: string;
|
|
8
|
+
username?: string;
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=expo-session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expo-session.d.ts","sourceRoot":"","sources":["../../../src/commands/migrate/expo-session.ts"],"names":[],"mappings":"AAqCA;;;;GAIG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAc5F"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
/**
|
|
6
|
+
* `eas login` / `expo login` both write here. This is the same file `eas
|
|
7
|
+
* whoami` reads to work without EXPO_TOKEN — the GraphQL API accepts a
|
|
8
|
+
* session secret via the `expo-session` header exactly like it accepts a
|
|
9
|
+
* personal access token via `Authorization: Bearer`.
|
|
10
|
+
*/
|
|
11
|
+
async function readExpoState() {
|
|
12
|
+
try {
|
|
13
|
+
const raw = await readFile(join(homedir(), '.expo', 'state.json'), 'utf8');
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
return parsed.auth?.sessionSecret ? parsed.auth : undefined;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function runEasLogin() {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const child = spawn('npx', ['--yes', 'eas-cli', 'login'], { stdio: 'inherit' });
|
|
24
|
+
child.on('error', reject);
|
|
25
|
+
child.on('exit', (code) => {
|
|
26
|
+
if (code === 0)
|
|
27
|
+
resolve();
|
|
28
|
+
else
|
|
29
|
+
reject(new Error(`\`eas login\` exited with code ${code}`));
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* An existing EAS/Expo session, or runs `eas login` interactively and reads
|
|
35
|
+
* the session it leaves behind. Lets this script skip the "paste a personal
|
|
36
|
+
* access token" step for anyone who already has `eas-cli` set up.
|
|
37
|
+
*/
|
|
38
|
+
export async function getExpoSession() {
|
|
39
|
+
const existing = await readExpoState();
|
|
40
|
+
if (existing?.sessionSecret) {
|
|
41
|
+
return { sessionSecret: existing.sessionSecret, username: existing.username };
|
|
42
|
+
}
|
|
43
|
+
console.log('Not logged in to Expo — running `eas login`…');
|
|
44
|
+
await runEasLogin();
|
|
45
|
+
const after = await readExpoState();
|
|
46
|
+
if (!after?.sessionSecret) {
|
|
47
|
+
throw new Error('`eas login` finished but left no session. Pass --expo-token instead.');
|
|
48
|
+
}
|
|
49
|
+
return { sessionSecret: after.sessionSecret, username: after.username };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=expo-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"expo-session.js","sourceRoot":"","sources":["../../../src/commands/migrate/expo-session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC;;;;;GAKG;AACH,KAAK,UAAU,aAAa;IAC1B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6B,CAAC;QAC3D,OAAO,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;QAChF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACxB,IAAI,IAAI,KAAK,CAAC;gBAAE,OAAO,EAAE,CAAC;;gBACrB,MAAM,CAAC,IAAI,KAAK,CAAC,kCAAkC,IAAI,EAAE,CAAC,CAAC,CAAC;QACnE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,QAAQ,GAAG,MAAM,aAAa,EAAE,CAAC;IACvC,IAAI,QAAQ,EAAE,aAAa,EAAE,CAAC;QAC5B,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,aAAa,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC;IAChF,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,8CAA8C,CAAC,CAAC;IAC5D,MAAM,WAAW,EAAE,CAAC;IAEpB,MAAM,KAAK,GAAG,MAAM,aAAa,EAAE,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,aAAa,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,sEAAsE,CAAC,CAAC;IAC1F,CAAC;IACD,OAAO,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/migrate/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBzC,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4F7D"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { AtlasApiError, apiRequest } from '../../client.js';
|
|
4
|
+
import { readResolvedAppConfig } from '../../config.js';
|
|
5
|
+
import { withPrompter } from '../../prompt.js';
|
|
6
|
+
import { withSpinner } from '../../spinner.js';
|
|
7
|
+
import { requireAuth } from './auth.js';
|
|
8
|
+
import { continueBuildNumbers } from './build-numbers.js';
|
|
9
|
+
import { migrateCredentials } from './credentials.js';
|
|
10
|
+
import { getAppIdentifiers, listExpoApps } from './eas-client.js';
|
|
11
|
+
import { getExpoSession } from './expo-session.js';
|
|
12
|
+
import { offerToLinkProject } from './link-project.js';
|
|
13
|
+
export function registerMigrateCommand(program) {
|
|
14
|
+
program
|
|
15
|
+
.command('migrate')
|
|
16
|
+
.description('Pick an EAS project and create the matching BuildAtlas project')
|
|
17
|
+
.option('--expo-token <token>', 'Expo access token. Skips using an `eas login` session.')
|
|
18
|
+
.option('--slug <slug>', 'BuildAtlas slug to use (default: the EAS project slug)')
|
|
19
|
+
.option('--dir <path>', 'Project directory to read app.json / app.config from', process.cwd())
|
|
20
|
+
.action(async (opts) => {
|
|
21
|
+
await run(async () => {
|
|
22
|
+
const config = await requireAuth();
|
|
23
|
+
const dir = resolve(opts.dir);
|
|
24
|
+
const local = await readResolvedAppConfig(dir);
|
|
25
|
+
// A token, if given, is the explicit override. Otherwise reuse whatever
|
|
26
|
+
// `eas login` already set up rather than making someone paste a personal
|
|
27
|
+
// access token they may not even have handy.
|
|
28
|
+
const auth = opts.expoToken
|
|
29
|
+
? { kind: 'token', token: opts.expoToken }
|
|
30
|
+
: process.env.EXPO_TOKEN
|
|
31
|
+
? { kind: 'token', token: process.env.EXPO_TOKEN }
|
|
32
|
+
: await (async () => {
|
|
33
|
+
const session = await getExpoSession();
|
|
34
|
+
if (session.username) {
|
|
35
|
+
console.log(pc.dim(` using EAS session for ${session.username}`));
|
|
36
|
+
}
|
|
37
|
+
return { kind: 'session', sessionSecret: session.sessionSecret };
|
|
38
|
+
})();
|
|
39
|
+
const apps = await withSpinner('fetching EAS projects', () => listExpoApps(auth));
|
|
40
|
+
if (apps.length === 0)
|
|
41
|
+
throw new Error('That account has no EAS projects.');
|
|
42
|
+
const app = await pickApp(apps, local);
|
|
43
|
+
const slug = opts.slug ?? app.slug;
|
|
44
|
+
// Local app.json wins when it disagrees — it's the current source of truth for
|
|
45
|
+
// this checkout — but EAS's own credentials fill in whatever local didn't have,
|
|
46
|
+
// so migrate doesn't need to run from inside a fully-configured project dir.
|
|
47
|
+
const remote = await withSpinner('reading EAS credentials', () => getAppIdentifiers(auth, app.id)).catch(() => ({}));
|
|
48
|
+
const bundleIdentifier = local?.bundleIdentifier ?? remote.bundleIdentifier;
|
|
49
|
+
const androidPackage = local?.androidPackage ?? remote.androidPackage;
|
|
50
|
+
const existing = await apiRequest(config, `/projects/${slug}`, {
|
|
51
|
+
token: config.token,
|
|
52
|
+
}).catch((err) => {
|
|
53
|
+
if (err instanceof AtlasApiError && err.code === 'not_found')
|
|
54
|
+
return undefined;
|
|
55
|
+
throw err;
|
|
56
|
+
});
|
|
57
|
+
// Either way, end up with a project to migrate credentials into. A project
|
|
58
|
+
// that already exists is not a reason to stop — it is the common case for
|
|
59
|
+
// someone re-running `migrate` after rotating a certificate on EAS, and
|
|
60
|
+
// stopping here used to skip credentials entirely.
|
|
61
|
+
let project;
|
|
62
|
+
if (existing) {
|
|
63
|
+
console.log(`${pc.dim('already exists —')} ${pc.bold(existing.project.slug)}`);
|
|
64
|
+
project = existing.project;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
project = (await withSpinner('creating BuildAtlas project', () => apiRequest(config, '/projects', {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
token: config.token,
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
slug,
|
|
72
|
+
name: app.name,
|
|
73
|
+
easProjectId: app.id,
|
|
74
|
+
bundleIdentifier,
|
|
75
|
+
androidPackage,
|
|
76
|
+
}),
|
|
77
|
+
}))).project;
|
|
78
|
+
console.log(`${pc.green('✔')} created project ${pc.bold(project.slug)} from ${app.fullName}`);
|
|
79
|
+
}
|
|
80
|
+
await continueBuildNumbers(config, project.slug, auth, app.id, { bundleIdentifier, androidPackage });
|
|
81
|
+
await migrateCredentials(config, project.slug, auth, app.id, app.fullName);
|
|
82
|
+
if (!bundleIdentifier || !androidPackage) {
|
|
83
|
+
console.log(pc.dim(` Bundle id / package name were not both found in ${dir} or on EAS — fill in what's ` +
|
|
84
|
+
`missing with \`buildatlas projects set --project ${project.slug}\`.`));
|
|
85
|
+
}
|
|
86
|
+
await offerToLinkProject(dir, project.slug);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Skips the menu when the directory this ran from already points at one of the
|
|
92
|
+
* visible apps — matched by EAS project id first, since a slug is only unique
|
|
93
|
+
* within an account and `app.json` does not say which account it belongs to.
|
|
94
|
+
*/
|
|
95
|
+
async function pickApp(apps, local) {
|
|
96
|
+
const detected = (local?.easProjectId && apps.find((a) => a.id === local.easProjectId)) ||
|
|
97
|
+
(local?.slug && apps.find((a) => a.slug === local.slug));
|
|
98
|
+
if (detected) {
|
|
99
|
+
console.log(pc.dim(` detected ${detected.fullName} from app config`));
|
|
100
|
+
return detected;
|
|
101
|
+
}
|
|
102
|
+
return withPrompter(async (p) => {
|
|
103
|
+
const chosen = await p.select('Which EAS project?', apps.map((a) => ({ value: a.fullName, label: a.fullName, hint: a.name })));
|
|
104
|
+
return apps.find((a) => a.fullName === chosen);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function run(fn) {
|
|
108
|
+
try {
|
|
109
|
+
await fn();
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const message = err instanceof AtlasApiError ? `${err.message} (${err.code})` : err.message;
|
|
113
|
+
console.error(pc.red('✖'), message);
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/commands/migrate/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Points a local checkout's `app.json` at the migrated BuildAtlas project, via
|
|
3
|
+
* the same `expo.extra.buildatlas.project` field `resolveProject` already reads
|
|
4
|
+
* (config.ts) — so `buildatlas build` and friends work from this directory
|
|
5
|
+
* without `--project` or `$BUILDATLAS_PROJECT` from here on.
|
|
6
|
+
*
|
|
7
|
+
* Only touches a static app.json: a dynamic `app.config.js`/`.ts` is code, not
|
|
8
|
+
* data, and rewriting it would mean guessing where to inject the field. When
|
|
9
|
+
* there is no app.json at all — a project configured purely through a dynamic
|
|
10
|
+
* config — `offerToCreateAppJson` below handles that instead.
|
|
11
|
+
*/
|
|
12
|
+
export declare function offerToLinkProject(dir: string, slug: string): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=link-project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-project.d.ts","sourceRoot":"","sources":["../../../src/commands/migrate/link-project.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6CjF"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { dynamicConfigIn } from '../../config.js';
|
|
5
|
+
import { withPrompter } from '../../prompt.js';
|
|
6
|
+
/**
|
|
7
|
+
* Points a local checkout's `app.json` at the migrated BuildAtlas project, via
|
|
8
|
+
* the same `expo.extra.buildatlas.project` field `resolveProject` already reads
|
|
9
|
+
* (config.ts) — so `buildatlas build` and friends work from this directory
|
|
10
|
+
* without `--project` or `$BUILDATLAS_PROJECT` from here on.
|
|
11
|
+
*
|
|
12
|
+
* Only touches a static app.json: a dynamic `app.config.js`/`.ts` is code, not
|
|
13
|
+
* data, and rewriting it would mean guessing where to inject the field. When
|
|
14
|
+
* there is no app.json at all — a project configured purely through a dynamic
|
|
15
|
+
* config — `offerToCreateAppJson` below handles that instead.
|
|
16
|
+
*/
|
|
17
|
+
export async function offerToLinkProject(dir, slug) {
|
|
18
|
+
const path = join(dir, 'app.json');
|
|
19
|
+
const raw = await readFile(path, 'utf8').catch(() => undefined);
|
|
20
|
+
if (raw === undefined) {
|
|
21
|
+
await offerToCreateAppJson(dir, path, slug);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(raw);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const expo = parsed.expo ?? {};
|
|
32
|
+
const current = expo.extra?.buildatlas?.project ?? expo.slug;
|
|
33
|
+
if (current === slug || !process.stdin.isTTY)
|
|
34
|
+
return;
|
|
35
|
+
const link = await withPrompter((p) => p.confirm(`Point app.json at BuildAtlas project "${slug}" for this directory?`, true));
|
|
36
|
+
if (!link)
|
|
37
|
+
return;
|
|
38
|
+
expo.extra = { ...expo.extra, buildatlas: { ...expo.extra?.buildatlas, project: slug } };
|
|
39
|
+
parsed.expo = expo;
|
|
40
|
+
await writeFile(path, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
41
|
+
console.log(`${pc.green('✔')} app.json now points \`buildatlas\` commands at ${pc.bold(slug)}`);
|
|
42
|
+
// Both files existing is common — an app.config.js/ts that does `import base
|
|
43
|
+
// from './app.json'` and layers plugins/env on top. resolveProject reads app.json
|
|
44
|
+
// directly either way, so this write takes effect for `buildatlas` regardless —
|
|
45
|
+
// but `expo config` itself only sees it if the dynamic config actually spreads
|
|
46
|
+
// app.json's `extra`, which we cannot check without running that file's code.
|
|
47
|
+
const dynamic = await dynamicConfigIn(dir);
|
|
48
|
+
if (dynamic) {
|
|
49
|
+
console.log(pc.dim(` Note: ${dynamic} is also here. \`buildatlas\` reads app.json directly, so this ` +
|
|
50
|
+
`takes effect either way — but if ${dynamic} sets "extra" without spreading ` +
|
|
51
|
+
`app.json's, \`expo config\` itself won't show it.`));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* No static app.json — if there's a dynamic app.config instead, editing its
|
|
56
|
+
* JavaScript isn't safe to automate. But `resolveProject` (config.ts) checks
|
|
57
|
+
* app.json before it ever evaluates a dynamic config, and its own
|
|
58
|
+
* `noProjectAdvice` documents exactly this escape hatch: a minimal app.json
|
|
59
|
+
* holding only the buildatlas link, which reads cleanly alongside app.config.js
|
|
60
|
+
* without the two ever needing to agree on anything else.
|
|
61
|
+
*/
|
|
62
|
+
async function offerToCreateAppJson(dir, path, slug) {
|
|
63
|
+
const dynamic = await dynamicConfigIn(dir);
|
|
64
|
+
if (!dynamic || !process.stdin.isTTY)
|
|
65
|
+
return;
|
|
66
|
+
const create = await withPrompter((p) => p.confirm(`No app.json here (found ${dynamic} instead). Create one just to point \`buildatlas\` ` +
|
|
67
|
+
`commands at project "${slug}"?`, true));
|
|
68
|
+
if (!create)
|
|
69
|
+
return;
|
|
70
|
+
const content = { expo: { extra: { buildatlas: { project: slug } } } };
|
|
71
|
+
await writeFile(path, `${JSON.stringify(content, null, 2)}\n`);
|
|
72
|
+
console.log(`${pc.green('✔')} wrote ${pc.bold('app.json')} — \`buildatlas\` commands from here now use ${pc.bold(slug)}`);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=link-project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-project.js","sourceRoot":"","sources":["../../../src/commands/migrate/link-project.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs `fn` under the spinner, and on failure — a dropped connection, a 504 from
|
|
3
|
+
* a cold worker, anything transient — offers to try again rather than silently
|
|
4
|
+
* giving up on a step whose only cost of re-running is a few seconds. Skips the
|
|
5
|
+
* prompt outside a TTY (CI, piped input) since there's no one there to answer it.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withRetry<T>(label: string, fn: () => Promise<T>): Promise<T | undefined>;
|
|
8
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../../../src/commands/migrate/retry.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAW9F"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { withPrompter } from '../../prompt.js';
|
|
3
|
+
import { withSpinner } from '../../spinner.js';
|
|
4
|
+
/**
|
|
5
|
+
* Runs `fn` under the spinner, and on failure — a dropped connection, a 504 from
|
|
6
|
+
* a cold worker, anything transient — offers to try again rather than silently
|
|
7
|
+
* giving up on a step whose only cost of re-running is a few seconds. Skips the
|
|
8
|
+
* prompt outside a TTY (CI, piped input) since there's no one there to answer it.
|
|
9
|
+
*/
|
|
10
|
+
export async function withRetry(label, fn) {
|
|
11
|
+
for (;;) {
|
|
12
|
+
try {
|
|
13
|
+
return await withSpinner(label, fn);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.log(pc.dim(` ${label} failed: ${err.message}`));
|
|
17
|
+
if (!process.stdin.isTTY)
|
|
18
|
+
return undefined;
|
|
19
|
+
const retry = await withPrompter((p) => p.confirm(' Try again?', true));
|
|
20
|
+
if (!retry)
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../../../src/commands/migrate/retry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAI,KAAa,EAAE,EAAoB;IACpE,SAAS,CAAC;QACR,IAAI,CAAC;YACH,OAAO,MAAM,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,KAAK,KAAK,YAAa,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YACpE,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK;gBAAE,OAAO,SAAS,CAAC;YAC3C,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC,CAAC;YACzE,IAAI,CAAC,KAAK;gBAAE,OAAO,SAAS,CAAC;QAC/B,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/config.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export declare function defaultProject(): string | undefined;
|
|
|
37
37
|
export interface ResolvedProject {
|
|
38
38
|
slug: string;
|
|
39
39
|
/** Where the slug came from. Reported when it was inferred, not stated. */
|
|
40
|
-
source: 'flag' | 'env' | 'config' | 'app.json' | 'app.config.js';
|
|
40
|
+
source: 'flag' | 'env' | 'buildatlas.json' | 'config' | 'app.json' | 'app.config.js';
|
|
41
41
|
}
|
|
42
42
|
export interface ExpoConfigValues {
|
|
43
43
|
slug?: string;
|
|
@@ -53,6 +53,7 @@ export interface ExpoConfigValues {
|
|
|
53
53
|
/** Everything a caller might want from the app config, static or dynamic. */
|
|
54
54
|
export declare function readResolvedAppConfig(dir: string): Promise<ExpoConfigValues | undefined>;
|
|
55
55
|
export declare function resolveProject(explicit: string | undefined, dir?: string): Promise<ResolvedProject>;
|
|
56
|
+
export declare function dynamicConfigIn(dir: string): Promise<string | undefined>;
|
|
56
57
|
/**
|
|
57
58
|
* What to tell someone whose directory has no usable static config.
|
|
58
59
|
*
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAyE,CAAC;AAEtG;;;GAGG;AACH,QAAA,MAAM,YAAY;;;;iBAIhB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAEvD,eAAO,MAAM,UAAU,cAAoD,CAAC;AAS5E;;;;;;GAMG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAUhE;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,WAAW,CAAC,CAqBvD;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAOnE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,SAAS,CAEnD;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,MAAM,EAAE,MAAM,GAAG,KAAK,GAAG,iBAAiB,GAAG,QAAQ,GAAG,UAAU,GAAG,eAAe,CAAC;CACtF;AAqFD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8DAA8D;IAC9D,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,6EAA6E;AAC7E,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAiB9F;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,GAAG,GAAE,MAAsB,GAC1B,OAAO,CAAC,eAAe,CAAC,CAwC1B;AAWD,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU9E;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBlE"}
|
package/dist/config.js
CHANGED
|
@@ -5,6 +5,7 @@ import { dirname, join } from 'node:path';
|
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import pc from 'picocolors';
|
|
7
7
|
import { z } from 'zod';
|
|
8
|
+
import { readDescriptor } from './build-source.js';
|
|
8
9
|
const execFile = promisify(execFileCb);
|
|
9
10
|
/**
|
|
10
11
|
* The instance this CLI ships pointed at, so a first-time user does not have to type a
|
|
@@ -183,6 +184,13 @@ export async function resolveProject(explicit, dir = process.cwd()) {
|
|
|
183
184
|
const fromEnv = defaultProject();
|
|
184
185
|
if (fromEnv)
|
|
185
186
|
return { slug: fromEnv, source: 'env' };
|
|
187
|
+
// The descriptor, before the Expo config. `project` here says which BuildAtlas
|
|
188
|
+
// project this directory builds into; `expo.slug` is a different name for a different
|
|
189
|
+
// thing that only coincided with it by habit — and a bare React Native or Flutter
|
|
190
|
+
// project has no Expo config to read at all.
|
|
191
|
+
const descriptor = await readDescriptor(dir);
|
|
192
|
+
if (descriptor?.project)
|
|
193
|
+
return { slug: descriptor.project, source: 'buildatlas.json' };
|
|
186
194
|
// Only app.json. Evaluating a dynamic app.config.js means running the project's
|
|
187
195
|
// own JavaScript just to learn a name, which is not a trade worth making.
|
|
188
196
|
try {
|
|
@@ -204,8 +212,9 @@ export async function resolveProject(explicit, dir = process.cwd()) {
|
|
|
204
212
|
if (dynamic?.slug) {
|
|
205
213
|
return { slug: dynamic.slug, source: 'app.config.js' };
|
|
206
214
|
}
|
|
207
|
-
throw new Error('No project specified.
|
|
208
|
-
|
|
215
|
+
throw new Error('No project specified. Add "project" to buildatlas.json, pass --project <slug>, set ' +
|
|
216
|
+
'BUILDATLAS_PROJECT, or run this from a directory whose app.json has an ' +
|
|
217
|
+
`"expo.slug".\n${await noProjectAdvice(dir)}`);
|
|
209
218
|
}
|
|
210
219
|
/**
|
|
211
220
|
* The dynamic config filenames Expo accepts, in its own resolution order.
|
|
@@ -215,7 +224,7 @@ export async function resolveProject(explicit, dir = process.cwd()) {
|
|
|
215
224
|
* is exactly what the old message did.
|
|
216
225
|
*/
|
|
217
226
|
const DYNAMIC_CONFIGS = ['app.config.ts', 'app.config.js', 'app.config.mjs', 'app.config.cjs'];
|
|
218
|
-
async function dynamicConfigIn(dir) {
|
|
227
|
+
export async function dynamicConfigIn(dir) {
|
|
219
228
|
for (const name of DYNAMIC_CONFIGS) {
|
|
220
229
|
try {
|
|
221
230
|
await readFile(join(dir, name), 'utf8');
|