liftbay 0.5.0 → 0.5.2

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/dist/index.js CHANGED
@@ -25969,7 +25969,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync }
25969
25969
  import { homedir } from "os";
25970
25970
  import { join } from "path";
25971
25971
 
25972
- // ../sdk/node_modules/openapi-fetch/dist/index.mjs
25972
+ // node_modules/openapi-fetch/dist/index.mjs
25973
25973
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
25974
25974
  var supportsRequestInitExt = () => {
25975
25975
  return typeof process === "object" && Number.parseInt(process?.versions?.node?.substring(0, 2)) >= 18 && process.versions.undici;
@@ -26445,7 +26445,7 @@ function removeTrailingSlash(url) {
26445
26445
  return url;
26446
26446
  }
26447
26447
 
26448
- // ../sdk/dist/index.js
26448
+ // node_modules/@liftbay/sdk/dist/index.js
26449
26449
  var LiftbayApiError = class extends Error {
26450
26450
  constructor(status2, code, message, fields = {}) {
26451
26451
  super(message);
@@ -28582,6 +28582,11 @@ function writeAppJson(data, cwd = process.cwd()) {
28582
28582
 
28583
28583
  // src/prompts.ts
28584
28584
  var import_picocolors3 = __toESM(require_picocolors(), 1);
28585
+ function fit(text2, reserve = 8) {
28586
+ const width = Math.max(20, (process.stdout.columns || 80) - reserve);
28587
+ const plain = text2.replace(/\x1b\[[0-9;]*m/g, "");
28588
+ return plain.length <= width ? text2 : `${plain.slice(0, width - 1)}\u2026`;
28589
+ }
28585
28590
  var symbol2 = (state) => state === "submit" ? import_picocolors3.default.green("\u25C7") : state === "cancel" ? import_picocolors3.default.red("\u25A0") : state === "error" ? import_picocolors3.default.yellow("\u25B2") : import_picocolors3.default.cyan("\u25C6");
28586
28591
  function checkboxes(opts) {
28587
28592
  const required = opts.required ?? true;
@@ -28983,7 +28988,7 @@ async function status(lb, config, refresh = true) {
28983
28988
  }
28984
28989
  async function waitFor(lb, config, message, done) {
28985
28990
  const spin = spinner();
28986
- spin.start(`${message} ${import_picocolors6.default.dim("(Ctrl-C to stop)")}`);
28991
+ spin.start(fit(message));
28987
28992
  const until = Date.now() + WAIT_MINUTES * 6e4;
28988
28993
  while (Date.now() < until) {
28989
28994
  await sleep2(4e3);
@@ -28998,7 +29003,13 @@ async function waitFor(lb, config, message, done) {
28998
29003
  }
28999
29004
  async function waitForInstall(lb, config, attempt) {
29000
29005
  const spin = spinner();
29001
- spin.start(`Waiting for the GitHub App to be installed ${import_picocolors6.default.dim("(Ctrl-C to stop)")}`);
29006
+ spin.start(fit("Waiting for the GitHub App to be installed"));
29007
+ const explain = (text2, spinnerText) => {
29008
+ spin.stop(import_picocolors6.default.dim("\u2026"));
29009
+ log.info(text2);
29010
+ spin.start(fit(spinnerText));
29011
+ };
29012
+ let noRepoAccessShown = false;
29002
29013
  const until = Date.now() + WAIT_MINUTES * 6e4;
29003
29014
  let last = "waiting";
29004
29015
  while (Date.now() < until) {
@@ -29006,7 +29017,7 @@ async function waitForInstall(lb, config, attempt) {
29006
29017
  const a2 = attempt ? (await lb.GET("/v1/orgs/{org}/github/install/{attempt}", { params: { path: { org: config.org, attempt } } })).data : void 0;
29007
29018
  if (a2 && a2.status !== last) {
29008
29019
  last = a2.status;
29009
- if (a2.status === "needs_sign_in") spin.message(`GitHub is done. Sign in to Liftbay in that browser to finish linking ${import_picocolors6.default.dim("(Ctrl-C to stop)")}`);
29020
+ if (a2.status === "needs_sign_in") explain("GitHub is done. Sign in to Liftbay in that browser to finish linking.", "Waiting for you to sign in to Liftbay in the browser");
29010
29021
  else if (a2.status !== "waiting" && a2.status !== "connected") {
29011
29022
  spin.error("GitHub didn't connect");
29012
29023
  return { ok: false, message: a2.message };
@@ -29018,7 +29029,10 @@ async function waitForInstall(lb, config, attempt) {
29018
29029
  return { ok: true, status: s };
29019
29030
  }
29020
29031
  if (a2?.status === "connected") {
29021
- spin.message(`Installed on ${s.repo.split("/")[0]}, but without access to ${s.repo}: on GitHub, Configure the app and select the repository ${import_picocolors6.default.dim("(Ctrl-C to stop)")}`);
29032
+ if (!noRepoAccessShown) {
29033
+ noRepoAccessShown = true;
29034
+ explain(`Installed on ${s.repo.split("/")[0]}, but without access to ${s.repo}. On GitHub, Configure the app and select the repository.`, "Waiting for access to the repository");
29035
+ }
29022
29036
  }
29023
29037
  }
29024
29038
  spin.error("Stopped waiting");
@@ -29206,7 +29220,7 @@ async function readyToBuild(branch, interactive) {
29206
29220
  }
29207
29221
  }
29208
29222
  async function build(opts) {
29209
- let config = requireConfig();
29223
+ const config = requireConfig();
29210
29224
  const lb = client();
29211
29225
  const interactive = isInteractive();
29212
29226
  const path2 = { org: config.org, project: config.project };
@@ -29247,25 +29261,26 @@ async function build(opts) {
29247
29261
  platforms = choice === "all" ? profile.platforms : [choice];
29248
29262
  } else platforms = profile.platforms;
29249
29263
  const storeIos = platforms.includes("ios") && !opts.simulator && profile.distribution === "store";
29250
- let iosEngine = opts.engine ?? config.build?.iosEngine ?? "auto";
29264
+ let iosEngine = opts.engine ?? "auto";
29251
29265
  if (!storeIos && iosEngine === "xcode-cloud") {
29252
- if (opts.engine === "xcode-cloud") fail(`Xcode Cloud makes iOS store builds only; profile "${profileName}" is ${opts.simulator ? "a simulator" : `an ${profile.distribution}`} build. Use a store profile, or --engine github-actions.`);
29253
- iosEngine = "github-actions";
29266
+ fail(`Xcode Cloud makes iOS store builds only; profile "${profileName}" is ${opts.simulator ? "a simulator" : `an ${profile.distribution}`} build. Use a store profile, or --engine github-actions.`);
29254
29267
  }
29255
29268
  if (storeIos) {
29256
- if (!opts.engine && !config.build?.iosEngine && interactive) {
29269
+ if (!opts.engine && interactive) {
29270
+ const setting = (await unwrap(lb.GET("/v1/orgs/{org}/projects/{project}", { params: { path: path2 } }))).iosEngine ?? "github-actions";
29271
+ const label = (e) => e === "xcode-cloud" ? "Xcode Cloud" : "GitHub Actions";
29257
29272
  iosEngine = cancelled(
29258
29273
  await select({
29259
- message: "Where should iOS builds run?",
29260
- options: [
29261
- { value: "github-actions", label: "GitHub Actions", hint: "macOS runners on your GitHub minutes" },
29262
- { value: "xcode-cloud", label: "Xcode Cloud", hint: "25 free hours a month with the Apple Developer Program" }
29263
- ]
29274
+ message: "Where should this iOS build run?",
29275
+ options: ["github-actions", "xcode-cloud"].map((e) => ({
29276
+ value: e,
29277
+ label: label(e),
29278
+ hint: [e === setting ? "project default" : "", e === "xcode-cloud" ? "25 free hours a month with the Apple Developer Program" : "macOS runners on your GitHub minutes"].filter(Boolean).join(" \xB7 ")
29279
+ })),
29280
+ initialValue: setting
29264
29281
  })
29265
29282
  );
29266
- config = { ...config, build: { ...config.build, iosEngine } };
29267
- writeConfig(config);
29268
- log.info(import_picocolors7.default.dim("Saved in liftbay.json (commit it). Override once with --engine."));
29283
+ if (iosEngine !== setting) log.info(import_picocolors7.default.dim(`Just this build. Change the project default in the dashboard's project settings, or with ${cmd("liftbay ci init --ios-engine " + iosEngine)}.`));
29269
29284
  }
29270
29285
  if (iosEngine === "xcode-cloud") {
29271
29286
  const xc = await unwrap(lb.GET("/v1/orgs/{org}/projects/{project}/xcode-cloud", { params: { path: path2 } }));
@@ -29543,7 +29558,7 @@ async function xcodeCloudConnect(opts) {
29543
29558
  last = s.step;
29544
29559
  if (!wait || s.step === "apple_key") fail(s.step === "apple_key" ? s.message : "Not connected yet. Run this again once the step above is done.");
29545
29560
  const spin = spinner();
29546
- spin.start("Waiting for that step (checking every 10 seconds; Ctrl-C to stop)");
29561
+ spin.start(fit("Waiting for that step (checking every 10 seconds)"));
29547
29562
  await sleep5(1e4);
29548
29563
  spin.stop("Checking again");
29549
29564
  }
@@ -30108,16 +30123,17 @@ async function ciInit(app, config, opts) {
30108
30123
  let iosEngine = opts.iosEngine;
30109
30124
  if (iosEngine && iosEngine !== "github-actions" && iosEngine !== "xcode-cloud") fail("--ios-engine must be github-actions or xcode-cloud");
30110
30125
  if (platforms.includes("ios") && !iosEngine) {
30111
- iosEngine = config.build?.iosEngine ?? (opts.yes || !isInteractive() ? "github-actions" : cancelled(
30126
+ const current = (await unwrap(lb.GET("/v1/orgs/{org}/projects/{project}", { params: { path: { org: config.org, project: config.project } } }))).iosEngine;
30127
+ iosEngine = opts.yes || !isInteractive() ? current ?? "github-actions" : cancelled(
30112
30128
  await select({
30113
- message: "Where should iOS builds run?",
30129
+ message: "Where should iOS store builds run? (you can pick for each build too)",
30114
30130
  options: [
30115
30131
  { value: "github-actions", label: "GitHub Actions", hint: "macOS runners on your GitHub minutes; ready now" },
30116
30132
  { value: "xcode-cloud", label: "Xcode Cloud", hint: "store builds, 25 free hours a month with the Apple Developer Program; one-time setup after iOS signing" }
30117
30133
  ],
30118
- initialValue: "github-actions"
30134
+ initialValue: current ?? "github-actions"
30119
30135
  })
30120
- ));
30136
+ );
30121
30137
  }
30122
30138
  if (platforms.includes("ios") && !app.bundleId) log.warn(`No iOS bundle identifier found. Set ${import_picocolors13.default.cyan("expo.ios.bundleIdentifier")} in app.json before the first iOS build.`);
30123
30139
  if (platforms.includes("android") && !app.androidPackage) log.warn(`No Android package found. Set ${import_picocolors13.default.cyan("expo.android.package")} in app.json before the first Android build.`);
@@ -30155,12 +30171,13 @@ async function ciInit(app, config, opts) {
30155
30171
  log.info(import_picocolors13.default.dim(`Variables kept on expo.dev (EAS environment variables) aren't in eas.json: ${import_picocolors13.default.cyan("npx eas env:pull --environment production")}, then ${import_picocolors13.default.cyan(cmd("liftbay env import .env.local --environment production"))}.`));
30156
30172
  }
30157
30173
  const profiles = Object.fromEntries(Object.entries(base).map(([name, prof]) => [name, { ...prof, platforms }]));
30158
- const next = { ...config, build: { profiles, triggers: config.build?.triggers ?? [], ...iosEngine ? { iosEngine } : {} } };
30174
+ if (iosEngine) await unwrap(lb.PATCH("/v1/orgs/{org}/projects/{project}", { params: { path: path2 }, body: { iosEngine } }));
30175
+ const next = { ...config, build: { profiles, triggers: config.build?.triggers ?? [] } };
30159
30176
  writeConfig(next);
30160
30177
  note(
30161
30178
  Object.entries(profiles).map(([name, prof]) => `${import_picocolors13.default.bold(name.padEnd(12))} env ${prof.environment.padEnd(12)} ${prof.distribution === "store" ? "store" : "internal"} \xB7 ${prof.platforms.join(", ")}`).join("\n") + (iosEngine ? `
30162
30179
 
30163
- iOS builds run on ${iosEngine === "xcode-cloud" ? "Xcode Cloud" : "GitHub Actions"}` : ""),
30180
+ iOS store builds run on ${iosEngine === "xcode-cloud" ? "Xcode Cloud" : "GitHub Actions"} (project setting)` : ""),
30164
30181
  "Build profiles"
30165
30182
  );
30166
30183
  if (iosEngine === "xcode-cloud") {
@@ -30549,7 +30566,7 @@ async function publish(opts) {
30549
30566
  let done = 0;
30550
30567
  for (const m of prep.missing) {
30551
30568
  const f = bySha.get(m.sha256);
30552
- spin.message(`Uploading ${++done}/${prep.missing.length}: ${f.path}`);
30569
+ spin.message(fit(`Uploading ${++done}/${prep.missing.length}: ${f.path}`));
30553
30570
  const res = await fetch(m.uploadUrl, { method: "PUT", body: readFileSync10(f.abs), headers: { "Content-Type": f.contentType } });
30554
30571
  if (!res.ok) fail(`Upload failed for ${f.path} (${res.status})`);
30555
30572
  }
@@ -30635,7 +30652,7 @@ async function channels() {
30635
30652
  }
30636
30653
 
30637
30654
  // src/index.ts
30638
- var program2 = new Command("liftbay").description("Build, ship and update React Native apps").version("0.5.0").option("--json", "print the result as JSON on stdout (progress goes to stderr; never prompts). Also LIFTBAY_JSON=1").hook("preAction", () => {
30655
+ var program2 = new Command("liftbay").description("Build, ship and update React Native apps").version("0.5.2").option("--json", "print the result as JSON on stdout (progress goes to stderr; never prompts). Also LIFTBAY_JSON=1").hook("preAction", () => {
30639
30656
  if (program2.opts().json || process.env.LIFTBAY_JSON === "1") enableJson();
30640
30657
  });
30641
30658
  var wrap = (fn) => async (...args) => {
@@ -30645,14 +30662,14 @@ var wrap = (fn) => async (...args) => {
30645
30662
  reportError(e);
30646
30663
  }
30647
30664
  };
30648
- var setupOptions = (cmd2) => cmd2.option("--org <slug>", "organization").option("--project <slug>", "project slug (created if it doesn't exist)").option("--platforms <list>", "ios, android or all").option("--runtime-policy <policy>", "appVersion or fingerprint").option("--ios-engine <engine>", "where iOS builds run: github-actions or xcode-cloud").option("--no-eas", "don't use eas.json's build profiles and env, even if there is one").option("-y, --yes", "accept defaults, no prompts").option("--api-url <url>", "API URL (for self-hosted or local development)").option("--dashboard-url <url>", "dashboard URL");
30665
+ var setupOptions = (cmd2) => cmd2.option("--org <slug>", "organization").option("--project <slug>", "project slug (created if it doesn't exist)").option("--platforms <list>", "ios, android or all").option("--runtime-policy <policy>", "appVersion or fingerprint").option("--ios-engine <engine>", "project setting: where iOS store builds run, github-actions or xcode-cloud").option("--no-eas", "don't use eas.json's build profiles and env, even if there is one").option("-y, --yes", "accept defaults, no prompts").option("--api-url <url>", "API URL (for self-hosted or local development)").option("--dashboard-url <url>", "dashboard URL");
30649
30666
  program2.command("login").description("Sign in through the browser").option("--api-url <url>", "API URL").option("--dashboard-url <url>", "dashboard URL").option("--no-open", "don't open the browser").option("--no-wait", "start the sign-in and exit; run `liftbay login` again after approving to finish (for agents)").action(wrap(login));
30650
30667
  program2.command("logout").description("Sign out and remove the saved token").action(wrap(logout));
30651
30668
  program2.command("whoami").description("Show the signed-in user and organizations").action(wrap(whoami));
30652
30669
  setupOptions(program2.command("init").description("Set up this app: link the project, builds, then OTA updates").option("--no-ota", "skip OTA updates")).action(wrap(initCommand));
30653
30670
  var ci = program2.command("ci").description("Builds");
30654
30671
  setupOptions(ci.command("init").description("Set up build profiles, environments and app identifiers")).action(wrap(ciInitCommand));
30655
- program2.command("build").description("Build on your GitHub Actions (Android on Linux, iOS on macOS) and follow the logs").option("--platform <platform>", "ios, android or all (asked in a terminal; otherwise the profile's platforms)").option("--profile <name>", "build profile from liftbay.json (asked in a terminal; otherwise production)").option("--ref <ref>", "branch, tag or commit to build (default: current branch)").option("-m, --message <text>", "build message").option("--simulator", "iOS: build for the simulator instead of a signed .ipa").option("--no-wait", "queue and exit without following the logs").option("--submit", "submit store builds when they succeed: TestFlight, or a Google Play track").option("--track <track>", "Play track for --submit: internal, alpha, beta or production").option("--engine <engine>", "where iOS builds run: github-actions or xcode-cloud (default: build.iosEngine in liftbay.json; asked in a terminal)").action(wrap(build));
30672
+ program2.command("build").description("Build on your GitHub Actions (Android on Linux, iOS on macOS) and follow the logs").option("--platform <platform>", "ios, android or all (asked in a terminal; otherwise the profile's platforms)").option("--profile <name>", "build profile from liftbay.json (asked in a terminal; otherwise production)").option("--ref <ref>", "branch, tag or commit to build (default: current branch)").option("-m, --message <text>", "build message").option("--simulator", "iOS: build for the simulator instead of a signed .ipa").option("--no-wait", "queue and exit without following the logs").option("--submit", "submit store builds when they succeed: TestFlight, or a Google Play track").option("--track <track>", "Play track for --submit: internal, alpha, beta or production").option("--engine <engine>", "where this iOS store build runs: github-actions or xcode-cloud (asked in a terminal; otherwise the project's setting)").action(wrap(build));
30656
30673
  var builds = program2.command("builds").description("Look at builds: list, status, logs, artifacts");
30657
30674
  builds.command("list").description("Recent builds, newest first").option("--platform <platform>", "ios or android").option("--status <list>", "comma-separated: queued, preparing, running, uploading, succeeded, failed, cancelled").option("--limit <n>", "how many", "20").action(wrap(buildsList));
30658
30675
  builds.command("view <number>").description("Status, phases and artifacts of one build").action(wrap(buildsView));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liftbay",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Build, sign, submit and update React Native apps from your own CI.",
5
5
  "keywords": [
6
6
  "react-native",
@@ -49,7 +49,7 @@
49
49
  "devDependencies": {
50
50
  "@clack/core": "1.5.1",
51
51
  "@clack/prompts": "^1.8.1",
52
- "@liftbay/sdk": "^0.1.1",
52
+ "@liftbay/sdk": "^0.1.2",
53
53
  "@types/node": "^26.6.4",
54
54
  "@types/qrcode": "^1.5.6",
55
55
  "@types/node-forge": "^1.3.14",
@@ -60,7 +60,7 @@ Useful flags:
60
60
 
61
61
  To set up one part on its own, use `liftbay ci init --yes --json` (builds) or `liftbay ota init --yes --json` (OTA).
62
62
 
63
- Pass `--ios-engine github-actions|xcode-cloud` for where iOS **store** builds run; it's saved as `build.iosEngine` in `liftbay.json`. Xcode Cloud uses the Apple team's free hours, and needs iOS signing and `liftbay xcode-cloud connect` first. Internal (ad hoc) and simulator builds always run on GitHub Actions.
63
+ `--ios-engine github-actions|xcode-cloud` sets a **project setting**: where iOS **store** builds run unless a build picks. Builds you start without `--engine`, and every build started by a git push, use it. It's also in the dashboard's project settings. Xcode Cloud uses the Apple team's free hours, and needs iOS signing and `liftbay xcode-cloud connect` first; until it's connected (or when it's out of hours), those builds fall back to GitHub Actions and say so in the log. Internal (ad hoc) and simulator builds always run on GitHub Actions.
64
64
 
65
65
  Then get GitHub ready. Builds start the workflow through the Liftbay GitHub App:
66
66
 
@@ -159,7 +159,7 @@ Statuses: `queued` → `preparing` → `running` → `uploading` → `succeeded`
159
159
  Other flags:
160
160
  - `--ref <branch|tag|sha>`: defaults to the current branch, which must be pushed.
161
161
  - `--simulator`: unsigned iOS simulator build; needs no Apple account.
162
- - `--engine github-actions|xcode-cloud`: overrides `build.iosEngine` for this build (Xcode Cloud: iOS store builds only)
162
+ - `--engine github-actions|xcode-cloud`: for this build only, instead of the project's setting (Xcode Cloud: iOS store builds only)
163
163
  - `-m "message"`
164
164
 
165
165
  **Xcode Cloud (optional):** `liftbay xcode-cloud connect --json`. Its error says which one-time step the user has to do in App Store Connect.