Compare commits

..
36 Commits
Author SHA1 Message Date
Szymon Dziedzic 981f7c9727 v12.5.0 2024-09-23 17:25:42 +02:00
Stanisław Chmiela 24245d76cf [eas-cli] Add submit:internal (#2571)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

I want to add support for submissions as part of workflows, where, when we start the job, we don't have access to the repository. So, we need to resolve submission config _somewhere_, ideally where we are isolated, can easily clone project, install dependencies and resolve dynamic config.

As we do with `eas build:internal`, I expect it will make sense to do `eas submit:internal` which will do what `eas submit` does usually — resolve all the credentials, using local filesystem or Expo servers  and then fetch the _actual_ keys from Expo servers.

# How

First, I changed structure of `SubmitCommand`/`Submitter` a bit — previously, `SubmitCommand` would create the `Submitter` and call `submitAsync` _inside_ `runAsync()`. After my changes `SubmitCommand` will create _and return_ the `Submitter` and the call site will need to call `submitAsync`.

By doing it this way I was able to add a `getSubmissionInputAsync` to `BaseSubmitter` which returns all-resolved submission input, as-it-would-have-been-used-in-submission-mutation. This ensures the same behavior for `eas submit` and `eas submit:internal`.

Once we have that, `eas submit:internal` is relatively straightforward:
1. create `SubmitCommand`, get `Submitter`.
2. `getSubmissionInputAsync`.
3. fetch necessary credentials by IDs from `www` (https://github.com/expo/universe/pull/16793).
4. validate that we have it all (https://github.com/expo/eas-build/pull/443) and print the JSON.

# Test Plan

I was able to run `easd submit:internal` locally and got the JSON output.
2024-09-20 22:25:33 +02:00
Cedric van Putten d42cbe0146 fix(eas-cli): avoid malforming app.json with empty .expo object (#2573)
* fix(eas-cli): avoid malforming `app.json` with empty `.expo` object

* docs(cli): add changelog entry

* docs(cli): fix changelog entry

* test(eas-cli): drop the legacy test
2024-09-19 21:19:23 +02:00
Stanisław Chmiela 2cf6dd49db [eas-cli] Let iOS bundle identifier override always (#2576)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

In a submit job we're going to have an IPA/APK to submit. We don't want to resolve any bundle identifiers from the project, we can use one from that IPA/APK.

# How

Let the override env variable override bundle identifier resolution if the workflow is managed too.

# Test Plan

I used it to submit an IPA with workflows.
2024-09-19 16:17:01 +02:00
Expo Bot 09c5038c58 Upgrade eas-build dependencies (#2577)
⬆️ Upgrades [`eas-build`](https://github.com/expo/eas-build) dependencies.
2024-09-19 15:50:53 +02:00
Cedric van Putten d33ebe1ae4 refactor(eas-cli): reword dev domain prompt to use "preview URL" (#2572)
* refactor(eas-cli): reword dev domain prompt to use "preview URL"

* docs(eas-cli): add changelog entry

* docs(eas-cli): fix changelog entry
2024-09-17 16:03:31 +02:00
Cedric van Putten 41fd76c154 fix(eas-cli): check if export is available before validating project (#2569)
* fix(eas-cli): check if export is available before validating project

* docs(eas-cli): add changelog entry

* docs(eas-cli): fix changelog entry
2024-09-16 12:20:01 +02:00
Radosław Krzemień dadf58fa70 [eas-cli] [ENG-10146] Allow submission of builds in progress (#2543)
* [eas-cli] Add tests

Added tests for allowing to select in progress builds

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Allow in progress builds

When prompting the user with a list of builds the in-progress builds are now returned first

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Adjust description

When prompting the user with a list of builds the build status is now displayed let the user know which are finished and which are still in progress

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Reformat

Adjusted formatting

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Reformat

Adjusted formatting

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Lint fix

lint --fix

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Add changelog

Added the changelog entry since the bot does not seem to be working

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Return more statuses and sort

Query returning the potential builds for submission now also returns build in `new` and `in-queue` statuses. All builds are then sorted decreasingly by `createdAt` and up to `limit` are returned

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Lint

yarn lint --fix

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to

* [eas-cli] Add mapping

Created build status mapping instead string modification

See: https://linear.app/expo/issue/ENG-10146/fr-add-option-to-trigger-auto-submit-while-waiting-for-build-to
2024-09-16 11:38:34 +02:00
Archimedes TrajanoandSzymon Dziedzic a7f939fb5f log command execution to assist in debugging local builds (#2526)
* log command execution to assist in debugging local builds

* Update packages/eas-cli/src/build/local.ts

Co-authored-by: Szymon Dziedzic <[email protected]>

* import and changelog

* Update packages/eas-cli/src/build/local.ts

Co-authored-by: Szymon Dziedzic <[email protected]>

---------

Co-authored-by: Szymon Dziedzic <[email protected]>
2024-09-16 11:03:23 +02:00
Expo CI e54b5a243c update CHANGELOG.md 2024-09-14 14:15:52 +00:00
Cedric van Putten d2a89c1d77 v12.4.1 2024-09-14 15:10:44 +01:00
Cedric van Putten 9c4ff98d68 refactor(eas-cli): remove the cursor space when prompting dev domain after input (#2568)
* refactor(eas-cli): remove the cursor space when prompting dev domain after input

* docs(eas-cli): add changelog entry
2024-09-14 15:08:46 +01:00
Cedric van Putten d7dfef0c36 refactor(eas-cli): enhance the --environment help description (#2567)
* refactor(eas-cli): enhance the `--environment` help description

* docs(eas-cli): add changelog entry

* docs(eas-cli): add missing periods to `eas worker:alias --help`

* refactor(eas-cli): enhance the `--environment` help description
2024-09-14 15:03:40 +01:00
Cedric van Putten fdb30eeacc fix(eas-cli): restore "export not found" error and hide recent exports (#2566)
* fix(eas-cli): hide project export date when not older than 1m

* fix(eas-cli): add back no export error

* docs(eas-cli): add changelog entry

* chore(eas-cli): add `--platform=web` to error command

* docs(eas-cli): fix changelog entry
2024-09-14 15:03:20 +01:00
Expo CI d4c6440753 update CHANGELOG.md 2024-09-14 12:12:25 +00:00
Cedric van Putten 87b0cf41a8 v12.4.0 2024-09-14 13:07:15 +01:00
Cedric van Putten 7847b5297c refactor(eas-cli): always default to static uploads and add modified time (#2565)
* refactor(eas-cli): always default to static uploads and add modified time

* docs(eas-cli): add changelog entry

* chore(eas-cli): resolve linting issue

* fix(eas-cli): error when there are no deployments

* fix(eas-cli): remove unused `exp` var

* fix(eas-cli): fix json output again
2024-09-14 13:05:49 +01:00
Cedric van Putten 539ef8f3ad refactor(eas-cli): polish the dev domain prompt (#2564)
* refactor(eas-cli): polish the dev domain prompt

* docs(eas-cli): add changelog entry

* refactor(eas-cli): simplify the "already taken" message

* feature(eas-cli): add suggested project url to avoid conflicts
2024-09-14 12:44:55 +01:00
Cedric van Putten 819259ab1a feature(eas-cli): add worker:alias|promote --prod flag to promote existing deployments (#2563)
* feature(eas-cli): add `worker:alias --prod` flag to promote existing deployments

This also includes some clean ups and log utility sharing

* feature(eas-cli): support `worker:promote --prod --id`

* refactor(eas-cli): rename `/serverless/deployments` -> `/hosting/deployments`

* docs(eas-cli): add changelog entry

* chore(eas-cli): resolve linting issues

* fix(eas-cli): disable ora when possibly asking for a dev domain

* chore(eas-cli): resolve typing issue
2024-09-14 09:20:06 +01:00
Cedric van Putten 47e1989c9c feature(eas-cli): add worker:alias --json support for 3rd parties and custom tooling (#2562)
* feature(eas-cli): add `worker:alias --json` flag to integrate with 3rd parties

* docs(eas-cli): add changelog entry

* docs(eas-cli): fix changelog entry

* fix(eas-cli): lowercase deployment identifier

* fix(eas-cli): resolve linting issues
2024-09-13 16:14:21 +01:00
Cedric van Putten fa3262951b feature(eas-cli): add worker --json support for 3rd parties and custom tooling (#2561)
* feature(eas-cli): add `worker --json` flag to integrate with 3rd parties

* docs(eas-cli): add changelog entry

* refactor(eas-cli): rename `expoDashboardUrl` -> `dashboardUrl`

* chore(eas-cli): resolve linting issues

* docs(eas-cli): fix changelog entry
2024-09-13 16:09:04 +01:00
Cedric van Putten 66ce64e0a3 feature(eas-cli): add worker --export-dir to select exported directory (#2560)
* feature(eas-cli): add `worker --export-dir` flag to select export folder

* docs(eas-cli): add changelog entry

* fix(eas-cli): use `path.join` instead of `path.resolve`

`path.resolve` throws when the path does not exist, `path.join` uses our own validation errors.

* docs(eas-cli): fix changelog entry
2024-09-13 15:48:41 +01:00
Phil Pluckthun 5c553cee7b [eas-cli] Add --environment argument to worker:deploy (#2557)
* Add `--environment` argument to worker:deploy

* Add to changelog

* Apply lints
2024-09-13 13:15:30 +01:00
Cedric van Putten 2fb079d4bb refactor(eas-cli): update eas worker:alias with same polishes from eas worker (#2558)
* refactor(eas-cli): reuse same working in `eas worker:alis` from `eas worker`

* refactor(eas-cli): reuse same result table from `eas worker`

* docs(eas-cli): update changelog entry

* refactor(eas-cli): use `Alias URL` instead of `Aliased URL`
2024-09-13 13:06:47 +01:00
Cedric van Putten d15cd90c81 refactor(eas-cli): support eas worker --production and clean up command output (#2555)
* refactor(eas-cli): add support for both `--prod` and `--production` flags in `eas worker`

* chore: add changelog entry

* refactor(eas-cli): also tweak `--help` output

* refactor(eas-cli): move `--prod` before `--alias`/`--id` in usage

* refactor(eas-cli): clean up ora spinners and add table output

* refactor(eas-cli): simplify project type detection

* refactor(eas-cli): simplify alias and production spinners

* docs(eas-cli): update changelog entry

* chore: resolve linting issues

* refactor(eas-cli): use `Alias URL` instead of `Aliased URL`

* fix(eas-cli): mark `production` as bold in ora spinners
2024-09-13 12:53:11 +01:00
Cedric van Putten 0fbd3e47f8 fix(eas-cli): avoid adding expo.extra data from config plugins in eas init (#2554)
* fix(eas-cli): avoid adding `expo.extra` data from config plugins in `eas init`

Note that in the newer `@expo/config` package, modifying now also merges existing properties. Once thats released, we can use that instead of manually merging this data.

* docs(eas-cli): add changelog entry
2024-09-12 15:10:40 +02:00
Cedric van Putten 12efaf12cc feature(eas-cli): add worker --id flag (#2552)
* feature(eas-cli): add `worker --id` flag

* docs: add changelog entry
2024-09-12 13:02:47 +02:00
Cedric van PuttenandSzymon Dziedzic a404bb383d feature(eas-cli): add worker --alias flag (#2551)
* feature(eas-cli): add `worker --alias` flag

* docs: add changelog entry

* fix(eas-cli): add missing `ora.start`

Co-authored-by: Szymon Dziedzic <[email protected]>

---------

Co-authored-by: Szymon Dziedzic <[email protected]>
2024-09-12 12:43:01 +02:00
Cedric van PuttenandSzymon Dziedzic feaa8cbf22 feature(eas-cli): add worker --prod flag and hint for production deployments (#2550)
* feature(eas-cli): add `worker --prod` flag and hint for production deployments

* docs: add changelog entry

* refactor(eas-cli): add Syzmons PR feedback

Co-authored-by: Szymon Dziedzic <[email protected]>

---------

Co-authored-by: Szymon Dziedzic <[email protected]>
2024-09-12 12:37:00 +02:00
Cedric van Putten 542ea7a791 feature(eas-cli): add worker:alias command (#2548)
* feature(eas-cli): add `worker:alias` command to assign aliases

* feature(eas-cli): add worker selection when assigning alias

* refactor(eas-cli): clean up comments and prompt for alias

* refactor(eas-cli): clean up command and flag descriptions

* chore: resolve linting issues

* docs: add changelog entry
2024-09-12 12:25:29 +02:00
dependabot[bot] 7d80b0540f Bump express from 4.19.2 to 4.20.0 (#2546)
Bumps [express](https://github.com/expressjs/express) from 4.19.2 to 4.20.0.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0)

---
updated-dependencies:
- dependency-name: express
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-12 09:14:25 +02:00
dependabot[bot] cc4e0ebda1 Bump dset from 3.1.2 to 3.1.4 (#2553)
Bumps [dset](https://github.com/lukeed/dset) from 3.1.2 to 3.1.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/lukeed/dset/releases">dset's releases</a>.</em></p>
<blockquote>
<h2>v3.1.3</h2>
<h2>Patches</h2>
<ul>
<li>Add <code>&quot;types&quot;</code> export conditions for TypeScript <code>&quot;nodenext&quot;</code>/<code>&quot;node16&quot;</code> resolution: <a href="https://redirect.github.com/lukeed/dset/issues/40">#40</a>
<em>Thank you <a href="https://github.com/Akkuma"><code>@​Akkuma</code></a></em></li>
</ul>
<hr />
<p><strong>Full Changelog</strong>: <a href="https://github.com/lukeed/dset/compare/v3.1.2...v3.1.3">https://github.com/lukeed/dset/compare/v3.1.2...v3.1.3</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/lukeed/dset/commit/05b1ec0f8cebe3b5d5667285bc7da90f38a7215e"><code>05b1ec0</code></a> 3.1.4</li>
<li><a href="https://github.com/lukeed/dset/commit/16d6154e085bef01e99f01330e5a421a7f098afa"><code>16d6154</code></a> fix: prevent <strong>proto</strong> assignment via implicit string</li>
<li><a href="https://github.com/lukeed/dset/commit/48f14a1a4b2985c30663133e4699ddab69a393ac"><code>48f14a1</code></a> 3.1.3</li>
<li><a href="https://github.com/lukeed/dset/commit/aca90fb54b908de0042b7da0d45ef3ca0ea9671a"><code>aca90fb</code></a> chore: add &quot;types&quot; conditions for TS &quot;nodenext&quot; resolution (<a href="https://redirect.github.com/lukeed/dset/issues/40">#40</a>)</li>
<li>See full diff in <a href="https://github.com/lukeed/dset/compare/v3.1.2...v3.1.4">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dset&package-manager=npm_and_yarn&previous-version=3.1.2&new-version=3.1.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/expo/eas-cli/network/alerts).

</details>
2024-09-12 09:13:49 +02:00
Phil Pluckthun 1621fb3a9b feat(cli): Source @expo/env dotenv vars for worker deployments (#2545)
* Add `@expo/env`

* Source env variables from ".env" via `@expo/env` to deployment

* Add CHANGELOG

* Increase requested range for `@expo/env`

* Apply lints
2024-09-11 13:03:08 +01:00
Phil Pluckthun 51b5946854 chore(cli): Add --non-interactive flag to fix worker:deploy initialisation (#2544) 2024-09-10 18:00:03 +01:00
Szymon Dziedzic b8ed63cadc [eas-cli] improve error message when CFBundleShortVersionString has invalid format (#2542)
<!-- If this PR requires a changelog entry, add it by commenting the PR with the command `/changelog-entry [breaking-change|new-feature|bug-fix|chore] [message]`. -->
<!-- You can skip the changelog check by labeling the PR with "no changelog". -->

# Why

https://exponent-internal.slack.com/archives/C017N0N99RA/p1725915931423649

# How

Improve error message to make it more actionable

# Test Plan

Test manually

![Screenshot 2024-09-10 at 12.39.27.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/9YRXgAETSTRMfjZ0IP35/0c5ad653-3e55-43e0-bc12-aa4b30f730eb.png)

![Screenshot 2024-09-10 at 12.34.51.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/9YRXgAETSTRMfjZ0IP35/2e8f9e21-1d06-4c6c-8cc0-f263c9455bae.png)
2024-09-10 16:01:42 +02:00
Expo CI 8fc0648419 update CHANGELOG.md 2024-09-09 15:41:29 +00:00
39 changed files with 6041 additions and 1738 deletions

No files matched your search

+46 -1
View File
@@ -8,12 +8,57 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- Add `--non-interactive` and `--force` support when `--id` is not passed to the `eas init` command. ([#1983](https://github.com/expo/eas-cli/pull/1983) by [@mymattcarroll](https://github.com/mymattcarroll))
- Log command execution to assist in debugging local builds. ([#2526](https://github.com/expo/eas-cli/pull/2526) by [@trajano](https://github.com/trajano))
- Allow submitting builds in progress ([#2543](https://github.com/expo/eas-cli/pull/2543) by [@radoslawkrzemien](https://github.com/radoslawkrzemien))
- Use `EAS_DANGEROUS_OVERRIDE_ANDROID_APPLICATION_ID` and `EAS_DANGEROUS_OVERRIDE_IOS_BUNDLE_IDENTIFIER` environment variables as overrides of the Android application ID and iOS bundle identifier in managed workflow too. ([#2576](https://github.com/expo/eas-cli/pull/2576) by [@sjchmiela](https://github.com/sjchmiela))
### 🐛 Bug fixes
- Avoid malforming `app.json` with empty `.expo` object. ([#2573](https://github.com/expo/eas-cli/pull/2573) by [@byCedric](https://github.com/byCedric))
### 🧹 Chores
## [12.4.1](https://github.com/expo/eas-cli/releases/tag/v12.4.1) - 2024-09-14
## [12.4.0](https://github.com/expo/eas-cli/releases/tag/v12.4.0) - 2024-09-14
### 🎉 New features
- Add `worker:alias` command to assign aliases from the CLI. ([#2548](https://github.com/expo/eas-cli/pull/2548) by [@byCedric](https://github.com/byCedric))
- Add `worker --prod` flag to deploy to production from the CLI. ([#2550](https://github.com/expo/eas-cli/pull/2550) by [@byCedric](https://github.com/byCedric))
- Add `worker --alias` flag to assign custom aliases when deploying. ([#2551](https://github.com/expo/eas-cli/pull/2551) by [@byCedric](https://github.com/byCedric)))
- Add `worker --id` flag to use a custom deployment identifier. ([#2552](https://github.com/expo/eas-cli/pull/2552) by [@byCedric](https://github.com/byCedric)))
- Add `worker --environment` flag to deploy with EAS environment variables. ([#2557](https://github.com/expo/eas-cli/pull/2557) by [@kitten](https://github.com/kitten)))
- Add `worker --export-dir` flag to select exported directory. ([#2560](https://github.com/expo/eas-cli/pull/2560) by [@byCedric](https://github.com/byCedric)))
- Add `worker --json` flag to allow integrating with 3rd parties and custom tooling. ([#2561](https://github.com/expo/eas-cli/pull/2561) by [@byCedric](https://github.com/byCedric)))
- Add `worker:alias --json` flag to allow integrating with 3rd parties and custom tooling. ([#2562](https://github.com/expo/eas-cli/pull/2562) by [@byCedric](https://github.com/byCedric)))
### 🐛 Bug fixes
- Avoid merging `expo.extra` plugin-generated data with `expo.extra.eas.projectId` in `eas init`. ([#2554](https://github.com/expo/eas-cli/pull/2554) by [@byCedric](https://github.com/byCedric)))
- Restore "export not found" error and hide recent export timestamps. ([#2566](https://github.com/expo/eas-cli/pull/2566) by [@byCedric](https://github.com/byCedric)))
- Check if export is available before validating project ID in `eas worker`. ([#2569](https://github.com/expo/eas-cli/pull/2569) by [@byCedric](https://github.com/byCedric)))
### 🧹 Chores
- Make error message for invalid CFBundleShortVersionString more descriptive and actionable. Improve CFBundleShortVersionString validation regex. ([#2542](https://github.com/expo/eas-cli/pull/2542) by [@szdziedzic](https://github.com/szdziedzic))
- Add missing `--non-interactive` argument to `worker:deploy` command. ([#2544](https://github.com/expo/eas-cli/pull/2544) by [@kitten](https://github.com/kitten))
- Source `@expo/env` dotenv files for worker deployments. ([#2545](https://github.com/expo/eas-cli/pull/2545) by [@kitten](https://github.com/kitten))
- Support `worker --production` and clean up command output. ([#2555](https://github.com/expo/eas-cli/pull/2555) by [@byCedric](https://github.com/byCedric)))
- Unify both `worker` and `worker:alias` command output. ([#2558](https://github.com/expo/eas-cli/pull/2558) by [@byCedric](https://github.com/byCedric)))
- Share similar table/json output in both `worker` and `worker:alias` command outputs. ([#2563](https://github.com/expo/eas-cli/pull/2563) by [@byCedric](https://github.com/byCedric)))
- Polish the project URL prompt when setting up new projects. ([#2564](https://github.com/expo/eas-cli/pull/2564) by [@byCedric](https://github.com/byCedric)))
- Always assume `static` exports in `eas deploy` and add modified time. ([#2565](https://github.com/expo/eas-cli/pull/2565) by [@byCedric](https://github.com/byCedric)))
- Update the `eas worker --help` `--environment` description. ([#2567](https://github.com/expo/eas-cli/pull/2567) by [@byCedric](https://github.com/byCedric)))
- Remove the cursor space after selecting project dev domain. ([#2568](https://github.com/expo/eas-cli/pull/2568) by [@byCedric](https://github.com/byCedric)))
- Reword the dev domain prompt to mention "preview URL". ([#2572](https://github.com/expo/eas-cli/pull/2572) by [@byCedric](https://github.com/byCedric)))
## [12.3.0](https://github.com/expo/eas-cli/releases/tag/v12.3.0) - 2024-09-09
### 🎉 New features
- Add `--non-interactive` and `--force` support when `--id` is not passed to the `eas init` command. ([#1983](https://github.com/expo/eas-cli/pull/1983) by [@mymattcarroll](https://github.com/mymattcarroll))
## [12.1.1](https://github.com/expo/eas-cli/releases/tag/v12.1.1) - 2024-09-09
### 🐛 Bug fixes
+1 -1
View File
@@ -1,5 +1,5 @@
{
"npmClient": "yarn",
"version": "12.3.0",
"version": "12.5.0",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
+60 -60
View File
@@ -143,7 +143,7 @@ ALIASES
$ eas login
```
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/account/login.ts)_
_See code: [packages/eas-cli/src/commands/account/login.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/account/login.ts)_
## `eas account:logout`
@@ -160,7 +160,7 @@ ALIASES
$ eas logout
```
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/account/logout.ts)_
_See code: [packages/eas-cli/src/commands/account/logout.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/account/logout.ts)_
## `eas account:view`
@@ -177,7 +177,7 @@ ALIASES
$ eas whoami
```
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/account/view.ts)_
_See code: [packages/eas-cli/src/commands/account/view.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/account/view.ts)_
## `eas analytics [STATUS]`
@@ -191,7 +191,7 @@ DESCRIPTION
display or change analytics settings
```
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/analytics.ts)_
_See code: [packages/eas-cli/src/commands/analytics.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/analytics.ts)_
## `eas autocomplete [SHELL]`
@@ -243,7 +243,7 @@ DESCRIPTION
create a branch
```
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/branch/create.ts)_
_See code: [packages/eas-cli/src/commands/branch/create.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/branch/create.ts)_
## `eas branch:delete [NAME]`
@@ -264,7 +264,7 @@ DESCRIPTION
delete a branch
```
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/branch/delete.ts)_
_See code: [packages/eas-cli/src/commands/branch/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/branch/delete.ts)_
## `eas branch:list`
@@ -284,7 +284,7 @@ DESCRIPTION
list all branches
```
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/branch/list.ts)_
_See code: [packages/eas-cli/src/commands/branch/list.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/branch/list.ts)_
## `eas branch:rename`
@@ -304,7 +304,7 @@ DESCRIPTION
rename a branch
```
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/branch/rename.ts)_
_See code: [packages/eas-cli/src/commands/branch/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/branch/rename.ts)_
## `eas branch:view [NAME]`
@@ -327,7 +327,7 @@ DESCRIPTION
view a branch
```
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/branch/view.ts)_
_See code: [packages/eas-cli/src/commands/branch/view.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/branch/view.ts)_
## `eas build`
@@ -364,7 +364,7 @@ DESCRIPTION
start a build
```
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/index.ts)_
_See code: [packages/eas-cli/src/commands/build/index.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/index.ts)_
## `eas build:cancel [BUILD_ID]`
@@ -383,7 +383,7 @@ DESCRIPTION
cancel a build
```
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/cancel.ts)_
_See code: [packages/eas-cli/src/commands/build/cancel.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/cancel.ts)_
## `eas build:configure`
@@ -400,7 +400,7 @@ DESCRIPTION
configure the project to support EAS Build
```
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/configure.ts)_
_See code: [packages/eas-cli/src/commands/build/configure.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/configure.ts)_
## `eas build:delete [BUILD_ID]`
@@ -419,7 +419,7 @@ DESCRIPTION
delete a build
```
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/delete.ts)_
_See code: [packages/eas-cli/src/commands/build/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/delete.ts)_
## `eas build:inspect`
@@ -454,7 +454,7 @@ DESCRIPTION
inspect the state of the project at specific build stages, useful for troubleshooting
```
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/inspect.ts)_
_See code: [packages/eas-cli/src/commands/build/inspect.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/inspect.ts)_
## `eas build:list`
@@ -504,7 +504,7 @@ DESCRIPTION
list all builds for your project
```
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/list.ts)_
_See code: [packages/eas-cli/src/commands/build/list.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/list.ts)_
## `eas build:resign`
@@ -533,7 +533,7 @@ DESCRIPTION
re-sign a build archive
```
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/resign.ts)_
_See code: [packages/eas-cli/src/commands/build/resign.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/resign.ts)_
## `eas build:run`
@@ -559,7 +559,7 @@ DESCRIPTION
run simulator/emulator builds from eas-cli
```
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/run.ts)_
_See code: [packages/eas-cli/src/commands/build/run.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/run.ts)_
## `eas build:submit`
@@ -609,7 +609,7 @@ DESCRIPTION
get the latest version from EAS servers
```
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/version/get.ts)_
_See code: [packages/eas-cli/src/commands/build/version/get.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/version/get.ts)_
## `eas build:version:set`
@@ -628,7 +628,7 @@ DESCRIPTION
update version of an app
```
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/version/set.ts)_
_See code: [packages/eas-cli/src/commands/build/version/set.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/version/set.ts)_
## `eas build:version:sync`
@@ -647,7 +647,7 @@ DESCRIPTION
update a version in native code with a value stored on EAS servers
```
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/version/sync.ts)_
_See code: [packages/eas-cli/src/commands/build/version/sync.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/version/sync.ts)_
## `eas build:view [BUILD_ID]`
@@ -664,7 +664,7 @@ DESCRIPTION
view a build for your project
```
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/build/view.ts)_
_See code: [packages/eas-cli/src/commands/build/view.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/build/view.ts)_
## `eas channel:create [NAME]`
@@ -685,7 +685,7 @@ DESCRIPTION
create a channel
```
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/channel/create.ts)_
_See code: [packages/eas-cli/src/commands/channel/create.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/channel/create.ts)_
## `eas channel:edit [NAME]`
@@ -707,7 +707,7 @@ DESCRIPTION
point a channel at a new branch
```
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/channel/edit.ts)_
_See code: [packages/eas-cli/src/commands/channel/edit.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/channel/edit.ts)_
## `eas channel:list`
@@ -727,7 +727,7 @@ DESCRIPTION
list all channels
```
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/channel/list.ts)_
_See code: [packages/eas-cli/src/commands/channel/list.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/channel/list.ts)_
## `eas channel:rollout [CHANNEL]`
@@ -760,7 +760,7 @@ DESCRIPTION
Roll a new branch out on a channel incrementally.
```
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/channel/rollout.ts)_
_See code: [packages/eas-cli/src/commands/channel/rollout.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/channel/rollout.ts)_
## `eas channel:view [NAME]`
@@ -783,7 +783,7 @@ DESCRIPTION
view a channel
```
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/channel/view.ts)_
_See code: [packages/eas-cli/src/commands/channel/view.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/channel/view.ts)_
## `eas config`
@@ -804,7 +804,7 @@ DESCRIPTION
display project configuration (app.json + eas.json)
```
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/config.ts)_
_See code: [packages/eas-cli/src/commands/config.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/config.ts)_
## `eas credentials`
@@ -821,7 +821,7 @@ DESCRIPTION
manage credentials
```
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/credentials/index.ts)_
_See code: [packages/eas-cli/src/commands/credentials/index.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/credentials/index.ts)_
## `eas credentials:configure-build`
@@ -839,7 +839,7 @@ DESCRIPTION
Set up credentials for building your project.
```
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
_See code: [packages/eas-cli/src/commands/credentials/configure-build.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/credentials/configure-build.ts)_
## `eas device:create`
@@ -853,7 +853,7 @@ DESCRIPTION
register new Apple Devices to use for internal distribution
```
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/device/create.ts)_
_See code: [packages/eas-cli/src/commands/device/create.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/device/create.ts)_
## `eas device:delete`
@@ -873,7 +873,7 @@ DESCRIPTION
remove a registered device from your account
```
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/device/delete.ts)_
_See code: [packages/eas-cli/src/commands/device/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/device/delete.ts)_
## `eas device:list`
@@ -894,7 +894,7 @@ DESCRIPTION
list all registered devices for your account
```
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/device/list.ts)_
_See code: [packages/eas-cli/src/commands/device/list.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/device/list.ts)_
## `eas device:rename`
@@ -915,7 +915,7 @@ DESCRIPTION
rename a registered device
```
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/device/rename.ts)_
_See code: [packages/eas-cli/src/commands/device/rename.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/device/rename.ts)_
## `eas device:view [UDID]`
@@ -929,7 +929,7 @@ DESCRIPTION
view a device for your project
```
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/device/view.ts)_
_See code: [packages/eas-cli/src/commands/device/view.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/device/view.ts)_
## `eas diagnostics`
@@ -943,7 +943,7 @@ DESCRIPTION
display environment info
```
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/diagnostics.ts)_
_See code: [packages/eas-cli/src/commands/diagnostics.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/diagnostics.ts)_
## `eas help [COMMAND]`
@@ -1051,7 +1051,7 @@ DESCRIPTION
validate the local store configuration
```
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/metadata/lint.ts)_
_See code: [packages/eas-cli/src/commands/metadata/lint.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/metadata/lint.ts)_
## `eas metadata:pull`
@@ -1068,7 +1068,7 @@ DESCRIPTION
generate the local store configuration from the app stores
```
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/metadata/pull.ts)_
_See code: [packages/eas-cli/src/commands/metadata/pull.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/metadata/pull.ts)_
## `eas metadata:push`
@@ -1085,7 +1085,7 @@ DESCRIPTION
sync the local store configuration to the app stores
```
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/metadata/push.ts)_
_See code: [packages/eas-cli/src/commands/metadata/push.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/metadata/push.ts)_
## `eas onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1115,7 +1115,7 @@ DESCRIPTION
open the project page in a web browser
```
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/open.ts)_
_See code: [packages/eas-cli/src/commands/open.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/open.ts)_
## `eas project:info`
@@ -1129,7 +1129,7 @@ DESCRIPTION
information about the current project
```
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/project/info.ts)_
_See code: [packages/eas-cli/src/commands/project/info.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/project/info.ts)_
## `eas project:init`
@@ -1152,7 +1152,7 @@ ALIASES
$ eas init
```
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/project/init.ts)_
_See code: [packages/eas-cli/src/commands/project/init.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/project/init.ts)_
## `eas project:onboarding [TARGET_PROJECT_DIRECTORY]`
@@ -1170,7 +1170,7 @@ ALIASES
$ eas onboarding
```
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/project/onboarding.ts)_
_See code: [packages/eas-cli/src/commands/project/onboarding.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/project/onboarding.ts)_
## `eas secret:create`
@@ -1193,7 +1193,7 @@ DESCRIPTION
create an environment secret on the current project or owner account
```
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/secret/create.ts)_
_See code: [packages/eas-cli/src/commands/secret/create.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/secret/create.ts)_
## `eas secret:delete`
@@ -1211,7 +1211,7 @@ DESCRIPTION
delete an environment secret by ID
```
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/secret/delete.ts)_
_See code: [packages/eas-cli/src/commands/secret/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/secret/delete.ts)_
## `eas secret:list`
@@ -1225,7 +1225,7 @@ DESCRIPTION
list environment secrets available for your current app
```
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/secret/list.ts)_
_See code: [packages/eas-cli/src/commands/secret/list.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/secret/list.ts)_
## `eas secret:push`
@@ -1245,7 +1245,7 @@ DESCRIPTION
read environment secrets from env file and store on the server
```
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/secret/push.ts)_
_See code: [packages/eas-cli/src/commands/secret/push.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/secret/push.ts)_
## `eas submit`
@@ -1276,7 +1276,7 @@ ALIASES
$ eas build:submit
```
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/submit.ts)_
_See code: [packages/eas-cli/src/commands/submit.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/submit.ts)_
## `eas update`
@@ -1310,7 +1310,7 @@ DESCRIPTION
publish an update group
```
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/index.ts)_
_See code: [packages/eas-cli/src/commands/update/index.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/index.ts)_
## `eas update:configure`
@@ -1328,7 +1328,7 @@ DESCRIPTION
configure the project to support EAS Update
```
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/configure.ts)_
_See code: [packages/eas-cli/src/commands/update/configure.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/configure.ts)_
## `eas update:delete GROUPID`
@@ -1349,7 +1349,7 @@ DESCRIPTION
delete all the updates in an update group
```
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/delete.ts)_
_See code: [packages/eas-cli/src/commands/update/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/delete.ts)_
## `eas update:list`
@@ -1371,7 +1371,7 @@ DESCRIPTION
view the recent updates
```
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/list.ts)_
_See code: [packages/eas-cli/src/commands/update/list.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/list.ts)_
## `eas update:republish`
@@ -1399,7 +1399,7 @@ DESCRIPTION
roll back to an existing update
```
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/republish.ts)_
_See code: [packages/eas-cli/src/commands/update/republish.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/republish.ts)_
## `eas update:roll-back-to-embedded`
@@ -1427,7 +1427,7 @@ DESCRIPTION
roll back to the embedded update
```
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
_See code: [packages/eas-cli/src/commands/update/roll-back-to-embedded.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/roll-back-to-embedded.ts)_
## `eas update:rollback`
@@ -1447,7 +1447,7 @@ DESCRIPTION
roll back to an embedded update or an existing update
```
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/rollback.ts)_
_See code: [packages/eas-cli/src/commands/update/rollback.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/rollback.ts)_
## `eas update:view GROUPID`
@@ -1467,7 +1467,7 @@ DESCRIPTION
update group details
```
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/update/view.ts)_
_See code: [packages/eas-cli/src/commands/update/view.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/update/view.ts)_
## `eas webhook:create`
@@ -1488,7 +1488,7 @@ DESCRIPTION
create a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/webhook/create.ts)_
_See code: [packages/eas-cli/src/commands/webhook/create.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/webhook/create.ts)_
## `eas webhook:delete [ID]`
@@ -1508,7 +1508,7 @@ DESCRIPTION
delete a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/webhook/delete.ts)_
_See code: [packages/eas-cli/src/commands/webhook/delete.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/webhook/delete.ts)_
## `eas webhook:list`
@@ -1526,7 +1526,7 @@ DESCRIPTION
list webhooks
```
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/webhook/list.ts)_
_See code: [packages/eas-cli/src/commands/webhook/list.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/webhook/list.ts)_
## `eas webhook:update`
@@ -1548,7 +1548,7 @@ DESCRIPTION
update a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/webhook/update.ts)_
_See code: [packages/eas-cli/src/commands/webhook/update.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/webhook/update.ts)_
## `eas webhook:view ID`
@@ -1565,7 +1565,7 @@ DESCRIPTION
view a webhook
```
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v12.3.0/packages/eas-cli/src/commands/webhook/view.ts)_
_See code: [packages/eas-cli/src/commands/webhook/view.ts](https://github.com/expo/eas-cli/blob/v12.5.0/packages/eas-cli/src/commands/webhook/view.ts)_
## `eas whoami`
+3635 -1244
View File
File diff suppressed because it is too large. Load diff
+8 -6
View File
@@ -1,7 +1,7 @@
{
"name": "eas-cli",
"description": "EAS command line tool",
"version": "12.3.0",
"version": "12.5.0",
"author": "Expo <[email protected]>",
"bin": {
"eas": "./bin/run"
@@ -13,8 +13,9 @@
"@expo/config": "8.5.4",
"@expo/config-plugins": "7.8.4",
"@expo/config-types": "50.0.0",
"@expo/eas-build-job": "1.0.133",
"@expo/eas-json": "12.0.0",
"@expo/eas-build-job": "1.0.136",
"@expo/eas-json": "12.5.0",
"@expo/env": "^0.3.0",
"@expo/json-file": "8.2.37",
"@expo/logger": "1.0.117",
"@expo/multipart-body-parser": "1.1.0",
@@ -28,7 +29,7 @@
"@expo/results": "1.0.0",
"@expo/rudder-sdk-node": "1.1.1",
"@expo/spawn-async": "1.7.0",
"@expo/steps": "1.0.134",
"@expo/steps": "1.0.136",
"@expo/timeago.js": "1.0.0",
"@oclif/core": "^1.26.2",
"@oclif/plugin-autocomplete": "^2.3.10",
@@ -85,7 +86,8 @@
"turndown": "7.1.2",
"untildify": "4.0.0",
"uuid": "9.0.1",
"wrap-ansi": "7.0.0"
"wrap-ansi": "7.0.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@graphql-codegen/cli": "5.0.0",
@@ -113,7 +115,7 @@
"@types/wrap-ansi": "3.0.0",
"axios": "0.28.1",
"eslint-plugin-graphql": "4.0.0",
"express": "4.19.2",
"express": "4.20.0",
"memfs": "3.4.13",
"mockdate": "3.0.5",
"nock": "13.4.0",
@@ -5,6 +5,7 @@ import { vol } from 'memfs';
import os from 'os';
import type { XCBuildConfiguration } from 'xcode';
import { learnMore } from '../../../log';
import { readPlistAsync } from '../../../utils/plist';
import { resolveVcsClient } from '../../../vcs';
import {
@@ -316,7 +317,9 @@ describe(readShortVersionAsync, () => {
vcsClient
)
).rejects.toThrowError(
'CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: 0.0.7.1.028'
`The required format for "CFBundleShortVersionString" in Info.plist is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: 0.0.7.1.028. Edit the "CFBundleShortVersionString" in your Info.plist to match the required format. ${learnMore(
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
)}`
);
});
});
+24 -8
View File
@@ -11,7 +11,7 @@ import { Target } from '../../credentials/ios/types';
import { AppPlatform } from '../../graphql/generated';
import { AppVersionMutation } from '../../graphql/mutations/AppVersionMutation';
import { AppVersionQuery } from '../../graphql/queries/AppVersionQuery';
import Log from '../../log';
import Log, { learnMore } from '../../log';
import { ora } from '../../ora';
import { findApplicationTarget } from '../../project/ios/target';
import { getNextBuildNumber, isValidBuildNumber } from '../../project/ios/versions';
@@ -23,7 +23,7 @@ import { Client } from '../../vcs/vcs';
import { updateAppJsonConfigAsync } from '../utils/appJson';
import { bumpAppVersionAsync, ensureStaticConfigExists } from '../utils/version';
const SHORT_VERSION_REGEX = /^\d+.\d+.\d+$/;
const SHORT_VERSION_REGEX = /^\d+(\.\d+){0,2}$/;
export enum BumpStrategy {
APP_VERSION,
@@ -100,11 +100,27 @@ export async function bumpVersionInAppJsonAsync({
}
}
function validateShortVersion(shortVersion: string | undefined): void {
function validateShortVersion({
shortVersion,
workflow,
}: {
shortVersion: string | undefined;
workflow: Workflow;
}): void {
if (shortVersion && !SHORT_VERSION_REGEX.test(shortVersion)) {
throw new Error(
`CFBundleShortVersionString (version field in app.json/app.config.js) must be a period-separated list of three non-negative integers. Current value: ${shortVersion}`
);
if (workflow === Workflow.MANAGED) {
throw new Error(
`The required format for "version" field from app.json/app.config.ts is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "version" field in your app.json/app.config.ts to match the required format. ${learnMore(
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
)}`
);
} else {
throw new Error(
`The required format for "CFBundleShortVersionString" in Info.plist is one to three period-separated integers, such as 10.14.1. The string can only contain numeric characters (0-9) and periods. Current value: ${shortVersion}. Edit the "CFBundleShortVersionString" in your Info.plist to match the required format. ${learnMore(
'https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring'
)}`
);
}
}
}
@@ -122,10 +138,10 @@ export async function readShortVersionAsync(
infoPlist.CFBundleShortVersionString &&
evaluateTemplateString(infoPlist.CFBundleShortVersionString, buildSettings);
validateShortVersion(shortVersion);
validateShortVersion({ shortVersion, workflow });
return shortVersion;
} else {
validateShortVersion(exp.version);
validateShortVersion({ shortVersion: exp.version, workflow });
return exp.version;
}
}
+19 -11
View File
@@ -3,6 +3,7 @@ import spawnAsync from '@expo/spawn-async';
import { ChildProcess } from 'child_process';
import semver from 'semver';
import Log from '../log';
import { ora } from '../ora';
const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
@@ -55,19 +56,26 @@ export async function runLocalBuildAsync(
};
process.on('SIGINT', interruptHandler);
try {
const mergedEnv = {
...env,
...process.env,
EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR,
...(options.skipCleanup || options.skipNativeBuild
? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' }
: {}),
...(options.skipNativeBuild ? { EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: '1' } : {}),
...(options.artifactsDir ? { EAS_LOCAL_BUILD_ARTIFACTS_DIR: options.artifactsDir } : {}),
...(options.artifactPath ? { EAS_LOCAL_BUILD_ARTIFACT_PATH: options.artifactPath } : {}),
};
// log command execution to assist in debugging local builds
Log.debug('Running local build, using local-build-plugin', {
command,
args,
env: mergedEnv,
});
const spawnPromise = spawnAsync(command, args, {
stdio: options.verbose ? 'inherit' : 'pipe',
env: {
...env,
...process.env,
EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR,
...(options.skipCleanup || options.skipNativeBuild
? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' }
: {}),
...(options.skipNativeBuild ? { EAS_LOCAL_BUILD_SKIP_NATIVE_BUILD: '1' } : {}),
...(options.artifactsDir ? { EAS_LOCAL_BUILD_ARTIFACTS_DIR: options.artifactsDir } : {}),
...(options.artifactPath ? { EAS_LOCAL_BUILD_ARTIFACT_PATH: options.artifactPath } : {}),
},
env: mergedEnv,
});
childProcess = spawnPromise.child;
await spawnPromise;
@@ -18,13 +18,15 @@ import { Actor, getActorUsername } from '../../../user/User';
* Save an EAS project ID to the appropriate field in the app config.
*
* @deprecated Should not be used outside of context functions except in the init command.
* @deprecated Starting from `@expo/config` from SDK 52, the `modifyConfigAsync` function is merging existing data. Once this is released, we can use that instead of manually merging.
*/
export async function saveProjectIdToAppConfigAsync(
projectDir: string,
projectId: string,
options: { env?: Env } = {}
): Promise<void> {
const exp = getPrivateExpoConfig(projectDir, options);
// NOTE(cedric): we disable plugins to avoid writing plugin-generated content to `expo.extra`
const exp = getPrivateExpoConfig(projectDir, { skipPlugins: true, ...options });
const result = await createOrModifyExpoConfigAsync(
projectDir,
{
@@ -0,0 +1,217 @@
import { Platform, SubmissionConfig } from '@expo/eas-build-job';
import { EasJsonAccessor, EasJsonUtils } from '@expo/eas-json';
import { Flags } from '@oclif/core';
import nullthrows from 'nullthrows';
import { z } from 'zod';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import {
AndroidSubmissionConfigInput,
IosSubmissionConfigInput,
SubmissionAndroidReleaseStatus,
SubmissionAndroidTrack,
} from '../../graphql/generated';
import { AppStoreConnectApiKeyQuery } from '../../graphql/queries/AppStoreConnectApiKeyQuery';
import { GoogleServiceAccountKeyQuery } from '../../graphql/queries/GoogleServiceAccountKeyQuery';
import AndroidSubmitCommand from '../../submit/android/AndroidSubmitCommand';
import { SubmissionContext, createSubmissionContextAsync } from '../../submit/context';
import IosSubmitCommand from '../../submit/ios/IosSubmitCommand';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import GitNoCommitClient from '../../vcs/clients/gitNoCommit';
/**
* This command will be run on the EAS workers.
* This command resolves credentials and other
* configuration, that normally would be included in the
* job and metadata objects, and prints them to stdout.
*/
export default class SubmitInternal extends EasCommand {
static override hidden = true;
static override flags = {
platform: Flags.enum<Platform>({
options: [Platform.ANDROID, Platform.IOS],
required: true,
}),
profile: Flags.string({
description:
'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
}),
id: Flags.string({
description: 'ID of the build to submit',
required: true,
}),
};
static override contextDefinition = {
...this.ContextOptions.LoggedIn,
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.ProjectDir,
...this.ContextOptions.Analytics,
...this.ContextOptions.Vcs,
};
async runAsync(): Promise<void> {
const { flags } = await this.parse(SubmitInternal);
// This command is always run with implicit --non-interactive and --json options
enableJsonOutput();
const {
loggedIn: { actor, graphqlClient },
privateProjectConfig: { exp, projectId, projectDir },
analytics,
vcsClient,
} = await this.getContextAsync(SubmitInternal, {
nonInteractive: true,
vcsClientOverride: new GitNoCommitClient(),
});
const submissionProfile = await EasJsonUtils.getSubmitProfileAsync(
EasJsonAccessor.fromProjectPath(projectDir),
flags.platform,
flags.profile
);
const ctx = await createSubmissionContextAsync({
platform: flags.platform,
projectDir,
profile: submissionProfile,
archiveFlags: {
id: flags.id,
},
nonInteractive: true,
isVerboseFastlaneEnabled: false,
actor,
graphqlClient,
analytics,
exp,
projectId,
vcsClient,
specifiedProfile: flags.profile,
});
let config;
if (ctx.platform === Platform.IOS) {
const command = new IosSubmitCommand(ctx as SubmissionContext<Platform.IOS>);
const submitter = await command.runAsync();
const mutationInput = await submitter.getSubmissionInputAsync();
const iosConfig = mutationInput.submissionConfig;
const ascApiKeyJson = await getAppStoreConnectApiKeyJsonAsync({
iosConfig,
graphqlClient,
});
const configInput: z.input<typeof SubmissionConfig.Ios.SchemaZ> = {
ascAppIdentifier: iosConfig.ascAppIdentifier,
isVerboseFastlaneEnabled: iosConfig.isVerboseFastlaneEnabled ?? undefined,
...(ascApiKeyJson
? { ascApiJsonKey: ascApiKeyJson }
: {
appleIdUsername: nullthrows(iosConfig.appleIdUsername),
appleAppSpecificPassword: nullthrows(iosConfig.appleAppSpecificPassword),
}),
};
config = SubmissionConfig.Ios.SchemaZ.parse(configInput);
} else if (ctx.platform === Platform.ANDROID) {
const command = new AndroidSubmitCommand(ctx as SubmissionContext<Platform.ANDROID>);
const submitter = await command.runAsync();
const mutationInput = await submitter.getSubmissionInputAsync();
const androidConfig = mutationInput.submissionConfig;
const changesNotSentForReview = androidConfig.changesNotSentForReview ?? undefined;
const releaseStatus = androidConfig.releaseStatus
? graphQlReleaseStatusToConfigReleaseStatus[androidConfig.releaseStatus]
: undefined;
const googleServiceAccountKeyJson = nullthrows(
await getGoogleServiceAccountKeyJsonAsync({
androidConfig,
graphqlClient,
})
);
const track = graphQlTrackToConfigTrack[androidConfig.track];
const configInput: z.input<typeof SubmissionConfig.Android.SchemaZ> = {
changesNotSentForReview,
googleServiceAccountKeyJson,
track,
...(releaseStatus === SubmissionConfig.Android.ReleaseStatus.IN_PROGRESS
? {
releaseStatus: SubmissionConfig.Android.ReleaseStatus.IN_PROGRESS,
rollout: androidConfig.rollout ?? undefined,
}
: { releaseStatus }),
};
config = SubmissionConfig.Android.SchemaZ.parse(configInput);
} else {
throw new Error(`Unsupported platform: ${ctx.platform}`);
}
printJsonOnlyOutput({ config });
}
}
async function getGoogleServiceAccountKeyJsonAsync({
androidConfig,
graphqlClient,
}: {
androidConfig: AndroidSubmissionConfigInput;
graphqlClient: ExpoGraphqlClient;
}): Promise<string | null> {
if (androidConfig.googleServiceAccountKeyJson) {
return androidConfig.googleServiceAccountKeyJson;
} else if (androidConfig.googleServiceAccountKeyId) {
const key = await GoogleServiceAccountKeyQuery.getByIdAsync(
graphqlClient,
androidConfig.googleServiceAccountKeyId
);
return key.keyJson;
}
return null;
}
async function getAppStoreConnectApiKeyJsonAsync({
iosConfig,
graphqlClient,
}: {
iosConfig: IosSubmissionConfigInput;
graphqlClient: ExpoGraphqlClient;
}): Promise<string | null> {
if (iosConfig.ascApiKey) {
return JSON.stringify({
key_id: iosConfig.ascApiKey.keyIdentifier,
issuer_id: iosConfig.ascApiKey.issuerIdentifier,
key: iosConfig.ascApiKey.keyP8,
});
} else if (iosConfig.ascApiKeyId) {
const key = await AppStoreConnectApiKeyQuery.getByIdAsync(graphqlClient, iosConfig.ascApiKeyId);
return JSON.stringify({
key_id: key.keyIdentifier,
issuer_id: key.issuerIdentifier,
key: key.keyP8,
});
}
return null;
}
const graphQlReleaseStatusToConfigReleaseStatus = {
[SubmissionAndroidReleaseStatus.Draft]: SubmissionConfig.Android.ReleaseStatus.DRAFT,
[SubmissionAndroidReleaseStatus.InProgress]: SubmissionConfig.Android.ReleaseStatus.IN_PROGRESS,
[SubmissionAndroidReleaseStatus.Completed]: SubmissionConfig.Android.ReleaseStatus.COMPLETED,
[SubmissionAndroidReleaseStatus.Halted]: SubmissionConfig.Android.ReleaseStatus.HALTED,
};
const graphQlTrackToConfigTrack = {
[SubmissionAndroidTrack.Production]: SubmissionConfig.Android.ReleaseTrack.PRODUCTION,
[SubmissionAndroidTrack.Beta]: SubmissionConfig.Android.ReleaseTrack.BETA,
[SubmissionAndroidTrack.Alpha]: SubmissionConfig.Android.ReleaseTrack.ALPHA,
[SubmissionAndroidTrack.Internal]: SubmissionConfig.Android.ReleaseTrack.INTERNAL,
};
@@ -0,0 +1,233 @@
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import { WorkerDeploymentAliasFragment } from '../../graphql/generated';
import Log from '../../log';
import { Ora, ora } from '../../ora';
import { promptAsync } from '../../prompts';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import {
assignWorkerDeploymentAliasAsync,
assignWorkerDeploymentProductionAsync,
selectWorkerDeploymentOnAppAsync,
} from '../../worker/deployment';
import { formatWorkerDeploymentJson, formatWorkerDeploymentTable } from '../../worker/utils/logs';
interface DeployAliasFlags {
nonInteractive: boolean;
json: boolean;
aliasName?: string | null;
deploymentIdentifier?: string | null;
isProduction: boolean;
}
interface RawDeployAliasFlags {
'non-interactive': boolean;
json: boolean;
alias?: string;
id?: string;
prod: boolean;
}
export default class WorkerAlias extends EasCommand {
static override description = 'Assign deployment aliases';
static override aliases = ['deploy:alias', 'deploy:promote'];
// TODO(@kitten): Keep command hidden until worker deployments are live
static override hidden = true;
static override state = 'beta';
static override flags = {
prod: Flags.boolean({
aliases: ['production'],
description: 'Promote an existing deployment to production.',
default: false,
}),
alias: Flags.string({
description: 'Custom alias to assign to the existing deployment.',
helpValue: 'name',
required: false,
}),
id: Flags.string({
description: 'Unique identifier of an existing deployment.',
helpValue: 'xyz123',
required: false,
}),
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
...this.ContextOptions.DynamicProjectConfig,
...this.ContextOptions.ProjectDir,
...this.ContextOptions.LoggedIn,
};
override async runAsync(): Promise<void> {
const { flags: rawFlags } = await this.parse(WorkerAlias);
const flags = this.sanitizeFlags(rawFlags);
if (flags.json) {
enableJsonOutput();
}
Log.warn('EAS Worker Deployments are in beta and subject to breaking changes.');
const {
getDynamicPrivateProjectConfigAsync,
loggedIn: { graphqlClient },
} = await this.getContextAsync(WorkerAlias, {
nonInteractive: true,
});
const { projectId } = await getDynamicPrivateProjectConfigAsync();
const aliasName = await resolveDeploymentAliasAsync(flags);
const deploymentId = await resolveDeploymentIdAsync({
...flags,
graphqlClient,
projectId,
aliasName,
});
let progress: null | Ora = null;
let deploymentAlias: null | Awaited<ReturnType<typeof assignWorkerDeploymentAliasAsync>> = null;
if (aliasName) {
try {
progress = ora(chalk`Assigning alias {bold ${aliasName}} to deployment`).start();
deploymentAlias = await assignWorkerDeploymentAliasAsync({
graphqlClient,
appId: projectId,
deploymentId,
aliasName,
});
progress.text = chalk`Assigned alias {bold ${aliasName}} to deployment`;
} catch (error: any) {
progress?.fail(chalk`Failed to assign {bold ${aliasName}} alias to deployment`);
throw error;
}
}
let deploymentProdAlias: null | Awaited<
ReturnType<typeof assignWorkerDeploymentProductionAsync>
> = null;
if (flags.isProduction) {
try {
progress = (progress ?? ora()).start(chalk`Promoting deployment to {bold production}`);
deploymentProdAlias = await assignWorkerDeploymentProductionAsync({
graphqlClient,
appId: projectId,
deploymentId,
});
progress.text = chalk`Promoted deployment to {bold production}`;
} catch (error: any) {
progress?.fail(chalk`Failed to promote deployment to {bold production}`);
throw error;
}
}
progress?.succeed(
!deploymentAlias
? chalk`Promoted deployment to {bold production}`
: chalk`Promoted deployment to {bold production} with alias {bold ${deploymentAlias.aliasName}}`
);
// Either use the alias, or production deployment information
const deployment = deploymentAlias?.workerDeployment ?? deploymentProdAlias?.workerDeployment;
if (flags.json) {
printJsonOnlyOutput(
formatWorkerDeploymentJson({
projectId,
deployment: deployment!,
aliases: [deploymentAlias].filter(Boolean) as WorkerDeploymentAliasFragment[],
production: deploymentProdAlias,
})
);
return;
}
Log.addNewLineIfNone();
Log.log(`🎉 Your deployment is modified`);
Log.addNewLineIfNone();
Log.log(
formatWorkerDeploymentTable({
projectId,
deployment: deployment!,
aliases: [deploymentAlias].filter(Boolean) as WorkerDeploymentAliasFragment[],
production: deploymentProdAlias,
})
);
}
private sanitizeFlags(flags: RawDeployAliasFlags): DeployAliasFlags {
return {
nonInteractive: flags['non-interactive'],
json: flags['json'],
aliasName: flags.alias?.trim().toLowerCase(),
deploymentIdentifier: flags.id?.trim().toLowerCase(),
isProduction: flags.prod,
};
}
}
async function resolveDeploymentAliasAsync(flags: DeployAliasFlags): Promise<string | null> {
if (flags.aliasName?.trim()) {
return flags.aliasName.trim().toLowerCase();
}
// Skip alias prompt when promoting deployments to prod
if (flags.isProduction) {
return null;
}
if (flags.nonInteractive) {
throw new Error('The `--alias` flag must be set when running in `--non-interactive` mode.');
}
const { alias: aliasName } = await promptAsync({
type: 'text',
name: 'alias',
message: 'Enter the alias to assign to a deployment',
validate: (value: string) => !!value.trim(),
hint: 'The alias name is case insensitive and must be URL safe',
});
return aliasName.trim().toLowerCase();
}
async function resolveDeploymentIdAsync({
graphqlClient,
deploymentIdentifier,
aliasName,
projectId,
nonInteractive,
}: DeployAliasFlags & {
graphqlClient: ExpoGraphqlClient;
projectId: string;
}): Promise<string> {
if (deploymentIdentifier) {
return deploymentIdentifier;
}
if (nonInteractive) {
throw new Error('The `--id` flag must be set when running in `--non-interactive` mode.');
}
const deployment = await selectWorkerDeploymentOnAppAsync({
graphqlClient,
appId: projectId,
selectTitle: chalk`deployment to assign the {underline ${aliasName}} alias`,
});
if (!deployment) {
throw new Error(
'No deployments found for this project, create a new deployment with "eas deploy"'
);
}
return deployment.deploymentIdentifier;
}
+272 -76
View File
@@ -1,14 +1,31 @@
import { format as formatTimeAgo } from '@expo/timeago.js';
import { Flags } from '@oclif/core';
import chalk from 'chalk';
import fs from 'node:fs';
import * as path from 'node:path';
import EasCommand from '../../commandUtils/EasCommand';
import { EASEnvironmentFlag, EasNonInteractiveAndJsonFlags } from '../../commandUtils/flags';
import {
EnvironmentVariableEnvironment,
WorkerDeploymentAliasFragment,
} from '../../graphql/generated';
import Log from '../../log';
import { ora } from '../../ora';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { createProgressTracker } from '../../utils/progress';
import * as WorkerAssets from '../../worker/assets';
import { getSignedDeploymentUrlAsync } from '../../worker/deployment';
import {
assignWorkerDeploymentAliasAsync,
assignWorkerDeploymentProductionAsync,
getSignedDeploymentUrlAsync,
} from '../../worker/deployment';
import { UploadParams, batchUploadAsync, uploadAsync } from '../../worker/upload';
import {
formatWorkerDeploymentJson,
formatWorkerDeploymentTable,
getDeploymentUrlFromFullName,
} from '../../worker/utils/logs';
const isDirectory = (directoryPath: string): Promise<boolean> =>
fs.promises
@@ -16,16 +33,59 @@ const isDirectory = (directoryPath: string): Promise<boolean> =>
.then(stat => stat.isDirectory())
.catch(() => false);
interface DeployFlags {
nonInteractive: boolean;
json: boolean;
isProduction: boolean;
aliasName?: string;
environment?: EnvironmentVariableEnvironment;
deploymentIdentifier?: string;
exportDir: string;
}
interface RawDeployFlags {
'non-interactive': boolean;
environment?: string;
json: boolean;
prod: boolean;
alias?: string;
id?: string;
'export-dir': string;
}
export default class WorkerDeploy extends EasCommand {
static override description = 'deploy an Expo web build';
static override description = 'Deploy your Expo web build';
static override aliases = ['deploy'];
static override usage = [chalk`deploy {dim [options]}`, `deploy --prod`];
// TODO(@kitten): Keep command hidden until worker deployments are live
static override hidden = true;
static override state = 'beta';
static override flags = {
// TODO(@kitten): Allow deployment identifier to be specified
prod: Flags.boolean({
aliases: ['production'],
description: 'Create a new production deployment.',
default: false,
}),
alias: Flags.string({
description: 'Custom alias to assign to the new deployment.',
helpValue: 'name',
}),
id: Flags.string({
description: 'Custom unique identifier for the new deployment.',
helpValue: 'xyz123',
}),
'export-dir': Flags.string({
description: 'Directory where the Expo project was exported.',
helpValue: 'dir',
default: 'dist',
}),
environment: {
...EASEnvironmentFlag.environment,
description: 'Deploy with EAS Environment Variables matching the specified environment.',
},
...EasNonInteractiveAndJsonFlags,
};
static override contextDefinition = {
@@ -35,70 +95,41 @@ export default class WorkerDeploy extends EasCommand {
};
async runAsync(): Promise<void> {
const { flags: rawFlags } = await this.parse(WorkerDeploy);
const flags = this.sanitizeFlags(rawFlags);
if (flags.json) {
enableJsonOutput();
}
Log.warn('EAS Worker Deployments are in beta and subject to breaking changes.');
const {
getDynamicPrivateProjectConfigAsync,
loggedIn: { graphqlClient },
} = await this.getContextAsync(WorkerDeploy, {
nonInteractive: true,
});
projectDir,
} = await this.getContextAsync(WorkerDeploy, flags);
const { projectId, projectDir, exp } = await getDynamicPrivateProjectConfigAsync();
const distPath = path.resolve(projectDir, 'dist');
const projectDist = await resolveExportedProjectAsync(flags, projectDir);
const { projectId } = await getDynamicPrivateProjectConfigAsync();
let distServerPath: string | null;
let distClientPath: string;
if (exp.web?.output === 'static') {
distClientPath = distPath;
distServerPath = null;
if (!(await isDirectory(distClientPath))) {
throw new Error(
`No "dist/" folder found. Prepare your project for deployment with "npx expo export"`
);
}
Log.log('Detected "static" worker deployment');
} else if (exp.web?.output === 'server') {
distClientPath = path.resolve(distPath, 'client');
distServerPath = path.resolve(distPath, 'server');
if (!(await isDirectory(distClientPath))) {
throw new Error(
`No "dist/client/" folder found. Prepare your project for deployment with "npx expo export"`
);
} else if (!(await isDirectory(distServerPath))) {
throw new Error(
`No "dist/server/" folder found. Prepare your project for deployment with "npx expo export"`
);
}
Log.log('Detected "server" worker deployment');
} else {
throw new Error(
`Single-page apps are not supported. Ensure that app.json key "expo.web.output" is set to "server" or "static".`
);
}
logExportedProjectInfo(projectDist);
async function* emitWorkerTarballAsync(
assetMap: WorkerAssets.AssetMap
): AsyncGenerator<WorkerAssets.FileEntry> {
yield ['assets.json', JSON.stringify(assetMap)];
// TODO: Create manifest from user configuration
const manifest = { env: {} };
yield ['manifest.json', JSON.stringify(manifest)];
if (distServerPath) {
const workerFiles = WorkerAssets.listWorkerFilesAsync(distServerPath);
async function* emitWorkerTarballAsync(params: {
assetMap: WorkerAssets.AssetMap;
manifest: WorkerAssets.Manifest;
}): AsyncGenerator<WorkerAssets.FileEntry> {
yield ['assets.json', JSON.stringify(params.assetMap)];
yield ['manifest.json', JSON.stringify(params.manifest)];
if (projectDist.type === 'server' && projectDist.serverPath) {
const workerFiles = WorkerAssets.listWorkerFilesAsync(projectDist.serverPath);
for await (const workerFile of workerFiles) {
yield [`server/${workerFile.normalizedPath}`, workerFile.data];
}
}
}
async function uploadTarballAsync(tarPath: string): Promise<any> {
const uploadUrl = await getSignedDeploymentUrlAsync(graphqlClient, exp, {
appId: projectId,
});
async function uploadTarballAsync(tarPath: string, uploadUrl: string): Promise<any> {
const { response } = await uploadAsync({
url: uploadUrl,
filePath: tarPath,
@@ -110,7 +141,10 @@ export default class WorkerDeploy extends EasCommand {
if (response.status === 413) {
throw new Error(
'Upload failed! (Payload too large)\n' +
`The files in "dist/server/" (at: ${distServerPath}) exceed the maximum file size (10MB gzip).`
`The files in "${path.relative(
projectDir,
projectDist.path
)}" (at: ${projectDir}) exceed the maximum file size (10MB gzip).`
);
} else if (!response.ok) {
throw new Error(`Upload failed! (${response.statusText})`);
@@ -133,7 +167,8 @@ export default class WorkerDeploy extends EasCommand {
// TODO(@kitten): Batch and upload multiple files in parallel
const uploadParams: UploadParams[] = [];
for await (const asset of WorkerAssets.listAssetMapFilesAsync(distClientPath, assetMap)) {
const assetPath = projectDist.type === 'server' ? projectDist.clientPath : projectDist.path;
for await (const asset of WorkerAssets.listAssetMapFilesAsync(assetPath, assetMap)) {
const uploadURL = uploads[asset.normalizedPath];
if (uploadURL) {
uploadParams.push({ url: uploadURL, filePath: asset.path });
@@ -154,9 +189,9 @@ export default class WorkerDeploy extends EasCommand {
const details = chalk.dim(
`(${progress.pending} Pending, ${progress.transferred} Completed, ${progress.total} Total)`
);
return `Uploading client assets: ${percent.padStart(3)}% ${details}`;
return `Uploading assets: ${percent.padStart(3)}% ${details}`;
},
completedMessage: 'Uploaded assets for serverless deployment',
completedMessage: 'Uploaded assets',
});
try {
@@ -176,37 +211,198 @@ export default class WorkerDeploy extends EasCommand {
updateProgress({ isComplete: true });
}
let progress = ora('Preparing worker upload');
let assetMap: WorkerAssets.AssetMap;
let tarPath: string;
try {
assetMap = await WorkerAssets.createAssetMapAsync(distClientPath);
tarPath = await WorkerAssets.packFilesIterableAsync(emitWorkerTarballAsync(assetMap));
} catch (error: any) {
progress.fail('Failed to prepare worker upload');
throw error;
}
progress.succeed('Prepared worker upload');
progress = ora('Creating worker deployment');
let deployResult: any;
let progress = ora('Preparing project').start();
try {
deployResult = await uploadTarballAsync(tarPath);
const manifest = await WorkerAssets.createManifestAsync(
{
environment: flags.environment,
projectDir,
projectId,
},
graphqlClient
);
assetMap = await WorkerAssets.createAssetMapAsync(
projectDist.type === 'server' ? projectDist.clientPath : projectDist.path
);
tarPath = await WorkerAssets.packFilesIterableAsync(
emitWorkerTarballAsync({
assetMap,
manifest,
})
);
const uploadUrl = await getSignedDeploymentUrlAsync(graphqlClient, {
appId: projectId,
deploymentIdentifier: flags.deploymentIdentifier,
// NOTE(cedric): this function might ask the user for a dev-domain name,
// when that happens, no ora spinner should be running.
onSetupDevDomain: () => progress.stop(),
nonInteractive: flags.nonInteractive,
});
progress.start('Creating deployment');
deployResult = await uploadTarballAsync(tarPath, uploadUrl);
progress.succeed('Created deployment');
} catch (error: any) {
progress.fail('Failed to create worker deployment');
progress.fail('Failed to create deployment');
throw error;
}
progress.succeed('Created worker deployment');
await uploadAssetsAsync(assetMap, deployResult.uploads);
const baseDomain = process.env.EXPO_STAGING ? 'staging.expo' : 'expo';
const deploymentURL = `https://${deployResult.fullName}.${baseDomain}.app`;
const deploymentsUrl = `https://${baseDomain}.dev/accounts/${exp.owner}/projects/${deployResult.name}/serverless/deployments`;
let deploymentAlias: null | Awaited<ReturnType<typeof assignWorkerDeploymentAliasAsync>> = null;
if (flags.aliasName) {
progress = ora(chalk`Assigning alias {bold ${flags.aliasName}} to deployment`).start();
try {
deploymentAlias = await assignWorkerDeploymentAliasAsync({
graphqlClient,
appId: projectId,
deploymentId: deployResult.id,
aliasName: flags.aliasName,
});
// Only stop the spinner when not promoting to production
if (!flags.isProduction) {
progress.succeed(chalk`Assigned alias {bold ${flags.aliasName}} to deployment`);
}
} catch (error: any) {
progress.fail(chalk`Failed to assign {bold ${flags.aliasName}} alias to deployment`);
throw error;
}
}
let deploymentProdAlias: null | Awaited<
ReturnType<typeof assignWorkerDeploymentProductionAsync>
> = null;
if (flags.isProduction) {
try {
if (!flags.aliasName) {
progress = ora(chalk`Promoting deployment to {bold production}`).start();
} else {
progress.text = chalk`Promoting deployment to {bold production}`;
}
deploymentProdAlias = await assignWorkerDeploymentProductionAsync({
graphqlClient,
appId: projectId,
deploymentId: deployResult.id,
});
progress.succeed(
!flags.aliasName
? chalk`Promoted deployment to {bold production}`
: chalk`Promoted deployment to {bold production} with alias {bold ${flags.aliasName}}`
);
} catch (error: any) {
progress.fail('Failed to promote deployment to production');
throw error;
}
}
if (flags.json) {
printJsonOnlyOutput(
formatWorkerDeploymentJson({
projectId,
deployment: {
deploymentIdentifier: deployResult.id,
url: getDeploymentUrlFromFullName(deployResult.fullName),
},
aliases: [deploymentAlias].filter(Boolean) as WorkerDeploymentAliasFragment[],
production: deploymentProdAlias,
})
);
return;
}
Log.addNewLineIfNone();
Log.log(`🎉 Your worker deployment is ready: ${deploymentURL}`);
Log.log(`🎉 Your deployment is ready`);
Log.addNewLineIfNone();
Log.log(`🔗 Manage on EAS: ${deploymentsUrl}`);
Log.log(
formatWorkerDeploymentTable({
projectId,
deployment: {
deploymentIdentifier: deployResult.id,
url: getDeploymentUrlFromFullName(deployResult.fullName),
},
aliases: [deploymentAlias].filter(Boolean) as WorkerDeploymentAliasFragment[],
production: deploymentProdAlias,
})
);
if (!deploymentProdAlias) {
Log.addNewLineIfNone();
Log.log('🚀 When you are ready to deploy to production:');
Log.log(chalk` $ eas deploy {bold --prod}`);
}
}
private sanitizeFlags(flags: RawDeployFlags): DeployFlags {
return {
nonInteractive: flags['non-interactive'],
json: flags['json'],
isProduction: !!flags.prod,
aliasName: flags.alias?.trim().toLowerCase(),
deploymentIdentifier: flags.id?.trim(),
exportDir: flags['export-dir'],
};
}
}
async function resolveExportedProjectAsync(
flags: DeployFlags,
projectDir: string
): Promise<
| { type: 'static'; modifiedAt: Date | null; path: string }
| {
type: 'server';
modifiedAt: Date | null;
path: string;
serverPath: string;
clientPath: string;
}
> {
const exportPath = path.join(projectDir, flags.exportDir);
const serverPath = path.join(exportPath, 'server');
const clientPath = path.join(exportPath, 'client');
const [hasServerPath, hasClientPath, exportStat] = await Promise.all([
isDirectory(serverPath),
isDirectory(clientPath),
fs.promises.stat(exportPath).catch(() => null),
]);
if (!exportStat?.isDirectory()) {
throw new Error(
`No "${flags.exportDir}/" folder found. Prepare your project for deployment with "npx expo export --platform web"`
);
}
if (hasServerPath && hasClientPath) {
return {
type: 'server',
path: exportPath,
modifiedAt: exportStat.mtime,
serverPath,
clientPath,
};
}
return { type: 'static', path: exportPath, modifiedAt: exportStat.mtime };
}
function logExportedProjectInfo(
project: Awaited<ReturnType<typeof resolveExportedProjectAsync>>
): void {
let modifiedAgo = '';
// Only show the timestamp for exports older than 1 minute
if (project.modifiedAt && Date.now() - project.modifiedAt.getTime() > 60_000) {
modifiedAgo = ` - exported ${formatTimeAgo(project.modifiedAt)}`;
}
Log.log(chalk`{dim > Project export: ${project.type}${modifiedAgo}}`);
}
+507 -190
View File
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,50 @@
import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../client';
import {
AppStoreConnectApiKeyByIdQuery,
AppStoreConnectApiKeyByIdQueryVariables,
} from '../generated';
export const AppStoreConnectApiKeyQuery = {
async getByIdAsync(
graphqlClient: ExpoGraphqlClient,
ascApiKeyId: string
): Promise<{
issuerIdentifier: string;
keyIdentifier: string;
keyP8: string;
}> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<AppStoreConnectApiKeyByIdQuery, AppStoreConnectApiKeyByIdQueryVariables>(
gql`
query AppStoreConnectApiKeyById($ascApiKeyId: ID!) {
appStoreConnectApiKey {
byId(id: $ascApiKeyId) {
id
issuerIdentifier
keyIdentifier
keyP8
}
}
}
`,
{ ascApiKeyId },
{
additionalTypenames: ['AppStoreConnectApiKey'],
}
)
.toPromise()
);
const key = data.appStoreConnectApiKey.byId;
return {
issuerIdentifier: key.issuerIdentifier,
keyIdentifier: key.keyIdentifier,
keyP8: key.keyP8,
};
},
};
@@ -0,0 +1,38 @@
import gql from 'graphql-tag';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../client';
import {
GoogleServiceAccountKeyByIdQuery,
GoogleServiceAccountKeyByIdQueryVariables,
} from '../generated';
export const GoogleServiceAccountKeyQuery = {
async getByIdAsync(
graphqlClient: ExpoGraphqlClient,
ascApiKeyId: string
): Promise<{ keyJson: string }> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<GoogleServiceAccountKeyByIdQuery, GoogleServiceAccountKeyByIdQueryVariables>(
gql`
query GoogleServiceAccountKeyById($ascApiKeyId: ID!) {
googleServiceAccountKey {
byId(id: $ascApiKeyId) {
id
keyJson
}
}
}
`,
{ ascApiKeyId },
{
additionalTypenames: ['GoogleServiceAccountKey'],
}
)
.toPromise()
);
return { keyJson: data.googleServiceAccountKey.byId.keyJson };
},
};
@@ -46,17 +46,5 @@ describe('expoConfig', () => {
await createOrModifyExpoConfigAsync('/app', { owner: 'ccheever' });
expect(modifyConfigAsync).toHaveBeenCalledWith('/app', { owner: 'ccheever' });
});
it('adds expo key if app.json is missing it', async () => {
jest.mocked(getConfigFilePaths).mockReturnValue({
staticConfigPath: '/app/app.json',
dynamicConfigPath: null,
});
jest.mocked(JsonFile.readAsync).mockResolvedValue({ name: 'App' });
await createOrModifyExpoConfigAsync('/app', { owner: 'ccheever' });
expect(JsonFile.writeAsync).toHaveBeenCalledWith('/app/app.json', { name: 'App', expo: {} });
expect(modifyConfigAsync).toHaveBeenCalledWith('/app', { owner: 'ccheever' });
});
});
});
@@ -65,9 +65,6 @@ export async function getApplicationIdFromBareAsync(
projectDir: string,
gradleContext?: GradleBuildContext
): Promise<string> {
if (env.overrideAndroidApplicationId) {
return env.overrideAndroidApplicationId;
}
const errorMessage = 'Could not read applicationId from Android project.';
if (gradleContext) {
@@ -109,6 +106,10 @@ export async function getApplicationIdAsync(
vcsClient: Client,
gradleContext?: GradleBuildContext
): Promise<string> {
if (env.overrideAndroidApplicationId) {
return env.overrideAndroidApplicationId;
}
const workflow = await resolveWorkflowAsync(projectDir, Platform.ANDROID, vcsClient);
if (workflow === Workflow.GENERIC) {
warnIfAndroidPackageDefinedInAppConfigForBareWorkflowProject(projectDir, exp);
+2 -17
View File
@@ -1,9 +1,7 @@
import { ExpoConfig, getConfig, getConfigFilePaths, modifyConfigAsync } from '@expo/config';
import { Env } from '@expo/eas-build-job';
import JsonFile from '@expo/json-file';
import fs from 'fs-extra';
import Joi from 'joi';
import nullthrows from 'nullthrows';
import path from 'path';
export type PublicExpoConfig = Omit<
@@ -18,6 +16,7 @@ export type PublicExpoConfig = Omit<
export interface ExpoConfigOptions {
env?: Env;
skipSDKVersionRequirement?: boolean;
skipPlugins?: boolean;
}
interface ExpoConfigOptionsInternal extends ExpoConfigOptions {
@@ -30,7 +29,6 @@ export async function createOrModifyExpoConfigAsync(
readOptions?: { skipSDKVersionRequirement?: boolean }
): ReturnType<typeof modifyConfigAsync> {
ensureExpoConfigExists(projectDir);
await ensureStaticExpoConfigIsValidAsync(projectDir);
if (readOptions) {
return await modifyConfigAsync(projectDir, exp, readOptions);
@@ -52,6 +50,7 @@ function getExpoConfigInternal(
const { exp } = getConfig(projectDir, {
skipSDKVersionRequirement: true,
...(opts.isPublicConfig ? { isPublicConfig: true } : {}),
...(opts.skipPlugins ? { skipPlugins: true } : {}),
});
const { error } = MinimalAppConfigSchema.validate(exp, {
@@ -93,20 +92,6 @@ export function ensureExpoConfigExists(projectDir: string): void {
}
}
async function ensureStaticExpoConfigIsValidAsync(projectDir: string): Promise<void> {
if (isUsingStaticExpoConfig(projectDir)) {
const staticConfigPath = nullthrows(getConfigFilePaths(projectDir).staticConfigPath);
const staticConfig = await JsonFile.readAsync(staticConfigPath);
// Add the "expo" key if it doesn't exist on app.json yet, such as in
// projects initialized with RNC CLI
if (!staticConfig?.expo) {
staticConfig.expo = {};
await JsonFile.writeAsync(staticConfigPath, staticConfig);
}
}
}
export function isUsingStaticExpoConfig(projectDir: string): boolean {
const paths = getConfigFilePaths(projectDir);
return !!(paths.staticConfigPath?.endsWith('app.json') && !paths.dynamicConfigPath);
@@ -59,14 +59,15 @@ export async function getBundleIdentifierAsync(
vcsClient: Client,
xcodeContext?: { targetName?: string; buildConfiguration?: string }
): Promise<string> {
if (env.overrideIosBundleIdentifier) {
return env.overrideIosBundleIdentifier;
}
const workflow = await resolveWorkflowAsync(projectDir, Platform.IOS, vcsClient);
if (workflow === Workflow.GENERIC) {
warnIfBundleIdentifierDefinedInAppConfigForBareWorkflowProject(projectDir, exp);
if (env.overrideIosBundleIdentifier) {
return env.overrideIosBundleIdentifier;
}
const xcodeProject = IOSConfig.XcodeUtils.getPbxproj(projectDir);
const isMultiScheme = IOSConfig.BuildScheme.getSchemesFromXcodeproj(projectDir).length > 1;
const isMultiTarget =
+17 -1
View File
@@ -7,7 +7,7 @@ import * as uuid from 'uuid';
import { getRecentBuildsForSubmissionAsync } from './utils/builds';
import { isExistingFileAsync, uploadAppArchiveAsync } from './utils/files';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { BuildFragment } from '../graphql/generated';
import { BuildFragment, BuildStatus } from '../graphql/generated';
import { BuildQuery } from '../graphql/queries/BuildQuery';
import { toAppPlatform } from '../graphql/types/AppPlatform';
import Log, { learnMore } from '../log';
@@ -89,6 +89,16 @@ export type ArchiveSource =
export type ResolvedArchiveSource = ArchiveUrlSource | ArchiveGCSSource | ArchiveBuildSource;
const buildStatusMapping: Record<BuildStatus, string> = {
[BuildStatus.New]: 'new',
[BuildStatus.InQueue]: 'in queue',
[BuildStatus.InProgress]: 'in progress',
[BuildStatus.Finished]: 'finished',
[BuildStatus.Errored]: 'errored',
[BuildStatus.PendingCancel]: 'canceled',
[BuildStatus.Canceled]: 'canceled',
};
export async function getArchiveAsync(
ctx: ArchiveResolverContext,
source: ArchiveSource
@@ -344,6 +354,7 @@ function formatBuildChoice(build: BuildFragment): prompts.Choice {
gitCommitMessage,
channel,
message,
status,
} = build;
const buildDate = new Date(updatedAt);
@@ -368,6 +379,7 @@ function formatBuildChoice(build: BuildFragment): prompts.Choice {
? chalk.bold(message.length > 200 ? `${message.slice(0, 200)}...` : message)
: null,
},
{ name: 'Status', value: buildStatusMapping[status] },
];
const filteredDescriptionArray: string[] = descriptionItems
@@ -385,6 +397,10 @@ function formatBuildChoice(build: BuildFragment): prompts.Choice {
async function handlePromptSourceAsync(
ctx: ArchiveResolverContext
): Promise<ResolvedArchiveSource> {
if (ctx.nonInteractive) {
throw new Error('Please run this command with appropriate input.');
}
const { sourceType: sourceTypeRaw } = await promptAsync({
name: 'sourceType',
type: 'select',
+6 -2
View File
@@ -65,9 +65,13 @@ export default abstract class BaseSubmitter<
return resolvedSourceOptions;
}
public async submitAsync(): Promise<SubmissionFragment> {
public async getSubmissionInputAsync(): Promise<SubmissionInput<P>> {
const resolvedSourceOptions = await this.getSourceOptionsAsync();
const input = await this.createSubmissionInputAsync(resolvedSourceOptions);
return await this.createSubmissionInputAsync(resolvedSourceOptions);
}
public async submitAsync(): Promise<SubmissionFragment> {
const input = await this.getSubmissionInputAsync();
return await this.createSubmissionWithAnalyticsAsync(input);
}
@@ -7,6 +7,7 @@ import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/creat
import {
AppPlatform,
BuildFragment,
BuildStatus,
SubmissionArchiveSourceType,
UploadSessionType,
} from '../../graphql/generated';
@@ -41,6 +42,17 @@ const MOCK_BUILD_FRAGMENT: Partial<BuildFragment> = {
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
status: BuildStatus.Finished,
};
const MOCK_IN_PROGRESS_BUILD_FRAGMENT: Partial<BuildFragment> = {
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
status: BuildStatus.InProgress,
};
const SOURCE_STUB_INPUT = {
@@ -246,7 +258,7 @@ describe(getArchiveAsync, () => {
expect(archive.sourceType).toBe(ArchiveSourceType.url);
});
it('handles build-list-select source', async () => {
it('handles build-list-select source for finished builds', async () => {
const projectId = uuidv4();
jest
.mocked(getRecentBuildsForSubmissionAsync)
@@ -269,6 +281,57 @@ describe(getArchiveAsync, () => {
expect(archive.sourceType).toBe(ArchiveSourceType.build);
});
it('handles build-list-select source for in-progress builds', async () => {
const projectId = uuidv4();
jest
.mocked(getRecentBuildsForSubmissionAsync)
.mockResolvedValueOnce([MOCK_IN_PROGRESS_BUILD_FRAGMENT as BuildFragment]);
jest
.mocked(promptAsync)
.mockResolvedValueOnce({ selectedBuild: MOCK_IN_PROGRESS_BUILD_FRAGMENT });
const archive = await getArchiveAsync(
{ ...SOURCE_STUB_INPUT, graphqlClient, projectId },
{
sourceType: ArchiveSourceType.buildList,
}
);
expect(getRecentBuildsForSubmissionAsync).toBeCalledWith(
graphqlClient,
toAppPlatform(SOURCE_STUB_INPUT.platform),
projectId,
{ limit: BUILD_LIST_ITEM_COUNT }
);
expect(archive.sourceType).toBe(ArchiveSourceType.build);
});
it('handles build-list-select source for both finished and in-progress builds', async () => {
const projectId = uuidv4();
jest
.mocked(getRecentBuildsForSubmissionAsync)
.mockResolvedValueOnce([
MOCK_IN_PROGRESS_BUILD_FRAGMENT as BuildFragment,
MOCK_BUILD_FRAGMENT as BuildFragment,
]);
jest.mocked(promptAsync).mockResolvedValueOnce({ selectedBuild: MOCK_BUILD_FRAGMENT });
const archive = await getArchiveAsync(
{ ...SOURCE_STUB_INPUT, graphqlClient, projectId },
{
sourceType: ArchiveSourceType.buildList,
}
);
expect(getRecentBuildsForSubmissionAsync).toBeCalledWith(
graphqlClient,
toAppPlatform(SOURCE_STUB_INPUT.platform),
projectId,
{ limit: BUILD_LIST_ITEM_COUNT }
);
expect(archive.sourceType).toBe(ArchiveSourceType.build);
});
it('prompts again if all builds have expired', async () => {
jest.mocked(getRecentBuildsForSubmissionAsync).mockResolvedValueOnce([
{
@@ -4,11 +4,7 @@ import { Result, result } from '@expo/results';
import AndroidSubmitter, { AndroidSubmissionOptions } from './AndroidSubmitter';
import { ServiceAccountSource, ServiceAccountSourceType } from './ServiceAccountSource';
import {
SubmissionAndroidReleaseStatus,
SubmissionAndroidTrack,
SubmissionFragment,
} from '../../graphql/generated';
import { SubmissionAndroidReleaseStatus, SubmissionAndroidTrack } from '../../graphql/generated';
import Log from '../../log';
import {
AmbiguousApplicationIdError,
@@ -22,7 +18,7 @@ import { SubmissionContext } from '../context';
export default class AndroidSubmitCommand {
constructor(private ctx: SubmissionContext<Platform.ANDROID>) {}
async runAsync(): Promise<SubmissionFragment> {
async runAsync(): Promise<AndroidSubmitter> {
Log.addNewLineIfNone();
const archiveSource = this.resolveArchiveSource();
if (!archiveSource.ok) {
@@ -48,7 +44,7 @@ export default class AndroidSubmitCommand {
}
const submissionOptions = await this.getAndroidSubmissionOptionsAsync(archiveSourceValue);
const submitter = new AndroidSubmitter(this.ctx, submissionOptions, archive);
return await submitter.submitAsync();
return submitter;
}
private async getAndroidSubmissionOptionsAsync(
@@ -132,7 +132,9 @@ describe(AndroidSubmitCommand, () => {
vcsClient,
});
const command = new AndroidSubmitCommand(ctx);
await expect(command.runAsync()).rejects.toThrowError(
await expect(
command.runAsync().then(submitter => submitter.submitAsync())
).rejects.toThrowError(
'Google Service Account Keys cannot be set up in --non-interactive mode.'
);
});
@@ -170,7 +172,8 @@ describe(AndroidSubmitCommand, () => {
});
const command = new AndroidSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createAndroidSubmissionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
@@ -216,7 +219,8 @@ describe(AndroidSubmitCommand, () => {
});
const command = new AndroidSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createAndroidSubmissionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
@@ -264,7 +268,8 @@ describe(AndroidSubmitCommand, () => {
vcsClient,
});
const command = new AndroidSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createAndroidSubmissionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
@@ -329,7 +334,8 @@ describe(AndroidSubmitCommand, () => {
});
const command = new AndroidSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createAndroidSubmissionAsync).toHaveBeenCalledWith(
graphqlClient,
@@ -391,7 +397,8 @@ describe(AndroidSubmitCommand, () => {
});
const command = new AndroidSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createAndroidSubmissionAsync).toHaveBeenCalledWith(
graphqlClient,
@@ -461,7 +468,8 @@ describe(AndroidSubmitCommand, () => {
});
const command = new AndroidSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createAndroidSubmissionAsync).toHaveBeenCalledWith(
graphqlClient,
@@ -12,7 +12,6 @@ import {
import { AscApiKeySource, AscApiKeySourceType } from './AscApiKeySource';
import IosSubmitter, { IosSubmissionOptions } from './IosSubmitter';
import { MissingCredentialsError } from '../../credentials/errors';
import { SubmissionFragment } from '../../graphql/generated';
import Log, { learnMore } from '../../log';
import { ArchiveSource, ArchiveSourceType, getArchiveAsync } from '../ArchiveSource';
import { refreshContextSubmitProfileAsync, resolveArchiveSource } from '../commons';
@@ -21,7 +20,7 @@ import { SubmissionContext } from '../context';
export default class IosSubmitCommand {
constructor(private ctx: SubmissionContext<Platform.IOS>) {}
async runAsync(): Promise<SubmissionFragment> {
async runAsync(): Promise<IosSubmitter> {
Log.addNewLineIfNone();
const archiveSource = this.resolveArchiveSource();
if (!archiveSource.ok) {
@@ -47,7 +46,7 @@ export default class IosSubmitCommand {
}
const options = await this.resolveSubmissionOptionsAsync(archiveSourceValue);
const submitter = new IosSubmitter(this.ctx, options, archive);
return await submitter.submitAsync();
return submitter;
}
private async resolveSubmissionOptionsAsync(
@@ -109,7 +109,7 @@ describe(IosSubmitCommand, () => {
vcsClient,
});
const command = new IosSubmitCommand(ctx);
await expect(command.runAsync()).rejects.toThrow(
await expect(command.runAsync().then(submitter => submitter.submitAsync())).rejects.toThrow(
'EXPO_APPLE_APP_SPECIFIC_PASSWORD must be in the format xxxx-xxxx-xxxx-xxxx, where x is a lowercase letter.'
);
@@ -144,7 +144,9 @@ describe(IosSubmitCommand, () => {
vcsClient,
});
const command = new IosSubmitCommand(ctx);
await expect(command.runAsync()).rejects.toThrowError();
await expect(
command.runAsync().then(submitter => submitter.submitAsync())
).rejects.toThrowError();
});
});
@@ -180,7 +182,8 @@ describe(IosSubmitCommand, () => {
vcsClient,
});
const command = new IosSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createIosSubmissionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
@@ -245,7 +248,8 @@ describe(IosSubmitCommand, () => {
vcsClient,
});
const command = new IosSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createIosSubmissionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
@@ -304,7 +308,8 @@ describe(IosSubmitCommand, () => {
vcsClient,
});
const command = new IosSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createIosSubmissionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
@@ -369,7 +374,8 @@ describe(IosSubmitCommand, () => {
specifiedProfile: 'specificProfile',
});
const command = new IosSubmitCommand(ctx);
await command.runAsync();
const submitter = await command.runAsync();
await submitter.submitAsync();
expect(SubmissionMutation.createIosSubmissionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: projectId,
+2 -1
View File
@@ -23,7 +23,8 @@ export async function submitAsync<T extends Platform>(
ctx.platform === Platform.ANDROID
? new AndroidSubmitCommand(ctx as SubmissionContext<Platform.ANDROID>)
: new IosSubmitCommand(ctx as SubmissionContext<Platform.IOS>);
return await command.runAsync();
const submitter = await command.runAsync();
return await submitter.submitAsync();
},
{
attemptEvent: SubmissionEvent.SUBMIT_COMMAND_ATTEMPT,
@@ -0,0 +1,226 @@
import { v4 as uuidv4 } from 'uuid';
import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
import {
AppPlatform,
BuildFragment,
BuildStatus,
SubmissionArchiveSourceType,
} from '../../../graphql/generated';
import { BuildQuery } from '../../../graphql/queries/BuildQuery';
import { getRecentBuildsForSubmissionAsync } from '../builds';
jest.mock('../../../graphql/queries/BuildQuery', () => ({
BuildQuery: {
viewBuildsOnAppAsync: jest.fn(),
},
}));
const ARCHIVE_SOURCE = {
type: SubmissionArchiveSourceType.Url,
url: 'https://url.to/archive.tar.gz',
};
const MOCK_BUILD_FRAGMENTS: Partial<BuildFragment>[] = Array(5).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.Finished,
}));
const MOCK_IN_PROGRESS_BUILD_FRAGMENTS: Partial<BuildFragment>[] = Array(2).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.InProgress,
}));
const MOCK_IN_QUEUE_BUILD_FRAGMENTS = Array(2).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.InQueue,
}));
const MOCK_NEW_BUILD_FRAGMENTS = Array(1).map(() => ({
id: uuidv4(),
artifacts: {
buildUrl: ARCHIVE_SOURCE.url,
},
appVersion: '1.2.3',
platform: AppPlatform.Android,
updatedAt: Date.now(),
createdAt: Date.now(),
status: BuildStatus.New,
}));
describe(getRecentBuildsForSubmissionAsync, () => {
let graphqlClient: ExpoGraphqlClient;
beforeEach(() => {
graphqlClient = {} as any as ExpoGraphqlClient;
});
it('returns finished builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce(
MOCK_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_BUILD_FRAGMENTS.length)
) as BuildFragment[]
);
const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);
expect(result).toMatchObject(
MOCK_BUILD_FRAGMENTS.slice(0, Math.min(limit, MOCK_BUILD_FRAGMENTS.length))
);
});
it('returns in-progress builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce(
MOCK_IN_PROGRESS_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_PROGRESS_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce([] as BuildFragment[]);
const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);
expect(result).toMatchObject(
MOCK_IN_PROGRESS_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_PROGRESS_BUILD_FRAGMENTS.length)
)
);
});
it('returns in-queue builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce(
MOCK_IN_QUEUE_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_QUEUE_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[]);
const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);
expect(result).toMatchObject(
MOCK_IN_QUEUE_BUILD_FRAGMENTS.slice(0, Math.min(limit, MOCK_IN_QUEUE_BUILD_FRAGMENTS.length))
);
});
it('returns new builds', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce(
MOCK_NEW_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_NEW_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[])
.mockResolvedValueOnce([] as BuildFragment[]);
const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);
expect(result).toMatchObject(
MOCK_NEW_BUILD_FRAGMENTS.slice(0, Math.min(limit, MOCK_NEW_BUILD_FRAGMENTS.length))
);
});
it('returns up to "limit" newest builds regardless of status', async () => {
const appId = uuidv4();
const limit = 2;
jest
.mocked(BuildQuery.viewBuildsOnAppAsync)
.mockResolvedValueOnce(
MOCK_NEW_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_NEW_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce(
MOCK_IN_QUEUE_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_QUEUE_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce(
MOCK_IN_PROGRESS_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_IN_PROGRESS_BUILD_FRAGMENTS.length)
) as BuildFragment[]
)
.mockResolvedValueOnce(
MOCK_BUILD_FRAGMENTS.slice(
0,
Math.min(limit, MOCK_BUILD_FRAGMENTS.length)
) as BuildFragment[]
);
const result = await getRecentBuildsForSubmissionAsync(
graphqlClient,
AppPlatform.Android,
appId,
{ limit }
);
expect(result).toMatchObject([MOCK_NEW_BUILD_FRAGMENTS[0], MOCK_IN_QUEUE_BUILD_FRAGMENTS[1]]);
});
});
+25 -10
View File
@@ -8,14 +8,29 @@ export async function getRecentBuildsForSubmissionAsync(
appId: string,
{ limit = 1 }: { limit?: number } = {}
): Promise<BuildFragment[]> {
return await BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId,
limit,
offset: 0,
filter: {
platform,
distribution: DistributionType.Store,
status: BuildStatus.Finished,
},
});
const allowedStatuses = [
BuildStatus.New,
BuildStatus.InQueue,
BuildStatus.InProgress,
BuildStatus.Finished,
];
const buildsPromises: Promise<BuildFragment[]>[] = [];
for (const buildStatus of allowedStatuses) {
buildsPromises.push(
BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
appId,
limit,
offset: 0,
filter: {
platform,
distribution: DistributionType.Store,
status: buildStatus,
},
})
);
}
const builds = (await Promise.all(buildsPromises)).reduce((acc, value) => acc.concat(value), []);
builds.sort((buildA, buildB) => (buildA.createdAt > buildB.createdAt ? -1 : 1));
return builds.slice(0, limit);
}
@@ -1,7 +1,14 @@
import { instance, mock } from 'ts-mockito';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
import { App, Runtime, UpdateBranch, UpdateChannel, UpdateInsights } from '../../graphql/generated';
import {
App,
DeploymentResult,
Runtime,
UpdateBranch,
UpdateChannel,
UpdateInsights,
} from '../../graphql/generated';
import { ChannelQuery } from '../../graphql/queries/ChannelQuery';
import { getBranchNameFromChannelNameAsync } from '../getBranchNameFromChannelNameAsync';
@@ -102,6 +109,7 @@ function mockUpdateChannel({
branchMapping:
'{"data":[{"branchId":"f9dc4dbb-663f-4a19-8cf2-a783b484d2db","branchMappingLogic":"true"}],"version":0}',
updateBranches: branchNames ? mockUpdateBranches(branchNames) : [],
isPaused: false,
__typename: 'UpdateChannel',
};
}
@@ -127,6 +135,7 @@ function mockUpdateBranches(branchNames: string[]): UpdateBranch[] {
runtimeVersion: '1.0.0',
runtime: {} as Runtime, // Temporary fix to resolve type errors
insights: {} as UpdateInsights, // Temporary fix to resolve type errors
deployments: {} as DeploymentResult, // Temporary fix to resolve type errors
platform: 'ios',
manifestFragment: '...',
isRollBackToEmbedded: false,
+38
View File
@@ -1,3 +1,4 @@
import { get as getEnv } from '@expo/env';
import { Gzip, GzipOptions } from 'minizlib';
import { HashOptions, createHash, randomBytes } from 'node:crypto';
import fs, { createWriteStream } from 'node:fs';
@@ -6,6 +7,10 @@ import path from 'node:path';
import { pipeline } from 'node:stream/promises';
import { pack } from 'tar-stream';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { EnvironmentVariableEnvironment } from '../graphql/generated';
import { EnvironmentVariablesQuery } from '../graphql/queries/EnvironmentVariablesQuery';
/** Returns whether a file or folder is ignored */
function isIgnoredName(name: string): boolean {
switch (name) {
@@ -87,6 +92,39 @@ async function createAssetMapAsync(
return map;
}
export interface Manifest {
env: Record<string, string | undefined>;
}
interface CreateManifestParams {
projectId: string;
projectDir: string;
environment?: EnvironmentVariableEnvironment;
}
/** Creates a manifest configuration sent up for deployment */
export async function createManifestAsync(
params: CreateManifestParams,
graphqlClient: ExpoGraphqlClient
): Promise<Manifest> {
let env: Record<string, string | undefined>;
if (params.environment) {
env = Object.fromEntries(
(
await EnvironmentVariablesQuery.byAppIdWithSensitiveAsync(graphqlClient, {
appId: params.projectId,
environment: params.environment,
})
).map(variable => [variable.name, variable.value ?? undefined])
);
} else {
// NOTE: This is required for the .env resolution
process.env.NODE_ENV = 'production';
env = getEnv(params.projectDir).env;
}
return { env };
}
interface WorkerFileEntry {
normalizedPath: string;
path: string;
+162 -23
View File
@@ -1,24 +1,32 @@
import { ExpoConfig } from '@expo/config-types';
import { CombinedError as GraphqlError } from '@urql/core';
import chalk from 'chalk';
import { DeploymentsMutation } from './mutations';
import { DeploymentsQuery } from './queries';
import { EXPO_BASE_DOMAIN } from './utils/logs';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { WorkerDeploymentFragment } from '../graphql/generated';
import Log from '../log';
import { promptAsync } from '../prompts';
import { memoize } from '../utils/expodash/memoize';
import { selectPaginatedAsync } from '../utils/relay';
export async function getSignedDeploymentUrlAsync(
graphqlClient: ExpoGraphqlClient,
exp: ExpoConfig,
deploymentVariables: {
options: {
appId: string;
deploymentIdentifier?: string | null;
/** Callback which is invoked when the project is going to setup the dev domain */
onSetupDevDomain?: () => any;
/** If the terminal is running in non interactive mode or not */
nonInteractive?: boolean;
}
): Promise<string> {
try {
return await DeploymentsMutation.createSignedDeploymentUrlAsync(
graphqlClient,
deploymentVariables
);
return await DeploymentsMutation.createSignedDeploymentUrlAsync(graphqlClient, {
appId: options.appId,
deploymentIdentifier: options.deploymentIdentifier,
});
} catch (error: any) {
const isMissingDevDomain = (error as GraphqlError)?.graphQLErrors?.some(e =>
['APP_NO_DEV_DOMAIN_NAME'].includes(e?.extensions?.errorCode as string)
@@ -27,40 +35,111 @@ export async function getSignedDeploymentUrlAsync(
if (!isMissingDevDomain) {
throw error;
}
if (options.nonInteractive) {
throw new Error(
'The project URL needs to be set up, but the terminal is running in non-interactive mode.'
);
}
const suggestedDevDomainName = await DeploymentsQuery.getSuggestedDevDomainByAppIdAsync(
graphqlClient,
{ appId: options.appId }
);
options.onSetupDevDomain?.();
await chooseDevDomainNameAsync({
graphqlClient,
appId: deploymentVariables.appId,
slug: exp.slug,
appId: options.appId,
initial: suggestedDevDomainName,
});
return await DeploymentsMutation.createSignedDeploymentUrlAsync(
graphqlClient,
deploymentVariables
);
return await DeploymentsMutation.createSignedDeploymentUrlAsync(graphqlClient, {
appId: options.appId,
deploymentIdentifier: options.deploymentIdentifier,
});
}
}
type PromptInstance = {
cursorOffset: number;
placeholder: boolean;
rendered: string;
initial: string;
done: boolean;
get value(): string;
set value(input: string);
};
const DEV_DOMAIN_INVALID_START_END_CHARACTERS = /^[^a-z0-9]+|[^a-z0-9-]+$/;
const DEV_DOMAIN_INVALID_REPLACEMENT_HYPHEN = /[^a-z0-9-]+/;
const DEV_DOMAIN_INVALID_MULTIPLE_HYPHENS = /(-{2,})/;
/**
* Format a dev domain name to match whats allowed on the backend.
* This is equal to our `DEV_DOMAIN_NAME_REGEX`, but implemented as a filtering function
* to help users find a valid name while typing.
*/
function formatDevDomainName(name = ''): string {
return name
.toLowerCase()
.replace(DEV_DOMAIN_INVALID_REPLACEMENT_HYPHEN, '-')
.replace(DEV_DOMAIN_INVALID_START_END_CHARACTERS, '')
.replace(DEV_DOMAIN_INVALID_MULTIPLE_HYPHENS, '-')
.trim();
}
async function chooseDevDomainNameAsync({
graphqlClient,
appId,
slug,
initial,
}: {
graphqlClient: ExpoGraphqlClient;
appId: string;
slug: string;
initial: string;
}): Promise<void> {
const validationMessage = 'The project does not have a dev domain name.';
const rootDomain = `.${EXPO_BASE_DOMAIN}.app`;
const memoizedFormatDevDomainName = memoize(formatDevDomainName);
const { name } = await promptAsync({
type: 'text',
name: 'name',
message: 'Choose a dev domain name for your project:',
validate: value => (value && value.length > 3 ? true : validationMessage),
initial: slug,
message: 'Choose a preview URL for your project:',
initial,
validate: (value: string) => {
if (!value) {
return 'You have to choose a preview URL for your project';
}
if (value.length < 3) {
return 'Preview URLs must be at least 3 characters long';
}
if (value.endsWith('-')) {
return 'Preview URLs cannot end with a hyphen (-)';
}
return true;
},
onState(this: PromptInstance, state: { value?: string }) {
const value = memoizedFormatDevDomainName(state.value);
if (value !== state.value) {
this.value = value;
}
},
onRender(this: PromptInstance, kleur) {
this.cursorOffset = -rootDomain.length - 1;
if (this.done) {
// Remove the space for the cursor when the prompt is done
this.rendered = this.value + kleur.dim(`${rootDomain}`);
} else if (this.placeholder) {
this.rendered = kleur.dim(`${this.initial} ${rootDomain}`);
} else {
this.rendered = this.value + kleur.dim(` ${rootDomain}`);
}
},
});
if (!name) {
throw new Error('Prompt failed');
throw new Error('No preview URL provided, aborting deployment.');
}
try {
@@ -70,7 +149,7 @@ async function chooseDevDomainNameAsync({
});
if (!success) {
throw new Error('Failed to assign dev domain name');
throw new Error('Failed to assign preview URL');
}
} catch (error: any) {
const isChosenNameTaken = (error as GraphqlError)?.graphQLErrors?.some(e =>
@@ -78,8 +157,8 @@ async function chooseDevDomainNameAsync({
);
if (isChosenNameTaken) {
Log.error(`The entered dev domain name "${name}" is taken. Choose a different name.`);
await chooseDevDomainNameAsync({ graphqlClient, appId, slug });
Log.error(`The preview URL "${name}" is already taken, choose a different URL.`);
await chooseDevDomainNameAsync({ graphqlClient, appId, initial });
}
if (!isChosenNameTaken) {
@@ -87,3 +166,63 @@ async function chooseDevDomainNameAsync({
}
}
}
export async function assignWorkerDeploymentAliasAsync({
graphqlClient,
appId,
deploymentId,
aliasName,
}: {
graphqlClient: ExpoGraphqlClient;
appId: string;
deploymentId: string;
aliasName: string;
}): ReturnType<typeof DeploymentsMutation.assignAliasAsync> {
return await DeploymentsMutation.assignAliasAsync(graphqlClient, {
appId,
deploymentId,
aliasName,
});
}
export async function assignWorkerDeploymentProductionAsync({
graphqlClient,
appId,
deploymentId,
}: {
graphqlClient: ExpoGraphqlClient;
appId: string;
deploymentId: string;
}): ReturnType<typeof DeploymentsMutation.assignAliasAsync> {
return await DeploymentsMutation.assignAliasAsync(graphqlClient, {
appId,
deploymentId,
aliasName: null, // this will assign the deployment as production
});
}
export async function selectWorkerDeploymentOnAppAsync({
graphqlClient,
appId,
selectTitle,
pageSize,
}: {
graphqlClient: ExpoGraphqlClient;
appId: string;
selectTitle?: string;
pageSize?: number;
}): ReturnType<typeof selectPaginatedAsync<WorkerDeploymentFragment>> {
return await selectPaginatedAsync({
pageSize: pageSize ?? 25,
printedType: selectTitle ?? 'worker deployment',
queryAsync: async queryParams =>
await DeploymentsQuery.getAllDeploymentsPaginatedAsync(graphqlClient, {
...queryParams,
appId,
}),
getTitleAsync: async (deployment: WorkerDeploymentFragment) =>
chalk`${deployment.deploymentIdentifier}{dim - created at: ${new Date(
deployment.createdAt
).toLocaleString()}}`,
});
}
@@ -0,0 +1,11 @@
import gql from 'graphql-tag';
export const WorkerDeploymentFragmentNode = gql`
fragment WorkerDeploymentFragment on WorkerDeployment {
id
url
deploymentIdentifier
deploymentDomain
createdAt
}
`;
@@ -0,0 +1,9 @@
import gql from 'graphql-tag';
export const WorkerDeploymentAliasFragmentNode = gql`
fragment WorkerDeploymentAliasFragment on WorkerDeploymentAlias {
id
aliasName
url
}
`;
+45
View File
@@ -1,9 +1,14 @@
import assert from 'assert';
import { print } from 'graphql';
import gql from 'graphql-tag';
import { WorkerDeploymentFragmentNode } from './fragments/WorkerDeployment';
import { WorkerDeploymentAliasFragmentNode } from './fragments/WorkerDeploymentAlias';
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../graphql/client';
import {
AssignAliasMutation,
AssignAliasMutationVariables,
AssignDevDomainNameMutation,
AssignDevDomainNameMutationVariables,
CreateDeploymentUrlMutation,
@@ -68,4 +73,44 @@ export const DeploymentsMutation = {
return data.devDomainName.assignDevDomainName.name === devDomainNameVariables.name;
},
async assignAliasAsync(
graphqlClient: ExpoGraphqlClient,
aliasVariables: { appId: string; deploymentId: string; aliasName: string | null }
): Promise<AssignAliasMutation['deployments']['assignAlias']> {
const data = await withErrorHandlingAsync(
graphqlClient
.mutation<AssignAliasMutation, AssignAliasMutationVariables>(
gql`
mutation AssignAlias(
$appId: ID!
$deploymentId: ID!
$aliasName: WorkerDeploymentIdentifier
) {
deployments {
assignAlias(
appId: $appId
deploymentIdentifier: $deploymentId
aliasName: $aliasName
) {
id
...WorkerDeploymentAliasFragment
workerDeployment {
id
...WorkerDeploymentFragment
}
}
}
}
${print(WorkerDeploymentFragmentNode)}
${print(WorkerDeploymentAliasFragmentNode)}
`,
aliasVariables
)
.toPromise()
);
return data.deployments.assignAlias;
},
};
+89
View File
@@ -0,0 +1,89 @@
import { print } from 'graphql';
import gql from 'graphql-tag';
import { WorkerDeploymentFragmentNode } from './fragments/WorkerDeployment';
import type { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
import { withErrorHandlingAsync } from '../graphql/client';
import {
type PaginatedWorkerDeploymentsQuery,
type PaginatedWorkerDeploymentsQueryVariables,
SuggestedDevDomainNameQuery,
SuggestedDevDomainNameQueryVariables,
type WorkerDeploymentFragment,
} from '../graphql/generated';
import type { Connection } from '../utils/relay';
export const DeploymentsQuery = {
async getAllDeploymentsPaginatedAsync(
graphqlClient: ExpoGraphqlClient,
{ appId, first, after, last, before }: PaginatedWorkerDeploymentsQueryVariables
): Promise<Connection<WorkerDeploymentFragment>> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<PaginatedWorkerDeploymentsQuery, PaginatedWorkerDeploymentsQueryVariables>(
gql`
query PaginatedWorkerDeployments(
$appId: String!
$first: Int
$after: String
$last: Int
$before: String
) {
app {
byId(appId: $appId) {
id
workerDeployments(first: $first, after: $after, last: $last, before: $before) {
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
edges {
cursor
node {
id
...WorkerDeploymentFragment
}
}
}
}
}
}
${print(WorkerDeploymentFragmentNode)}
`,
{ appId, first, after, last, before },
{ additionalTypenames: ['WorkerDeployment'] }
)
.toPromise()
);
return data.app.byId.workerDeployments;
},
async getSuggestedDevDomainByAppIdAsync(
graphqlClient: ExpoGraphqlClient,
{ appId }: SuggestedDevDomainNameQueryVariables
): Promise<string> {
const data = await withErrorHandlingAsync(
graphqlClient
.query<SuggestedDevDomainNameQuery, SuggestedDevDomainNameQueryVariables>(
gql`
query SuggestedDevDomainName($appId: String!) {
app {
byId(appId: $appId) {
id
suggestedDevDomainName
}
}
}
`,
{ appId },
{ additionalTypenames: ['App'] }
)
.toPromise()
);
return data.app.byId.suggestedDevDomainName;
},
};
+82
View File
@@ -0,0 +1,82 @@
import chalk from 'chalk';
import type {
WorkerDeploymentAliasFragment,
WorkerDeploymentFragment,
} from '../../graphql/generated';
import formatFields, { type FormatFieldsItem } from '../../utils/formatFields';
export const EXPO_BASE_DOMAIN = process.env.EXPO_STAGING ? 'staging.expo' : 'expo';
export function getDeploymentUrlFromFullName(deploymentFullName: string): string {
return `https://${deploymentFullName}.${EXPO_BASE_DOMAIN}.app`;
}
export function getDashboardUrl(projectId: string): string {
return `https://${EXPO_BASE_DOMAIN}.dev/projects/${projectId}/hosting/deployments`;
}
type WorkerDeploymentData = {
/** Used to generate the dashboard URL to `expo.dev` */
projectId: string;
/** The actual deployment information */
deployment: Pick<WorkerDeploymentFragment, 'deploymentIdentifier' | 'url'>;
/** All modified aliases of the deployment, if any */
aliases?: WorkerDeploymentAliasFragment[];
/** The production promoting alias of the deployment, if any */
production?: WorkerDeploymentAliasFragment | null;
};
export function formatWorkerDeploymentTable(data: WorkerDeploymentData): string {
const fields: FormatFieldsItem[] = [
{ label: 'Dashboard', value: getDashboardUrl(data.projectId) },
{ label: 'Deployment URL', value: data.deployment.url },
];
if (data.aliases?.length) {
fields.push({ label: 'Alias URL', value: data.aliases[0].url });
}
if (data.production) {
fields.push({ label: 'Production URL', value: data.production.url });
}
const lastUrlField = fields[fields.length - 1];
lastUrlField.value = chalk.cyan(lastUrlField.value);
return formatFields(fields);
}
type WorkerDeploymentOutput = {
/** The absolute URL to the dashboard on `expo.dev` */
dashboardUrl: string;
/** The deployment information */
deployment: {
identifier: string;
url: string;
aliases?: { id: string; name: string; url: string }[];
production?: { id: string; url: string };
};
};
export function formatWorkerDeploymentJson(data: WorkerDeploymentData): WorkerDeploymentOutput {
return {
dashboardUrl: getDashboardUrl(data.projectId),
deployment: {
identifier: data.deployment.deploymentIdentifier,
url: data.deployment.url,
aliases: !data.aliases?.length
? undefined
: data.aliases.map(alias => ({
id: alias.id,
name: alias.aliasName,
url: alias.url,
})),
production: !data.production
? undefined
: {
id: data.production.id,
url: data.production.url,
},
},
};
}
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@expo/eas-json",
"description": "A library for interacting with eas.json",
"version": "12.0.0",
"version": "12.5.0",
"author": "Expo <[email protected]>",
"bugs": "https://github.com/expo/eas-cli/issues",
"dependencies": {
"@babel/code-frame": "7.23.5",
"@expo/eas-build-job": "1.0.133",
"@expo/eas-build-job": "1.0.136",
"chalk": "4.1.2",
"env-string": "1.0.1",
"fs-extra": "11.2.0",
Generated
+93 -39
View File
@@ -1467,16 +1467,27 @@
slugify "^1.3.4"
sucrase "^3.20.0"
"@expo/[email protected]3":
version "1.0.133"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.133.tgz#342a58c79d523cb1364f6329a9aec07ffb019021"
integrity sha512-dlY3tl1A0GDIGs4WOOuvK08jvR6cfXCmCKTCqZ9ioePscZ3jbeGmadLa1nTvMSe9k8vFdfrHgkKmfs78hI7sbw==
"@expo/[email protected]6":
version "1.0.136"
resolved "https://registry.yarnpkg.com/@expo/eas-build-job/-/eas-build-job-1.0.136.tgz#caae63772f80b6e013731e4f6e00900e7dc05522"
integrity sha512-IEQ3ttjLm5TpgrQ8WNlEBflbNghknLBewExCOyz8T+9busMh6jnFsKZgqLrDVWAG0cMPnQ3ybHVaRqNadP781Q==
dependencies:
"@expo/logger" "1.0.117"
joi "^17.13.1"
semver "^7.6.2"
zod "^3.23.8"
"@expo/env@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@expo/env/-/env-0.3.0.tgz#a66064e5656e0e48197525f47f3398034fdf579e"
integrity sha512-OtB9XVHWaXidLbHvrVDeeXa09yvTl3+IQN884sO6PhIi2/StXfgSH/9zC7IvzrDB8kW3EBJ1PPLuCUJ2hxAT7Q==
dependencies:
chalk "^4.0.0"
debug "^4.3.4"
dotenv "~16.4.5"
dotenv-expand "~11.0.6"
getenv "^1.0.0"
"@expo/fingerprint@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@expo/fingerprint/-/fingerprint-0.6.0.tgz#77366934673d4ecea37284109b4dd67f9e6a7487"
@@ -1672,12 +1683,12 @@
dependencies:
cross-spawn "^7.0.3"
"@expo/[email protected]4":
version "1.0.134"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.134.tgz#c7765a3fb2c0fdec458f168365b1c80cc59632cf"
integrity sha512-9WZUhq1Jggipy+cTcN/pi92I911jaOS4VoEWjuKbhp5G5HU/oHBSfR3ZapEopA9N9xzBv9I/GYzRbPiMHPXaxw==
"@expo/[email protected]6":
version "1.0.136"
resolved "https://registry.yarnpkg.com/@expo/steps/-/steps-1.0.136.tgz#144f51c3c541b5c0b7813abf50c543c80413600a"
integrity sha512-5TQ/zYI9wQc5hAwIOC25K0Pa1wRCpbqkSB6iq5Z6I06gwle+ayaq2YWIy47S1XHBIJ9JSbQ2hsHJBYvJfbKYGQ==
dependencies:
"@expo/eas-build-job" "1.0.133"
"@expo/eas-build-job" "1.0.136"
"@expo/logger" "1.0.117"
"@expo/spawn-async" "^1.7.2"
arg "^5.0.2"
@@ -4876,10 +4887,10 @@ bl@^4.0.3, bl@^4.1.0:
inherits "^2.0.4"
readable-stream "^3.4.0"
[email protected].2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
[email protected].3:
version "1.20.3"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
dependencies:
bytes "3.1.2"
content-type "~1.0.5"
@@ -4889,7 +4900,7 @@ [email protected]:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
qs "6.13.0"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -6188,6 +6199,13 @@ dotenv-expand@~10.0.0:
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-10.0.0.tgz#12605d00fb0af6d0a592e6558585784032e4ef37"
integrity sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==
dotenv-expand@~11.0.6:
version "11.0.6"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-11.0.6.tgz#f2c840fd924d7c77a94eff98f153331d876882d3"
integrity sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==
dependencies:
dotenv "^16.4.4"
[email protected], dotenv@~16.3.1:
version "16.3.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
@@ -6198,10 +6216,15 @@ dotenv@^16.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d"
integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==
dotenv@^16.4.4, dotenv@~16.4.5:
version "16.4.5"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f"
integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==
dset@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a"
integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==
version "3.1.4"
resolved "https://registry.yarnpkg.com/dset/-/dset-3.1.4.tgz#f8eaf5f023f068a036d08cd07dc9ffb7d0065248"
integrity sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==
dtrace-provider@~0.8:
version "0.8.8"
@@ -6274,6 +6297,11 @@ encodeurl@~1.0.2:
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
encodeurl@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
encoding@^0.1.12, encoding@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9"
@@ -6995,37 +7023,37 @@ expect@^29.7.0:
jest-message-util "^29.7.0"
jest-util "^29.7.0"
express@4.19.2:
version "4.19.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
express@4.20.0:
version "4.20.0"
resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48"
integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.2"
body-parser "1.20.3"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.6.0"
cookie-signature "1.0.6"
debug "2.6.9"
depd "2.0.0"
encodeurl "~1.0.2"
encodeurl "~2.0.0"
escape-html "~1.0.3"
etag "~1.8.1"
finalhandler "1.2.0"
fresh "0.5.2"
http-errors "2.0.0"
merge-descriptors "1.0.1"
merge-descriptors "1.0.3"
methods "~1.1.2"
on-finished "2.4.1"
parseurl "~1.3.3"
path-to-regexp "0.1.7"
path-to-regexp "0.1.10"
proxy-addr "~2.0.7"
qs "6.11.0"
range-parser "~1.2.1"
safe-buffer "5.2.1"
send "0.18.0"
serve-static "1.15.0"
send "0.19.0"
serve-static "1.16.0"
setprototypeof "1.2.0"
statuses "2.0.1"
type-is "~1.6.18"
@@ -10350,10 +10378,10 @@ meow@^8.1.2:
type-fest "^0.18.0"
yargs-parser "^20.2.3"
[email protected].1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
[email protected].3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
merge-stream@^2.0.0:
version "2.0.0"
@@ -11785,10 +11813,10 @@ path-scurry@^1.11.1:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
[email protected].7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
[email protected].10:
version "0.1.10"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
path-type@^3.0.0:
version "3.0.0"
@@ -12096,6 +12124,13 @@ [email protected]:
dependencies:
side-channel "^1.0.4"
[email protected]:
version "6.13.0"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
dependencies:
side-channel "^1.0.6"
[email protected]:
version "0.2.0"
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
@@ -12686,6 +12721,25 @@ [email protected]:
range-parser "~1.2.1"
statuses "2.0.1"
[email protected]:
version "0.19.0"
resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
dependencies:
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
encodeurl "~1.0.2"
escape-html "~1.0.3"
etag "~1.8.1"
fresh "0.5.2"
http-errors "2.0.0"
mime "1.6.0"
ms "2.1.3"
on-finished "2.4.1"
range-parser "~1.2.1"
statuses "2.0.1"
sentence-case@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f"
@@ -12695,10 +12749,10 @@ sentence-case@^3.0.4:
tslib "^2.0.3"
upper-case-first "^2.0.2"
[email protected]5.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
[email protected]6.0:
version "1.16.0"
resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
dependencies:
encodeurl "~1.0.2"
escape-html "~1.0.3"