@delacour/warden-e2e 0.4.0 → 0.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.
@@ -2685,6 +2685,71 @@ function createConsoleCapture(options = {}) {
2685
2685
  };
2686
2686
  }
2687
2687
 
2688
+ // ../../packages/e2e/src/devtools/logbox.script.ts
2689
+ var LOGBOX_SCRIPT = `function (g) {
2690
+ var req = g.__r;
2691
+ if (typeof req !== "function" || typeof req.getModules !== "function") return "no-require";
2692
+ var modules = req.getModules();
2693
+ if (!modules || typeof modules.forEach !== "function") return "no-require";
2694
+ var data = null;
2695
+ modules.forEach(function (mod) {
2696
+ if (data || !mod || typeof mod.verboseName !== "string" || !mod.isInitialized) return;
2697
+ var name = mod.verboseName.replace(/\\\\/g, "/");
2698
+ if (!/(^|\\/)react-native\\/Libraries\\/LogBox\\/Data\\/LogBoxData\\.js$/.test(name)) return;
2699
+ var exports = mod.publicModule && mod.publicModule.exports;
2700
+ if (exports && typeof exports.addIgnorePatterns === "function") data = exports;
2701
+ });
2702
+ if (!data) return "not-ready";
2703
+ data.addIgnorePatterns([/[\\s\\S]*/]);
2704
+ if (typeof data.setDisabled === "function") data.setDisabled(true);
2705
+ if (typeof data.setSelectedLog === "function") data.setSelectedLog(-1);
2706
+ return "off";
2707
+ }`;
2708
+ var LOGBOX_EXPRESSION = `(${LOGBOX_SCRIPT})(typeof globalThis !== "undefined" ? globalThis : (typeof global !== "undefined" ? global : this))`;
2709
+
2710
+ // ../../packages/e2e/src/devtools/logbox.ts
2711
+ var INTERVAL_MS = 250;
2712
+ var ATTEMPTS = 80;
2713
+ var timerSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2714
+ function muteLogBox(session, options = {}) {
2715
+ const sleep = options.sleep ?? timerSleep;
2716
+ const intervalMs = options.intervalMs ?? INTERVAL_MS;
2717
+ const attempts = options.attempts ?? ATTEMPTS;
2718
+ let generation = 0;
2719
+ let stopped = false;
2720
+ async function tryOnce() {
2721
+ const reply = await session.evaluate(LOGBOX_EXPRESSION);
2722
+ if (!reply.success)
2723
+ return reply.error.message;
2724
+ return reply.data === "off" ? undefined : String(reply.data);
2725
+ }
2726
+ async function apply(mine) {
2727
+ const current = () => !stopped && mine === generation;
2728
+ let last = "no reply";
2729
+ for (let attempt = 1;attempt <= attempts && current(); attempt++) {
2730
+ last = await tryOnce();
2731
+ if (last === undefined)
2732
+ return;
2733
+ if (attempt < attempts)
2734
+ await sleep(intervalMs);
2735
+ }
2736
+ if (current())
2737
+ options.log?.(`devtools: LogBox left on (${last} after ${attempts} attempts)`);
2738
+ }
2739
+ const start = () => apply(++generation);
2740
+ const off = session.on("reconnected", () => {
2741
+ start();
2742
+ });
2743
+ const settled = start();
2744
+ return {
2745
+ settled,
2746
+ stop() {
2747
+ stopped = true;
2748
+ off();
2749
+ }
2750
+ };
2751
+ }
2752
+
2688
2753
  // ../../packages/e2e/src/devtools/network.ts
2689
2754
  import { z as z3 } from "zod";
2690
2755
 
@@ -2825,10 +2890,10 @@ var pageEntrySchema = z3.looseObject({
2825
2890
  responseBodyTruncated: z3.boolean().optional(),
2826
2891
  error: z3.string().optional()
2827
2892
  });
