vibeview 0.17.1 → 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 +40 -28
- package/dist/{chunk-SYFMOPJW.js → chunk-FG2J2BGS.js} +99 -8
- package/dist/{chunk-3IMJ7FNN.js → chunk-T6FS4ELD.js} +56 -12
- package/dist/cli.js +629 -108
- package/dist/{server-AZLB25BY.js → server-GMC6F545.js} +3 -3
- package/dist/{upload-app-XRB7CKA3.js → upload-app-4PE5BEP7.js} +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
60
|
-
|
|
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
|
|
127
|
-
|
|
|
128
|
-
| `vibeview login` / `logout` / `whoami`
|
|
129
|
-
| `vibeview dev`
|
|
130
|
-
| `vibeview build`
|
|
131
|
-
| `vibeview builds list/logs/download/cancel/delete-artifact` | Manage cloud builds and their artifacts
|
|
132
|
-
| `vibeview credentials add/generate/list/rm`
|
|
133
|
-
| `vibeview
|
|
134
|
-
| `vibeview
|
|
135
|
-
| `vibeview
|
|
136
|
-
| `vibeview
|
|
137
|
-
| `vibeview
|
|
138
|
-
| `vibeview
|
|
139
|
-
| `vibeview
|
|
140
|
-
| `vibeview
|
|
141
|
-
| `vibeview
|
|
142
|
-
| `vibeview
|
|
143
|
-
| `vibeview
|
|
144
|
-
| `vibeview
|
|
145
|
-
| `vibeview list-
|
|
146
|
-
| `vibeview list-
|
|
147
|
-
| `vibeview
|
|
148
|
-
| `vibeview
|
|
149
|
-
| `vibeview run-
|
|
150
|
-
| `vibeview run-
|
|
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
|
-
|
|
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 (
|
|
293
|
-
if (
|
|
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
|
}
|
|
@@ -424,18 +429,104 @@ var ApiClient = class {
|
|
|
424
429
|
async deleteSigningCredential(id) {
|
|
425
430
|
await this.requestRaw("DELETE", `/signing-credentials/${id}`);
|
|
426
431
|
}
|
|
432
|
+
/** GET /api/v1/signing-credentials/asc-keys. Requires VIEWER role. Metadata
|
|
433
|
+
* only — the .p8 is never returned. */
|
|
434
|
+
async listAscKeys() {
|
|
435
|
+
return this.requestRaw("GET", "/signing-credentials/asc-keys");
|
|
436
|
+
}
|
|
437
|
+
/** POST /api/v1/signing-credentials/asc-keys (multipart). Requires ADMIN
|
|
438
|
+
* role. Same-name upload is a rotation upsert server-side. Field names are
|
|
439
|
+
* converted to the route's snake_case here, at the client boundary, like
|
|
440
|
+
* addSigningCredential. */
|
|
441
|
+
async addAscKey(body) {
|
|
442
|
+
const formData = new FormData();
|
|
443
|
+
if (body.name !== void 0) formData.append("name", body.name);
|
|
444
|
+
formData.append("issuer_id", body.issuerId);
|
|
445
|
+
formData.append("key_id", body.keyId);
|
|
446
|
+
formData.append("file", new Blob([new Uint8Array(body.file)]), body.filename);
|
|
447
|
+
return this.requestFormData("POST", "/signing-credentials/asc-keys", formData);
|
|
448
|
+
}
|
|
449
|
+
/** DELETE /api/v1/signing-credentials/asc-keys/{id}. Requires ADMIN role. */
|
|
450
|
+
async deleteAscKey(id) {
|
|
451
|
+
await this.requestRaw("DELETE", `/signing-credentials/asc-keys/${id}`);
|
|
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
|
+
}
|
|
506
|
+
/** GET /api/v1/signing-credentials/{id}/material. Requires ADMIN role;
|
|
507
|
+
* audited server-side. 404s (NotFoundError) for non-generated rows —
|
|
508
|
+
* uploaded material is never readable back. */
|
|
509
|
+
async getSigningCredentialMaterial(id) {
|
|
510
|
+
return this.requestRaw("GET", `/signing-credentials/${id}/material`);
|
|
511
|
+
}
|
|
427
512
|
/**
|
|
428
|
-
* POST /api/v1/signing-credentials/generate. Requires ADMIN role. Generates
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
513
|
+
* POST /api/v1/signing-credentials/generate. Requires ADMIN role. Generates a signing
|
|
514
|
+
* credential server-side and stores it. `platform` omitted means android
|
|
515
|
+
* (the server default); for android the response carries the keystore
|
|
516
|
+
* bytes and passwords inline, for ios/tvos it is a plain credential (no
|
|
517
|
+
* inline material — see getSigningCredentialMaterial). As with
|
|
518
|
+
* addSigningCredential, the CLI's camelCase (`keyAlias`, `bundleId`,
|
|
519
|
+
* `ascKey`) is converted to the route's snake_case at this boundary and
|
|
520
|
+
* nowhere else.
|
|
433
521
|
*/
|
|
434
522
|
async generateSigningCredential(body) {
|
|
435
523
|
return this.requestRaw("POST", "/signing-credentials/generate", {
|
|
436
524
|
name: body.name,
|
|
525
|
+
...body.platform !== void 0 ? { platform: body.platform } : {},
|
|
437
526
|
...body.app !== void 0 ? { app: body.app } : {},
|
|
438
527
|
...body.keyAlias !== void 0 ? { key_alias: body.keyAlias } : {},
|
|
528
|
+
...body.bundleId !== void 0 ? { bundle_id: body.bundleId } : {},
|
|
529
|
+
...body.ascKey !== void 0 ? { asc_key: body.ascKey } : {},
|
|
439
530
|
...body.overwrite !== void 0 ? { overwrite: body.overwrite } : {}
|
|
440
531
|
});
|
|
441
532
|
}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
loadConfig,
|
|
6
6
|
resolveAuth,
|
|
7
7
|
uploadAppBuild
|
|
8
|
-
} from "./chunk-
|
|
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 {
|
|
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 && ${
|
|
470
|
+
buildCommand: `yarn install --immutable && ${iosArchiveExportCommand(ws)}`,
|
|
438
471
|
artifact: IOS_PRODUCTION_ARTIFACT
|
|
439
472
|
};
|
|
440
473
|
}
|
|
@@ -1000,8 +1033,13 @@ function persistPickedAppId(cwd, cfg, platform, appId) {
|
|
|
1000
1033
|
saveDevConfig(cwd, next);
|
|
1001
1034
|
return next;
|
|
1002
1035
|
}
|
|
1003
|
-
var
|
|
1004
|
-
|
|
1036
|
+
var APP_PICKER_SENTINEL = {
|
|
1037
|
+
CREATE_NEW_APP: "__vibeview_create_new_app__",
|
|
1038
|
+
DONT_LINK_APP: "__vibeview_dont_link_app__"
|
|
1039
|
+
};
|
|
1040
|
+
function isAppPickerSentinel(value) {
|
|
1041
|
+
return Object.values(APP_PICKER_SENTINEL).includes(value);
|
|
1042
|
+
}
|
|
1005
1043
|
async function resolveCloudAppIds(cwd, cfgRef, platforms, opts, listApps) {
|
|
1006
1044
|
const createFromArtifact = /* @__PURE__ */ new Set();
|
|
1007
1045
|
const missing = platforms.filter((p) => !cfgRef.current?.platforms?.[p]?.appId);
|
|
@@ -1041,18 +1079,23 @@ async function resolveCloudAppIds(cwd, cfgRef, platforms, opts, listApps) {
|
|
|
1041
1079
|
// Under --production a "Don't link an app" row leads the list (the
|
|
1042
1080
|
// neutral default, since the create-from-artifact escape hatch below
|
|
1043
1081
|
// is suppressed there): production builds aren't linked to an app
|
|
1044
|
-
// unless the user explicitly wants the link — see
|
|
1045
|
-
...opts.production ? [{ value: DONT_LINK_APP, name: "Don't link an app" }] : [],
|
|
1082
|
+
// unless the user explicitly wants the link — see APP_PICKER_SENTINEL.
|
|
1083
|
+
...opts.production ? [{ value: APP_PICKER_SENTINEL.DONT_LINK_APP, name: "Don't link an app" }] : [],
|
|
1046
1084
|
...apps.map((a) => ({ value: a.public_id, name: a.name })),
|
|
1047
1085
|
// No create-from-artifact row under --production: a production build
|
|
1048
1086
|
// never creates an app (internal_builds returns before app creation),
|
|
1049
1087
|
// so offering it would promise something that can't happen. Picking
|
|
1050
1088
|
// an EXISTING app is still meaningful — app-scoped signing
|
|
1051
1089
|
// credentials resolve by app.
|
|
1052
|
-
...opts.production ? [] : [
|
|
1090
|
+
...opts.production ? [] : [
|
|
1091
|
+
{
|
|
1092
|
+
value: APP_PICKER_SENTINEL.CREATE_NEW_APP,
|
|
1093
|
+
name: "Create a new app from this build"
|
|
1094
|
+
}
|
|
1095
|
+
]
|
|
1053
1096
|
]
|
|
1054
1097
|
});
|
|
1055
|
-
if (appId
|
|
1098
|
+
if (isAppPickerSentinel(appId)) {
|
|
1056
1099
|
createFromArtifact.add(platform);
|
|
1057
1100
|
continue;
|
|
1058
1101
|
}
|
|
@@ -3361,6 +3404,7 @@ export {
|
|
|
3361
3404
|
readMetroState,
|
|
3362
3405
|
killOwnedMetro,
|
|
3363
3406
|
buildCommand,
|
|
3407
|
+
formatDuration,
|
|
3364
3408
|
BUILD_TERMINAL_STATUSES,
|
|
3365
3409
|
fetchFinalSummary,
|
|
3366
3410
|
newTransitionState,
|