@delacour/warden-e2e 0.0.0-stage → 0.2.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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/chunk-7ns30pbp.mjs +5832 -0
  3. package/index.d.ts +1 -0
  4. package/index.mjs +1047 -0
  5. package/package.json +49 -4
  6. package/preload.d.ts +1 -0
  7. package/preload.mjs +4788 -0
  8. package/types/core/src/allocate.d.ts +23 -0
  9. package/types/core/src/builds/app-option.schema.d.ts +37 -0
  10. package/types/core/src/builds/install.d.ts +95 -0
  11. package/types/core/src/builds/resolve.d.ts +74 -0
  12. package/types/core/src/config.defaults.d.ts +25 -0
  13. package/types/core/src/exec.d.ts +18 -0
  14. package/types/core/src/liveness.d.ts +12 -0
  15. package/types/core/src/providers/android.d.ts +132 -0
  16. package/types/core/src/providers/health.d.ts +45 -0
  17. package/types/core/src/providers/provider.types.d.ts +56 -0
  18. package/types/core/src/store.d.ts +105 -0
  19. package/types/core/src/types.d.ts +126 -0
  20. package/types/e2e/src/alert-buttons.d.ts +41 -0
  21. package/types/e2e/src/alert-reader.d.ts +25 -0
  22. package/types/e2e/src/app.d.ts +62 -0
  23. package/types/e2e/src/device-devtools.d.ts +26 -0
  24. package/types/e2e/src/device-link.d.ts +43 -0
  25. package/types/e2e/src/device.d.ts +90 -0
  26. package/types/e2e/src/devtools/cdp.d.ts +65 -0
  27. package/types/e2e/src/devtools/console.d.ts +59 -0
  28. package/types/e2e/src/devtools/devtools.types.d.ts +169 -0
  29. package/types/e2e/src/devtools/facet.d.ts +28 -0
  30. package/types/e2e/src/devtools/inspector.d.ts +48 -0
  31. package/types/e2e/src/devtools/jsonl.d.ts +4 -0
  32. package/types/e2e/src/devtools/native-log.d.ts +50 -0
  33. package/types/e2e/src/devtools/network.d.ts +33 -0
  34. package/types/e2e/src/devtools/network.script.d.ts +24 -0
  35. package/types/e2e/src/devtools/session.d.ts +82 -0
  36. package/types/e2e/src/devtools/tree.d.ts +23 -0
  37. package/types/e2e/src/devtools/tree.script.d.ts +33 -0
  38. package/types/e2e/src/driver/alerts.d.ts +29 -0
  39. package/types/e2e/src/driver/android/adb-session.d.ts +52 -0
  40. package/types/e2e/src/driver/android/android-alert.d.ts +4 -0
  41. package/types/e2e/src/driver/android/android-animations.d.ts +29 -0
  42. package/types/e2e/src/driver/android/android-app.d.ts +32 -0
  43. package/types/e2e/src/driver/android/android-argv.d.ts +122 -0
  44. package/types/e2e/src/driver/android/android-device.d.ts +44 -0
  45. package/types/e2e/src/driver/android/android-driver.d.ts +60 -0
  46. package/types/e2e/src/driver/android/android-dump.d.ts +46 -0
  47. package/types/e2e/src/driver/android/android-state.d.ts +69 -0
  48. package/types/e2e/src/driver/android/uiad-backend.d.ts +63 -0
  49. package/types/e2e/src/driver/android/uiad-client.d.ts +60 -0
  50. package/types/e2e/src/driver/android/uiad-device.d.ts +70 -0
  51. package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +8 -0
  52. package/types/e2e/src/driver/android/uiad-ops.d.ts +62 -0
  53. package/types/e2e/src/driver/android/uiad-protocol.d.ts +209 -0
  54. package/types/e2e/src/driver/android/uiautomator.d.ts +33 -0
  55. package/types/e2e/src/driver/driver.types.d.ts +231 -0
  56. package/types/e2e/src/driver/errors.d.ts +13 -0
  57. package/types/e2e/src/driver/gesture-geometry.d.ts +48 -0
  58. package/types/e2e/src/driver/ios/hid-recovery.d.ts +36 -0
  59. package/types/e2e/src/driver/ios/ios-driver.d.ts +63 -0
  60. package/types/e2e/src/driver/ios/ios-session.d.ts +30 -0
  61. package/types/e2e/src/driver/ios/locate-helper.d.ts +38 -0
  62. package/types/e2e/src/driver/ios/normalize.d.ts +11 -0
  63. package/types/e2e/src/driver/ios/simctl.d.ts +102 -0
  64. package/types/e2e/src/driver/ios/simd-client.d.ts +68 -0
  65. package/types/e2e/src/driver/ios/simd-protocol.d.ts +433 -0
  66. package/types/e2e/src/driver/ios/simd-transport.d.ts +43 -0
  67. package/types/e2e/src/driver/ios/type-budget.d.ts +6 -0
  68. package/types/e2e/src/driver/observation-cache.d.ts +66 -0
  69. package/types/e2e/src/driver/open.d.ts +29 -0
  70. package/types/e2e/src/driver/settle.d.ts +43 -0
  71. package/types/e2e/src/driver/span-detail.d.ts +12 -0
  72. package/types/e2e/src/driver/traced-driver.d.ts +14 -0
  73. package/types/e2e/src/driver/typing.d.ts +48 -0
  74. package/types/e2e/src/driver/unwrap.d.ts +13 -0
  75. package/types/e2e/src/env.d.ts +78 -0
  76. package/types/e2e/src/errors.d.ts +26 -0
  77. package/types/e2e/src/expect/expect.d.ts +27 -0
  78. package/types/e2e/src/expect/matchers.d.ts +72 -0
  79. package/types/e2e/src/fixture-session.d.ts +38 -0
  80. package/types/e2e/src/fixtures.d.ts +14 -0
  81. package/types/e2e/src/index.d.ts +11 -0
  82. package/types/e2e/src/perf/budgets.d.ts +33 -0
  83. package/types/e2e/src/perf/device-perf.d.ts +45 -0
  84. package/types/e2e/src/perf/perf.types.d.ts +59 -0
  85. package/types/e2e/src/preload.d.ts +29 -0
  86. package/types/e2e/src/runner/artifacts.d.ts +13 -0
  87. package/types/e2e/src/runner/attempt.d.ts +63 -0
  88. package/types/e2e/src/runner/callsite.d.ts +2 -0
  89. package/types/e2e/src/runner/failure.d.ts +40 -0
  90. package/types/e2e/src/runner/fixtures.types.d.ts +60 -0
  91. package/types/e2e/src/runner/results.d.ts +48 -0
  92. package/types/e2e/src/runner/scope.d.ts +64 -0
  93. package/types/e2e/src/runner/span.d.ts +40 -0
  94. package/types/e2e/src/runner/step.d.ts +7 -0
  95. package/types/e2e/src/runner/test.d.ts +59 -0
  96. package/types/e2e/src/runner/trace.d.ts +87 -0
  97. package/types/e2e/src/screen/actions.d.ts +93 -0
  98. package/types/e2e/src/screen/context.d.ts +134 -0
  99. package/types/e2e/src/screen/field.d.ts +121 -0
  100. package/types/e2e/src/screen/locator.d.ts +233 -0
  101. package/types/e2e/src/screen/nodes.d.ts +53 -0
  102. package/types/e2e/src/screen/poll.d.ts +57 -0
  103. package/types/e2e/src/screen/queries.d.ts +34 -0
  104. package/types/e2e/src/screen/query.d.ts +60 -0
  105. package/types/e2e/src/screen/react-locator.d.ts +30 -0
  106. package/types/e2e/src/screen/render.d.ts +42 -0
  107. package/types/e2e/src/screen/roles.d.ts +24 -0
  108. package/types/e2e/src/screen/screen.d.ts +61 -0
  109. package/types/e2e/src/screen/selector.d.ts +38 -0
  110. package/types/e2e/src/screen/text.d.ts +13 -0
  111. package/types/e2e/src/session.d.ts +124 -0
  112. package/types/e2e/src/traced-device.d.ts +9 -0
  113. package/types/e2e/src/version.d.ts +7 -0
  114. package/types/types/src/result.d.ts +12 -0
  115. package/README.md +0 -3
