vibeview 0.18.0 → 0.19.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/README.md CHANGED
@@ -49,15 +49,22 @@ cloud device. Add `--production` for a **signed release build** on iOS,
49
49
  tvOS, Android, or Android TV, using signing credentials you store once:
50
50
 
51
51
  ```bash
52
- # Apple: your distribution certificate + provisioning profile
52
+ # Apple: connect your team once with an App Store Connect API key,
53
+ # then VibeView creates the certificate + provisioning profile for you
54
+ vibeview credentials asc-key add --key-id <K> --issuer-id <I> --file AuthKey_<K>.p8
55
+ vibeview credentials generate --platform ios --name release --app <app-id>
56
+
57
+ # ...or upload an exported certificate + profile yourself
53
58
  vibeview credentials add --platform ios --file cert.p12 --profile app.mobileprovision
54
59
 
55
60
  # Android: upload a keystore — or have VibeView generate one (no Java needed)
56
61
  vibeview credentials generate --platform android --name release
57
62
  ```
58
63
 
59
- Credentials are stored encrypted and never readable back. Build-time
60
- secrets work the same way:
64
+ Uploaded credentials are stored encrypted and never readable back;
65
+ credentials VibeView generated can be re-downloaded by org admins with
66
+ `vibeview credentials download <name>` (every download is audited).
67
+ Build-time secrets work the same way:
61
68
 