2828
- var timerSleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2893
+ var timerSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
2829
2894
  async function attachNetwork(session, options = {}) {
2830
2895
  const ring = options.ring ?? DEFAULT_RING2;
2831
- const sleep = options.sleep ?? timerSleep;
2896
+ const sleep = options.sleep ?? timerSleep2;
2832
2897
  const pollMs = options.pollMs ?? DEFAULT_POLL_MS2;
2833
2898
  const installExpression = networkInstallExpression({
2834
2899
  ring,
@@ -3074,13 +3139,13 @@ var RECONNECT_BUDGET_MS = 30000;
3074
3139
  var RELOAD_WAIT_MS = 5000;
3075
3140
  var HANDSHAKE_OPTIONAL_TIMEOUT_MS = 3000;
3076
3141
  var RELOAD_EXPRESSION = '(function(){var g=globalThis;if(g.expo&&typeof g.expo.reloadAppAsync==="function"){setTimeout(function(){g.expo.reloadAppAsync("warden e2e reloadJs")},0);return "expo"}var m=g.nativeModuleProxy&&g.nativeModuleProxy.DevSettings;if(m&&typeof m.reload==="function"){setTimeout(function(){m.reload()},0);return "devsettings"}return false})()';
3077
- var timerSleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3142
+ var timerSleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3078
3143
  async function connectDevtools(o) {
3079
3144
  const { metroUrl } = o;
3080
3145
  if (!metroUrl) {
3081
3146
  return devtoolsErr("devtools_unavailable", "no Metro URL: release builds have no devtools (start a dev client)");
3082
3147
  }
3083
- const sleep = o.sleep ?? timerSleep2;
3148
+ const sleep = o.sleep ?? timerSleep3;
3084
3149
  const log = o.log ?? (() => {
3085
3150
  return;
3086
3151
  });
@@ -3856,7 +3921,7 @@ var scriptResultSchema = z5.union([
3856
3921
  z5.object({ screen: z5.string().optional(), components: z5.array(componentSchema), truncated: z5.boolean() })
3857
3922
  ]);
3858
3923
  var bindingPayloadSchema2 = z5.object({ requestId: z5.string(), result: z5.unknown() });
3859
- var timerSleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3924
+ var timerSleep4 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3860
3925
  var requestCounter = 0;
3861
3926
  function scriptError(message) {
3862
3927
  if (message === "no-hook")
@@ -3919,7 +3984,7 @@ async function viaBinding(session, expression, requestId, timeoutMs) {
3919
3984
  }
3920
3985
  async function fetchTree(session, options = {}, deps = {}) {
3921
3986
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
3922
- const resolved = { sleep: deps.sleep ?? timerSleep3, now: deps.now ?? Date.now };
3987
+ const resolved = { sleep: deps.sleep ?? timerSleep4, now: deps.now ?? Date.now };
3923
3988
  requestCounter += 1;
3924
3989
  const requestId = `tree-${Date.now().toString(36)}-${requestCounter}`;
3925
3990
  const config = {
@@ -3970,6 +4035,7 @@ async function openDevtoolsFacet(o) {
3970
4035
  return attached;
3971
4036
  }
3972
4037
  const network = attached.data;
4038
+ const logbox = o.muteLogBox ? muteLogBox(session, { log: o.log }) : undefined;
3973
4039
  return ok({
3974
4040
  evaluate: (expression, options) => session.evaluate(expression, options),
3975
4041
  reloadJs: () => session.reloadJs(),
@@ -3995,6 +4061,7 @@ async function openDevtoolsFacet(o) {
3995
4061
  return ok(paths);
3996
4062
  },
3997
4063
  close() {
4064
+ logbox?.stop();
3998
4065
  network.stop();
3999
4066
  session.close();
4000
4067
  }
@@ -5203,7 +5270,8 @@ var openDevtools = (env) => openDevtoolsFacet({
5203
5270
  WebSocket,
5204
5271
  timeoutMs: 5000,
5205
5272
  log: (line) => process.stderr.write(`[devtools] ${line}
5206
- `)
5273
+ `),
5274
+ muteLogBox: true
5207
5275
  });
5208
5276
  var SESSION_KEY = Symbol.for("@delacour/warden-e2e/session");
5209
5277
  var slot = () => globalThis;
@@ -5950,6 +6018,6 @@ function onlyWasSeen() {
5950
6018
 
5951
6019
  // ../../packages/e2e/src/version.ts
5952
6020
  var WORKSPACE_VERSION = "0.0.0-workspace";
5953
- var WARDEN_E2E_VERSION2 = "0.4.0";
6021
+ var WARDEN_E2E_VERSION2 = "0.5.0";
5954
6022
 
5955
6023
  export { E2eError2, noteSpanDetail, addSpanDetail, canObserve, canDetectAlerts, settleDetailOf, DEFAULT_SETTLE_QUIET_MS, DEFAULT_SETTLE_TIMEOUT_MS, settleByHash, snapshotHash, ok, err, swipeVector, screenRect, fieldValue, PINCH_MS, ROTATE_MS, PATH_MS, centreOf, pinchRadius, rotateRadius, requireGesture, assertScale, assertDegrees, isInteractiveRole, resolveRole, normalizeText, matchesText, indexSnapshot, testIdQuery, textQuery, roleQuery, selectorExpression, renderSnapshot, renderHeader, resolve, locateByTestIdViaReact, isLocator, Locator, pickOne, textOf, scrollUntilVisible, bunExec, execError, driverError, driverErr, logEventSchema, fatalEventSchema, responseSchema, mapWireError, encodeRequest, bunSpawn, stdioTransport, spawnSimd, connectSimd, isHidLost, ENV_NAMES, DEFAULT_ASSERTION_TIMEOUT_MS, readEnv, runStep, setDefaultHidRecovery, setDefaultNativeLog, setDefaultDriverFactory, hasDefaultDriverFactory, setDefaultFixtureFactory, hasDefaultFixtureFactory, attemptSession, getSession, parseBudgets, createDevicePerf, perfStartFor, spawnWarden, hash6, attemptDir, screenshotPath, specIdOf, realNativeLogDeps, captureNativeLog, callerFile, BUN_TIMEOUT_MARGIN_MS2, ownHook, currentScope, createScope, runInScope, hooksFor, resolveUse, registrationDefaults, markOnly, onlyWasSeen, WORKSPACE_VERSION, WARDEN_E2E_VERSION2 };
package/index.mjs CHANGED
@@ -31,7 +31,7 @@ import {
31
31
  registrationDefaults,
32
32
  markOnly,
33
33
  WARDEN_E2E_VERSION2
34
- } from "./chunk-3874jt2j.mjs";
34
+ } from "./chunk-j9b17ade.mjs";
35
35
  // ../../packages/e2e/src/expect/expect.ts
36
36
  import { expect as bunExpect } from "bun:test";
37
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delacour/warden-e2e",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "TypeScript spec API for `warden e2e`: test, expect, screen, device, app",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "LICENSE"
38
38
  ],
39
39
  "peerDependencies": {
40
- "@delacour/warden": "0.4.0"
40
+ "@delacour/warden": "0.5.0"
41
41
  },
42
42
  "dependencies": {
43
43
  "zod": "^4.4.3"
package/preload.mjs CHANGED
@@ -68,7 +68,7 @@ import {
68
68
  onlyWasSeen,
69
69
  WORKSPACE_VERSION,
70
70
  WARDEN_E2E_VERSION2
71
- } from "./chunk-3874jt2j.mjs";
71
+ } from "./chunk-j9b17ade.mjs";
72
72
 
73
73
  // ../../packages/e2e/src/preload.ts
74
74
  import { afterAll, setDefaultTimeout } from "bun:test";
@@ -3983,6 +3983,8 @@ function createApp({ session, driver, env, log = stderrLog2, clock = systemClock
3983
3983
  if (options.env !== undefined)
3984
3984
  opts.env = options.env;
3985
3985
  await unwrap(() => driver.app.launch(bundleId, opts), `launch ${bundleId}`, session.signal);
3986
+ if (env.metroUrl !== undefined)
3987
+ session.devtools?.();
3986
3988
  if (options.url !== undefined) {
3987
3989
  const url = options.url;
3988
3990
  await unwrap(() => driver.app.openUrl(url), `open ${url}`, session.signal);
@@ -0,0 +1,185 @@
1
+ import { z } from "zod";
2
+ import { slimConfigSchema } from "../batch/preset.schema";
3
+ /** `e2e.<suite>.specs.<id|glob>`: what a spec covers and whether it gates. */
4
+ export declare const e2eSpecEntrySchema: z.ZodObject<{
5
+ entries: z.ZodDefault<z.ZodArray<z.ZodString>>;
6
+ paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
7
+ platforms: z.ZodOptional<z.ZodArray<z.ZodEnum<{
8
+ ios: "ios";
9
+ android: "android";
10
+ }>>>;
11
+ maxDepth: z.ZodOptional<z.ZodNumber>;
12
+ required: z.ZodOptional<z.ZodBoolean>;
13
+ }, z.core.$strict>;
14
+ /** How a suite's spec processes are laid out: one per device running its share of the specs, or one per spec. */
15
+ export type E2eIsolation = "device" | "spec";
16
+ type RunSettings = {
17
+ isolate?: E2eIsolation | undefined;
18
+ passes: number;
19
+ count?: number | undefined;
20
+ profile?: string | undefined;
21
+ devices?: Array<{
22
+ profile: string;
23
+ default: boolean;
24
+ }> | undefined;
25
+ };
26
+ /**
27
+ * The isolation a suite runs with: what it asked for (`isolate`), else one
28
+ * process per device, except that `passes` > 1 needs a process per spec (a spec's passes are
29
+ * re-runs of that spec).
30
+ */
31
+ export declare function suiteIsolation(suite: Pick<RunSettings, "isolate" | "passes">): E2eIsolation;
32
+ /** `e2e.<suite>.metro`: warden starts one Metro for the run and points the dev client at it. */
33
+ export declare const e2eMetroSchema: z.ZodObject<{
34
+ enabled: z.ZodDefault<z.ZodBoolean>;
35
+ port: z.ZodDefault<z.ZodString>;
36
+ launchArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
37
+ open: z.ZodDefault<z.ZodBoolean>;
38
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
39
+ prewarm: z.ZodDefault<z.ZodBoolean>;
40
+ readyTimeout: z.ZodDefault<z.ZodString>;
41
+ }, z.core.$strict>;
42
+ /** `e2e.<suite>.devices[]`: one leg of the run, with its own device pool. */
43
+ export declare const e2eDeviceLegSchema: z.ZodObject<{
44
+ profile: z.ZodString;
45
+ count: z.ZodDefault<z.ZodNumber>;
46
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
47
+ default: z.ZodDefault<z.ZodBoolean>;
48
+ }, z.core.$strict>;
49
+ /** `perf` (top level, or per `e2e.<suite>`): ceilings `device.perf.stop()` holds the report to. Each is lower-is-better. */
50
+ export declare const perfConfigSchema: z.ZodObject<{
51
+ budgets: z.ZodOptional<z.ZodObject<{
52
+ startupColdMs: z.ZodOptional<z.ZodNumber>;
53
+ jankPct: z.ZodOptional<z.ZodNumber>;
54
+ maxCommitMs: z.ZodOptional<z.ZodNumber>;
55
+ p95CpuPct: z.ZodOptional<z.ZodNumber>;
56
+ peakMemoryMb: z.ZodOptional<z.ZodNumber>;
57
+ }, z.core.$strict>>;
58
+ }, z.core.$strict>;
59
+ /** `e2e.<suite>.spec`: how warden runs the `*.e2e.ts` specs (`bun test` + `@delacour/warden-e2e`). */
60
+ export declare const e2eSpecSchema: z.ZodObject<{
61
+ bundleId: z.ZodOptional<z.ZodString>;
62
+ launchArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
63
+ timeout: z.ZodOptional<z.ZodString>;
64
+ actionTimeout: z.ZodOptional<z.ZodString>;
65
+ assertionTimeout: z.ZodOptional<z.ZodString>;
66
+ }, z.core.$strict>;
67
+ /**
68
+ * `e2e.<suite>` in `warden.config.ts`: specs under `specsDir`, how they map to source, how to run one.
69
+ * Paths and globs are relative to the config file's directory.
70
+ */
71
+ export declare const e2eSuiteSchema: z.ZodObject<{
72
+ project: z.ZodOptional<z.ZodString>;
73
+ specsDir: z.ZodString;
74
+ spec: z.ZodOptional<z.ZodObject<{
75
+ bundleId: z.ZodOptional<z.ZodString>;
76
+ launchArgs: z.ZodOptional<z.ZodArray<z.ZodString>>;
77
+ timeout: z.ZodOptional<z.ZodString>;
78
+ actionTimeout: z.ZodOptional<z.ZodString>;
79
+ assertionTimeout: z.ZodOptional<z.ZodString>;
80
+ }, z.core.$strict>>;
81
+ isolate: z.ZodOptional<z.ZodEnum<{
82
+ device: "device";
83
+ spec: "spec";
84
+ }>>;
85
+ shutdown: z.ZodOptional<z.ZodEnum<{
86
+ idle: "idle";
87
+ always: "always";
88
+ }>>;
89
+ jobTimeoutMs: z.ZodDefault<z.ZodNumber>;
90
+ devices: z.ZodOptional<z.ZodArray<z.ZodObject<{
91
+ profile: z.ZodString;
92
+ count: z.ZodDefault<z.ZodNumber>;
93
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
94
+ default: z.ZodDefault<z.ZodBoolean>;
95
+ }, z.core.$strict>>>;
96
+ metro: z.ZodOptional<z.ZodObject<{
97
+ enabled: z.ZodDefault<z.ZodBoolean>;
98
+ port: z.ZodDefault<z.ZodString>;
99
+ launchArgs: z.ZodDefault<z.ZodArray<z.ZodString>>;
100
+ open: z.ZodDefault<z.ZodBoolean>;
101
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
102
+ prewarm: z.ZodDefault<z.ZodBoolean>;
103
+ readyTimeout: z.ZodDefault<z.ZodString>;
104
+ }, z.core.$strict>>;
105
+ tsconfig: z.ZodOptional<z.ZodString>;
106
+ routerRoot: z.ZodOptional<z.ZodString>;
107
+ platform: z.ZodOptional<z.ZodEnum<{
108
+ ios: "ios";
109
+ android: "android";
110
+ }>>;
111
+ base: z.ZodDefault<z.ZodString>;
112
+ maxDepth: z.ZodOptional<z.ZodNumber>;
113
+ runAll: z.ZodDefault<z.ZodArray<z.ZodString>>;
114
+ ignore: z.ZodDefault<z.ZodArray<z.ZodString>>;
115
+ always: z.ZodDefault<z.ZodArray<z.ZodString>>;
116
+ include: z.ZodDefault<z.ZodArray<z.ZodString>>;
117
+ exclude: z.ZodDefault<z.ZodArray<z.ZodString>>;
118
+ scope: z.ZodDefault<z.ZodArray<z.ZodString>>;
119
+ unmatched: z.ZodDefault<z.ZodEnum<{
120
+ skip: "skip";
121
+ "run-all": "run-all";
122
+ }>>;
123
+ unmapped: z.ZodDefault<z.ZodEnum<{
124
+ run: "run";
125
+ skip: "skip";
126
+ }>>;
127
+ passes: z.ZodDefault<z.ZodNumber>;
128
+ retry: z.ZodDefault<z.ZodNumber>;
129
+ count: z.ZodOptional<z.ZodNumber>;
130
+ profile: z.ZodOptional<z.ZodString>;
131
+ app: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
132
+ installMode: z.ZodOptional<z.ZodEnum<{
133
+ clean: "clean";
134
+ reinstall: "reinstall";
135
+ }>>;
136
+ resetKeychain: z.ZodOptional<z.ZodBoolean>;
137
+ clean: z.ZodOptional<z.ZodBoolean>;
138
+ variant: z.ZodOptional<z.ZodString>;
139
+ kind: z.ZodOptional<z.ZodEnum<{
140
+ release: "release";
141
+ dev: "dev";
142
+ simulator: "simulator";
143
+ }>>;
144
+ }, z.core.$strict>]>>;
145
+ ports: z.ZodOptional<z.ZodArray<z.ZodString>>;
146
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
147
+ serve: z.ZodOptional<z.ZodString>;
148
+ serveReady: z.ZodOptional<z.ZodString>;
149
+ serveTimeout: z.ZodOptional<z.ZodString>;
150
+ setup: z.ZodOptional<z.ZodString>;
151
+ slim: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
152
+ except: z.ZodOptional<z.ZodArray<z.ZodString>>;
153
+ keep: z.ZodOptional<z.ZodArray<z.ZodString>>;
154
+ }, z.core.$strict>]>>;
155
+ warmRunners: z.ZodOptional<z.ZodUndefined>;
156
+ warmDrivers: z.ZodOptional<z.ZodBoolean>;
157
+ headless: z.ZodOptional<z.ZodBoolean>;
158
+ perf: z.ZodOptional<z.ZodObject<{
159
+ budgets: z.ZodOptional<z.ZodObject<{
160
+ startupColdMs: z.ZodOptional<z.ZodNumber>;
161
+ jankPct: z.ZodOptional<z.ZodNumber>;
162
+ maxCommitMs: z.ZodOptional<z.ZodNumber>;
163
+ p95CpuPct: z.ZodOptional<z.ZodNumber>;
164
+ peakMemoryMb: z.ZodOptional<z.ZodNumber>;
165
+ }, z.core.$strict>>;
166
+ }, z.core.$strict>>;
167
+ required: z.ZodDefault<z.ZodBoolean>;
168
+ specs: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
169
+ entries: z.ZodDefault<z.ZodArray<z.ZodString>>;
170
+ paths: z.ZodDefault<z.ZodArray<z.ZodString>>;
171
+ platforms: z.ZodOptional<z.ZodArray<z.ZodEnum<{
172
+ ios: "ios";
173
+ android: "android";
174
+ }>>>;
175
+ maxDepth: z.ZodOptional<z.ZodNumber>;
176
+ required: z.ZodOptional<z.ZodBoolean>;
177
+ }, z.core.$strict>>>;
178
+ }, z.core.$strict>;
179
+ export type E2eSpecConfig = z.infer<typeof e2eSpecSchema>;
180
+ export type E2eMetroConfig = z.infer<typeof e2eMetroSchema>;
181
+ export type E2eDeviceLeg = z.infer<typeof e2eDeviceLegSchema>;
182
+ export type E2eSpecEntryConfig = z.infer<typeof e2eSpecEntrySchema>;
183
+ export type E2eSlimConfig = z.infer<typeof slimConfigSchema>;
184
+ export type E2eSuiteConfig = z.infer<typeof e2eSuiteSchema>;
185
+ export {};
@@ -0,0 +1,59 @@
1
+ import { z } from "zod";
2
+ /** `slim`: on/off, or the categories (`except`, ids) and single labels (`keep`) to leave running. */
3
+ export declare const slimConfigSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
4
+ except: z.ZodOptional<z.ZodArray<z.ZodString>>;
5
+ keep: z.ZodOptional<z.ZodArray<z.ZodString>>;
6
+ }, z.core.$strict>]>;
7
+ export declare const duration: z.ZodString;
8
+ export declare const portSpec: z.ZodString;
9
+ /** `batches.<name>` in `warden.config.ts`: a saved `warden batch` invocation. */
10
+ export declare const batchPresetSchema: z.ZodObject<{
11
+ project: z.ZodOptional<z.ZodString>;
12
+ platform: z.ZodEnum<{
13
+ ios: "ios";
14
+ android: "android";
15
+ }>;
16
+ count: z.ZodOptional<z.ZodNumber>;
17
+ max: z.ZodOptional<z.ZodNumber>;
18
+ profile: z.ZodOptional<z.ZodString>;
19
+ runtime: z.ZodOptional<z.ZodString>;
20
+ label: z.ZodOptional<z.ZodString>;
21
+ ttl: z.ZodOptional<z.ZodString>;
22
+ wait: z.ZodOptional<z.ZodString>;
23
+ ports: z.ZodOptional<z.ZodArray<z.ZodString>>;
24
+ app: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
25
+ installMode: z.ZodOptional<z.ZodEnum<{
26
+ clean: "clean";
27
+ reinstall: "reinstall";
28
+ }>>;
29
+ resetKeychain: z.ZodOptional<z.ZodBoolean>;
30
+ clean: z.ZodOptional<z.ZodBoolean>;
31
+ variant: z.ZodOptional<z.ZodString>;
32
+ kind: z.ZodOptional<z.ZodEnum<{
33
+ release: "release";
34
+ dev: "dev";
35
+ simulator: "simulator";
36
+ }>>;
37
+ }, z.core.$strict>]>>;
38
+ headless: z.ZodOptional<z.ZodBoolean>;
39
+ slim: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
40
+ except: z.ZodOptional<z.ZodArray<z.ZodString>>;
41
+ keep: z.ZodOptional<z.ZodArray<z.ZodString>>;
42
+ }, z.core.$strict>]>>;
43
+ retry: z.ZodOptional<z.ZodNumber>;
44
+ passes: z.ZodOptional<z.ZodNumber>;
45
+ env: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
46
+ serve: z.ZodOptional<z.ZodString>;
47
+ serveReady: z.ZodOptional<z.ZodString>;
48
+ serveTimeout: z.ZodOptional<z.ZodString>;
49
+ jobs: z.ZodOptional<z.ZodArray<z.ZodString>>;
50
+ jobsFrom: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
51
+ command: z.ZodString;
52
+ }, z.core.$strict>]>>;
53
+ record: z.ZodOptional<z.ZodString>;
54
+ logs: z.ZodOptional<z.ZodString>;
55
+ cmd: z.ZodArray<z.ZodString>;
56
+ }, z.core.$strict>;
57
+ export type BatchPresetConfig = z.infer<typeof batchPresetSchema>;
58
+ /** Preset names that would shadow the platform operand of `warden batch`. */
59
+ export declare const RESERVED_PRESET_NAMES: readonly string[];
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { type BuildKind } from "./kinds";
2
3
  /**
3
4
  * What to do with the app's installed state before a run:
4
5
  * - unset: install only when the device lacks this build; a different build is installed over the old one.
@@ -14,7 +15,8 @@ export type InstallMode = z.infer<typeof installModeSchema>;
14
15
  * `app` on a batch preset / e2e suite: `true` installs the project's app on every device first;
15
16
  * `{ installMode }` also resets the installed app (see {@link InstallMode}); `{ resetKeychain: true }`
16
17
  * (needs `installMode`) also resets the iOS simulator's keychain;
17
- * `{ variant }` installs that `projects[].variants` build instead of the project's default one.
18
+ * `{ variant }` installs that `projects[].variants` build instead of the project's default one;
19
+ * `{ kind }` installs that build kind (`dev` | `simulator` | `release`) instead (not together with `variant`).
18
20
  * `{ clean: true }` is the deprecated spelling of `{ installMode: "reinstall" }`.
19
21
  */
20
22
  export declare const appOptionSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
@@ -25,6 +27,11 @@ export declare const appOptionSchema: z.ZodUnion<readonly [z.ZodBoolean, z.ZodOb
25
27
  resetKeychain: z.ZodOptional<z.ZodBoolean>;
26
28
  clean: z.ZodOptional<z.ZodBoolean>;
27
29
  variant: z.ZodOptional<z.ZodString>;
30
+ kind: z.ZodOptional<z.ZodEnum<{
31
+ release: "release";
32
+ dev: "dev";
33
+ simulator: "simulator";
34
+ }>>;
28
35
  }, z.core.$strict>]>;
29
36
  export type AppOption = z.infer<typeof appOptionSchema>;
30
37
  export type AppSwitches = {
@@ -32,6 +39,7 @@ export type AppSwitches = {
32
39
  installMode?: InstallMode;
33
40
  resetKeychain?: true;
34
41
  variant?: string;
42
+ kind?: BuildKind;
35
43
  };
36
44
  /** The switches `app` stands for. An object means install (`app: {}` = `app: true`). */
37
45
  export declare function appSwitches(app: AppOption | undefined): AppSwitches;
@@ -0,0 +1,62 @@
1
+ import type { Platform } from "../types";
2
+ import type { AppFramework } from "./native-ids";
3
+ /** EAS build profile searched when the project config names none. */
4
+ export declare const DEFAULT_EAS_PROFILE = "development-simulator";
5
+ /** Xcode / Gradle build configuration the local build produces (`build.configuration`). */
6
+ export type BuildConfiguration = "Debug" | "Release";
7
+ /**
8
+ * Makes a fresh (CNG) checkout buildable: `expo run:ios --no-install` skips CocoaPods, so generate
9
+ * `ios/` when it has no Podfile and run `pod install` when the Pods sandbox is missing or out of sync
10
+ * with `Podfile.lock` (the check Xcode's own build phase makes). A no-op on a prepared project.
11
+ */
12
+ export declare const IOS_NATIVE_PREPARE: string;
13
+ /** Local build command (run in the project root) when `build.<platform>` is not configured. */
14
+ export declare function defaultBuildCommand(platform: Platform, configuration: BuildConfiguration, framework?: AppFramework): string;
15
+ /** Android twin of `IOS_NATIVE_PREPARE`: generate `android/` when a CNG checkout has none. */
16
+ export declare const ANDROID_NATIVE_PREPARE = "{ [ -f android/gradlew ] || bunx expo prebuild --platform android --no-install; }";
17
+ /**
18
+ * Local build command of the `simulator` kind: the Release configuration for a simulator / emulator; a repack swaps the
19
+ * JS in. iOS: Xcode's bundling phase is skipped with `SKIP_BUNDLING=1`. Android: plain `assembleRelease`. Excluding
20
+ * `createBundleReleaseJsAndAssets` (`-x`) fails on RN 0.86 / AGP 9 ("Querying the mapped value ... before task ... has
21
+ * completed"), and the bundler's `drawable-*` / `raw` images are compiled into the APK's resource table by that task:
22
+ * without them the Android repack finds no image in the base and always falls back to a full build.
23
+ */
24
+ export declare function simulatorBuildCommand(platform: Platform, framework?: AppFramework): string;
25
+ /**
26
+ * What a kind's JS key (and a `release` kind's build key) covers when `kinds.<kind>.jsInputs` is not set: every file git
27
+ * tracks (or would) under the project root except the native projects and dependencies. A monorepo workspace package the
28
+ * bundle embeds is not under the root: list it (`../../packages/ui/src/**`) in `jsInputs`.
29
+ */
30
+ export declare const DEFAULT_JS_INPUTS: readonly string[];
31
+ /** The Debug defaults. */
32
+ export declare const DEFAULT_BUILD_COMMAND: Record<Platform, string>;
33
+ /** `warden prune` default budget. */
34
+ export declare const DEFAULT_MAX_CACHE_SIZE = "20G";
35
+ /** EAS poll interval while a build for the hash is in flight. */
36
+ export declare const EAS_POLL_MS = 30000;
37
+ /** Give up waiting on EAS after this long (then fall through to a local build). */
38
+ export declare const EAS_TIMEOUT_MS: number;
39
+ /** With `eas.trigger`, wait this long for a build to appear before triggering the workflow. */
40
+ export declare const EAS_TRIGGER_GRACE_MS: number;
41
+ /** Local native build timeout. */
42
+ export declare const LOCAL_BUILD_TIMEOUT_MS: number;
43
+ /** Fingerprint command timeout. */
44
+ export declare const FINGERPRINT_TIMEOUT_MS: number;
45
+ /** Build-lock lease ttl; the lease also carries the holder's pid so it lives as long as the process. */
46
+ export declare const BUILD_LOCK_TTL_MS: number;
47
+ /** How long a resolver waits for another process's build/download of the same hash. */
48
+ export declare const BUILD_LOCK_WAIT_MS: number;
49
+ export declare const BUILD_LOCK_POLL_MS = 2000;
50
+ /**
51
+ * Pre-warden cache roots (`<dir>/<hash>/*.app|*.apk`) by project name, used when the project config
52
+ * sets no `cacheDirs`. Builds found there are copied into the warden cache on first use.
53
+ */
54
+ export declare const LEGACY_CACHE_DIRS: Readonly<Record<string, readonly string[]>>;
55
+ /** ccache sloppiness for Xcode/clang builds: lets worktrees with different mtimes/PCH state still hit. */
56
+ export declare const CCACHE_SLOPPINESS = "clang_index_store,file_stat_matches,include_file_ctime,include_file_mtime,ivfsoverlay,pch_defines,modules,system_headers,time_macros";
57
+ /** Subdirectory of WARDEN_HOME holding the shared ccache. */
58
+ export declare const CCACHE_DIR_NAME = "ccache";
59
+ /** First Xcode major with compilation caching (`COMPILATION_CACHE_ENABLE_CACHING`). */
60
+ export declare const XCODE_COMPILATION_CACHE_MIN_MAJOR = 26;
61
+ /** Timeout for the quick `git` / `xcodebuild -version` probes before a build. */
62
+ export declare const PROBE_TIMEOUT_MS = 10000;
@@ -0,0 +1,122 @@
1
+ import { type Result } from "../../../types/src/result";
2
+ import type { z } from "zod";
3
+ import type { Platform } from "../types";
4
+ import { type BuildConfiguration } from "./builds.defaults";
5
+ import type { projectConfigSchema, wardenConfigSchema } from "./config.schema";
6
+ import type { BuildKind, ProjectKinds } from "./kinds";
7
+ import { type AppFramework } from "./native-ids";
8
+ /** The preferred config file, named in hints. */
9
+ export declare const CONFIG_FILE = "warden.config.ts";
10
+ /** Still read, so JSON configs keep working. */
11
+ export declare const JSON_CONFIG_FILE = "warden.config.json";
12
+ /** Every file name `findWardenConfig` looks for, in preference order; at most one may exist per dir. */
13
+ export declare const CONFIG_FILES: string[];
14
+ /** Parsed (defaults applied). The documented input types live in `define-config.ts`. */
15
+ export type ParsedProjectConfig = z.infer<typeof projectConfigSchema>;
16
+ export type ParsedWardenConfig = z.infer<typeof wardenConfigSchema>;
17
+ export type EasSettings = {
18
+ profile: string;
19
+ workflow?: string;
20
+ trigger: boolean;
21
+ };
22
+ /** A project ready for the build cache: absolute root, defaults applied. */
23
+ export type Project = {
24
+ name: string;
25
+ root: string;
26
+ bundleId: {
27
+ ios?: string;
28
+ android?: string;
29
+ };
30
+ fingerprintCommand?: string;
31
+ /** set = JS-aware key (`fingerprint.include: "native+js"`): globs of the JS sources the key covers */
32
+ jsInputs?: string[];
33
+ /** undefined = EAS not used (no `eas` config and no `eas.json`) */
34
+ eas?: EasSettings;
35
+ build: Record<Platform, string>;
36
+ /** Debug unless `build.configuration` says Release */
37
+ buildConfiguration: BuildConfiguration;
38
+ /** false = `build.cache: false`: no shared compiler caches in local builds */
39
+ compilerCache?: false;
40
+ /** false = `build.prebuiltReactNative: false`: never opt iOS builds into the precompiled RN core */
41
+ prebuiltReactNative?: false;
42
+ /** the `variants.<name>` this project was resolved with; undefined = the project's own settings */
43
+ variant?: string;
44
+ /** `projects[].kinds`: per-kind overrides of the built-in dev / simulator / release builds (see `kinds.ts`) */
45
+ kinds?: ProjectKinds;
46
+ /** set by `projectForKind`: the kind this project is resolved as; undefined = the kind-less flow */
47
+ kind?: BuildKind;
48
+ /** set by `projectForKind`: env of the local build (`kinds.<kind>.env`) */
49
+ buildEnv?: Record<string, string>;
50
+ cacheDirs: string[];
51
+ /** `expo` (expo start + a dev client) or bare `react-native` (react-native start, the app itself) */
52
+ framework: AppFramework;
53
+ /** where the project came from; `react-native` = autodetected from a bare app's package.json + native projects */
54
+ origin: "config" | "app.json" | "app.config" | "react-native";
55
+ /** which lookup found it: the repo's own config, the `~/.warden` overlay config, or autodetected from the app config */
56
+ source: ConfigSource;
57
+ };
58
+ /** `repo` = a `warden.config.*` in the checkout; `overlay` = one in `<WARDEN_HOME>/projects/<key>/`; `auto` = none (app.json). */
59
+ export type ConfigSource = "repo" | "overlay" | "auto";
60
+ /** A parsed warden config and where it was found. */
61
+ export type FoundConfig = {
62
+ /** the config's own dir: what a config function sees as `configDir`, and what suite `specsDir`s resolve against */
63
+ dir: string;
64
+ config: ParsedWardenConfig;
65
+ source: Exclude<ConfigSource, "auto">;
66
+ /** what `projects[].root` resolves against: `dir` for a repo config, the checkout root for an overlay one */
67
+ rootDir: string;
68
+ };
69
+ /** Validate a config value; `file` names it in the error. */
70
+ export declare function parseWardenConfig(raw: unknown, file?: string): Result<ParsedWardenConfig>;
71
+ /** Pick the project containing `start` (deepest root wins); a single project is picked regardless. */
72
+ export declare function selectProject(projects: readonly Project[], start: string, name?: string): Result<Project>;
73
+ export type LoadProjectInput = {
74
+ /** `--project` dir or cwd */
75
+ start: string;
76
+ env: Record<string, string | undefined>;
77
+ /** stop walking up here (git toplevel); default filesystem root */
78
+ stopAt?: string;
79
+ name?: string;
80
+ /** `--bundle-id` for the requested platform */
81
+ bundleId?: Project["bundleId"];
82
+ /** `projects[].variants.<name>` to resolve the project with */
83
+ variant?: string;
84
+ /** a missing `variant` falls back to the project's own settings instead of erroring (`warden dev`) */
85
+ variantOptional?: boolean;
86
+ /** `<WARDEN_HOME>/projects/<key>/` (see `overlay.ts`): its config is used when the repo has none */
87
+ overlayDir?: string;
88
+ /** the app's slug when `app.json` has none (read from `expo config` for a dynamic `app.config.*`) */
89
+ appName?: string;
90
+ };
91
+ /** Is there a warden config file in `start` or a parent (to `stopAt`)? File names only: nothing is evaluated. */
92
+ export declare function hasRepoConfig(start: string, stopAt?: string): boolean;
93
+ /**
94
+ * The nearest `warden.config.{ts,mts,js,mjs,json}` walking up from `start` (to `stopAt`), parsed;
95
+ * undefined = none. Else the one in `overlayDir`, if given (project roots then resolve against the
96
+ * checkout: `stopAt`, else `start`). A config function gets `{ configDir, env }`.
97
+ */
98
+ export declare function findWardenConfig(start: string, stopAt?: string, env?: Record<string, string | undefined>, overlayDir?: string): Result<FoundConfig | undefined>;
99
+ /**
100
+ * Find the project for `start`: nearest warden config walking up (to `stopAt`), else the
101
+ * `overlayDir` config, else the nearest Expo app (`app.json` / `app.config.*`). `--bundle-id` overrides the configured id.
102
+ */
103
+ export declare function loadProject(input: LoadProjectInput): Result<Project>;
104
+ /**
105
+ * The app dirs below `root` (a monorepo root): its `package.json` workspaces, else `apps/*` and
106
+ * `packages/*`, that hold a React Native app (`isAppDir`). Sorted, no duplicates.
107
+ */
108
+ export declare function appDirsBelow(root: string): string[];
109
+ /**
110
+ * Where the project lookup really starts: `start`, unless nothing is found from there upwards and
111
+ * exactly one app sits below it (a monorepo root), then that app's dir.
112
+ */
113
+ export declare function appStart(start: string, stopAt?: string, overlayDir?: string): string;
114
+ /** The apps below `start` to pick from, when nothing is found upwards and there are several; else empty. */
115
+ export declare function appChoices(start: string, stopAt?: string, overlayDir?: string): string[];
116
+ /**
117
+ * The app dir `loadProject` would autodetect from `start` whose bundle id only Expo can read: no warden
118
+ * config in reach, a dynamic `app.config.*`, and neither `app.json` nor `bundleOverride` names a bundle id.
119
+ * Undefined otherwise. File names and `app.json` only: nothing is evaluated.
120
+ */
121
+ export declare function dynamicAppConfigDir(start: string, stopAt: string | undefined, bundleOverride?: Project["bundleId"], overlayDir?: string): string | undefined;
122
+ export declare function bundleIdFor(project: Project, platform: Platform): Result<string>;