expo-release 2.3.1 → 2.4.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/operate.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { DeployType, Operation, ReleasePlatform } from './args';
2
+ import { type EASBuild } from './decide';
2
3
  import { type CommandRunner } from './run';
3
4
  export type { Operation };
4
5
  export type OperationInput = {
@@ -18,16 +19,22 @@ export type OperationInput = {
18
19
  updateExtraArgs?: string;
19
20
  exportSourceMaps?: boolean;
20
21
  dryRun?: boolean;
22
+ /** Called with the native build `eas build` queued, when it printed one. */
23
+ onQueuedBuild?: (build: EASBuild) => void;
21
24
  readPackageJson?: () => Promise<unknown>;
22
25
  hasSourceMaps?: () => Promise<boolean>;
23
26
  };
24
27
  export type OperationResult = 'skipped' | 'ota' | 'native-build' | 'recovery';
28
+ /** Splits like a shell: whitespace separates, single or double quotes group. */
29
+ export declare function splitArgs(value: string | undefined): string[];
25
30
  export declare function updateMessage(input: {
26
31
  profile: string;
27
32
  fingerprint?: string;
28
33
  message?: string;
29
34
  }): string;
30
35
  export declare function parseExpoSdkMajor(version: string | undefined): number | null;
36
+ /** The Expo SDK major from package.json, or null when it is not a version. */
37
+ export declare function readExpoSdkMajor(readPackageJson?: () => Promise<unknown>): Promise<number | null>;
31
38
  export declare function requireEnvironmentIfNeeded(input: Pick<OperationInput, 'environment' | 'readPackageJson'>): Promise<void>;
32
39
  export declare function operateRelease(input: OperationInput, commandRunner?: CommandRunner): Promise<OperationResult>;
33
- export declare function writeOperateGitHubOutput(result: OperationResult): Promise<void>;
40
+ export declare function writeOperateGitHubOutput(result: OperationResult, queuedBuild?: EASBuild): Promise<void>;
package/dist/run.d.ts CHANGED
@@ -1,17 +1,57 @@
1
1
  export type CommandRunner = (command: string[]) => Promise<string>;
2
+ /**
3
+ * The app's own eas-cli (node_modules/.bin, searched up to the repository
4
+ * root) wins over PATH: its lockfile pins the version. `EAS_BIN` wins over both.
5
+ */
2
6
  export declare function easCommand(...args: string[]): string[];
7
+ /** `node_modules/.bin/<name>` from the working directory up to the repository root. */
8
+ export declare function localBin(name: string): string | null;
9
+ /** A failed command. `tail` holds its last non-empty output lines. */
10
+ export declare class CommandError extends Error {
11
+ readonly tail: string[];
12
+ constructor(message: string, tail: string[]);
13
+ }
14
+ /** Runs a command for its stdout. stderr streams to the log. */
3
15
  export declare function runCaptured(command: string[]): Promise<string>;
16
+ /** Like runCaptured, but prints nothing: for checks that quote the error. */
17
+ export declare function runSilent(command: string[]): Promise<string>;
18
+ /** Runs a command with its output in the log. Resolves its stdout. */
19
+ export declare function runInherited(command: string[]): Promise<string>;
4
20
  export declare function describeFailure(command: string[], exitCode: number, output: {
5
21
  stderr: string;
6
22
  stdout: string;
7
23
  }): string;
8
- export declare function runInherited(command: string[]): Promise<string>;
24
+ /**
25
+ * Parses the last JSON document in `output`. `preamble` is whatever came
26
+ * before it: eas-cli prints warnings to stdout before it switches to JSON-only
27
+ * output, for example when `npx expo config` failed and it fell back.
28
+ */
29
+ export declare function parseJsonDocument(output: string): {
30
+ value: unknown;
31
+ preamble: string;
32
+ };
33
+ /** Parses JSON output, logging any text printed before it. */
9
34
  export declare function parseJson(output: string): unknown;
10
35
  export declare function getRecord(value: unknown, context: string): Record<string, unknown>;
11
36
  export declare function parseFingerprintHash(output: string): string;
12
37
  export declare function quoteCommand(command: string[]): string;
13
38
  export declare function dryRunCommand(command: string[]): void;
39
+ export type Stage = 'doctor' | 'diff' | 'fingerprint' | 'build list' | 'errored build' | 'fingerprint diff' | 'native build' | 'ota export' | 'ota update' | 'pre-update' | 'post-update' | 'recovery';
40
+ /** A failed release step: what ran, and the last lines it printed. */
41
+ export declare class StageError extends Error {
42
+ readonly stage: Stage;
43
+ readonly tail: string[];
44
+ constructor(stage: Stage, message: string, tail?: string[]);
45
+ }
46
+ /** Runs one release step, logs how long it took, and names it on failure. */
47
+ export declare function stage<T>(name: Stage, run: () => Promise<T>): Promise<T>;
48
+ /**
49
+ * The fingerprint fails when eas-cli printed anything before its JSON: that
50
+ * means the app config did not load and eas-cli fell back to its bundled
51
+ * `@expo/config`, whose fingerprint may not be the one EAS builds with.
52
+ * `explainConfig` runs `expo config` to surface the real error.
53
+ */
14
54
  export declare function generateFingerprint(input: {
15
55
  platform: string;
16
56
  profile: string;
17
- }, commandRunner: CommandRunner): Promise<string>;
57
+ }, commandRunner: CommandRunner, explainConfig?: () => Promise<string>): Promise<string>;
@@ -1,3 +1,3 @@
1
1
  export { ErrorBoundary, type ErrorBoundaryFallbackProps, type ErrorBoundaryProps } from './error-boundary';
