@tamagui/native-ci 2.7.7 → 3.0.0-beta.637.1

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 (45) hide show
  1. package/dist/android-utils.mjs +62 -0
  2. package/dist/android-utils.mjs.map +1 -0
  3. package/dist/cache.mjs +54 -70
  4. package/dist/cache.mjs.map +1 -1
  5. package/dist/cli.mjs +378 -403
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/constants.mjs +3 -2
  8. package/dist/constants.mjs.map +1 -1
  9. package/dist/deps.mjs +52 -64
  10. package/dist/deps.mjs.map +1 -1
  11. package/dist/detox.mjs +220 -202
  12. package/dist/detox.mjs.map +1 -1
  13. package/dist/fingerprint.mjs +35 -33
  14. package/dist/fingerprint.mjs.map +1 -1
  15. package/dist/index.js +10 -10
  16. package/dist/index.mjs +10 -10
  17. package/dist/ios-utils.mjs +318 -0
  18. package/dist/ios-utils.mjs.map +1 -0
  19. package/dist/metro.mjs +137 -139
  20. package/dist/metro.mjs.map +1 -1
  21. package/dist/runner.mjs +62 -65
  22. package/dist/runner.mjs.map +1 -1
  23. package/package.json +4 -4
  24. package/src/cli.ts +3 -3
  25. package/src/constants.ts +1 -1
  26. package/src/detox.ts +1 -0
  27. package/src/index.ts +2 -2
  28. package/src/metro.ts +2 -2
  29. package/src/run-detox-android.ts +1 -1
  30. package/src/run-detox-ios.ts +1 -1
  31. package/types/{android.d.ts → android-utils.d.ts} +1 -1
  32. package/types/android-utils.d.ts.map +1 -0
  33. package/types/cli.d.ts +1 -1
  34. package/types/constants.d.ts +2 -2
  35. package/types/detox.d.ts.map +1 -1
  36. package/types/index.d.ts +2 -2
  37. package/types/index.d.ts.map +1 -1
  38. package/types/{ios.d.ts → ios-utils.d.ts} +1 -1
  39. package/types/ios-utils.d.ts.map +1 -0
  40. package/dist/index.js.map +0 -1
  41. package/dist/index.mjs.map +0 -1
  42. package/types/android.d.ts.map +0 -1
  43. package/types/ios.d.ts.map +0 -1
  44. /package/src/{android.ts → android-utils.ts} +0 -0
  45. /package/src/{ios.ts → ios-utils.ts} +0 -0
package/dist/cli.mjs CHANGED
@@ -1,15 +1,16 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
  import { generateFingerprint, generatePreFingerprintHash } from "./fingerprint.mjs";
3
- import { createCacheKey, saveFingerprintToKV, getFingerprintFromKV, saveCache, loadCache } from "./cache.mjs";
4
- import { setGitHubOutput, isGitHubActions, isCI } from "./runner.mjs";
5
- import { ensureIosDeps, ensureAndroidDeps, ensureMaestro, printDepsStatus } from "./deps.mjs";
3
+ import { createCacheKey, getFingerprintFromKV, loadCache, saveCache, saveFingerprintToKV } from "./cache.mjs";
4
+ import { isCI, isGitHubActions, setGitHubOutput } from "./runner.mjs";
5
+ import { ensureAndroidDeps, ensureIosDeps, ensureMaestro, printDepsStatus } from "./deps.mjs";
6
6
  import { withMetro } from "./metro.mjs";
7
7
  import { runDetoxTests } from "./detox.mjs";
8
- import { ensureIOSFolder, ensureIOSApp, ensureBootedSimulator, ensureAppInstalled, getBootedSimulatorUDID, getMaestroBundleId } from "./ios";
9
- import { setupAndroidDevice, ensureAndroidFolder } from "./android";
8
+ import { ensureAppInstalled, ensureBootedSimulator, ensureIOSApp, ensureIOSFolder, getBootedSimulatorUDID, getMaestroBundleId } from "./ios-utils.mjs";
9
+ import { ensureAndroidFolder, setupAndroidDevice } from "./android-utils.mjs";
10
10
  import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { tmpdir } from "node:os";
12
12
  import { join } from "node:path";
13
+
13
14
  const HELP = `
14
15
  native-ci - Native CI/CD helpers for Expo apps
15
16
 
@@ -63,9 +64,9 @@ EXAMPLES:
63
64
  native-ci fingerprint-test
64
65
  `;
