vibeview 0.19.0 → 0.20.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 CHANGED
@@ -74,12 +74,33 @@ vibeview webhooks add --url https://ci.example.com/hooks/vibeview
74
74
  Webhooks POST a signed payload to your endpoint whenever a build finishes.
75
75
  Manage running builds with `vibeview builds list | logs | download | cancel`.
76
76
 
77
+ Set `"autoIncrement": true` in a platform's `build` block and every
78
+ production build gets a server-assigned build number, injected into the
79
+ build as `VIBEVIEW_BUILD_NUMBER` (inspect or seed the counters with
80
+ `vibeview versions list/set`).
81
+
77
82
  Expo managed projects work out of the box — the default build command runs
78
83
  `npx expo prebuild` in the cloud, so no `ios/` or `android/` directory is
79
84
  needed.
80
85
 
86
+ When a production build succeeds, ship it straight to the stores — no
87
+ Xcode, no Play Console upload page:
88
+
89
+ ```bash
90
+ vibeview submit --platform tvos --latest # → TestFlight
91
+ vibeview submit --platform android --track internal # → a Play track
92
+ ```
93
+
94
+ TestFlight uploads use your stored App Store Connect key; Play uploads need
95
+ a Google Play service account key stored once with
96
+ `vibeview credentials play-key add --file service-account.json`. Choose a
97
+ Play track, upload as a draft, or start a staged rollout — and store your
98
+ defaults in `vibeview.json` so `vibeview submit` alone does the right
99
+ thing.
100
+
81
101
  [Cloud Builds docs →](https://vibeview.io/docs/cloud-builds) ·
82
102
  [App Signing →](https://vibeview.io/docs/app-signing) ·
103
+ [Store Submission →](https://vibeview.io/docs/store-submission) ·
83
104
  [Build Webhooks →](https://vibeview.io/docs/build-webhooks)
84
105
 
85
106
  ## Let your AI agent drive the device
@@ -140,8 +161,11 @@ Output formats: `human`, `json`, and `junit` for any CI system. The exit code re
140
161
  | `vibeview dev` | Live development on a cloud device |
141
162
  | `vibeview build` | Build your app — locally, or on the build fleet with `--cloud` (add `--production` for a signed release) |
142
163
  | `vibeview builds list/logs/download/cancel/delete-artifact` | Manage cloud builds and their artifacts |
164
+ | `vibeview versions list/set` | Server-side build-number counters for `autoIncrement` production builds |
143
165
  | `vibeview credentials add/generate/list/rm/download` | App-signing credentials for production builds (`generate` covers iOS, tvOS, and Android) |
144
166
  | `vibeview credentials asc-key add/list/rm` | Connect an Apple team via App Store Connect API key |
167
+ | `vibeview credentials play-key add/list/rm` | Store a Google Play service-account key for Play submissions |
168
+ | `vibeview submit` | Upload a production build to TestFlight or a Google Play track |
145
169
  | `vibeview env list/set/rm/pull` | Build variables and secrets injected into cloud builds |
146
170
  | `vibeview webhooks add/list/rm/test` | Notify your systems when builds finish (signed payloads) |
147
171
  | `vibeview dev-status` / `dev-stop` | Check on, or stop, a backgrounded dev session |
@@ -484,7 +484,10 @@ var ApiClient = class {
484
484
  platform: body.platform,
485
485
  ...body.track !== void 0 ? { track: body.track } : {},
486
486
  ...body.package !== void 0 ? { package: body.package } : {},
487
- ...body.ascKey !== void 0 ? { asc_key: body.ascKey } : {}
487
+ ...body.ascKey !== void 0 ? { asc_key: body.ascKey } : {},
488
+ ...body.releaseStatus !== void 0 ? { release_status: body.releaseStatus } : {},
489
+ ...body.rollout !== void 0 ? { rollout: body.rollout } : {},
490
+ ...body.changesNotSentForReview !== void 0 ? { changes_not_sent_for_review: body.changesNotSentForReview } : {}
488
491
  });
489
492
  }
490
493
  /** GET /api/v1/submissions?build=<id> — that build's submissions, newest
@@ -503,6 +506,22 @@ var ApiClient = class {
503
506
  const qs = offset !== void 0 ? `?offset=${offset}` : "";
504
507
  return this.requestRaw("GET", `/submissions/${id}/log${qs}`);
505
508
  }
509
+ /** GET /api/v1/apps/{appPublicId}/versions — the app's server-side
510
+ * build-number counters, one per platform that has ever allocated (or
511
+ * been `versions set`) a number. */
512
+ async listAppVersions(appId) {
513
+ return this.requestRaw("GET", `/apps/${encodeURIComponent(appId)}/versions`);
514
+ }
515
+ /** PUT /api/v1/apps/{appPublicId}/versions/{platform} {build_number} —
516
+ * seed or overwrite one counter (org-scoped, DEVELOPER role, audited
517
+ * server-side). Returns the updated counter. */
518
+ async setAppVersion(appId, platform, buildNumber) {
519
+ return this.requestRaw(
520
+ "PUT",
521
+ `/apps/${encodeURIComponent(appId)}/versions/${encodeURIComponent(platform)}`,
522
+ { build_number: buildNumber }
523
+ );
524
+ }
506
525
  /** GET /api/v1/signing-credentials/{id}/material. Requires ADMIN role;
507
526
  * audited server-side. 404s (NotFoundError) for non-generated rows —
508
527
  * uploaded material is never readable back. */
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ApiClient,
4
+ UsageError,
4
5
  createSpinner,
5
6
  loadConfig,
6
7
  resolveAuth,
7
8
  uploadAppBuild
8
- } from "./chunk-FG2J2BGS.js";
9
+ } from "./chunk-MCCXPIGE.js";
9
10
 