2
- export { checkForOTAUpdate, type ReloadApp, type UseOTAUpdatesOptions, useOTAUpdates } from './ota';
2
+ export { checkForOTAUpdate, getReleaseInfo, type ReleaseInfo, type ReloadApp, type UseOTAUpdatesOptions, useOTAUpdates } from './ota';
3
3
  export { createPushNotifications, type KeyValueStorage, type NotificationListeners, type PushNotifications, type PushNotificationsOptions, type UsePushNotificationsOptions, usePushNotifications } from './push';
@@ -21,29 +21,76 @@ class ErrorBoundary extends Component {
21
21
  // src/runtime/ota.ts
22
22
  import * as Updates from "expo-updates";
23
23
  import { useEffect, useRef } from "react";
24
- async function checkForOTAUpdate(onUpdateReady) {
24
+ import { AppState } from "react-native";
25
+ function getReleaseInfo() {
26
+ return {
27
+ updateId: Updates.updateId ?? null,
28
+ runtimeVersion: Updates.runtimeVersion ?? null,
29
+ channel: Updates.channel ?? null,
30
+ createdAt: Updates.createdAt ?? null,
31
+ isEmbeddedLaunch: Updates.isEmbeddedLaunch === true
32
+ };
33
+ }
34
+ async function downloadUpdate() {
25
35
  const update = await Updates.checkForUpdateAsync();
26
36
  if (!update.isAvailable)
27
- return false;
37
+ return null;
28
38
  const result = await Updates.fetchUpdateAsync();
29
39
  if (!result.isNew)
40
+ return null;
41
+ const id = result.manifest?.id;
42
+ return typeof id === "string" ? id : "";
43
+ }
44
+ async function checkForOTAUpdate(onUpdateReady) {
45
+ if (await downloadUpdate() === null)
30
46
  return false;
31
47
  onUpdateReady(() => Updates.reloadAsync());
32
48
  return true;
33
49
  }
34
50
  function useOTAUpdates(onUpdateReady, options = {}) {
35
51
  const {
36
- enabled = Updates.isEnabled && !(typeof __DEV__ !== "undefined" && __DEV__)
52
+ enabled = Updates.isEnabled && !(typeof __DEV__ !== "undefined" && __DEV__),
53
+ recheckOnForeground
37
54
  } = options;
38
55
  const callbacks = useRef({ onUpdateReady, onError: options.onError });
39
56
  callbacks.current = { onUpdateReady, onError: options.onError };
40
- const checked = useRef(false);
57
+ const state = useRef({
58
+ checked: false,
59
+ running: false,
60
+ lastCheck: 0,
61
+ readyId: null
62
+ });
41
63
  useEffect(() => {
42
- if (!enabled || checked.current)
64
+ if (!enabled)
65
+ return;
66
+ const check = () => {
67
+ const current = state.current;
68
+ if (current.running)
69
+ return;
70
+ current.running = true;
71
+ current.lastCheck = Date.now();
72
+ downloadUpdate().then((id) => {
73
+ if (id === null || id !== "" && id === current.readyId)
74
+ return;
75
+ current.readyId = id;
76
+ callbacks.current.onUpdateReady(() => Updates.reloadAsync());
77
+ }).catch((error) => (callbacks.current.onError ?? ((e) => console.warn("OTA update check failed", e)))(error)).finally(() => {
78
+ current.running = false;
79
+ });
80
+ };
81
+ if (!state.current.checked) {
82
+ state.current.checked = true;
83
+ check();
84
+ }
85
+ if (recheckOnForeground === undefined)
43
86
  return;
44
- checked.current = true;
45
- checkForOTAUpdate((reload) => callbacks.current.onUpdateReady(reload)).catch((error) => callbacks.current.onError?.(error));
46
- }, [enabled]);
87
+ const subscription = AppState.addEventListener("change", (next) => {
88
+ if (next === "active" && Date.now() - state.current.lastCheck >= recheckOnForeground * 60000) {
89
+ check();
90
+ }
91
+ });
92
+ return () => subscription.remove();
93
+ }, [enabled, recheckOnForeground]);
47
94
  }
