ubriot 0.2.0 → 0.2.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 (3) hide show
  1. package/README.md +2 -1
  2. package/dist/index.js +147 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ubriot
2
2
 
3
- CI/CD CLI for Expo and React Native. Ship iOS and Android native builds, submit to TestFlight and Google Play, and push OTA updates — all from one command.
3
+ Mobile release CLI. Ship iOS and Android native builds, submit to TestFlight and Google Play, and push OTA updates — all from one command.
4
4
 
5
5
  ## Install
6
6
 
@@ -29,6 +29,7 @@ ubriot update # publish an OTA update
29
29
  | `ubriot push setup --app <name>` | Set up native push notifications |
30
30
  | `ubriot apps list` | List your release apps |
31
31
  | `ubriot builds list --app <name>` | List recent builds |
32
+ | `ubriot builds watch <buildId>` | Watch a build live: streaming logs + status until it finishes |
32
33
 
33
34
  ## Documentation
34
35
 
package/dist/index.js CHANGED
@@ -1,5 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  Object.defineProperty(exports, "__esModule", { value: true });
4
37
  const commander_1 = require("commander");
5
38
  const readline_1 = require("./readline");
@@ -567,6 +600,7 @@ apps
567
600
  .option("--android-id <id>", "Android application ID")
568
601
  .option("--ios-bundle <id>", "iOS bundle identifier")
569
602
  .option("--google-play-track <track>", "Default Google Play track", "internal")
603
+ .option("--expo-project-root <path>", "Path to the Expo app inside the repo (monorepos), e.g. products/myapp/mobile")
570
604
  .action(async (opts) => {
571
605
  (0, config_1.requireAuth)();
572
606
  try {
@@ -578,6 +612,7 @@ apps
578
612
  androidApplicationId: opts.androidId,
579
613
  iosBundleIdentifier: opts.iosBundle,
580
614
  googlePlayTrack: opts.googlePlayTrack,
615
+ expoProjectRoot: opts.expoProjectRoot,
581
616
  });
582
617
  console.log(`Created app: ${data.name} (id: ${data.id}, slug: ${data.slug})`);
583
618
  }
@@ -611,6 +646,7 @@ apps
611
646
  .description("Update release app settings (TestFlight mapping, auto-submit)")
612
647
  .requiredOption("--app <appId>", "Release app id")
613
648
  .option("--ios-bundle <id>", "iOS bundle identifier")
649
+ .option("--expo-project-root <path>", "Path to the Expo app inside the repo (monorepos)")
614
650
  .option("--android-id <id>", "Android application ID / package name")
615
651
  .option("--asc-app-id <id>", "App Store Connect app id (from ASC app URL)")
616
652
  .option("--clear-asc-app-id", "Remove stored ASC app id (use bundle id lookup)", false)
@@ -625,6 +661,8 @@ apps
625
661
  const body = {};
626
662
  if (opts.iosBundle !== undefined)
627
663
  body.iosBundleIdentifier = opts.iosBundle || null;
664
+ if (opts.expoProjectRoot !== undefined)
665
+ body.expoProjectRoot = opts.expoProjectRoot;
628
666
  if (opts.androidId !== undefined)
629
667
  body.androidApplicationId = opts.androidId || null;
630
668
  if (opts.clearAscAppId)
@@ -660,8 +698,11 @@ commander_1.program
660
698
  .option("--profile <profile>", "Build profile", "production")
661
699
  .option("--repo <url>", "Override repo URL")
662
700
  .option("--branch <branch>", "Branch to build")
701
+ .option("--local [path]", "Build from a local directory instead of cloning from remote (path defaults to CWD)")
663
702
  .option("--track <track>", "Google Play track for Android", "internal")
664
703
  .option("--no-submit", "Build only; do not submit to TestFlight / Google Play")