62
69
  ```bash
63
70
  vibeview env set NPM_TOKEN "..." # secret by default, injected into builds
@@ -67,6 +74,10 @@ vibeview webhooks add --url https://ci.example.com/hooks/vibeview
67
74
  Webhooks POST a signed payload to your endpoint whenever a build finishes.
68
75
  Manage running builds with `vibeview builds list | logs | download | cancel`.
69
76
 
77
+ Expo managed projects work out of the box — the default build command runs
78
+ `npx expo prebuild` in the cloud, so no `ios/` or `android/` directory is
79
+ needed.
80
+
70
81
  [Cloud Builds docs →](https://vibeview.io/docs/cloud-builds) ·
71
82
  [App Signing →](https://vibeview.io/docs/app-signing) ·
72
83
  [Build Webhooks →](https://vibeview.io/docs/build-webhooks)
@@ -123,31 +134,32 @@ Output formats: `human`, `json`, and `junit` for any CI system. The exit code re
123
134
 
124
135
  ## All commands
125
136
 
126
- | Command | Purpose |
127
- | --- | --- |
128
- | `vibeview login` / `logout` / `whoami` | Authenticate via browser, sign out, show who you are |
129
- | `vibeview dev` | Live development on a cloud device |
130
- | `vibeview build` | Build your app — locally, or on the build fleet with `--cloud` (add `--production` for a signed release) |
131
- | `vibeview builds list/logs/download/cancel/delete-artifact` | Manage cloud builds and their artifacts |
132
- | `vibeview credentials add/generate/list/rm` | App-signing credentials for production builds |
133
- | `vibeview env list/set/rm/pull` | Build variables and secrets injected into cloud builds |
134
- | `vibeview webhooks add/list/rm/test` | Notify your systems when builds finish (signed payloads) |
135
- | `vibeview dev-status` / `dev-stop` | Check on, or stop, a backgrounded dev session |
136
- | `vibeview agent-setup` | Make VibeView discoverable to your coding agent (skill + MCP) |
137
- | `vibeview mcp` | Run the MCP server so an agent can drive the device |
138
- | `vibeview ui-tree` | The current screen's elements, each with a ref to act on |
139
- | `vibeview logs` | Recent app logs from the device (console output, errors, crashes) |
140
- | `vibeview screenshot` | Capture the screen to a PNG |
141
- | `vibeview tap` / `type` / `scroll` / `press` | Drive the device |
142
- | `vibeview scroll-to` / `drag` / `alert` / `find` | Search, precise gestures, iOS system alerts |
143
- | `vibeview upload-app <file>` | Upload a build (zipped simulator `.app`, or `.apk`) |
144
- | `vibeview list-apps` | List your apps |
145
- | `vibeview list-devices` | List available device types |
146
- | `vibeview list-suites` | List test suites |
147
- | `vibeview create-test [name]` | Create a test case |
148
- | `vibeview run-test <test-id>` | Run a single test |
149
- | `vibeview run-suite <suite-id>` | Run a test suite |
150
- | `vibeview run-suites` | Run multiple suites |
137
+ | Command | Purpose |
138
+ | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
139
+ | `vibeview login` / `logout` / `whoami` | Authenticate via browser, sign out, show who you are |
140
+ | `vibeview dev` | Live development on a cloud device |
141
+ | `vibeview build` | Build your app — locally, or on the build fleet with `--cloud` (add `--production` for a signed release) |
142
+ | `vibeview builds list/logs/download/cancel/delete-artifact` | Manage cloud builds and their artifacts |
143
+ | `vibeview credentials add/generate/list/rm/download` | App-signing credentials for production builds (`generate` covers iOS, tvOS, and Android) |
144
+ | `vibeview credentials asc-key add/list/rm` | Connect an Apple team via App Store Connect API key |
145
+ | `vibeview env list/set/rm/pull` | Build variables and secrets injected into cloud builds |
146
+ | `vibeview webhooks add/list/rm/test` | Notify your systems when builds finish (signed payloads) |
147
+ | `vibeview dev-status` / `dev-stop` | Check on, or stop, a backgrounded dev session |
148
+ | `vibeview agent-setup` | Make VibeView discoverable to your coding agent (skill + MCP) |
149
+ | `vibeview mcp` | Run the MCP server so an agent can drive the device |
150
+ | `vibeview ui-tree` | The current screen's elements, each with a ref to act on |
151
+ | `vibeview logs` | Recent app logs from the device (console output, errors, crashes) |
152
+ | `vibeview screenshot` | Capture the screen to a PNG |
153
+ | `vibeview tap` / `type` / `scroll` / `press` | Drive the device |
154
+ | `vibeview scroll-to` / `drag` / `alert` / `find` | Search, precise gestures, iOS system alerts |
155
+ | `vibeview upload-app <file>` | Upload a build (zipped simulator `.app`, or `.apk`) |
156
+ | `vibeview list-apps` | List your apps |
157
+ | `vibeview list-devices` | List available device types |
158
+ | `vibeview list-suites` | List test suites |
159
+ | `vibeview create-test [name]` | Create a test case |
160
+ | `vibeview run-test <test-id>` | Run a single test |
161
+ | `vibeview run-suite <suite-id>` | Run a test suite |
162
+ | `vibeview run-suites` | Run multiple suites |
151
163
 
152
164
  Run any command with `--help` for its options.
153
165
 
@@ -287,10 +287,15 @@ var ApiClient = class {
287
287
  const qs = offset !== void 0 ? `?offset=${offset}` : "";
288
288
  return this.requestRaw("GET", `/builds/${id}/logs${qs}`);
289
289
  }
290
- async listCloudBuilds(limit, offset) {
290
+ /** GET /api/v1/builds/ — the filter values are forwarded verbatim; the
291
+ * backend accepts comma-separated lists for platform/status. */
292
+ async listCloudBuilds(opts) {
291
293
  const params = new URLSearchParams();
292
- if (limit !== void 0) params.set("limit", String(limit));
293
- if (offset !== void 0) params.set("offset", String(offset));
294
+ if (opts?.platform !== void 0) params.set("platform", opts.platform);
295
+ if (opts?.status !== void 0) params.set("status", opts.status);
296
+ if (opts?.distribution !== void 0) params.set("distribution", opts.distribution);
297
+ if (opts?.limit !== void 0) params.set("limit", String(opts.limit));
298
+ if (opts?.offset !== void 0) params.set("offset", String(opts.offset));
294
299
  const qs = params.toString();
295
300
  return this.requestRaw("GET", `/builds/${qs ? `?${qs}` : ""}`);
296
301
  }
@@ -445,6 +450,59 @@ var ApiClient = class {
445
450
  async deleteAscKey(id) {
446
451
  await this.requestRaw("DELETE", `/signing-credentials/asc-keys/${id}`);
447
452
  }
453
+ /** GET /api/v1/signing-credentials/play-keys. Requires VIEWER role.
454
+ * Metadata only — the service-account JSON is never returned. */
455
+ async listPlayKeys() {
456
+ return this.requestRaw("GET", "/signing-credentials/play-keys");
457
+ }
458
+ /** POST /api/v1/signing-credentials/play-keys (multipart). Requires ADMIN
459
+ * role. Same-name upload is a rotation upsert server-side — mirrors
460
+ * addAscKey exactly (there are no extra id fields: the client_email is
461
+ * read out of the JSON server-side). */
462
+ async addPlayKey(body) {
463
+ const formData = new FormData();
464
+ if (body.name !== void 0) formData.append("name", body.name);
465
+ formData.append("file", new Blob([new Uint8Array(body.file)]), body.filename);
466
+ return this.requestFormData("POST", "/signing-credentials/play-keys", formData);
467
+ }
468
+ /** DELETE /api/v1/signing-credentials/play-keys/{id}. Requires ADMIN role. */
469
+ async deletePlayKey(id) {
470
+ await this.requestRaw("DELETE", `/signing-credentials/play-keys/${id}`);
471
+ }
472
+ /** POST /api/v1/submissions — submit a succeeded production build's
473
+ * artifact to TestFlight (ios/tvos) or a Play track (android/androidtv).
474
+ * `platform` is always sent: the backend derives the real platform from
475
+ * the build row and 400s on a mismatch, so a pasted wrong --build id
476
+ * fails loudly instead of silently submitting to the other store family.
477
+ * As with addSigningCredential, the CLI's camelCase (`ascKey`) is
478
+ * converted to the route's snake_case at this boundary and nowhere else.
479
+ * 4xx details (including the 409 "already active" refusal and the 400
480
+ * platform-mismatch) surface verbatim via extractDetail. */
481
+ async createSubmission(body) {
482
+ return this.requestRaw("POST", "/submissions", {
483
+ build: body.build,
484
+ platform: body.platform,
485
+ ...body.track !== void 0 ? { track: body.track } : {},
486
+ ...body.package !== void 0 ? { package: body.package } : {},
487
+ ...body.ascKey !== void 0 ? { asc_key: body.ascKey } : {}
488
+ });
489
+ }
490
+ /** GET /api/v1/submissions?build=<id> — that build's submissions, newest
491
+ * first. */
492
+ async listSubmissions(build) {
493
+ return this.requestRaw("GET", `/submissions?build=${encodeURIComponent(build)}`);
494
+ }
495
+ /** GET /api/v1/submissions/{id}. */
496
+ async getSubmission(id) {
497
+ return this.requestRaw("GET", `/submissions/${id}`);
498
+ }
499
+ /** GET /api/v1/submissions/{id}/log[?offset=N] — same {content, offset,
500
+ * status} shape as getCloudBuildLogs, so the follow loops share one
501
+ * response type. */
502
+ async getSubmissionLog(id, offset) {
503
+ const qs = offset !== void 0 ? `?offset=${offset}` : "";
504
+ return this.requestRaw("GET", `/submissions/${id}/log${qs}`);
505
+ }
448
506
  /** GET /api/v1/signing-credentials/{id}/material. Requires ADMIN role;
449
507
  * audited server-side. 404s (NotFoundError) for non-generated rows —
450
508
  * uploaded material is never readable back. */
@@ -5,7 +5,7 @@ import {
5
5
  loadConfig,
6
6
  resolveAuth,
7
7
  uploadAppBuild
8
- } from "./chunk-BJEDH672.js";
8
+ } from "./chunk-FG2J2BGS.js";
9
9
 
10
10
  // src/lib/dev-state.ts
11
11
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
@@ -358,6 +358,9 @@ function detectFlavor(cwd) {
358
358
  if (isFile(join4(cwd, "manifest"))) return "roku";
359
359
  return "unknown";
360
360
  }
361
+ function isManagedExpo(cwd) {
362
+ return detectFlavor(cwd) === "expo" && !existsSync3(join4(cwd, "android", "gradlew")) && iosWorkspaceName(cwd) === null;
363
+ }
361
364
  function iosWorkspaceName(cwd) {
362
365
  try {
363
366
  const found = readdirSync2(join4(cwd, "ios")).find((f) => f.endsWith(".xcworkspace"));
@@ -420,21 +423,51 @@ function defaultsFor(cwd, platform) {
420
423
  var ANDROID_PRODUCTION_ARTIFACT = "android/app/build/outputs/bundle/release/*.aab";
421
424
  var ANDROID_PRODUCTION_COMMAND = "yarn install --immutable && cd android && ./gradlew bundleRelease";
422
425
  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";
427
+ function expoSanitizedName(name) {
428
+ return name.replace(/[\W_]+/g, "").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
429
+ }
430
+ function managedExpoIosName(cwd) {
431
+ if (existsSync3(join4(cwd, "app.config.js")) || existsSync3(join4(cwd, "app.config.ts"))) {
432
+ return null;
433
+ }
434
+ const raw = readJson(cwd, "app.config.json") ?? readJson(cwd, "app.json");
435
+ if (!raw) return null;
436
+ const config = "expo" in raw ? raw.expo : raw;
437
+ if (typeof config !== "object" || config === null) return null;
438
+ const name = config.name;
439
+ if (typeof name !== "string") return null;
440
+ return expoSanitizedName(name) || null;
441
+ }
442
+ function iosArchiveExportCommand(ws) {
443
+ const archivePath = `ios/build/${ws}.xcarchive`;
444
+ const archiveCmd = `xcodebuild archive -workspace ios/${ws}.xcworkspace -scheme ${ws} -configuration Release -archivePath ${archivePath} CODE_SIGN_STYLE=Manual \${VIBEVIEW_DEVELOPMENT_TEAM:+"DEVELOPMENT_TEAM=$VIBEVIEW_DEVELOPMENT_TEAM"} \${VIBEVIEW_PROVISIONING_PROFILE:+"PROVISIONING_PROFILE_SPECIFIER=$VIBEVIEW_PROVISIONING_PROFILE"} \${VIBEVIEW_CODE_SIGN_IDENTITY:+"CODE_SIGN_IDENTITY=$VIBEVIEW_CODE_SIGN_IDENTITY"}`;
445
+ const exportCmd = `xcodebuild -exportArchive -archivePath ${archivePath} -exportPath ios/build -exportOptionsPlist "$VIBEVIEW_EXPORT_OPTIONS_PLIST"`;
446
+ return `${archiveCmd} && ${exportCmd}`;
447
+ }
423
448
  function productionDefaultsFor(cwd, platform) {
424
449
  if (platform === "android" || platform === "androidtv") {
425
- return { buildCommand: ANDROID_PRODUCTION_COMMAND, artifact: ANDROID_PRODUCTION_ARTIFACT };
450
+ return {
451
+ buildCommand: isManagedExpo(cwd) ? MANAGED_EXPO_ANDROID_PRODUCTION_COMMAND : ANDROID_PRODUCTION_COMMAND,
452
+ artifact: ANDROID_PRODUCTION_ARTIFACT
453
+ };
426
454
  }
427
455
  if (platform === "tvos") {
428
456
  return { artifact: IOS_PRODUCTION_ARTIFACT };
429
457
  }
430
458
  if (platform === "ios") {
459
+ if (isManagedExpo(cwd)) {
460
+ const name = managedExpoIosName(cwd);
461
+ if (!name) return { artifact: IOS_PRODUCTION_ARTIFACT };
462
+ return {
463
+ buildCommand: "yarn install --immutable && npx expo prebuild --platform ios && " + iosArchiveExportCommand(name),
464
+ artifact: IOS_PRODUCTION_ARTIFACT
465
+ };
466
+ }
431
467
  const ws = iosWorkspaceName(cwd);
432
468
  if (!ws) return { artifact: IOS_PRODUCTION_ARTIFACT };
433
- const archivePath = `ios/build/${ws}.xcarchive`;
434
- const archiveCmd = `xcodebuild archive -workspace ios/${ws}.xcworkspace -scheme ${ws} -configuration Release -archivePath ${archivePath} CODE_SIGN_STYLE=Manual \${VIBEVIEW_DEVELOPMENT_TEAM:+"DEVELOPMENT_TEAM=$VIBEVIEW_DEVELOPMENT_TEAM"} \${VIBEVIEW_PROVISIONING_PROFILE:+"PROVISIONING_PROFILE_SPECIFIER=$VIBEVIEW_PROVISIONING_PROFILE"} \${VIBEVIEW_CODE_SIGN_IDENTITY:+"CODE_SIGN_IDENTITY=$VIBEVIEW_CODE_SIGN_IDENTITY"}`;
435
- const exportCmd = `xcodebuild -exportArchive -archivePath ${archivePath} -exportPath ios/build -exportOptionsPlist "$VIBEVIEW_EXPORT_OPTIONS_PLIST"`;
436
469
  return {
437
- buildCommand: `yarn install --immutable && ${archiveCmd} && ${exportCmd}`,
470
+ buildCommand: `yarn install --immutable && ${iosArchiveExportCommand(ws)}`,
438
471
  artifact: IOS_PRODUCTION_ARTIFACT
439
472
  };
440
473
  }
@@ -3371,6 +3404,7 @@ export {
3371
3404
  readMetroState,
3372
3405
  killOwnedMetro,
3373
3406
  buildCommand,
3407
+ formatDuration,
3374
3408
  BUILD_TERMINAL_STATUSES,
3375
3409
  fetchFinalSummary,
3376
3410
  newTransitionState,