48
95
  // src/runtime/push.ts
49
96
  import * as Device from "expo-device";
@@ -68,12 +115,19 @@ function createPushNotifications(options) {
68
115
  shouldSetBadge: false
69
116
  }
70
117
  });
71
- Notifications.setNotificationChannelAsync("default", options.androidChannel ?? {
118
+ function setAndroidChannel(channel = {
72
119
  name: "Default",
73
120
  importance: Notifications.AndroidImportance.HIGH,
74
121
  vibrationPattern: [0, 250, 250, 250]
75
- }).catch((error) => onError("Failed to create the notification channel", error));
122
+ }) {
123
+ return Notifications.setNotificationChannelAsync("default", channel).then(() => {
124
+ return;
125
+ }).catch((error) => onError("Failed to create the notification channel", error));
126
+ }
127
+ setAndroidChannel(options.androidChannel);
76
128
  let pendingPrompt = null;
129
+ const deliveredResponses = new Set;
130
+ const responseKey = (response) => `${response.notification?.request?.identifier}:${response.notification?.date}:${response.actionIdentifier}`;
77
131
  const getPermissionStatus = () => Notifications.getPermissionsAsync();
78
132
  async function read(key) {
79
133
  try {
@@ -163,6 +217,7 @@ function createPushNotifications(options) {
163
217
  requestPermissions,
164
218
  getPushToken,
165
219
  syncToken,
220
+ setAndroidChannel,
166
221
  prompt() {
167
222
  pendingPrompt ??= runPrompt().catch((error) => {
168
223
  onError("Failed to prompt for notifications", error);
@@ -185,14 +240,18 @@ function createPushNotifications(options) {
185
240
  listen({ onReceived, onResponse } = {}) {
186
241
  const refresh = (devicePushToken) => refreshToken(devicePushToken).catch((error) => onError("Failed to refresh the push token", error));
187
242
  refresh();
243
+ const deliver = onResponse && ((response) => {
244
+ deliveredResponses.add(responseKey(response));
245
+ onResponse(response);
246
+ });
188
247
  const subscriptions = [
189
248
  Notifications.addPushTokenListener(refresh),
190
249
  onReceived && Notifications.addNotificationReceivedListener(onReceived),
191
- onResponse && Notifications.addNotificationResponseReceivedListener(onResponse)
250
+ deliver && Notifications.addNotificationResponseReceivedListener(deliver)
192
251
  ];
193
- const launchResponse = onResponse ? Notifications.getLastNotificationResponse() : null;
194
- if (launchResponse)
195
- onResponse?.(launchResponse);
252
+ const launchResponse = deliver ? Notifications.getLastNotificationResponse() : null;
253
+ if (launchResponse && !deliveredResponses.has(responseKey(launchResponse)))
254
+ deliver?.(launchResponse);
196
255
  return () => {
197
256
  for (const subscription of subscriptions)
198
257
  subscription?.remove();
@@ -254,6 +313,7 @@ export {
254
313
  ErrorBoundary,
255
314
  checkForOTAUpdate,
256
315
  createPushNotifications,
316
+ getReleaseInfo,
257
317
  useOTAUpdates,
258
318
  usePushNotifications
259
319
  };
@@ -1,5 +1,17 @@
1
1
  /** Restarts the app on the downloaded update. */
2
2
  export type ReloadApp = () => Promise<void>;
3
+ /** Which release is running, for crash reports and a version screen. */
4
+ export interface ReleaseInfo {
5
+ /** The running EAS update; null for the build's embedded bundle and in Expo Go. */
6
+ updateId: string | null;
7
+ runtimeVersion: string | null;
8
+ channel: string | null;
9
+ /** When the running update (or the embedded bundle) was published. */
10
+ createdAt: Date | null;
11
+ isEmbeddedLaunch: boolean;
12
+ }
13
+ /** The running release, read from expo-updates. Safe in Expo Go and development. */
14
+ export declare function getReleaseInfo(): ReleaseInfo;
3
15
  /**
4
16
  * Checks for an EAS update and downloads it. Calls `onUpdateReady` once a new
5
17
  * update is on disk; show a "Restart" prompt there and call `reload` to apply.
@@ -9,11 +21,19 @@ export declare function checkForOTAUpdate(onUpdateReady: (reload: ReloadApp) =>
9
21
  export interface UseOTAUpdatesOptions {
10
22
  /** Defaults to off in development and wherever expo-updates is disabled (Expo Go). */
11
23
  enabled?: boolean;
12
- /** A failed check is retried on the next launch; defaults to ignoring it. */
24
+ /**
25
+ * Check again when the app returns to the foreground, at most once per this
26
+ * many minutes. Off by default: an app kept open for days then gets a fix
27
+ * only after the OS kills it.
28
+ */
29
+ recheckOnForeground?: number;
30
+ /** A failed check is retried on the next launch or foreground. Defaults to console.warn. */
13
31
  onError?: (error: unknown) => void;
14
32
  }
15
33
  /**
16
- * Checks for an update once per mount. The callbacks are read through refs, so
17
- * an unstable toast function never re-runs the check or stacks prompts.
34
+ * Checks for an update once per mount, and on foreground with
35
+ * `recheckOnForeground`. `onUpdateReady` runs once per downloaded update. The
36
+ * callbacks are read through refs, so an unstable toast function never re-runs
37
+ * the check or stacks prompts.
18
38
  */
19
39
  export declare function useOTAUpdates(onUpdateReady: (reload: ReloadApp) => void, options?: UseOTAUpdatesOptions): void;
@@ -19,7 +19,10 @@ export interface PushNotificationsOptions {
19
19
  onError?: (message: string, error: unknown) => void;
20
20
  /** Defaults to what expo-notifications reads from the app config. */
21
21
  projectId?: string;
22
- /** Android channel `default`, needed for heads-up notifications. */
22
+ /**
23
+ * Android channel `default`, needed for heads-up notifications. Call
24
+ * `setAndroidChannel` again when its texts depend on the locale.
25
+ */
23
26
  androidChannel?: Notifications.NotificationChannelInput;
24
27
  /** How notifications show while the app is in the foreground. */
25
28
  foregroundBehavior?: Notifications.NotificationBehavior;
@@ -29,7 +32,10 @@ export interface PushNotificationsOptions {
29
32
  }
30
33
  export interface NotificationListeners {
31
34
  onReceived?: (notification: Notifications.Notification) => void;
32
- /** Also called with the response that launched the app, if any. */
35
+ /**
36
+ * Also called with the response that launched the app, if any. Each
37
+ * response is delivered once per app start, however often `listen` runs.
38
+ */
33
39
  onResponse?: (response: Notifications.NotificationResponse) => void;
34
40
  }
35
41
  export type PushNotifications = ReturnType<typeof createPushNotifications>;
@@ -43,6 +49,8 @@ export declare function createPushNotifications(options: PushNotificationsOption
43
49
  requestPermissions: () => Promise<boolean>;
44
50
  getPushToken: (devicePushToken?: Notifications.DevicePushToken) => Promise<string | null>;
45
51
  syncToken: () => Promise<boolean>;
52
+ /** Recreates the Android `default` channel, for example after a locale change. */
53
+ setAndroidChannel: (channel?: Notifications.NotificationChannelInput) => Promise<void | undefined>;
46
54
  /**
47
55
  * Asks for push once per install (after sign-in, say), then registers the
48
56
  * token. Never throws; concurrent calls share one dialog.
@@ -50,7 +58,11 @@ export declare function createPushNotifications(options: PushNotificationsOption
50
58
  prompt(): Promise<boolean>;
51
59
  /** Turns push on from settings. Resolves whether it is on. */
52
60
  enable(): Promise<boolean>;
53
- /** Removes this device's token from your backend. */
61
+ /**
62
+ * Removes this device's token from your backend, and stops `listen` from
63
+ * re-registering it. Rejects when `registerDevice(null)` does, so a
64
+ * settings screen can show the failure; catch it on sign-out.
65
+ */
54
66
  disable(): Promise<void>;
55
67
  /**
56
68
  * Subscribes to notifications and keeps the registered token current: on
@@ -1,5 +1,6 @@
1
1
  import type { ReleasePlatform } from './args';
2
2
  export declare const SOURCE_MAP_OUTPUT_DIR = "dist";
3
+ /** `EXPO_BIN`, else the app's own expo CLI, else `bunx expo`. */
3
4
  export declare function expoCommand(...args: string[]): string[];
4
5
  export declare function sourceMapExportCommand(platform: ReleasePlatform): string[];
5
6
  export declare function sourceMapUpdateArgs(): string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-release",
3
- "version": "2.3.1",
3
+ "version": "2.4.0",
4
4
  "description": "Fingerprint-driven Expo EAS release: OTA update or native store build, plus app runtime helpers",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -58,14 +58,15 @@
58
58
  "verify:package": "node scripts/verify-package.mjs"
59
59
  },
60
60
  "dependencies": {
61
- "hanzio": "^3.1.0",
61
+ "hanzio": "^3.4.0",
62
62
  "zod": "^4.3.6"
63
63
  },
64
64
  "peerDependencies": {
65
65
  "expo-device": ">=55",
66
66
  "expo-notifications": ">=55",
67
67
  "expo-updates": ">=55",
68
- "react": ">=19"
68
+ "react": ">=19",
69
+ "react-native": ">=0.79"
69
70
  },
70
71
  "peerDependenciesMeta": {
71
72
  "expo-device": {
@@ -79,6 +80,9 @@
79
80
  },
80
81
  "react": {
81
82
  "optional": true
83
+ },
84
+ "react-native": {
85
+ "optional": true
82
86
  }
83
87
  },
84
88
  "devDependencies": {
package/src/args.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import type { CommandContext } from 'hanzio/cli'
2
2
  import { z } from 'zod'
3
+ import type { BuildProfile } from './eas-json'
3
4
 
4
5
  export const DEFAULT_IMPACT_PATHS = ['apps/expo/'] as const
5
6
 
6
7
  export type ReleasePlatform = 'ios' | 'android'
7
8
 
8
- export type CommandName = 'release' | 'decide' | 'operate'
9
+ export type CommandName = 'release' | 'decide' | 'operate' | 'doctor'
9
10
 
10
11
  export type Operation =
11
12
  | 'deploy'
@@ -48,10 +49,16 @@ const text = (env: string) =>
48
49
  .optional()
49
50
  .meta({ env: `EXPO_RELEASE_${env}` })
50
51
 
52
+ // hanzio/cli skips blank env values but keeps padding (`platform: |` in YAML adds a newline).
53
+ const trimmed = <T extends z.ZodType>(schema: T) =>
54
+ z.preprocess(
55
+ (value) => (typeof value === 'string' ? value.trim() : value),
56
+ schema
57
+ )
58
+
51
59
  // `--auto-submit false` takes a value, so these are not boolean switches.
52
60
  const trueFalse = (env: string, fallback: 'true' | 'false') =>
53
- z
54
- .enum(['true', 'false'])
61
+ trimmed(z.enum(['true', 'false']))
55
62
  .default(fallback)
56
63
  .transform((value) => value === 'true')
57
64
  .meta({ env: `EXPO_RELEASE_${env}` })
@@ -65,13 +72,12 @@ export const releaseFlags = z
65
72
  .meta({ env: 'EXPO_RELEASE_PROFILE' })
66
73
  .describe('EAS build profile, also the default update channel'),
67
74
  base: text('BASE').describe(
68
- 'Git base SHA for the impact diff (falls back to GITHUB_EVENT_BEFORE)'
69
- ),
70
- head: text('HEAD').describe(
71
- 'Git head SHA for the impact diff (falls back to GITHUB_SHA)'
75
+ 'Git base SHA for the impact diff (default: the merge base on a pull request, else github.event.before)'
72
76
  ),
73
- platform: z
74
- .enum(['ios', 'android'])
77
+ head: text('HEAD')
78
+ .meta({ env: ['EXPO_RELEASE_HEAD', 'GITHUB_SHA'] })
79
+ .describe('Git head SHA for the impact diff'),
80
+ platform: trimmed(z.enum(['ios', 'android']))
75
81
  .default('ios')
76
82
  .meta({ env: 'EXPO_RELEASE_PLATFORM' })
77
83
  .describe('Native platform'),
@@ -95,27 +101,27 @@ export const releaseFlags = z
95
101
  ignoreExpoImpact: trueFalse('IGNORE_EXPO_IMPACT', 'false').describe(
96
102
  'Check the fingerprint even when no impact path changed'
97
103
  ),
98
- operation: z
99
- .enum([
104
+ operation: trimmed(
105
+ z.enum([
100
106
  'deploy',
101
107
  'force-native',
102
108
  'retry-ota',
103
109
  'republish',
104
110
  'rollback-embedded'
105
111
  ])
112
+ )
106
113
  .default('deploy')
107
114
  .meta({ env: 'EXPO_RELEASE_OPERATION' })
108
115
  .describe('What to run'),
109
- deployType: z
110
- .enum(['none', 'ota', 'native'])
116
+ deployType: trimmed(z.enum(['none', 'ota', 'native']))
111
117
  .default('none')
112
118
  .meta({ env: 'EXPO_RELEASE_DEPLOY_TYPE' })
113
119
  .describe('Decision type for operate'),
114
120
  channel: text('CHANNEL').describe(
115
- 'EAS Update channel (default: the profile)'
121
+ 'EAS Update channel (default: the eas.json profile channel, else the profile name)'
116
122
  ),
117
123
  environment: text('ENVIRONMENT').describe(
118
- 'EAS environment for eas update (required for Expo SDK 55+)'
124
+ 'EAS environment for eas update (required for Expo SDK 55+, where it defaults to the eas.json profile environment)'
119
125
  ),
120
126
  autoSubmit: trueFalse('AUTO_SUBMIT', 'true').describe(
121
127
  'Pass --auto-submit on native builds'
@@ -141,7 +147,14 @@ export const releaseFlags = z
141
147
  'Fingerprint hash to use instead of generating one'
142
148
  ),
143
149
  dryRun: z
144
- .boolean()
150
+ .preprocess(
151
+ // hanzio/cli makes booleans of exact `true`/`false`; this takes padded ones too.
152
+ (value) =>
153
+ typeof value === 'string' && /^\s*(true|false)\s*$/.test(value)
154
+ ? value.trim() === 'true'
155
+ : value,
156
+ z.boolean()
157
+ )
145
158
  .meta({ env: 'EXPO_RELEASE_DRY_RUN' })
146
159
  .describe('Print mutating EAS commands without running them')
147
160
  })
@@ -156,16 +169,18 @@ export const releaseFlags = z
156
169
 
157
170
  export type ReleaseFlags = z.output<typeof releaseFlags>
158
171
 
172
+ /** Flags win over the eas.json build profile, which wins over the profile name. */
159
173
  export function toReleaseOptions(
160
174
  command: CommandName,
161
- { context: flags, env }: CommandContext<ReleaseFlags>
175
+ { context: flags }: Pick<CommandContext<ReleaseFlags>, 'context'>,
176
+ buildProfile: BuildProfile | null = null
162
177
  ): ReleaseOptions {
178
+ const environment = flags.environment ?? buildProfile?.environment
163
179
  return {
164
180
  ...flags,
165
181
  command,
166
- base: flags.base ?? env.GITHUB_EVENT_BEFORE,
167
- head: flags.head ?? env.GITHUB_SHA,
168
- channel: flags.channel ?? flags.profile
182
+ channel: flags.channel ?? buildProfile?.channel ?? flags.profile,
183
+ ...(environment ? { environment } : {})
169
184
  }
170
185
  }
171
186