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.
- package/README.md +2 -1
- package/dist/index.js +147 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ubriot
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
686
|
-
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")
|