704
+ .option("--reprovision-ios", "Force-regenerate the iOS provisioning profile before building", false)
705
+ .option("--ios-capability <type...>", "Apple capability to ensure on the bundle ID (e.g. HEALTHKIT); regenerates the profile if newly added")
665
706
  .option("--poll-interval <seconds>", "Polling interval while waiting", "15")
666
707
  .option("--timeout <minutes>", "Maximum wait time for the build and store submit", "180")
667
708
  .action(async (platform, opts) => {
@@ -679,11 +720,51 @@ commander_1.program
679
720
  const client = (0, api_1.makeClient)();
680
721
  const pollIntervalMs = parsePositiveNumber(opts.pollInterval, 15, "--poll-interval") * 1000;
681
722
  const timeoutMs = parsePositiveNumber(opts.timeout, 180, "--timeout") * 60 * 1000;
723
+ // --local builds from a file:// path so the worker clones locally without needing a remote push
724
+ let repoUrl = opts.repo;
725
+ let branch = opts.branch;
726
+ if (opts.local !== undefined) {
727
+ const { resolve } = await Promise.resolve().then(() => __importStar(require("path")));
728
+ const { execSync } = await Promise.resolve().then(() => __importStar(require("child_process")));
729
+ const localPath = opts.local === true ? process.cwd() : resolve(opts.local);
730
+ repoUrl = `file://${localPath}`;
731
+ if (!branch) {
732
+ try {
733
+ branch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: localPath, stdio: "pipe" }).toString().trim();
734
+ }
735
+ catch { }
736
+ }
737
+ console.log(`Building from local path: ${localPath} (branch: ${branch ?? "default"})`);
738
+ }
739
+ // EAS-style auto-provisioning: ensure a provisioning profile exists for iOS.
740
+ if (normalizedPlatform === "ios") {
741
+ const provisionExplicit = Boolean(opts.reprovisionIos || opts.iosCapability?.length);
742
+ const provisionBody = {};
743
+ if (opts.reprovisionIos)
744
+ provisionBody.force = true;
745
+ if (opts.iosCapability?.length)
746
+ provisionBody.capabilities = opts.iosCapability;
747
+ try {
748
+ const { data: prov } = await client.post(`/ubriot/apps/${encodeURIComponent(app)}/provision-ios`, provisionBody);
749
+ if (prov?.status === "provisioned") {
750
+ const caps = prov.capabilitiesAdded?.length ? ` [capabilities added: ${prov.capabilitiesAdded.join(", ")}]` : "";
751
+ console.log(`Auto-provisioned iOS signing: ${prov.profileName} (${prov.bundleId})${caps}`);
752
+ }
753
+ }
754
+ catch (provErr) {
755
+ const msg = provErr?.response?.data?.message ?? provErr?.message ?? "";
756
+ // When reprovisioning or capabilities were explicitly requested, a provisioning
757
+ // failure means the build is doomed to fail signing — stop here instead.
758
+ if (provisionExplicit)
759
+ throw new Error(`iOS provisioning failed: ${msg}`);
760
+ console.log(`⚠ Auto-provisioning skipped: ${msg}`);
761
+ }
762
+ }
682
763
  const { data } = await client.post(`/ubriot/apps/${encodeURIComponent(app)}/builds`, {
683
764
  platform: normalizedPlatform,
684
765
  profile: opts.profile,
685
- repoUrl: opts.repo,
686
- branch: opts.branch,
766
+ repoUrl,
767
+ branch,
687
768
  });
688
769
  console.log(`Queued build #${data.id} (${data.platform} / ${data.profile}) — status: ${data.status}`);
689
770
  const finished = await waitForBuild(client, data.id, pollIntervalMs, timeoutMs);
@@ -818,6 +899,70 @@ builds
818
899
  (0, api_1.handleError)(err);
819
900
  }
820
901
  });