@@ -0,0 +1,61 @@
1
+ import type { Point } from "../driver/driver.types";
2
+ import { type Momentum, type SwipeDirection } from "./actions";
3
+ import { type ScreenHost } from "./context";
4
+ import { type ActionOptions, Locator, type ScrollOptions } from "./locator";
5
+ import { type RoleOptions, type TestIdOptions, type TextQueryOptions } from "./queries";
6
+ import type { QueryRole } from "./roles";
7
+ import type { TextMatch } from "./text";
8
+ export type ScreenSwipeOptions = {
9
+ direction: SwipeDirection;
10
+ momentum?: Momentum;
11
+ } | {
12
+ from: Point;
13
+ to: Point;
14
+ durationMs?: number;
15
+ };
16
+ export type ScreenPinchOptions = {
17
+ scale: number;
18
+ center?: Point;
19
+ radius?: number;
20
+ durationMs?: number;
21
+ };
22
+ export type ScreenRotateOptions = {
23
+ degrees: number;
24
+ center?: Point;
25
+ radius?: number;
26
+ durationMs?: number;
27
+ };
28
+ /**
29
+ * `paths` are 1-2 fingers' routes in screen points, each with at least one finite point (give two or more for a
30
+ * real path; a single point is a held touch). More fingers than the driver's `capabilities.maxPointers` is `INVALID_ARGUMENT`.
31
+ */
32
+ export type ScreenPathOptions = {
33
+ paths: readonly (readonly Point[])[];
34
+ durationMs?: number;
35
+ };
36
+ /** Viewport-level gestures; `screen.swipe` is the same as `screen.gesture.swipe`. */
37
+ export type ScreenGestures = {
38
+ /** pinch about the screen centre (or `center`); `scale` above 1 zooms in */
39
+ pinch(options: ScreenPinchOptions): Promise<void>;
40
+ /** rotate about the screen centre (or `center`); positive `degrees` turn clockwise */
41
+ rotate(options: ScreenRotateOptions): Promise<void>;
42
+ /** replay 1-2 finger paths on one timeline; no observation, coordinates are used as given */
43
+ path(options: ScreenPathOptions): Promise<void>;
44
+ swipe(options: ScreenSwipeOptions): Promise<void>;
45
+ };
46
+ /** The `screen` of the spec API: lazy queries over the device, plus viewport-level gestures. */
47
+ export type Screen = {
48
+ getByTestId(id: TextMatch, options?: TestIdOptions): Locator;
49
+ getByText(text: TextMatch, options?: TextQueryOptions): Locator;
50
+ getByLabel(text: TextMatch, options?: TextQueryOptions): Locator;
51
+ getByPlaceholder(text: TextMatch, options?: TextQueryOptions): Locator;
52
+ getByValue(value: TextMatch, options?: TextQueryOptions): Locator;
53
+ getByRole(role: QueryRole, nameOrOptions?: TextMatch | RoleOptions, options?: Omit<RoleOptions, "name">): Locator;
54
+ /** the agent-CLI selector grammar: `locator("id=sign-in role=button nth=1")` */
55
+ locator(selector: string): Locator;
56
+ tapAt(point: Point, options?: ActionOptions): Promise<void>;
57
+ swipe(options: ScreenSwipeOptions): Promise<void>;
58
+ gesture: ScreenGestures;
59
+ scrollUntilVisible(target: Locator, options?: ScrollOptions): Promise<void>;
60
+ };
61
+ export declare function createScreen(host: ScreenHost): Screen;
@@ -0,0 +1,38 @@
1
+ import { type Result } from "../../../types/src/result";
2
+ import type { NodeEntry } from "./nodes";
3
+ import type { LocatorExpression } from "./query";
4
+ /** A selector value: literal text (bare or quoted on the wire) or a regular expression. */
5
+ export type SelectorValue = {
6
+ kind: "text";
7
+ value: string;
8
+ } | {
9
+ kind: "regex";
10
+ source: string;
11
+ flags: string;
12
+ };
13
+ /**
14
+ * The agent-CLI selector grammar: `id=<v> label=<v> text=<v> role=<v> value=<v> editable=true|false nth=<i>`.
15
+ * `id` is exact; `label`, `text` and `value` are case-insensitive substrings; a regex is applied as written.
16
+ */
17
+ export type ParsedSelector = {
18
+ id?: SelectorValue;
19
+ label?: SelectorValue;
20
+ text?: SelectorValue;
21
+ role?: SelectorValue;
22
+ value?: SelectorValue;
23
+ editable?: boolean;
24
+ nth?: number;
25
+ };
26
+ /** Parses `id=x label="Sign in" text=/regex/i role=button editable=true nth=1`. */
27
+ export declare function parseSelector(input: string): Result<ParsedSelector>;
28
+ /** The canonical text for a selector; `parseSelector(formatSelector(s))` equals `s`. */
29
+ export declare function formatSelector(selector: ParsedSelector): string;
30
+ /** The expression a selector stands for: one selector query, wrapped in `nth` when given. */
31
+ export declare function selectorToQuery(selector: ParsedSelector): LocatorExpression;
32
+ /** One node against one parsed selector (`nth` is not part of matching; see `selectorToQuery`). */
33
+ export declare function matchesSelector(entry: NodeEntry, selector: ParsedSelector): boolean;
34
+ /**
35
+ * Picks the target of an action among several matches: visible first, then the most exact text terms, then the smallest
36
+ * frame, then reading order.
37
+ */
38
+ export declare function pickMostSpecific(matches: readonly NodeEntry[], selector: ParsedSelector): NodeEntry | undefined;
@@ -0,0 +1,13 @@
1
+ /** A string (whole-text, or substring with `exact:false`) or a RegExp applied as written. */
2
+ export type TextMatch = string | RegExp;
3
+ export type TextMatchOptions = {
4
+ exact?: boolean;
5
+ };
6
+ /** Collapses every whitespace run (including no-break spaces) to one space and trims. */
7
+ export declare function normalizeText(text: string): string;
8
+ /**
9
+ * Whether `value` satisfies `match`. A string must equal the whole text after whitespace normalisation (case-sensitive);
10
+ * `exact:false` turns it into a case-insensitive substring. A RegExp is tested as written against the normalised value
11
+ * (its `g`/`y` flags are dropped so repeated calls are not stateful).
12
+ */
13
+ export declare function matchesText(value: string, match: TextMatch, options?: TextMatchOptions): boolean;
@@ -0,0 +1,124 @@
1
+ import { type DevtoolsFacet, type DevtoolsResult } from "./devtools/devtools.types";
2
+ import type { NativeLogDeps } from "./devtools/native-log";
3
+ import type { Driver, DriverResult } from "./driver/driver.types";
4
+ import { type HidRecoveryDeps } from "./driver/ios/hid-recovery";
5
+ import { type E2eEnv } from "./env";
6
+ import type { AttemptContext, FixtureFactory } from "./runner/fixtures.types";
7
+ import { type ResultsWriter } from "./runner/results";
8
+ import { type SpanRunner } from "./runner/span";
9
+ import type { TraceWriter } from "./runner/trace";
10
+ export type DriverFactory = (env: E2eEnv) => DriverResult<Driver>;
11
+ /** How to attach to the app's dev client; default {@link openDevtools}. Tests inject a stub. */
12
+ export type DevtoolsOpener = (env: E2eEnv) => Promise<DevtoolsResult<DevtoolsFacet>>;
13
+ /**
14
+ * Which inspector page is this env's: the bundle id and the device name. Several devices can share one Metro;
15
+ * without the name the newest page of any device would win and a reload or evaluate would hit another device's app.
16
+ */
17
+ export declare function devtoolsTarget(env: E2eEnv): {
18
+ appId: string;
19
+ deviceName?: string;
20
+ };
21
+ /** Metro's inspector for this env's app (`WARDEN_METRO_URL`), pinned by {@link devtoolsTarget}. */
22
+ export declare const openDevtools: DevtoolsOpener;
23
+ export type Session = {
24
+ readonly env: E2eEnv;
25
+ /** absolute `WARDEN_E2E_OUT` */
26
+ readonly out: string;
27
+ readonly results: ResultsWriter;
28
+ /** builds `app`/`device`/`screen` for each attempt (T4/T6); settable so tests can fake it */
29
+ fixtures: FixtureFactory | undefined;
30
+ /** how a failed attempt's `native.log` / crash reports are read; absent in unit tests (nothing is spawned) */
31
+ nativeLog: NativeLogDeps | undefined;
32
+ /**
33
+ * Present for iOS runs with recovery wired: after a test failed with a lost HID connection, reboots the sim and
34
+ * reopens the driver and devtools, once per session. Resolves `true` when the test is worth running again.
35
+ */
36
+ recoverHid: (() => Promise<boolean>) | undefined;
37
+ /** closes the open driver and devtools so the next call opens them afresh (the device was rebooted under them) */
38
+ resetDevice(): Promise<void>;
39
+ /** where the console reporter prints one line; default fd 1 */
40
+ readonly log: (line: string) => void;
41
+ /** the running attempt, if any */
42
+ readonly attempt: AttemptContext | undefined;
43
+ /** aborts at the running attempt's deadline; never aborts outside an attempt. Pass it to every driver call and poll. */
44
+ readonly signal: AbortSignal;
45
+ /** the running attempt's artifacts dir and `trace.jsonl` (read at call time by the `app`/`device` fixtures) */
46
+ readonly attemptDir: string | undefined;
47
+ readonly trace: TraceWriter | undefined;
48
+ /**
49
+ * opened on first call, then shared; a failed open is thrown as `E2eError{code:"DRIVER"}` and retried next call.
50
+ * Every call on it is a `driver.*` span in the running attempt's `trace.jsonl`, except a screen read that the
51
+ * observation cache answers from the snapshot a read just took (`driver/observation-cache.ts`; it offers `observe`).
52
+ */
53
+ driver(): Promise<Driver>;
54
+ /** the driver if it is already open; never opens one (failure capture must not start a device) */
55
+ peekDriver(): Driver | undefined;
56
+ /**
57
+ * The app's devtools (console, network, React tree, evaluate), attached on the first call and shared after;
58
+ * `devtools_unavailable` without `WARDEN_METRO_URL` (a release build). A failed attach is retried on the next call.
59
+ */
60
+ devtools(): Promise<DevtoolsResult<DevtoolsFacet>>;
61
+ /** the facet if it is already attached; never attaches one (failure capture must not connect to anything) */
62
+ peekDevtools(): DevtoolsFacet | undefined;
63
+ /** one reported step (`▸`/`▪` lines, `trace.jsonl`) that gives up when the attempt deadline aborts */
64
+ step<T>(kind: string, label: string, fn: () => Promise<T>): Promise<T>;
65
+ /** times one call as a `span` in the running attempt's `trace.jsonl` (no console line); just runs it outside an attempt */
66
+ span: SpanRunner;
67
+ /**
68
+ * Makes `attempt` the running one. With `replacing`, only when that is still the running one: an attempt's
69
+ * cleanup must not take the slot back from the next test that bun already started.
70
+ */
71
+ enterAttempt(attempt: AttemptContext, replacing?: AttemptContext): void;
72
+ /** Clears the running attempt; with `attempt`, only when that is the running one (never another test's). */
73
+ leaveAttempt(attempt?: AttemptContext): void;
74
+ /** idempotent; closes the driver only if it was opened */
75
+ close(): Promise<void>;
76
+ };
77
+ export type SessionOptions = {
78
+ /** parsed env; default `readEnv(process.env)` (throws `SESSION` when not under warden) */
79
+ env?: E2eEnv;
80
+ /** how to open the device; default {@link setDefaultDriverFactory}'s */
81
+ driverFactory?: DriverFactory;
82
+ /** how to attach devtools; default {@link openDevtools} */
83
+ devtools?: DevtoolsOpener;
84
+ /** native log reader; default {@link setDefaultNativeLog}'s */
85
+ nativeLog?: NativeLogDeps;
86
+ /** sim reboot seams; default {@link setDefaultHidRecovery}'s */
87
+ hidRecovery?: HidRecoveryDeps;
88
+ /** fixture factory; default {@link setDefaultFixtureFactory}'s */
89
+ fixtures?: FixtureFactory;
90
+ /** console line sink; default writes to fd 1 (bun test captures `process.stdout`, fd 1 is not captured) */
91
+ log?: (line: string) => void;
92
+ /** close from `process.on("exit")`; default true */
93
+ exitHook?: boolean;
94
+ };
95
+ /** Wires the HID-loss recovery used when a session gets none (`preload.ts` registers the real one). */
96
+ export declare function setDefaultHidRecovery(deps: HidRecoveryDeps | undefined): void;
97
+ /** Wires the native-log reader used when a session gets none (`preload.ts` registers the real one). */
98
+ export declare function setDefaultNativeLog(deps: NativeLogDeps | undefined): void;
99
+ /** Wires the factory used when `getSession` gets none (`driver/open.ts`'s opener, set by the package entry). */
100
+ export declare function setDefaultDriverFactory(factory: DriverFactory | undefined): void;
101
+ /** Whether a default driver factory is wired (so the preload leaves a test-installed fake alone). */
102
+ export declare function hasDefaultDriverFactory(): boolean;
103
+ /** Wires the `app`/`device`/`screen` factory used when `getSession` gets none. */
104
+ export declare function setDefaultFixtureFactory(factory: FixtureFactory | undefined): void;
105
+ /** Whether a default fixture factory is wired (so the preload leaves a test-installed one alone). */
106
+ export declare function hasDefaultFixtureFactory(): boolean;
107
+ export declare function createSession(opts?: SessionOptions): Session;
108
+ /** Which context one attempt's own fixtures use right now; `finished` once the runner is done with the attempt. */
109
+ export type AttemptBinding = {
110
+ readonly context: AttemptContext;
111
+ readonly finished: boolean;
112
+ };
113
+ /**
114
+ * `session` as one attempt's fixtures see it: `signal`, `attempt`, `attemptDir`, `trace`, `step` and `span` follow
115
+ * `binding` (that attempt's run context, then its cleanup context, then an aborted one), never whichever attempt is
116
+ * running when a call is made. So work the runner gave up on (a timed-out body that swallowed its `TIMEOUT`, an
117
+ * `afterEach` past its limit) fails its next driver call instead of tapping into the next test. Everything else is
118
+ * the shared session.
119
+ */
120
+ export declare function attemptSession(session: Session, binding: () => AttemptBinding): Session;
121
+ /** The process-wide session (`globalThis[Symbol.for("./session")]`), created on first call. */
122
+ export declare function getSession(opts?: SessionOptions): Session;
123
+ /** Closes and forgets the process-wide session, if any. */
124
+ export declare function resetSession(): Promise<void>;
@@ -0,0 +1,9 @@
1
+ import type { Device } from "./device";
2
+ import type { SpanRunner } from "./runner/span";
3
+ /**
4
+ * `device` with every call timed as a `device.<name>` span in the attempt's `trace.jsonl` (trace only: no console
5
+ * line, since a settle or an alert is not a step). The driver calls a device call makes are `driver.*` spans of
6
+ * their own, so a slow `device.openLink` shows what it spent its time on. Without a span runner (a bare
7
+ * `FixtureSession`) the device is returned unchanged.
8
+ */
9
+ export declare function traceDevice(device: Device, span: SpanRunner | undefined): Device;
@@ -0,0 +1,7 @@
1
+ /** What `WARDEN_E2E_VERSION` is when running from the workspace sources: not a published build, so no handshake. */
2
+ export declare const WORKSPACE_VERSION = "0.0.0-workspace";
3
+ /**
4
+ * The version of this `@delacour/warden-e2e`. Published builds carry warden's own version (lock-step);
5
+ * `warden e2e` hands its version to the spec process (`WARDEN_VERSION`) and the preload refuses to run when they differ.
6
+ */
7
+ export declare const WARDEN_E2E_VERSION: string;
@@ -0,0 +1,12 @@
1
+ export type SuccessResult<Success> = {
2
+ success: true;
3
+ data: Success;
4
+ };
5
+ export type ErrorResult<Error = string> = {
6
+ success: false;
7
+ error: Error;
8
+ };
9
+ export type Result<Success, Error = string> = SuccessResult<Success> | ErrorResult<Error>;
10
+ export type AsyncResult<Success, Error = string> = Promise<Result<Success, Error>>;
11
+ export declare function ok<T>(data: T): SuccessResult<T>;
12
+ export declare function err(error: unknown): ErrorResult<string>;
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.