ubriot 1.1.1 → 1.2.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.
package/dist/index.js CHANGED
@@ -43,7 +43,7 @@ const child_process_1 = require("child_process");
43
43
  const fs_1 = require("fs");
44
44
  const path_1 = require("path");
45
45
  const crypto_1 = require("crypto");
46
- commander_1.program.name("ubriot").description("Ubriot, mobile release infrastructure for teams that ship").version("1.1.1");
46
+ commander_1.program.name("ubriot").description("Ubriot, mobile release infrastructure for teams that ship").version("1.2.0");
47
47
  const MIME = {
48
48
  ".avif": "image/avif",
49
49
  ".bundle": "application/javascript",
@@ -1911,4 +1911,40 @@ migrate
1911
1911
  (0, api_1.handleError)(err);
1912
1912
  }
1913
1913
  });
1914
+ migrate
1915
+ .command("eas")
1916
+ .description("Migrate an Expo app from EAS Build/Update to Ubriot builds and OTA")
1917
+ .option("--project-dir <dir>", "App directory to scan", ".")
1918
+ .option("--expo-config <path>", "Path to app.json", "app.json")
1919
+ .option("--app <slug>", "Ubriot app slug for the update URL; defaults to expo.slug from app.json")
1920
+ .option("--channel <channel>", "OTA channel", "production")
1921
+ .option("--runtime-version <version>", "Runtime version to write into app.json")
1922
+ .option("--apply", "Apply the changes (default is a dry run)", false)
1923
+ .option("--yes", "Skip the clean-git-tree safety check", false)
1924
+ .action((opts) => {
1925
+ try {
1926
+ const plan = (0, migrate_1.planEasMigration)({
1927
+ projectDir: opts.projectDir,
1928
+ expoConfigPath: opts.expoConfig,
1929
+ app: opts.app,
1930
+ });
1931
+ (0, migrate_1.renderEasPlan)(plan);
1932
+ if (!plan.found)
1933
+ return;
1934
+ if (!opts.apply) {
1935
+ console.log("\nDry run. Re-run with --apply to make these changes.");
1936
+ return;
1937
+ }
1938
+ (0, migrate_1.applyEasMigration)({
1939
+ plan,
1940
+ apiBase: (0, config_1.readConfig)().apiBase,
1941
+ channel: opts.channel,
1942
+ runtimeVersion: opts.runtimeVersion,
1943
+ requireCleanGit: !opts.yes,
1944
+ });
1945
+ }
1946
+ catch (err) {
1947
+ (0, api_1.handleError)(err);
1948
+ }
1949
+ });
1914
1950
  commander_1.program.parse(process.argv);
package/dist/migrate.js CHANGED
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.planCodePushMigration = planCodePushMigration;
4
4
  exports.renderPlan = renderPlan;
5
5
  exports.applyCodePushMigration = applyCodePushMigration;
6
+ exports.planEasMigration = planEasMigration;
7
+ exports.renderEasPlan = renderEasPlan;
8
+ exports.applyEasMigration = applyEasMigration;
6
9
  const fs_1 = require("fs");
7
10
  const path_1 = require("path");
8
11
  const child_process_1 = require("child_process");
@@ -280,3 +283,132 @@ function applyCodePushMigration(input) {
280
283
  }
281
284
  console.log("\nBackups written as <file>.codepush.bak next to each edited file.");
282
285
  }