902
+ builds
903
+ .command("watch <buildId>")
904
+ .description("Watch a build live: streams logs and status changes until the build and any store submission finish")
905
+ .option("--poll-interval <seconds>", "Polling interval while watching", "5")
906
+ .option("--timeout <minutes>", "Maximum watch time", "180")
907
+ .option("--no-logs", "Only print status changes; skip streaming build logs")
908
+ .action(async (buildId, opts) => {
909
+ (0, config_1.requireAuth)();
910
+ try {
911
+ const client = (0, api_1.makeClient)();
912
+ const intervalMs = parsePositiveNumber(opts.pollInterval, 5, "--poll-interval") * 1000;
913
+ const timeoutMs = parsePositiveNumber(opts.timeout, 180, "--timeout") * 60 * 1000;
914
+ const deadline = Date.now() + timeoutMs;
915
+ let lastStatusLine = "";
916
+ let lastLogId = 0;
917
+ while (Date.now() <= deadline) {
918
+ const { data } = await client.get(`/ubriot/builds/${buildId}`);
919
+ if (opts.logs !== false) {
920
+ // The logs endpoint has no incremental cursor, so fetch all and print only ids we haven't seen.
921
+ try {
922
+ const { data: logs } = await client.get(`/ubriot/builds/${buildId}/logs`);
923
+ for (const l of logs) {
924
+ if (l.id <= lastLogId)
925
+ continue;
926
+ lastLogId = l.id;
927
+ const message = l.message.endsWith("\n") ? l.message : `${l.message}\n`;
928
+ process.stdout.write(`[${l.phase ?? "-"}] ${message}`);
929
+ }
930
+ }
931
+ catch {
932
+ // Logs are best-effort; a transient fetch error shouldn't kill the watch.
933
+ }
934
+ }
935
+ const statusLine = buildStatusLine(data);
936
+ if (statusLine !== lastStatusLine) {
937
+ console.log(`── ${statusLine}`);
938
+ lastStatusLine = statusLine;
939
+ }
940
+ const buildDone = BUILD_TERMINAL_STATUSES.has(data.status);
941
+ const ascPending = data.ascSubmissionStatus != null && !ASC_TERMINAL_STATUSES.has(data.ascSubmissionStatus);
942
+ const playPending = data.playSubmissionStatus != null && !PLAY_TERMINAL_STATUSES.has(data.playSubmissionStatus);
943
+ if (buildDone && !ascPending && !playPending) {
944
+ if (data.status !== "succeeded") {
945
+ throw new Error(`Build #${buildId} ${data.status}${data.errorMessage ? `: ${data.errorMessage}` : ""}`);
946
+ }
947
+ if (data.ascSubmissionStatus === "failed") {
948
+ throw new Error(`TestFlight submit for build #${buildId} failed${data.ascSubmissionError ? `: ${data.ascSubmissionError}` : ""}`);
949
+ }
950
+ if (data.playSubmissionStatus === "failed") {
951
+ throw new Error(`Google Play submit for build #${buildId} failed${data.playSubmissionError ? `: ${data.playSubmissionError}` : ""}`);
952
+ }
953
+ const asc = data.ascSubmissionStatus === "succeeded" ? " — TestFlight submission succeeded" : "";
954
+ const play = data.playSubmissionStatus === "succeeded" ? ` — Google Play submission succeeded${data.playTrack ? ` (${data.playTrack})` : ""}` : "";
955
+ console.log(`Build #${buildId} succeeded${asc}${play}.`);
956
+ return;
957
+ }
958
+ await sleep(intervalMs);
959
+ }
960
+ throw new Error(`Timed out watching build #${buildId} after ${opts.timeout} minutes`);
961
+ }
962
+ catch (err) {
963
+ (0, api_1.handleError)(err);
964
+ }
965
+ });
821
966
  builds
822
967
  .command("cancel <buildId>")
823
968
  .description("Cancel a queued/running build")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ubriot",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "CI/CD CLI for Expo and React Native — ship iOS, Android, and OTA updates from your terminal.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://ubriot.dev",