10
11
  // src/lib/dev-state.ts
11
12
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -332,6 +333,7 @@ import { input, select } from "@inquirer/prompts";
332
333
  // src/lib/project-detect.ts
333
334
  import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
334
335
  import { join as join4 } from "path";
336
+ import ignore from "ignore";
335
337
  function readJson(cwd, name) {
336
338
  try {
337
339
  return JSON.parse(readFileSync3(join4(cwd, name), "utf-8"));
@@ -358,8 +360,22 @@ function detectFlavor(cwd) {
358
360
  if (isFile(join4(cwd, "manifest"))) return "roku";
359
361
  return "unknown";
360
362
  }
361
- function isManagedExpo(cwd) {
362
- return detectFlavor(cwd) === "expo" && !existsSync3(join4(cwd, "android", "gradlew")) && iosWorkspaceName(cwd) === null;
363
+ function nativeDirShipped(cwd, dir) {
364
+ if (!existsSync3(join4(cwd, dir))) return false;
365
+ try {
366
+ const ig = ignore().add(readFileSync3(join4(cwd, ".gitignore"), "utf-8"));
367
+ return !ig.ignores(`${dir}/`);
368
+ } catch {
369
+ return true;
370
+ }
371
+ }
372
+ function isManagedExpo(cwd, platform) {
373
+ if (detectFlavor(cwd) !== "expo") return false;
374
+ const android = !nativeDirShipped(cwd, "android");
375
+ const ios = !nativeDirShipped(cwd, "ios");
376
+ if (platform === "android" || platform === "androidtv") return android;
377
+ if (platform === "ios" || platform === "tvos") return ios;
378
+ return android && ios;
363
379
  }
364
380
  function iosWorkspaceName(cwd) {
365
381
  try {
@@ -371,6 +387,18 @@ function iosWorkspaceName(cwd) {
371
387
  }
372
388
  var IOS_ARTIFACT = "ios/build/Build/Products/Debug-iphonesimulator/*.app";
373
389
  var ANDROID_ARTIFACT = "android/app/build/outputs/apk/debug/*.apk";
390
+ function installCommand(cwd) {
391
+ if (existsSync3(join4(cwd, "yarn.lock"))) return "yarn install --immutable";
392
+ if (existsSync3(join4(cwd, "pnpm-lock.yaml"))) return "pnpm install --frozen-lockfile";
393
+ if (existsSync3(join4(cwd, "package-lock.json"))) return "npm ci";
394
+ return "yarn install --immutable";
395
+ }
396
+ function managedExpoAndroidCommand(cwd) {
397
+ return `${installCommand(cwd)} && npx expo prebuild --platform android --no-install && cd android && ./gradlew assembleDebug`;
398
+ }
399
+ function managedExpoIosCommand(cwd, name) {
400
+ return `${installCommand(cwd)} && npx expo prebuild --platform ios && xcodebuild -workspace ios/${name}.xcworkspace -scheme ${name} -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build CODE_SIGNING_ALLOWED=NO`;
401
+ }
374
402
  var ROKU_DEFAULT_ARTIFACT = "out/channel.zip";
375
403
  var ROKU_ZIP_COMMAND = "zip -r out/channel.zip manifest source components images -x '*.DS_Store'";
376
404
  function rokuDefaults(cwd) {
@@ -400,15 +428,17 @@ function defaultsFor(cwd, platform) {
400
428
  const metroCommand = flavor === "expo" ? "npx expo start" : "yarn start";
401
429
  if (platform === "tvos" || platform === "androidtv") return { metroCommand };
402
430
  if (platform === "android") {
431
+ const managed = isManagedExpo(cwd, "android");
403
432
  return {
404
- buildCommand: flavor === "expo" ? "npx expo run:android --variant debug" : "cd android && ./gradlew assembleDebug",
433
+ buildCommand: managed ? managedExpoAndroidCommand(cwd) : "cd android && ./gradlew assembleDebug",
405
434
  artifact: ANDROID_ARTIFACT,
406
435
  metroCommand
407
436
  };
408
437
  }
409
- if (flavor === "expo") {
438
+ if (isManagedExpo(cwd, "ios")) {
439
+ const name = managedExpoIosName(cwd);
410
440
  return {
411
- buildCommand: "npx expo run:ios --configuration Debug",
441
+ buildCommand: name ? managedExpoIosCommand(cwd, name) : void 0,
412
442
  artifact: IOS_ARTIFACT,
413
443
  metroCommand
414
444
  };
@@ -421,9 +451,13 @@ function defaultsFor(cwd, platform) {
421
451
  };
422
452
  }
423
453
  var ANDROID_PRODUCTION_ARTIFACT = "android/app/build/outputs/bundle/release/*.aab";
424
- var ANDROID_PRODUCTION_COMMAND = "yarn install --immutable && cd android && ./gradlew bundleRelease";
454
+ function androidProductionCommand(cwd) {
455
+ return `${installCommand(cwd)} && cd android && ./gradlew bundleRelease`;
456
+ }
425
457
  var IOS_PRODUCTION_ARTIFACT = "ios/build/*.ipa";
426
- var MANAGED_EXPO_ANDROID_PRODUCTION_COMMAND = "yarn install --immutable && npx expo prebuild --platform android --no-install && cd android && ./gradlew bundleRelease";
458
+ function managedExpoAndroidProductionCommand(cwd) {
459
+ return `${installCommand(cwd)} && npx expo prebuild --platform android --no-install && cd android && ./gradlew bundleRelease`;
460
+ }
427
461
  function expoSanitizedName(name) {
428
462
  return name.replace(/[\W_]+/g, "").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
429
463
  }
@@ -448,7 +482,7 @@ function iosArchiveExportCommand(ws) {
448
482
  function productionDefaultsFor(cwd, platform) {
449
483
  if (platform === "android" || platform === "androidtv") {
450
484
  return {
451
- buildCommand: isManagedExpo(cwd) ? MANAGED_EXPO_ANDROID_PRODUCTION_COMMAND : ANDROID_PRODUCTION_COMMAND,
485
+ buildCommand: isManagedExpo(cwd, "android") ? managedExpoAndroidProductionCommand(cwd) : androidProductionCommand(cwd),
452
486
  artifact: ANDROID_PRODUCTION_ARTIFACT
453
487
  };
454
488
  }
@@ -456,18 +490,18 @@ function productionDefaultsFor(cwd, platform) {
456
490
  return { artifact: IOS_PRODUCTION_ARTIFACT };
457
491
  }
458
492
  if (platform === "ios") {
459
- if (isManagedExpo(cwd)) {
493
+ if (isManagedExpo(cwd, "ios")) {
460
494
  const name = managedExpoIosName(cwd);
461
495
  if (!name) return { artifact: IOS_PRODUCTION_ARTIFACT };
462
496
  return {
463
- buildCommand: "yarn install --immutable && npx expo prebuild --platform ios && " + iosArchiveExportCommand(name),
497
+ buildCommand: `${installCommand(cwd)} && npx expo prebuild --platform ios && ` + iosArchiveExportCommand(name),
464
498
  artifact: IOS_PRODUCTION_ARTIFACT
465
499
  };
466
500
  }
467
501
  const ws = iosWorkspaceName(cwd);
468
502
  if (!ws) return { artifact: IOS_PRODUCTION_ARTIFACT };
469
503
  return {
470
- buildCommand: `yarn install --immutable && ${iosArchiveExportCommand(ws)}`,
504
+ buildCommand: `${installCommand(cwd)} && ${iosArchiveExportCommand(ws)}`,
471
505
  artifact: IOS_PRODUCTION_ARTIFACT
472
506
  };
473
507
  }
@@ -580,7 +614,7 @@ import { execFile, spawn } from "child_process";
580
614
  import { lstatSync, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
581
615
  import { tmpdir as tmpdir2 } from "os";
582
616
  import { join as join5 } from "path";
583
- import ignore from "ignore";
617
+ import ignore2 from "ignore";
584
618
  var NotAGitRepoError = class extends Error {
585
619
  constructor(message) {
586
620
  super(message);
@@ -607,7 +641,7 @@ async function listSourceFiles(projectRoot) {
607
641
  let filtered = rawPaths;
608
642
  const vvignorePath = join5(projectRoot, ".vvignore");
609
643
  if (statOrNull(vvignorePath)?.isFile()) {
610
- const ig = ignore().add(readFileSync4(vvignorePath, "utf-8"));
644
+ const ig = ignore2().add(readFileSync4(vvignorePath, "utf-8"));
611
645
  filtered = filtered.filter((p) => !ig.ignores(p));
612
646
  }
613
647
  const result = [];
@@ -1188,6 +1222,11 @@ async function runCloudBuildCommand(cwd, cfgRef, opts, deps) {
1188
1222
  for (const p of platforms) {
1189
1223
  try {
1190
1224
  validateBuildEnv(cfg?.platforms?.[p]?.build?.env, p);
1225
+ if (cfg?.platforms?.[p]?.build?.autoIncrement === true && opts.production && createFromArtifact.has(p)) {
1226
+ throw new UsageError(
1227
+ `platforms.${p}.build.autoIncrement needs the build linked to an app \u2014 set appId in vibeview.json (a brand-new app's first build can't auto-increment)`
1228
+ );
1229
+ }
1191
1230
  } catch (err) {
1192
1231
  fail(opts, err instanceof Error ? err.message : String(err));
1193
1232
  }
@@ -1232,6 +1271,14 @@ ${offenders}`);
1232
1271
  const production = productionBuilds.get(p);
1233
1272
  const command = production ? production.command : build.command;
1234
1273
  const artifactPath = production ? production.artifact : build.artifact;
1274
+ const autoIncrement = build.autoIncrement === true;
1275
+ if (autoIncrement && !production && !opts.json) {
1276
+ console.error(
1277
+ chalk2.yellow(
1278
+ `note: platforms.${p}.build.autoIncrement only applies to production builds (--production) \u2014 ignoring it for this simulator build`
1279
+ )
1280
+ );
1281
+ }
1235
1282
  return {
1236
1283
  platform: p,
1237
1284
  // OMITTED, never invented, for a platform whose app the artifact will
@@ -1243,7 +1290,8 @@ ${offenders}`);
1243
1290
  command,
1244
1291
  artifact_path: artifactPath,
1245
1292
  distribution: production ? "production" : "simulator",
1246
- ...build.env ? { env: build.env } : {}
1293
+ ...build.env ? { env: build.env } : {},
1294
+ ...autoIncrement && production ? { auto_increment: true } : {}
1247
1295
  };
1248
1296
  });
1249
1297
  const uploadSpinner = !opts.json ? createSpinner("uploading source...") : null;