286
+ function planEasMigration(input) {
287
+ const projectDir = (0, path_1.resolve)(input.projectDir);
288
+ const appJsonPath = (0, path_1.resolve)(projectDir, input.expoConfigPath);
289
+ const findings = [];
290
+ const warnings = [];
291
+ let slug = input.app;
292
+ let runtimeVersion;
293
+ let currentUpdateUrl;
294
+ let easProjectId;
295
+ if ((0, fs_1.existsSync)(appJsonPath)) {
296
+ try {
297
+ const expo = expoBlock(readJson(appJsonPath));
298
+ slug = slug ?? expo.slug;
299
+ if (typeof expo.runtimeVersion === "string")
300
+ runtimeVersion = expo.runtimeVersion;
301
+ currentUpdateUrl = expo.updates?.url;
302
+ easProjectId = expo.extra?.eas?.projectId;
303
+ if (currentUpdateUrl && /expo\.dev|u\.expo\.dev/.test(currentUpdateUrl)) {
304
+ findings.push({ file: (0, path_1.relative)(projectDir, appJsonPath), kind: "source-import", detail: `updates.url points at EAS (${currentUpdateUrl})` });
305
+ }
306
+ if (easProjectId) {
307
+ findings.push({ file: (0, path_1.relative)(projectDir, appJsonPath), kind: "package", detail: `extra.eas.projectId ${easProjectId}` });
308
+ }
309
+ }
310
+ catch {
311
+ warnings.push(`Could not parse ${(0, path_1.relative)(projectDir, appJsonPath)} as JSON.`);
312
+ }
313
+ }
314
+ else {
315
+ warnings.push(`No app.json found at ${(0, path_1.relative)(projectDir, appJsonPath)}.`);
316
+ }
317
+ const easJsonPath = (0, path_1.join)(projectDir, "eas.json");
318
+ const hasEasJson = (0, fs_1.existsSync)(easJsonPath);
319
+ if (hasEasJson)
320
+ findings.push({ file: "eas.json", kind: "package", detail: "EAS build/submit config present" });
321
+ const pkgPath = (0, path_1.join)(projectDir, "package.json");
322
+ if ((0, fs_1.existsSync)(pkgPath)) {
323
+ try {
324
+ const pkg = readJson(pkgPath);
325
+ const easCli = pkg.dependencies?.["eas-cli"] ?? pkg.devDependencies?.["eas-cli"];
326
+ if (easCli)
327
+ findings.push({ file: "package.json", kind: "package", detail: `eas-cli@${easCli}` });
328
+ }
329
+ catch {
330
+ warnings.push("Could not parse package.json.");
331
+ }
332
+ }
333
+ return {
334
+ found: findings.length > 0,
335
+ projectDir,
336
+ appJsonPath,
337
+ slug,
338
+ runtimeVersion,
339
+ currentUpdateUrl,
340
+ hasEasJson,
341
+ easProjectId,
342
+ findings,
343
+ warnings,
344
+ };
345
+ }
346
+ function renderEasPlan(plan) {
347
+ if (!plan.found) {
348
+ console.log("No EAS usage detected (no eas.json, EAS update URL, or eas-cli). Nothing to migrate.");
349
+ for (const w of plan.warnings)
350
+ console.log(` note: ${w}`);
351
+ return;
352
+ }
353
+ console.log(`Found ${plan.findings.length} EAS signal(s) in ${plan.projectDir}:\n`);
354
+ for (const f of plan.findings)
355
+ console.log(` [${f.kind}] ${f.file} ${f.detail}`);
356
+ console.log("\nUbriot runs your Expo app as-is; this does not remove Expo.");
357
+ console.log("\nPlan on --apply:");
358
+ console.log(` 1. Back up ${(0, path_1.relative)(plan.projectDir, plan.appJsonPath)} to <file>.eas.bak`);
359
+ console.log(` 2. Repoint expo.updates.url + channel header to Ubriot (EAS Update -> Ubriot OTA)`);
360
+ console.log(` 3. Leave eas.json in place; you switch the build/submit commands (see next steps)`);
361
+ if (plan.warnings.length) {
362
+ console.log("\nWarnings:");
363
+ for (const w of plan.warnings)
364
+ console.log(` ! ${w}`);
365
+ }
366
+ }
367
+ function applyEasMigration(input) {
368
+ const { plan, apiBase, channel } = input;
369
+ if (input.requireCleanGit) {
370
+ const res = (0, child_process_1.spawnSync)("git", ["status", "--porcelain"], { cwd: plan.projectDir, encoding: "utf8" });
371
+ if (res.status === 0 && res.stdout.trim().length > 0) {
372
+ throw new Error("Working tree is not clean. Commit or stash first, or re-run with --yes to override.");
373
+ }
374
+ }
375
+ const manual = [];
376
+ if (!(0, fs_1.existsSync)(plan.appJsonPath))
377
+ throw new Error("No app.json to update.");
378
+ backup(plan.appJsonPath);
379
+ const json = readJson(plan.appJsonPath);
380
+ const hasExpoKey = Boolean(json.expo);
381
+ const expo = hasExpoKey ? json.expo : json;
382
+ const slug = plan.slug ?? expo.slug;
383
+ if (!slug)
384
+ throw new Error("No app slug. Pass --app <slug> or set expo.slug in app.json.");
385
+ expo.updates = {
386
+ ...(expo.updates ?? {}),
387
+ enabled: true,
388
+ url: `${apiBase.replace(/\/$/, "")}/ubriot/updates/manifest?appId=${encodeURIComponent(slug)}`,
389
+ requestHeaders: { ...(expo.updates?.requestHeaders ?? {}), "expo-channel-name": channel },
390
+ };
391
+ const rv = input.runtimeVersion ?? plan.runtimeVersion;
392
+ if (rv)
393
+ expo.runtimeVersion = rv;
394
+ else if (!expo.runtimeVersion)
395
+ manual.push("Set expo.runtimeVersion in app.json (pass --runtime-version to do it automatically).");
396
+ if (hasExpoKey)
397
+ json.expo = expo;
398
+ (0, fs_1.writeFileSync)(plan.appJsonPath, `${JSON.stringify(json, null, 2)}\n`);
399
+ console.log(`\nApplied. Repointed ${(0, path_1.relative)(plan.projectDir, plan.appJsonPath)} to Ubriot OTA.`);
400
+ console.log("\nSwitch your workflow commands:");
401
+ console.log(` eas build -> ubriot ship ios --app ${slug} (and: ubriot ship android --app ${slug})`);
402
+ console.log(` eas submit -> handled by ubriot ship (drop --no-submit to submit)`);
403
+ console.log(` eas update -> ubriot update --app ${slug} --channel ${channel}`);
404
+ console.log("\nThen create or link the app if needed:");
405
+ console.log(` ubriot apps create --name "<App Name>" --slug ${slug} (skip if it already exists)`);
406
+ if (plan.hasEasJson)
407
+ manual.push("eas.json is left in place. Map its build profiles to `ubriot ship --profile`, then remove eas.json and eas-cli once you have fully moved.");
408
+ if (manual.length) {
409
+ console.log("\nManual steps still required:");
410
+ for (const m of manual)
411
+ console.log(` ! ${m}`);
412
+ }
413
+ console.log("\nBackup written as <file>.eas.bak next to app.json.");
414
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ubriot",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Mobile release infrastructure CLI. Build, ship to TestFlight and Google Play, and push OTA updates for iOS and Android from your terminal. React Native and Expo supported.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://ubriot.dev",