eas-cli 18.4.0 → 18.5.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.
Files changed (53) hide show
  1. package/README.md +92 -90
  2. package/build/build/android/prepareJob.js +2 -2
  3. package/build/build/ios/prepareJob.js +2 -2
  4. package/build/build/metadata.js +2 -1
  5. package/build/commandUtils/EasCommand.js +23 -2
  6. package/build/commandUtils/context/contextUtils/getProjectIdAsync.js +2 -0
  7. package/build/commandUtils/workflow/fetchLogs.js +11 -2
  8. package/build/commandUtils/workflow/types.d.ts +5 -1
  9. package/build/commandUtils/workflow/utils.js +22 -16
  10. package/build/commands/metadata/pull.d.ts +1 -0
  11. package/build/commands/metadata/pull.js +11 -4
  12. package/build/commands/metadata/push.d.ts +1 -0
  13. package/build/commands/metadata/push.js +11 -4
  14. package/build/commands/project/onboarding.js +3 -0
  15. package/build/commands/workflow/logs.js +12 -12
  16. package/build/graphql/generated.d.ts +673 -46
  17. package/build/graphql/generated.js +58 -20
  18. package/build/graphql/queries/UserQuery.js +3 -0
  19. package/build/graphql/types/Update.js +3 -0
  20. package/build/metadata/apple/config/reader.d.ts +5 -1
  21. package/build/metadata/apple/config/reader.js +8 -0
  22. package/build/metadata/apple/config/writer.d.ts +5 -1
  23. package/build/metadata/apple/config/writer.js +13 -0
  24. package/build/metadata/apple/data.d.ts +6 -2
  25. package/build/metadata/apple/rules/infoRestrictedWords.js +6 -1
  26. package/build/metadata/apple/tasks/age-rating.d.ts +1 -1
  27. package/build/metadata/apple/tasks/age-rating.js +19 -3
  28. package/build/metadata/apple/tasks/app-review-detail.js +7 -2
  29. package/build/metadata/apple/tasks/index.js +4 -0
  30. package/build/metadata/apple/tasks/previews.d.ts +18 -0
  31. package/build/metadata/apple/tasks/previews.js +208 -0
  32. package/build/metadata/apple/tasks/screenshots.d.ts +18 -0
  33. package/build/metadata/apple/tasks/screenshots.js +224 -0
  34. package/build/metadata/apple/types.d.ts +34 -1
  35. package/build/metadata/auth.d.ts +11 -1
  36. package/build/metadata/auth.js +96 -2
  37. package/build/metadata/download.d.ts +5 -1
  38. package/build/metadata/download.js +16 -8
  39. package/build/metadata/upload.d.ts +5 -1
  40. package/build/metadata/upload.js +11 -4
  41. package/build/project/projectUtils.d.ts +0 -2
  42. package/build/project/projectUtils.js +0 -12
  43. package/build/project/workflow.js +1 -1
  44. package/build/sentry.d.ts +2 -0
  45. package/build/sentry.js +22 -0
  46. package/build/update/utils.d.ts +2 -2
  47. package/build/update/utils.js +1 -0
  48. package/build/user/User.d.ts +2 -2
  49. package/build/user/User.js +3 -0
  50. package/build/user/expoBrowserAuthFlowLauncher.js +70 -13
  51. package/oclif.manifest.json +13 -1
  52. package/package.json +12 -11
  53. package/schema/metadata-0.json +36 -0
@@ -1,15 +1,19 @@
1
1
  import { ExpoConfig } from '@expo/config';
2
2
  import { SubmitProfile } from '@expo/eas-json';
3
3
  import { Analytics } from '../analytics/AnalyticsManager';
4
+ import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
4
5
  import { CredentialsContext } from '../credentials/context';
5
6
  /**
6
7
  * Generate a local store configuration from the stores.
7
8
  * Note, only App Store is supported at this time.
8
9
  */