65
66
  function createMaestroXcrunShim() {
66
- const binDir = mkdtempSync(join(tmpdir(), "native-ci-maestro-"));
67
- const xcrunPath = join(binDir, "xcrun");
68
- writeFileSync(xcrunPath, `#!/bin/sh
67
+ const binDir = mkdtempSync(join(tmpdir(), "native-ci-maestro-"));
68
+ const xcrunPath = join(binDir, "xcrun");
69
+ writeFileSync(xcrunPath, `#!/bin/sh
69
70
  if [ "$1" = "devicectl" ] && [ "$2" = "--json-output" ] && [ "$4" = "list" ] && [ "$5" = "devices" ]; then
70
71
  printf '{"result":{"devices":[]}}\\n' > "$3"
71
72
  exit 0
@@ -73,327 +74,307 @@ fi
73
74
 
74
75
  exec /usr/bin/xcrun "$@"
75
76
  `);
76
- chmodSync(xcrunPath, 493);
77
- return binDir;
77
+ chmodSync(xcrunPath, 493);
78
+ return binDir;
78
79
  }
79
80
  function parseArgs(argv) {
80
- const args2 = [];
81
- const options2 = {
82
- projectRoot: process.cwd(),
83
- config: "",
84
- recordLogs: "all",
85
- retries: 0,
86
- headless: false,
87
- prefix: "native-build",
88
- githubOutput: false,
89
- json: false,
90
- help: false
91
- };
92
- let i = 0;
93
- while (i < argv.length) {
94
- const arg = argv[i];
95
- if (arg === "--project-root" && argv[i + 1]) {
96
- options2.projectRoot = argv[++i];
97
- } else if (arg === "--config" && argv[i + 1]) {
98
- options2.config = argv[++i];
99
- } else if (arg === "--record-logs" && argv[i + 1]) {
100
- options2.recordLogs = argv[++i];
101
- } else if (arg === "--retries" && argv[i + 1]) {
102
- const val = Number.parseInt(argv[++i], 10);
103
- if (!Number.isNaN(val) && val >= 0) {
104
- options2.retries = val;
105
- }
106
- } else if (arg === "--prefix" && argv[i + 1]) {
107
- options2.prefix = argv[++i];
108
- } else if (arg === "--github-output") {
109
- options2.githubOutput = true;
110
- } else if (arg === "--json") {
111
- options2.json = true;
112
- } else if (arg === "--help" || arg === "-h") {
113
- options2.help = true;
114
- } else if (!arg.startsWith("-")) {
115
- args2.push(arg);
116
- }
117
- i++;
118
- }
119
- return {
120
- command: args2[0] || "",
121
- subcommand: args2[1] || "",
122
- args: args2.slice(2),
123
- options: options2
124
- };
81
+ const args2 = [];
82
+ const options2 = {
83
+ projectRoot: process.cwd(),
84
+ config: "",
85
+ recordLogs: "all",
86
+ retries: 0,
87
+ headless: false,
88
+ prefix: "native-build",
89
+ githubOutput: false,
90
+ json: false,
91
+ help: false
92
+ };
93
+ let i = 0;
94
+ while (i < argv.length) {
95
+ const arg = argv[i];
96
+ if (arg === "--project-root" && argv[i + 1]) {
97
+ options2.projectRoot = argv[++i];
98
+ } else if (arg === "--config" && argv[i + 1]) {
99
+ options2.config = argv[++i];
100
+ } else if (arg === "--record-logs" && argv[i + 1]) {
101
+ options2.recordLogs = argv[++i];
102
+ } else if (arg === "--retries" && argv[i + 1]) {
103
+ const val = Number.parseInt(argv[++i], 10);
104
+ if (!Number.isNaN(val) && val >= 0) {
105
+ options2.retries = val;
106
+ }
107
+ } else if (arg === "--prefix" && argv[i + 1]) {
108
+ options2.prefix = argv[++i];
109
+ } else if (arg === "--github-output") {
110
+ options2.githubOutput = true;
111
+ } else if (arg === "--json") {
112
+ options2.json = true;
113
+ } else if (arg === "--help" || arg === "-h") {
114
+ options2.help = true;
115
+ } else if (!arg.startsWith("-")) {
116
+ args2.push(arg);
117
+ }
118
+ i++;
119
+ }
120
+ return {
121
+ command: args2[0] || "",
122
+ subcommand: args2[1] || "",
123
+ args: args2.slice(2),
124
+ options: options2
125
+ };
125
126
  }
126
127
  function validatePlatform(value) {
127
- if (value !== "ios" && value !== "android") {
128
- console.error('Error: platform must be "ios" or "android"');
129
- process.exit(1);
130
- }
131
- return value;
128
+ if (value !== "ios" && value !== "android") {
129
+ console.error("Error: platform must be \"ios\" or \"android\"");
130
+ process.exit(1);
131
+ }
132
+ return value;
132
133
  }
133
134
  function getKVCredentials() {
134
- const url = process.env.KV_STORE_REDIS_REST_URL;
135
- const token = process.env.KV_STORE_REDIS_REST_TOKEN;
136
- if (!url || !token) {
137
- console.error("Error: KV_STORE_REDIS_REST_URL and KV_STORE_REDIS_REST_TOKEN required");
138
- process.exit(1);
139
- }
140
- return {
141
- url,
142
- token
143
- };
135
+ const url = process.env.KV_STORE_REDIS_REST_URL;
136
+ const token = process.env.KV_STORE_REDIS_REST_TOKEN;
137
+ if (!url || !token) {
138
+ console.error("Error: KV_STORE_REDIS_REST_URL and KV_STORE_REDIS_REST_TOKEN required");
139
+ process.exit(1);
140
+ }
141
+ return {
142
+ url,
143
+ token
144
+ };
144
145
  }
145
- const {
146
- command,
147
- subcommand,
148
- args,
149
- options
150
- } = parseArgs(process.argv.slice(2));
146
+ const { command, subcommand, args, options } = parseArgs(process.argv.slice(2));
151
147
  if (options.help || !command) {
152
- console.info(HELP);
153
- process.exit(options.help ? 0 : 1);
148
+ console.info(HELP);
149
+ process.exit(options.help ? 0 : 1);
154
150
  }
155
151
  try {
156
- switch (command) {
157
- // ========================================
158
- // Test Commands
159
- // ========================================
160
- case "test":
161
- {
162
- if (isCI() && !process.env.NATIVE_CI_FORCE_RUN) {
163
- console.info("Skipping native tests in CI (handled by separate workflow)");
164
- console.info("Set NATIVE_CI_FORCE_RUN=1 to force run");
165
- process.exit(0);
166
- }
167
- const platform = subcommand || "ios";
168
- if (platform === "ios") {
169
- ensureBootedSimulator();
170
- await ensureIosDeps();
171
- const config = options.config || "ios.sim.debug";
172
- console.info("=== iOS Detox Test Runner ===");
173
- console.info(`Config: ${config}`);
174
- console.info(`Project root: ${options.projectRoot}`);
175
- process.chdir(options.projectRoot);
176
- await ensureIOSFolder();
177
- await ensureIOSApp(config);
178
- const exitCode = await withMetro("ios", async () => {
179
- return runDetoxTests({
180
- config,
181
- projectRoot: options.projectRoot,
182
- recordLogs: options.recordLogs,
183
- retries: options.retries,
184
- testFiles: args.length > 0 ? args : void 0
185
- });
186
- });
187
- process.exit(exitCode);
188
- } else if (platform === "android") {
189
- await ensureAndroidDeps();
190
- const config = options.config || "android.emu.debug";
191
- console.info("=== Android Detox Test Runner ===");
192
- console.info(`Config: ${config}`);
193
- console.info(`Project root: ${options.projectRoot}`);
194
- console.info(`Headless: ${options.headless}`);
195
- process.chdir(options.projectRoot);
196
- await ensureAndroidFolder();
197
- await setupAndroidDevice();
198
- const exitCode = await withMetro("android", async () => {
199
- return runDetoxTests({
200
- config,
201
- projectRoot: options.projectRoot,
202
- recordLogs: options.recordLogs,
203
- retries: options.retries,
204
- headless: options.headless,
205
- testFiles: args.length > 0 ? args : void 0
206
- });
207
- });
208
- process.exit(exitCode);
209
- } else if (platform === "maestro") {
210
- ensureBootedSimulator();
211
- await ensureMaestro();
212
- const flow = args[0] || "";
213
- console.info("=== Maestro Test Runner ===");
214
- console.info(`Flow: ${flow || "all"}`);
215
- console.info(`Project root: ${options.projectRoot}`);
216
- process.chdir(options.projectRoot);
217
- const udid = getBootedSimulatorUDID();
218
- const bundleId = getMaestroBundleId(options.projectRoot);
219
- await ensureAppInstalled({
220
- projectRoot: options.projectRoot,
221
- bundleId,
222
- udid
223
- });
224
- const exitCode = await withMetro("ios", async () => {
225
- const {
226
- $
227
- } = await import("bun");
228
- const flowArg = flow ? `./flows/${flow}` : "./flows";
229
- const udidArgs = ["--udid", udid];
230
- const debugDir = `${options.projectRoot}/.maestro-debug`;
231
- const xcrunShimDir = createMaestroXcrunShim();
232
- const previousPath = process.env.PATH;
233
- try {
234
- process.env.PATH = `${xcrunShimDir}:${previousPath ?? ""}`;
235
- const result = await $`maestro test ${flowArg} --exclude-tags=util --no-ansi --debug-output=${debugDir} ${udidArgs}`.nothrow();
236
- return result.exitCode;
237
- } finally {
238
- if (previousPath === void 0) {
239
- delete process.env.PATH;
240
- } else {
241
- process.env.PATH = previousPath;
242
- }
243
- rmSync(xcrunShimDir, {
244
- recursive: true,
245
- force: true
246
- });
247
- }
248
- });
249
- process.exit(exitCode);
250
- } else if (platform === "all") {
251
- ensureBootedSimulator();
252
- console.info("=== Running All Native Tests ===\n");
253
- await ensureIosDeps();
254
- console.info("\n--- iOS Tests ---\n");
255
- process.chdir(options.projectRoot);
256
- await ensureIOSFolder();
257
- await ensureIOSApp(options.config || "ios.sim.debug");
258
- let iosExit = 0;
259
- try {
260
- iosExit = await withMetro("ios", async () => {
261
- return runDetoxTests({
262
- config: options.config || "ios.sim.debug",
263
- projectRoot: options.projectRoot,
264
- recordLogs: options.recordLogs,
265
- retries: options.retries
266
- });
267
- });
268
- } catch (err) {
269
- console.error("iOS tests failed:", err);
270
- iosExit = 1;
271
- }
272
- await ensureAndroidDeps();
273
- console.info("\n--- Android Tests ---\n");
274
- await ensureAndroidFolder();
275
- await setupAndroidDevice();
276
- let androidExit = 0;
277
- try {
278
- androidExit = await withMetro("android", async () => {
279
- return runDetoxTests({
280
- config: options.config || "android.emu.debug",
281
- projectRoot: options.projectRoot,
282
- recordLogs: options.recordLogs,
283
- retries: options.retries,
284
- headless: options.headless
285
- });
286
- });
287
- } catch (err) {
288
- console.error("Android tests failed:", err);
289
- androidExit = 1;
290
- }
291
- const success = iosExit === 0 && androidExit === 0;
292
- console.info(`
152
+ switch (command) {
153
+ case "test": {
154
+ if (isCI() && !process.env.NATIVE_CI_FORCE_RUN) {
155
+ console.info("Skipping native tests in CI (handled by separate workflow)");
156
+ console.info("Set NATIVE_CI_FORCE_RUN=1 to force run");
157
+ process.exit(0);
158
+ }
159
+ const platform = subcommand || "ios";
160
+ if (platform === "ios") {
161
+ ensureBootedSimulator();
162
+ await ensureIosDeps();
163
+ const config = options.config || "ios.sim.debug";
164
+ console.info("=== iOS Detox Test Runner ===");
165
+ console.info(`Config: ${config}`);
166
+ console.info(`Project root: ${options.projectRoot}`);
167
+ process.chdir(options.projectRoot);
168
+ await ensureIOSFolder();
169
+ await ensureIOSApp(config);
170
+ const exitCode = await withMetro("ios", async () => {
171
+ return runDetoxTests({
172
+ config,
173
+ projectRoot: options.projectRoot,
174
+ recordLogs: options.recordLogs,
175
+ retries: options.retries,
176
+ testFiles: args.length > 0 ? args : void 0
177
+ });
178
+ });
179
+ process.exit(exitCode);
180
+ } else if (platform === "android") {
181
+ await ensureAndroidDeps();
182
+ const config = options.config || "android.emu.debug";
183
+ console.info("=== Android Detox Test Runner ===");
184
+ console.info(`Config: ${config}`);
185
+ console.info(`Project root: ${options.projectRoot}`);
186
+ console.info(`Headless: ${options.headless}`);
187
+ process.chdir(options.projectRoot);
188
+ await ensureAndroidFolder();
189
+ await setupAndroidDevice();
190
+ const exitCode = await withMetro("android", async () => {
191
+ return runDetoxTests({
192
+ config,
193
+ projectRoot: options.projectRoot,
194
+ recordLogs: options.recordLogs,
195
+ retries: options.retries,
196
+ headless: options.headless,
197
+ testFiles: args.length > 0 ? args : void 0
198
+ });
199
+ });
200
+ process.exit(exitCode);
201
+ } else if (platform === "maestro") {
202
+ ensureBootedSimulator();
203
+ await ensureMaestro();
204
+ const flow = args[0] || "";
205
+ console.info("=== Maestro Test Runner ===");
206
+ console.info(`Flow: ${flow || "all"}`);
207
+ console.info(`Project root: ${options.projectRoot}`);
208
+ process.chdir(options.projectRoot);
209
+ const udid = getBootedSimulatorUDID();
210
+ const bundleId = getMaestroBundleId(options.projectRoot);
211
+ await ensureAppInstalled({
212
+ projectRoot: options.projectRoot,
213
+ bundleId,
214
+ udid
215
+ });
216
+ const exitCode = await withMetro("ios", async () => {
217
+ const { $ } = await import("bun");
218
+ const flowArg = flow ? `./flows/${flow}` : "./flows";
219
+ const udidArgs = ["--udid", udid];
220
+ const debugDir = `${options.projectRoot}/.maestro-debug`;
221
+ const xcrunShimDir = createMaestroXcrunShim();
222
+ const previousPath = process.env.PATH;
223
+ try {
224
+ process.env.PATH = `${xcrunShimDir}:${previousPath ?? ""}`;
225
+ const result = await $`maestro test ${flowArg} --exclude-tags=util --no-ansi --debug-output=${debugDir} ${udidArgs}`.nothrow();
226
+ return result.exitCode;
227
+ } finally {
228
+ if (previousPath === void 0) {
229
+ delete process.env.PATH;
230
+ } else {
231
+ process.env.PATH = previousPath;
232
+ }
233
+ rmSync(xcrunShimDir, {
234
+ recursive: true,
235
+ force: true
236
+ });
237
+ }
238
+ });
239
+ process.exit(exitCode);
240
+ } else if (platform === "all") {
241
+ ensureBootedSimulator();
242
+ console.info("=== Running All Native Tests ===\n");
243
+ await ensureIosDeps();
244
+ console.info("\n--- iOS Tests ---\n");
245
+ process.chdir(options.projectRoot);
246
+ await ensureIOSFolder();
247
+ await ensureIOSApp(options.config || "ios.sim.debug");
248
+ let iosExit = 0;
249
+ try {
250
+ iosExit = await withMetro("ios", async () => {
251
+ return runDetoxTests({
252
+ config: options.config || "ios.sim.debug",
253
+ projectRoot: options.projectRoot,
254
+ recordLogs: options.recordLogs,
255
+ retries: options.retries
256
+ });
257
+ });
258
+ } catch (err) {
259
+ console.error("iOS tests failed:", err);
260
+ iosExit = 1;
261
+ }
262
+ await ensureAndroidDeps();
263
+ console.info("\n--- Android Tests ---\n");
264
+ await ensureAndroidFolder();
265
+ await setupAndroidDevice();
266
+ let androidExit = 0;
267
+ try {
268
+ androidExit = await withMetro("android", async () => {
269
+ return runDetoxTests({
270
+ config: options.config || "android.emu.debug",
271
+ projectRoot: options.projectRoot,
272
+ recordLogs: options.recordLogs,
273
+ retries: options.retries,
274
+ headless: options.headless
275
+ });
276
+ });
277
+ } catch (err) {
278
+ console.error("Android tests failed:", err);
279
+ androidExit = 1;
280
+ }
281
+ const success = iosExit === 0 && androidExit === 0;
282
+ console.info(`
293
283
  === Test Results ===`);
294
- console.info(`iOS: ${iosExit === 0 ? "PASSED" : "FAILED"}`);
295
- console.info(`Android: ${androidExit === 0 ? "PASSED" : "FAILED"}`);
296
- process.exit(success ? 0 : 1);
297
- } else {
298
- console.error(`Unknown test platform: ${platform}`);
299
- console.info("Usage: native-ci test [ios|android|maestro|all]");
300
- process.exit(1);
301
- }
302
- break;
303
- }
304
- // ========================================
305
- // Dependency Commands
306
- // ========================================
307
- case "deps":
308
- {
309
- if (!subcommand || subcommand === "status") {
310
- printDepsStatus();
311
- } else if (subcommand === "install") {
312
- console.info("Installing all dependencies...\n");
313
- await ensureIosDeps();
314
- await ensureMaestro();
315
- console.info("\nAll dependencies installed!");
316
- } else if (subcommand === "install-ios") {
317
- await ensureIosDeps();
318
- } else if (subcommand === "install-android") {
319
- await ensureAndroidDeps();
320
- } else if (subcommand === "install-maestro") {
321
- await ensureMaestro();
322
- } else {
323
- console.error(`Unknown deps subcommand: ${subcommand}`);
324
- process.exit(1);
325
- }
326
- break;
327
- }
328
- // ========================================
329
- // Fingerprint Commands
330
- // ========================================
331
- case "fingerprint":
332
- {
333
- const platform = validatePlatform(subcommand);
334
- const result = await generateFingerprint({
335
- platform,
336
- projectRoot: options.projectRoot
337
- });
338
- if (options.githubOutput || isGitHubActions()) {
339
- setGitHubOutput("fingerprint", result.hash);
340
- setGitHubOutput("cache-key", createCacheKey({
341
- platform,
342
- fingerprint: result.hash,
343
- prefix: options.prefix
344
- }));
345
- }
346
- if (options.json) {
347
- console.info(JSON.stringify(result, null, 2));
348
- } else {
349
- console.info(result.hash);
350
- }
351
- break;
352
- }
353
- case "fingerprint-test":
354
- {
355
- const CACHE_FILE = ".fingerprint-cache.json";
356
- console.info("Generating fingerprints...\n");
357
- const iosResult = await generateFingerprint({
358
- platform: "ios",
359
- projectRoot: options.projectRoot
360
- });
361
- const androidResult = await generateFingerprint({
362
- platform: "android",
363
- projectRoot: options.projectRoot
364
- });
365
- const iosFingerprint = iosResult.hash;
366
- const androidFingerprint = androidResult.hash;
367
- console.info("Current fingerprints:");
368
- console.info(` iOS: ${iosFingerprint}`);
369
- console.info(` Android: ${androidFingerprint}`);
370
- console.info("");
371
- const cache = loadCache(CACHE_FILE);
372
- if (cache?.ios && cache?.android) {
373
- console.info("Previous fingerprints (from cache):");
374
- console.info(` iOS: ${cache.ios}`);
375
- console.info(` Android: ${cache.android}`);
376
- console.info("");
377
- const iosChanged = cache.ios !== iosFingerprint;
378
- const androidChanged = cache.android !== androidFingerprint;
379
- if (iosChanged || androidChanged) {
380
- console.info("Fingerprints changed!");
381
- if (iosChanged) console.info(" - iOS fingerprint changed (would trigger iOS rebuild)");
382
- if (androidChanged) console.info(" - Android fingerprint changed (would trigger Android rebuild)");
383
- } else {
384
- console.info("Fingerprints match - no rebuild needed");
385
- }
386
- } else {
387
- console.info("No previous fingerprints cached.");
388
- }
389
- saveCache(CACHE_FILE, {
390
- ios: iosFingerprint,
391
- android: androidFingerprint,
392
- timestamp: (/* @__PURE__ */new Date()).toISOString()
393
- });
394
- console.info(`
284
+ console.info(`iOS: ${iosExit === 0 ? "PASSED" : "FAILED"}`);
285
+ console.info(`Android: ${androidExit === 0 ? "PASSED" : "FAILED"}`);
286
+ process.exit(success ? 0 : 1);
287
+ } else {
288
+ console.error(`Unknown test platform: ${platform}`);
289
+ console.info("Usage: native-ci test [ios|android|maestro|all]");
290
+ process.exit(1);
291
+ }
292
+ break;
293
+ }
294
+ case "deps": {
295
+ if (!subcommand || subcommand === "status") {
296
+ printDepsStatus();
297
+ } else if (subcommand === "install") {
298
+ console.info("Installing all dependencies...\n");
299
+ await ensureIosDeps();
300
+ await ensureMaestro();
301
+ console.info("\nAll dependencies installed!");
302
+ } else if (subcommand === "install-ios") {
303
+ await ensureIosDeps();
304
+ } else if (subcommand === "install-android") {
305
+ await ensureAndroidDeps();
306
+ } else if (subcommand === "install-maestro") {
307
+ await ensureMaestro();
308
+ } else {
309
+ console.error(`Unknown deps subcommand: ${subcommand}`);
310
+ process.exit(1);
311
+ }
312
+ break;
313
+ }
314
+ case "fingerprint": {
315
+ const platform = validatePlatform(subcommand);
316
+ const result = await generateFingerprint({
317
+ platform,
318
+ projectRoot: options.projectRoot
319
+ });
320
+ if (options.githubOutput || isGitHubActions()) {
321
+ setGitHubOutput("fingerprint", result.hash);
322
+ setGitHubOutput("cache-key", createCacheKey({
323
+ platform,
324
+ fingerprint: result.hash,
325
+ prefix: options.prefix
326
+ }));
327
+ }
328
+ if (options.json) {
329
+ console.info(JSON.stringify(result, null, 2));
330
+ } else {
331
+ console.info(result.hash);
332
+ }
333
+ break;
334
+ }
335
+ case "fingerprint-test": {
336
+ const CACHE_FILE = ".fingerprint-cache.json";
337
+ console.info("Generating fingerprints...\n");
338
+ const iosResult = await generateFingerprint({
339
+ platform: "ios",
340
+ projectRoot: options.projectRoot
341
+ });
342
+ const androidResult = await generateFingerprint({
343
+ platform: "android",
344
+ projectRoot: options.projectRoot
345
+ });
346
+ const iosFingerprint = iosResult.hash;
347
+ const androidFingerprint = androidResult.hash;
348
+ console.info("Current fingerprints:");
349
+ console.info(` iOS: ${iosFingerprint}`);
350
+ console.info(` Android: ${androidFingerprint}`);
351
+ console.info("");
352
+ const cache = loadCache(CACHE_FILE);
353
+ if (cache?.ios && cache?.android) {
354
+ console.info("Previous fingerprints (from cache):");
355
+ console.info(` iOS: ${cache.ios}`);
356
+ console.info(` Android: ${cache.android}`);
357
+ console.info("");
358
+ const iosChanged = cache.ios !== iosFingerprint;
359
+ const androidChanged = cache.android !== androidFingerprint;
360
+ if (iosChanged || androidChanged) {
361
+ console.info("Fingerprints changed!");
362
+ if (iosChanged) console.info(" - iOS fingerprint changed (would trigger iOS rebuild)");
363
+ if (androidChanged) console.info(" - Android fingerprint changed (would trigger Android rebuild)");
364
+ } else {
365
+ console.info("Fingerprints match - no rebuild needed");
366
+ }
367
+ } else {
368
+ console.info("No previous fingerprints cached.");
369
+ }
370
+ saveCache(CACHE_FILE, {
371
+ ios: iosFingerprint,
372
+ android: androidFingerprint,
373
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
374
+ });
375
+ console.info(`
395
376
  Saved fingerprints to ${CACHE_FILE}`);
396
- console.info(`
377
+ console.info(`
397
378
  To test cache invalidation:
398
379
  1. Add a native dependency: yarn add react-native-mmkv
399
380
  2. Run this script again: native-ci fingerprint-test
@@ -401,91 +382,85 @@ To test cache invalidation:
401
382
  4. Remove the dependency: yarn remove react-native-mmkv
402
383
  5. Run this script again - fingerprints should match original
403
384
  `);
404
- break;
405
- }
406
- case "pre-hash":
407
- {
408
- const files = [subcommand, ...args].filter(Boolean);
409
- if (files.length === 0) {
410
- console.error("Error: at least one file required");
411
- process.exit(1);
412
- }
413
- const hash = generatePreFingerprintHash(files, options.projectRoot);
414
- if (options.githubOutput || isGitHubActions()) {
415
- setGitHubOutput("pre-fingerprint-hash", hash);
416
- }
417
- if (options.json) {
418
- console.info(JSON.stringify({
419
- hash,
420
- files
421
- }, null, 2));
422
- } else {
423
- console.info(hash);
424
- }
425
- break;
426
- }
427
- case "cache-key":
428
- {
429
- const platform = validatePlatform(subcommand);
430
- const fingerprint = args[0];
431
- if (!fingerprint) {
432
- console.error("Error: fingerprint is required");
433
- process.exit(1);
434
- }
435
- const cacheKey = createCacheKey({
436
- platform,
437
- fingerprint,
438
- prefix: options.prefix
439
- });
440
- if (options.githubOutput || isGitHubActions()) {
441
- setGitHubOutput("cache-key", cacheKey);
442
- }
443
- console.info(cacheKey);
444
- break;
445
- }
446
- // ========================================
447
- // KV Store Commands
448
- // ========================================
449
- case "kv-get":
450
- {
451
- const key = subcommand;
452
- if (!key) {
453
- console.error("Error: key is required");
454
- process.exit(1);
455
- }
456
- const kv = getKVCredentials();
457
- const value = await getFingerprintFromKV(kv, key);
458
- if (options.githubOutput || isGitHubActions()) {
459
- setGitHubOutput("value", value || "");
460
- setGitHubOutput("found", value ? "true" : "false");
461
- }
462
- if (value) {
463
- console.info(value);
464
- } else {
465
- process.exit(1);
466
- }
467
- break;
468
- }
469
- case "kv-set":
470
- {
471
- const key = subcommand;
472
- const value = args[0];
473
- if (!key || !value) {
474
- console.error("Error: key and value are required");
475
- process.exit(1);
476
- }
477
- const kv = getKVCredentials();
478
- await saveFingerprintToKV(kv, key, value);
479
- console.info("OK");
480
- break;
481
- }
482
- default:
483
- console.error(`Unknown command: ${command}`);
484
- console.info(HELP);
485
- process.exit(1);
486
- }
385
+ break;
386
+ }
387
+ case "pre-hash": {
388
+ const files = [subcommand, ...args].filter(Boolean);
389
+ if (files.length === 0) {
390
+ console.error("Error: at least one file required");
391
+ process.exit(1);
392
+ }
393
+ const hash = generatePreFingerprintHash(files, options.projectRoot);
394
+ if (options.githubOutput || isGitHubActions()) {
395
+ setGitHubOutput("pre-fingerprint-hash", hash);
396
+ }
397
+ if (options.json) {
398
+ console.info(JSON.stringify({
399
+ hash,
400
+ files
401
+ }, null, 2));
402
+ } else {
403
+ console.info(hash);
404
+ }
405
+ break;
406
+ }
407
+ case "cache-key": {
408
+ const platform = validatePlatform(subcommand);
409
+ const fingerprint = args[0];
410
+ if (!fingerprint) {
411
+ console.error("Error: fingerprint is required");
412
+ process.exit(1);
413
+ }
414
+ const cacheKey = createCacheKey({
415
+ platform,
416
+ fingerprint,
417
+ prefix: options.prefix
418
+ });
419
+ if (options.githubOutput || isGitHubActions()) {
420
+ setGitHubOutput("cache-key", cacheKey);
421
+ }
422
+ console.info(cacheKey);
423
+ break;
424
+ }
425
+ case "kv-get": {
426
+ const key = subcommand;
427
+ if (!key) {
428
+ console.error("Error: key is required");
429
+ process.exit(1);
430
+ }
431
+ const kv = getKVCredentials();
432
+ const value = await getFingerprintFromKV(kv, key);
433
+ if (options.githubOutput || isGitHubActions()) {
434
+ setGitHubOutput("value", value || "");
435
+ setGitHubOutput("found", value ? "true" : "false");
436
+ }
437
+ if (value) {
438
+ console.info(value);
439
+ } else {
440
+ process.exit(1);
441
+ }
442
+ break;
443
+ }
444
+ case "kv-set": {
445
+ const key = subcommand;
446
+ const value = args[0];
447
+ if (!key || !value) {
448
+ console.error("Error: key and value are required");
449
+ process.exit(1);
450
+ }
451
+ const kv = getKVCredentials();
452
+ await saveFingerprintToKV(kv, key, value);
453
+ console.info("OK");
454
+ break;
455
+ }
456
+ default:
457
+ console.error(`Unknown command: ${command}`);
458
+ console.info(HELP);
459
+ process.exit(1);
460
+ }
487
461
  } catch (error) {
488
- console.error("Error:", error.message);
489
- process.exit(1);
462
+ console.error("Error:", error.message);
463
+ process.exit(1);
490
464
  }
465
+
491
466
  //# sourceMappingURL=cli.mjs.map