9
- export declare function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }: {
10
+ export declare function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }: {
10
11
  projectDir: string;
11
12
  profile: SubmitProfile;
12
13
  exp: ExpoConfig;
13
14
  analytics: Analytics;
14
15
  credentialsCtx: CredentialsContext;
16
+ nonInteractive: boolean;
17
+ graphqlClient: ExpoGraphqlClient;
18
+ projectId: string;
15
19
  }): Promise<string>;
@@ -16,16 +16,21 @@ const prompts_1 = require("../prompts");
16
16
  * Generate a local store configuration from the stores.
17
17
  * Note, only App Store is supported at this time.
18
18
  */
19
- async function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }) {
19
+ async function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }) {
20
20
  const filePath = (0, resolve_1.getStaticConfigFilePath)({ projectDir, profile });
21
21
  const fileExists = await fs_extra_1.default.pathExists(filePath);
22
22
  if (fileExists) {
23
- const filePathRelative = path_1.default.relative(projectDir, filePath);
24
- const overwrite = await (0, prompts_1.confirmAsync)({
25
- message: `Do you want to overwrite the existing "${filePathRelative}"?`,
26
- });
27
- if (!overwrite) {
28
- throw new errors_1.MetadataValidationError(`Store config already exists at "${filePath}"`);
23
+ if (nonInteractive) {
24
+ log_1.default.log(`Overwriting existing store config at "${path_1.default.relative(projectDir, filePath)}".`);
25
+ }
26
+ else {
27
+ const filePathRelative = path_1.default.relative(projectDir, filePath);
28
+ const overwrite = await (0, prompts_1.confirmAsync)({
29
+ message: `Do you want to overwrite the existing "${filePathRelative}"?`,
30
+ });
31
+ if (!overwrite) {
32
+ throw new errors_1.MetadataValidationError(`Store config already exists at "${filePath}"`);
33
+ }
29
34
  }
30
35
  }
31
36
  const { app, auth } = await (0, auth_1.getAppStoreAuthAsync)({
@@ -33,6 +38,9 @@ async function downloadMetadataAsync({ projectDir, profile, exp, analytics, cred
33
38
  credentialsCtx,
34
39
  projectDir,
35
40
  profile,
41
+ nonInteractive,
42
+ graphqlClient,
43
+ projectId,
36
44
  });
37
45
  const { unsubscribeTelemetry, executionId } = await (0, telemetry_1.subscribeTelemetryAsync)(analytics, AnalyticsManager_1.MetadataEvent.APPLE_METADATA_DOWNLOAD, { app, auth });
38
46
  log_1.default.addNewLineIfNone();
@@ -40,7 +48,7 @@ async function downloadMetadataAsync({ projectDir, profile, exp, analytics, cred
40
48
  const errors = [];
41
49
  const config = (0, resolve_1.createAppleWriter)();
42
50
  const tasks = (0, tasks_1.createAppleTasks)();
43
- const taskCtx = { app };
51
+ const taskCtx = { app, projectDir };
44
52
  for (const task of tasks) {
45
53
  try {
46
54
  await task.prepareAsync({ context: taskCtx });
@@ -1,17 +1,21 @@
1
1
  import { ExpoConfig } from '@expo/config';
2
2
  import { SubmitProfile } from '@expo/eas-json';
3
3
  import { Analytics } from '../analytics/AnalyticsManager';
4
+ import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
4
5
  import { CredentialsContext } from '../credentials/context';
5
6
  /**
6
7
  * Sync a local store configuration with the stores.
7
8
  * Note, only App Store is supported at this time.
8
9
  */
9
- export declare function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }: {
10
+ export declare function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }: {
10
11
  projectDir: string;
11
12
  profile: SubmitProfile;
12
13
  exp: ExpoConfig;
13
14
  analytics: Analytics;
14
15
  credentialsCtx: CredentialsContext;
16
+ nonInteractive: boolean;
17
+ graphqlClient: ExpoGraphqlClient;
18
+ projectId: string;
15
19
  }): Promise<{
16
20
  appleLink: string;
17
21
  }>;
@@ -14,13 +14,16 @@ const prompts_1 = require("../prompts");
14
14
  * Sync a local store configuration with the stores.
15
15
  * Note, only App Store is supported at this time.
16
16
  */
17
- async function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }) {
18
- const storeConfig = await loadConfigWithValidationPromptAsync(projectDir, profile);
17
+ async function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }) {
18
+ const storeConfig = await loadConfigWithValidationPromptAsync(projectDir, profile, nonInteractive);
19
19
  const { app, auth } = await (0, auth_1.getAppStoreAuthAsync)({
20
20
  exp,
21
21
  credentialsCtx,
22
22
  projectDir,
23
23
  profile,
24
+ nonInteractive,
25
+ graphqlClient,
26
+ projectId,
24
27
  });
25
28
  const { unsubscribeTelemetry, executionId } = await (0, telemetry_1.subscribeTelemetryAsync)(analytics, AnalyticsManager_1.MetadataEvent.APPLE_METADATA_UPLOAD, { app, auth });
26
29
  log_1.default.addNewLineIfNone();
@@ -32,7 +35,7 @@ async function uploadMetadataAsync({ projectDir, profile, exp, analytics, creden
32
35
  // This version is the parent model of all changes we are going to push.
33
36
  version: config.getVersion()?.versionString,
34
37
  });
35
- const taskCtx = { app };
38
+ const taskCtx = { app, projectDir };
36
39
  for (const task of tasks) {
37
40
  try {
38
41
  await task.prepareAsync({ context: taskCtx });
@@ -55,12 +58,16 @@ async function uploadMetadataAsync({ projectDir, profile, exp, analytics, creden
55
58
  }
56
59
  return { appleLink: `https://appstoreconnect.apple.com/apps/${app.id}/appstore` };
57
60
  }
58
- async function loadConfigWithValidationPromptAsync(projectDir, profile) {
61
+ async function loadConfigWithValidationPromptAsync(projectDir, profile, nonInteractive) {
59
62
  try {
60
63
  return await (0, resolve_1.loadConfigAsync)({ projectDir, profile });
61
64
  }
62
65
  catch (error) {
63
66
  if (error instanceof errors_1.MetadataValidationError) {
67
+ if (nonInteractive) {
68
+ (0, errors_1.logMetadataValidationError)(error);
69
+ throw error;
70
+ }
64
71
  (0, errors_1.logMetadataValidationError)(error);
65
72
  log_1.default.newLine();
66
73
  log_1.default.warn('Without further updates, the current store configuration can fail to be synchronized with the App Store or pass App Store review.');
@@ -1,8 +1,6 @@
1
1
  import { ExpoConfig } from '@expo/config';
2
2
  import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
3
3
  import { AccountFragment } from '../graphql/generated';
4
- import { Actor } from '../user/User';
5
- export declare function getUsernameForBuildMetadataAndBuildJob(user: Actor): string | undefined;
6
4
  /**
7
5
  * Return a useful name describing the project config.
8
6
  * - dynamic: app.config.js
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getUsernameForBuildMetadataAndBuildJob = getUsernameForBuildMetadataAndBuildJob;
4
3
  exports.getProjectConfigDescription = getProjectConfigDescription;
5
4
  exports.isExpoUpdatesInstalled = isExpoUpdatesInstalled;
6
5
  exports.isExpoNotificationsInstalled = isExpoNotificationsInstalled;
@@ -26,17 +25,6 @@ const api_1 = require("../api");
26
25
  const AppQuery_1 = require("../graphql/queries/AppQuery");
27
26
  const log_1 = tslib_1.__importStar(require("../log"));
28
27
  const expoCli_1 = require("../utils/expoCli");
29
- function getUsernameForBuildMetadataAndBuildJob(user) {
30
- switch (user.__typename) {
31
- case 'User':
32
- return user.username;
33
- case 'SSOUser':
34
- return user.username;
35
- case 'Robot':
36
- // robot users don't have usernames
37
- return undefined;
38
- }
39
- }
40
28
  /**
41
29
  * Return a useful name describing the project config.
42
30
  * - dynamic: app.config.js
@@ -44,7 +44,7 @@ async function hasIgnoredIosProjectAsync(projectDir, vcsClient) {
44
44
  const pbxProjectPath = config_plugins_1.IOSConfig.Paths.getPBXProjectPath(projectDir);
45
45
  return await vcsClient.isFileIgnoredAsync(path_1.default.relative(vcsRootPath, pbxProjectPath));
46
46
  }
47
- finally {
47
+ catch {
48
48
  return false;
49
49
  }
50
50
  }
@@ -0,0 +1,2 @@
1
+ import * as Sentry from '@sentry/node';
2
+ export default Sentry;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const Sentry = tslib_1.__importStar(require("@sentry/node"));
5
+ const easCli_1 = require("./utils/easCli");
6
+ Sentry.init({
7
+ dsn: process.env.EAS_CLI_SENTRY_DSN,
8
+ enabled: !!process.env.EAS_CLI_SENTRY_DSN,
9
+ environment: getSentryEnvironment(),
10
+ release: easCli_1.easCliVersion ? `eas-cli@${easCli_1.easCliVersion}` : undefined,
11
+ });
12
+ Sentry.setTag('source', 'eas-cli');
13
+ function getSentryEnvironment() {
14
+ if (process.env.EXPO_LOCAL) {
15
+ return 'local';
16
+ }
17
+ else if (process.env.EXPO_STAGING) {
18
+ return 'staging';
19
+ }
20
+ return 'production';
21
+ }
22
+ exports.default = Sentry;
@@ -1,9 +1,9 @@
1
1
  import { ExpoConfig } from '@expo/config';
2
2
  import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
3
- import { AppPlatform, Robot, SsoUser, Update, UpdateBranchFragment, UpdateFragment, UpdatePublishMutation, User } from '../graphql/generated';
3
+ import { AppPlatform, PartnerActor, Robot, SsoUser, Update, UpdateBranchFragment, UpdateFragment, UpdatePublishMutation, User } from '../graphql/generated';
4
4
  import { RequestedPlatform } from '../platform';
5
5
  export type FormatUpdateParameter = Pick<Update, 'id' | 'createdAt' | 'message'> & {
6
- actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | null;
6
+ actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | Pick<PartnerActor, '__typename' | 'username'> | null;
7
7
  };
8
8
  export type UpdateJsonInfo = {
9
9
  branch: string;
@@ -110,6 +110,7 @@ function formatUpdateTitle(update) {
110
110
  let actorName;
111
111
  switch (actor?.__typename) {
112
112
  case 'User':
113
+ case 'PartnerActor':
113
114
  case 'SSOUser': {
114
115
  actorName = actor.username;
115
116
  break;
@@ -1,9 +1,9 @@
1
- import { CurrentUserQuery, Robot, SsoUser, User } from '../graphql/generated';
1
+ import { CurrentUserQuery, PartnerActor, Robot, SsoUser, User } from '../graphql/generated';
2
2
  export type Actor = NonNullable<CurrentUserQuery['meActor']>;
3
3
  /**
4
4
  * Resolve the name of the actor, either normal user, sso user or robot user.
5
5
  * This should be used whenever the "current user" needs to be displayed.
6
6
  * The display name CANNOT be used as project owner.
7
7
  */
8
- export declare function getActorDisplayName(actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | null): string;
8
+ export declare function getActorDisplayName(actor?: Pick<Robot, '__typename' | 'firstName'> | Pick<User, '__typename' | 'username'> | Pick<SsoUser, '__typename' | 'username'> | Pick<PartnerActor, '__typename' | 'username'> | null): string;
9
9
  export declare function getActorUsername(actor?: Actor): string | null;
@@ -15,6 +15,8 @@ function getActorDisplayName(actor) {
15
15
  return actor.firstName ? `${actor.firstName} (robot)` : 'robot';
16
16
  case 'SSOUser':
17
17
  return actor.username;
18
+ case 'PartnerActor':
19
+ return actor.username;
18
20
  case undefined:
19
21
  return 'unknown';
20
22
  }
@@ -23,6 +25,7 @@ function getActorUsername(actor) {
23
25
  switch (actor?.__typename) {
24
26
  case 'User':
25
27
  case 'SSOUser':
28
+ case 'PartnerActor':
26
29
  return actor.username;
27
30
  case 'Robot':
28
31
  case undefined:
@@ -4,20 +4,65 @@ exports.getSessionUsingBrowserAuthFlowAsync = getSessionUsingBrowserAuthFlowAsyn
4
4
  const tslib_1 = require("tslib");
5
5
  const assert_1 = tslib_1.__importDefault(require("assert"));
6
6
  const better_opn_1 = tslib_1.__importDefault(require("better-opn"));
7
+ const crypto_1 = tslib_1.__importDefault(require("crypto"));
7
8
  const http_1 = tslib_1.__importDefault(require("http"));
8
- const querystring_1 = tslib_1.__importDefault(require("querystring"));
9
9
  const api_1 = require("../api");
10
+ const fetch_1 = tslib_1.__importDefault(require("../fetch"));
10
11
  const log_1 = tslib_1.__importDefault(require("../log"));
12
+ const CLIENT_ID = 'eas-cli';
13
+ function generateCodeVerifier() {
14
+ return crypto_1.default.randomBytes(32).toString('base64url');
15
+ }
16
+ function generateCodeChallenge(codeVerifier) {
17
+ return crypto_1.default.createHash('sha256').update(codeVerifier).digest('base64url');
18
+ }
19
+ function generateState() {
20
+ return crypto_1.default.randomBytes(32).toString('base64url');
21
+ }
22
+ async function exchangeCodeForSessionSecretAsync({ code, codeVerifier, redirectUri, }) {
23
+ const tokenUrl = `${(0, api_1.getExpoApiBaseUrl)()}/v2/auth/token`;
24
+ const response = await (0, fetch_1.default)(tokenUrl, {
25
+ method: 'POST',
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ },
29
+ body: JSON.stringify({
30
+ grant_type: 'authorization_code',
31
+ code,
32
+ redirect_uri: redirectUri,
33
+ code_verifier: codeVerifier,
34
+ client_id: CLIENT_ID,
35
+ }),
36
+ });
37
+ const result = await response.json();
38
+ const sessionSecret = result?.data?.session_secret;
39
+ if (!sessionSecret) {
40
+ throw new Error('Failed to obtain session secret from token exchange.');
41
+ }
42
+ return sessionSecret;
43
+ }
11
44
  async function getSessionUsingBrowserAuthFlowAsync({ sso = false }) {
12
45
  const scheme = 'http';
13
46
  const hostname = 'localhost';
14
- const path = '/auth/callback';
47
+ const callbackPath = '/auth/callback';
15
48
  const expoWebsiteUrl = (0, api_1.getExpoWebsiteBaseUrl)();
49
+ const codeVerifier = generateCodeVerifier();
50
+ const codeChallenge = generateCodeChallenge(codeVerifier);
51
+ const state = generateState();
52
+ const buildRedirectUri = (port) => `${scheme}://${hostname}:${port}${callbackPath}`;
16
53
  const buildExpoLoginUrl = (port, sso) => {
17
- const params = querystring_1.default.stringify({
18
- confirm_account: true,
19
- app_redirect_uri: `${scheme}://${hostname}:${port}${path}`,
20
- });
54
+ // Note: we avoid URLSearchParams here because better-opn calls encodeURI()
55
+ // on the URL before passing it to AppleScript, which would double-encode
56
+ // the percent-encoded values from URLSearchParams.toString().
57
+ const params = [
58
+ `client_id=${CLIENT_ID}`,
59
+ `redirect_uri=${buildRedirectUri(port)}`,
60
+ `response_type=code`,
61
+ `code_challenge=${codeChallenge}`,
62
+ `code_challenge_method=S256`,
63
+ `state=${state}`,
64
+ `confirm_account=true`,
65
+ ].join('&');
21
66
  return `${expoWebsiteUrl}${sso ? '/sso-login' : '/login'}?${params}`;
22
67
  };
23
68
  // Start server and begin auth flow
@@ -34,22 +79,34 @@ async function getSessionUsingBrowserAuthFlowAsync({ sso = false }) {
34
79
  connection.destroy();
35
80
  }
36
81
  };
37
- try {
82
+ const handleRequestAsync = async () => {
38
83
  if (!(request.method === 'GET' && request.url?.includes('/auth/callback'))) {
39
84
  throw new Error('Unexpected login response.');
40
85
  }
41
86
  const url = new URL(request.url, `http:${request.headers.host}`);
42
- const sessionSecret = url.searchParams.get('session_secret');
43
- if (!sessionSecret) {
44
- throw new Error('Request missing session_secret search parameter.');
87
+ const code = url.searchParams.get('code');
88
+ const returnedState = url.searchParams.get('state');
89
+ if (!code) {
90
+ throw new Error('Request missing code search parameter.');
91
+ }
92
+ if (returnedState !== state) {
93
+ throw new Error('State mismatch. Possible CSRF attack.');
45
94
  }
95
+ const address = server.address();
96
+ (0, assert_1.default)(address !== null && typeof address === 'object');
97
+ const redirectUri = buildRedirectUri(address.port);
98
+ const sessionSecret = await exchangeCodeForSessionSecretAsync({
99
+ code,
100
+ codeVerifier,
101
+ redirectUri,
102
+ });
46
103
  resolve(sessionSecret);
47
104
  redirectAndCleanup('success');
48
- }
49
- catch (error) {
105
+ };
106
+ handleRequestAsync().catch(error => {
50
107
  redirectAndCleanup('error');
51
108
  reject(error);
52
- }
109
+ });
53
110
  });
54
111
  server.listen(0, hostname, () => {
55
112
  log_1.default.log('Waiting for browser login...');
@@ -5895,6 +5895,12 @@
5895
5895
  "hasDynamicHelp": false,
5896
5896
  "multiple": false,
5897
5897
  "type": "option"
5898
+ },
5899
+ "non-interactive": {
5900
+ "description": "Run the command in non-interactive mode.",
5901
+ "name": "non-interactive",
5902
+ "allowNo": false,
5903
+ "type": "boolean"
5898
5904
  }
5899
5905
  },
5900
5906
  "hasDynamicHelp": false,
@@ -5971,6 +5977,12 @@
5971
5977
  "hasDynamicHelp": false,
5972
5978
  "multiple": false,
5973
5979
  "type": "option"
5980
+ },
5981
+ "non-interactive": {
5982
+ "description": "Run the command in non-interactive mode.",
5983
+ "name": "non-interactive",
5984
+ "allowNo": false,
5985
+ "type": "boolean"
5974
5986
  }
5975
5987
  },
5976
5988
  "hasDynamicHelp": false,
@@ -9713,5 +9725,5 @@
9713
9725
  ]
9714
9726
  }
9715
9727
  },
9716
- "version": "18.4.0"
9728
+ "version": "18.5.0"
9717
9729
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eas-cli",
3
- "version": "18.4.0",
3
+ "version": "18.5.0",
4
4
  "description": "EAS command line tool",
5
5
  "keywords": [
6
6
  "cli",
@@ -28,7 +28,7 @@
28
28
  "scripts": {
29
29
  "postpack": "rimraf oclif.manifest.json",
30
30
  "prepack": "yarn rebuild && node ./scripts/prepack.js",
31
- "rebuild": "rimraf build *.tsbuildinfo && yarn build",
31
+ "rebuild": "rimraf build \"*.tsbuildinfo\" && yarn build",
32
32
  "build": "yarn typecheck-for-build && yarn copy-new-templates",
33
33
  "build-allow-unused": "tsc --project tsconfig.allowUnused.json",
34
34
  "watch": "yarn typecheck-for-build --watch --preserveWatchOutput",
@@ -39,19 +39,19 @@
39
39
  "version": "yarn run -T oclif readme && node scripts/patch-readme && git add README.md",
40
40
  "generate-graphql-code": "graphql-codegen --config graphql-codegen.yml",
41
41
  "verify-graphql-code": "./scripts/verify-graphql.sh",
42
- "clean": "rimraf dist build tmp node_modules yarn-error.log *.tsbuildinfo",
42
+ "clean": "rimraf dist build tmp node_modules yarn-error.log \"*.tsbuildinfo\"",
43
43
  "copy-new-templates": "rimraf build/commandUtils/new/templates && mkdir -p build/commandUtils/new && cp -r src/commandUtils/new/templates build/commandUtils/new"
44
44
  },
45
45
  "dependencies": {
46
- "@expo/apple-utils": "2.1.13",
46
+ "@expo/apple-utils": "2.1.16",
47
47
  "@expo/code-signing-certificates": "0.0.5",
48
- "@expo/config": "10.0.6",
49
- "@expo/config-plugins": "9.0.12",
50
- "@expo/eas-build-job": "18.4.0",
51
- "@expo/eas-json": "18.4.0",
48
+ "@expo/config": "55.0.10",
49
+ "@expo/config-plugins": "55.0.7",
50
+ "@expo/eas-build-job": "18.5.0",
51
+ "@expo/eas-json": "18.5.0",
52
52
  "@expo/env": "^1.0.0",
53
53
  "@expo/json-file": "8.3.3",
54
- "@expo/logger": "18.0.1",
54
+ "@expo/logger": "18.5.0",
55
55
  "@expo/multipart-body-parser": "2.0.0",
56
56
  "@expo/osascript": "2.1.4",
57
57
  "@expo/package-manager": "1.9.10",
@@ -63,11 +63,12 @@
63
63
  "@expo/results": "1.0.0",
64
64
  "@expo/rudder-sdk-node": "1.1.1",
65
65
  "@expo/spawn-async": "1.7.2",
66
- "@expo/steps": "18.4.0",
66
+ "@expo/steps": "18.5.0",
67
67
  "@expo/timeago.js": "1.0.0",
68
68
  "@oclif/core": "^4.8.3",
69
69
  "@oclif/plugin-autocomplete": "^3.2.40",
70
70
  "@segment/ajv-human-errors": "^2.1.2",
71
+ "@sentry/node": "7.77.0",
71
72
  "@urql/core": "4.0.11",
72
73
  "@urql/exchange-retry": "1.2.0",
73
74
  "ajv": "8.11.0",
@@ -243,5 +244,5 @@
243
244
  "engines": {
244
245
  "node": ">=20.0.0"
245
246
  },
246
- "gitHead": "4e202db843be2dca6450af4b45ee76b226a662ea"
247
+ "gitHead": "2e2dddb59ae9df5ad2395c74f77701341103b62e"
247
248
  }
@@ -315,6 +315,42 @@
315
315
  "liveEdits": true,
316
316
  "optional": true
317
317
  }
318
+ },
319
+ "screenshots": {
320
+ "type": "object",
321
+ "description": "Screenshots for this locale, organized by display type (e.g., APP_IPHONE_67, APP_IPAD_PRO_129)",
322
+ "additionalProperties": {
323
+ "type": "array",
324
+ "items": {
325
+ "type": "string"
326
+ }
327
+ }
328
+ },
329
+ "previews": {
330
+ "type": "object",
331
+ "description": "Video previews for this locale, organized by display type (e.g., IPHONE_67, IPAD_PRO_129)",
332
+ "additionalProperties": {
333
+ "oneOf": [
334
+ {
335
+ "type": "string"
336
+ },
337
+ {
338
+ "type": "object",
339
+ "properties": {
340
+ "path": {
341
+ "type": "string",
342
+ "description": "Video file path (relative to project root)"
343
+ },
344
+ "previewFrameTimeCode": {
345
+ "type": "string",
346
+ "description": "Optional preview frame time code (e.g., '00:05:00' for 5 seconds)"
347
+ }
348
+ },
349
+ "required": ["path"],
350
+ "additionalProperties": false
351
+ }
352
+ ]
353
+ }
318
354
  }
319
355
  }
320
356
  },