diff --git a/.github/scripts/createHelpRedirects.sh b/.github/scripts/createHelpRedirects.sh index 14ed9de953fc..1425939ff3ec 100755 --- a/.github/scripts/createHelpRedirects.sh +++ b/.github/scripts/createHelpRedirects.sh @@ -27,32 +27,6 @@ function checkCloudflareResult { declare -a ITEMS_TO_ADD while read -r line; do - # Split each line of the file into a source and destination so we can sanity check - # and compare against the current list. - read -r -a LINE_PARTS < <(echo "$line" | tr ',' ' ') - SOURCE_URL=${LINE_PARTS[0]} - DEST_URL=${LINE_PARTS[1]} - - # Make sure the format of the line is as execpted. - if [[ "${#LINE_PARTS[@]}" -gt 2 ]]; then - error "Found a line with more than one comma: $line" - exit 1 - fi - - # Basic sanity checking to make sure that the source and destination are in expected - # subdomains. - if ! [[ $SOURCE_URL =~ ^https://(community|help)\.expensify\.com ]]; then - error "Found source URL that is not a communityDot or helpDot URL: $SOURCE_URL" - exit 1 - fi - - if ! [[ $DEST_URL =~ ^https://(help|use)\.expensify\.com ]]; then - error "Found destination URL that is not a helpDot or useDot URL: $DEST_URL" - exit 1 - fi - - info "Source: $SOURCE_URL and destination: $DEST_URL appear to be formatted correctly." - ITEMS_TO_ADD+=("$line") # This line skips the first line in the csv because the first line is a header row. @@ -83,6 +57,9 @@ done | jq -n '. |= [inputs]') info "Adding redirects for $PUT_JSON" +# Dump $PUT_JSON into a file otherwise the curl request below will fail with too many arguments +echo "$PUT_JSON" > redirects.json + # We use PUT here instead of POST so that we replace the entire list in place. This has many benefits: # 1. We don't have to check if items are already in the list, allowing this script to run faster # 2. We can support deleting redirects this way by simply removing them from the list @@ -93,7 +70,7 @@ info "Adding redirects for $PUT_JSON" PUT_RESULT=$(curl -s --request PUT --url "https://api.cloudflare.com/client/v4/accounts/$ZONE_ID/rules/lists/$LIST_ID/items" \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $CLOUDFLARE_LIST_TOKEN" \ - --data "$PUT_JSON") + --data-binary @redirects.json) checkCloudflareResult "$PUT_RESULT" OPERATION_ID=$(echo "$PUT_RESULT" | jq -r .result.operation_id) diff --git a/.github/scripts/verifyRedirect.sh b/.github/scripts/verifyRedirect.sh index b8942cd5b23d..3d96ba17a799 100755 --- a/.github/scripts/verifyRedirect.sh +++ b/.github/scripts/verifyRedirect.sh @@ -3,7 +3,10 @@ # HelpDot - Verifies that redirects.csv does not have any duplicates # Duplicate sourceURLs break redirection on cloudflare pages +source scripts/shellUtils.sh + declare -r REDIRECTS_FILE="docs/redirects.csv" +declare -a ITEMS_TO_ADD declare -r RED='\033[0;31m' declare -r GREEN='\033[0;32m' @@ -22,5 +25,44 @@ if [[ DETECT_CYCLE_EXIT_CODE -eq 1 ]]; then exit 1 fi +while read -r line; do + # Split each line of the file into a source and destination so we can sanity check + # and compare against the current list. + read -r -a LINE_PARTS < <(echo "$line" | tr ',' ' ') + SOURCE_URL=${LINE_PARTS[0]} + DEST_URL=${LINE_PARTS[1]} + + # Make sure the format of the line is as expected. + if [[ "${#LINE_PARTS[@]}" -gt 2 ]]; then + error "Found a line with more than one comma: $line" + exit 1 + fi + + # Basic sanity checking to make sure that the source and destination are in expected + # subdomains. + if ! [[ $SOURCE_URL =~ ^https://(community|help)\.expensify\.com ]] || [[ $SOURCE_URL =~ \# ]]; then + error "Found source URL that is not a communityDot or helpDot URL, or contains a '#': $SOURCE_URL" + exit 1 +fi + + if ! [[ $DEST_URL =~ ^https://(help|use|integrations)\.expensify\.com|^https://www\.expensify\.org ]]; then + error "Found destination URL that is not a supported URL: $DEST_URL" + exit 1 + fi + + info "Source: $SOURCE_URL and destination: $DEST_URL appear to be formatted correctly." + + ITEMS_TO_ADD+=("$line") + +# This line skips the first line in the csv because the first line is a header row. +done <<< "$(tail +2 $REDIRECTS_FILE)" + +# Sanity check that we should actually be running this and we aren't about to delete +# every single redirect. +if [[ "${#ITEMS_TO_ADD[@]}" -lt 1 ]]; then + error "No items found to add, why are we running?" + exit 1 +fi + echo -e "${GREEN}The redirects.csv is valid!${NC}" exit 0 diff --git a/.gitignore b/.gitignore index e33ec43a01fe..a964cdef2fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,6 @@ web-build/ # Storage location for downloaded app source maps (see scripts/symbolicate-profile.ts) .sourcemaps/ + +# Jeykll +docs/.bundle diff --git a/android/app/build.gradle b/android/app/build.gradle index 6b3e2f51b55d..10b5f74ce660 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -108,8 +108,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1009002300 - versionName "9.0.23-0" + versionCode 1009002401 + versionName "9.0.24-1" // Supported language variants must be declared here to avoid from being removed during the compilation. // This also helps us to not include unnecessary language variants in the APK. resConfigs "en", "es" diff --git a/assets/images/feed.svg b/assets/images/feed.svg new file mode 100644 index 000000000000..2fd03eeadd00 --- /dev/null +++ b/assets/images/feed.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/simple-illustrations/simple-illustration__rules.svg b/assets/images/simple-illustrations/simple-illustration__rules.svg new file mode 100644 index 000000000000..6432f26d9ac6 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__rules.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg b/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg new file mode 100644 index 000000000000..1dcf4fd0b01b --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/config/webpack/webpack.dev.ts b/config/webpack/webpack.dev.ts index 1be311d15c37..80813adc1e3a 100644 --- a/config/webpack/webpack.dev.ts +++ b/config/webpack/webpack.dev.ts @@ -76,8 +76,8 @@ const getConfiguration = (environment: Environment): Promise => snapshot: { // A list of paths webpack trusts would not be modified while webpack is running managedPaths: [ - // Onyx can be modified on the fly, changes to other node_modules would not be reflected live - /([\\/]node_modules[\\/](?!react-native-onyx))/, + // Onyx and react-native-live-markdown can be modified on the fly, changes to other node_modules would not be reflected live + /([\\/]node_modules[\\/](?!react-native-onyx|@expensify\/react-native-live-markdown))/, ], }, }); diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md index 18b8a9ec31f9..d3dcda91ffcc 100644 --- a/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Configure-Quickbooks-Desktop.md @@ -2,87 +2,101 @@ title: Configure Quickbooks Desktop description: Configure Quickbooks Desktop --- -# How to configure export settings for QuickBooks Desktop -To Configure Settings, go to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Connections** and click **Configure**. Click on the Export tab. +Our new QuickBooks Desktop integration allows you to automate the import and export process with Expensify. -## Preferred Exporter -This person is used in QuickBooks Desktop as the export user. They will also receive notifications for errors. +# Step 1: Configure export settings +The following steps will determine how data will be exported from Expensify to QuickBooks Desktop. -## Date -Choose either the report's submitted date, the report's exported date, or the date of the last expense on the report when exporting reports to QuickBooks Desktop. +1. In Expensify, hover over **Settings** and click **Workspaces**. +2. Select the Workspace you want to connect to QuickBooks Desktop. +3. Click the **Connections** tab. +4. Click **Export** under the QuickBooks Desktop connection. +5. Review each of the following export settings: +- **Preferred Exporter**: This person is used in QuickBooks Desktop as the export user. They will receive notifications for errors, as well as prompts to export reports via the Home page of their Expensify account. +- **Date**: You can choose either the report’s submitted date, the report’s exported date, or the date of the last expense on the report when exporting reports to QuickBooks Desktop. +- **Unique reference numbers**: Enable this to allow the use of a unique reference number for each transaction. Disable this to use the same Report ID for all expenses from a certain report. +- **Reimbursable expenses**: Reimbursable options include: + - **Vendor Bill (recommended)**: A single itemized vendor bill for each Expensify report. An A/P account is required to export to a vendor bill. + - **Check**: A single itemized check for each Expensify report. + - **Journal Entry**: A single itemized journal entry for each Expensify report. +- **Non-reimbursable expenses**: Non-reimbursable options include: + - **Vendor Bill**: Each Expensify report results in a single itemized vendor bill. The bill is associated with the “vendor,” which is the individual responsible for creating or submitting the report in Expensify. + - **Credit Card expenses**: Each expense appears as a separate credit card transaction with a post date that matches your credit card statement. If you centrally manage company cards through your domain, you can export expenses from each card to a specific QuickBooks account by clicking Edit Exports next to each user’s card. To display the merchant name in the payee field in QuickBooks Desktop, ensure that a matching Vendor exists in QuickBooks. Expensify searches for an exact match during export. If no match is found, the payee is mapped to a Credit Card Misc. Vendor created by Expensify. + - **Debit Card expenses**: Expenses are exported as individual itemized checks for each Expensify report. The check is written to the “vendor,” which is the person who created or submitted the report in Expensify. -## Use unique reference numbers -Enable this to allow use of a unique reference number for each transaction. Disable this to use the same Report ID for all expenses from a certain report. +# Step 2: Configure coding/import settings -## Reimbursable expenses -* **Vendor Bill (recommended):** A single itemized vendor bill for each Expensify report. An A/P account is required to export to a vendor bill. -* **Check:** A single itemized check for each Expensify report. -* **Journal Entry:** A single itemized journal entry for each Expensify report. - * When exporting as journal entries to an Accounts Payable, this requires a vendor record, not an employee. The vendor record must have the email address of the report creator/submitter. - * If the report creator/submitter also has an employee record, you need to remove the email, because Expensify will try to export to the employee record first for journal entries. +The following steps help you determine how data will be imported from QuickBooks Online to Expensify: -**Note on negative expenses:** In general, you can export negative expenses successfully to QuickBooks Desktop regardless of which export option you choose. The one thing to keep in mind is that if you have Check selected as your export option, the total of the report can not be negative. +1. Click Import under the QuickBooks Online connection. +2. Review each of the following import settings: +- **Chart of Accounts**: The Chart of Accounts is automatically imported from QuickBooks Desktop as categories. This cannot be amended. +- **Classes**: Choose whether to import classes, which will be shown in Expensify as tags for expense-level coding. +- **Customers/Projects**: Choose whether to import customers/projects, which will be shown in Expensify as tags for expense-level coding. +- **Locations**: Choose whether to import locations, which will be shown in Expensify as tags for expense-level coding. -**Note on exporting to Employee Records:** If you want to export reports to your users' Employee Records instead of their Vendor Records, you will need to select Check or Journal Entry for your reimbursable export option. There isn't a way to export as a Vendor Bill to an Employee Record. If you are setting up Expensify users as employees, you will need to activate QuickBooks Desktop Payroll to view the Employee Profile tab where submitter's email addresses need to be entered. +# Step 3: Configure advanced settings +The following steps help you determine the advanced settings for your connection, like auto-sync and employee invitation settings. -## Non-reimbursable expenses -**Credit Card Expenses:** -* Each expense will appear as a separate credit card transaction. -* The posting date will match your credit card statement. -* To display the merchant name in the payee field in QuickBooks Desktop, ensure that a matching Vendor exists in QuickBooks. Expensify searches for an exact match during export. If no match is found, the payee is mapped to a **Credit Card Misc.** Vendor created by Expensify. -* If you're centrally managing company cards through Domain Control, you can export expenses from each card to a specific QuickBooks account (detailed instructions available). - -**Debit Card Expenses:** -* Expenses export as individual itemized checks for each Expensify report. -* The check is written to the "vendor," which is the person who created or submitted the report in Expensify. +1. Click **Advanced** under the QuickBooks Desktop connection. +2. **Enable or disable Auto-Sync**: If enabled, QuickBooks Desktop automatically communicates changes with Expensify to ensure that the data shared between the two systems is up to date. New report approvals/reimbursements will be synced during the next auto-sync period. -**Vendor Bill:** -* Each Expensify report results in a single itemized vendor bill. -* The bill is associated with the "vendor," which is the individual responsible for creating or submitting the report in Expensify. +# FAQ -# How to configure coding for QuickBooks Desktop -To Configure Settings, go to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Connections** and click **Configure**. Click on the Coding tab. +## **How do I manually sync my QuickBooks Desktop if I have Auto-Sync disabled?** -## Categories -Expensify's integration with QuickBooks brings in your Chart of Accounts as Categories in Expensify automatically. Here's how to manage them: -1. After connecting, go to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Categories** to view the accounts imported from QuickBooks Desktop. -2. You can use the enable/disable button to choose which Categories your employees can access. Additionally, you can set specific rules for each Category via the blue settings cog. -3. Expensify offers Auto-Categorization to automatically assign expenses to the appropriate expense categories. -4. If needed, you can edit the names of the imported Categories to simplify expense coding for your employees. Keep in mind that if you make changes to these accounts in QuickBooks Desktop, the category names in Expensify will update to match them during the next sync. -5. _**Important:**_ Each expense must have a category selected to export to QuickBooks Desktop. The selected category must be one imported from QuickBooks Desktop; you cannot manually create categories within Expensify policy settings. +To manually sync your connection: -## Classes -Classes can be imported from QuickBooks as either tags (line-item level) or report fields (header level). +1. In Expensify, hover over **Settings** and select **Workspaces**. +2. Click the Workspace name that is connected to QuickBooks Desktop. +3. Click the **Connections** tab on the left. +4. Click **Sync Now** under QuickBooks Desktop. -## Customers/Projects -You can bring in Customers/Projects from QuickBooks into Expensify in two ways: as tags (at the line-item level) or as report fields (at the header level). If you're utilizing Billable Expenses in Expensify, here's what you need to know: -* Customers/Projects must be enabled if you're using Billable Expenses. -* Expenses marked as "Billable" need to be tagged with a Customer/Project to successfully export them to QuickBooks. +{% include info.html %} +For manual syncing, we recommend completing this process at least once a week and/or after making changes in QuickBooks Desktop that could impact how reports export from Expensify. Changes may include adjustments to your chart of accounts, vendors, employees, customers/jobs, or items. Remember: Both the Web Connector and QuickBooks Desktop need to be running for syncing or exporting to work. +{% include end-info.html %} -## Items -Items can be imported from QuickBooks as categories alongside your expense accounts. +## **Can I sync Expensify and QuickBooks Desktop (QBD) and use the platforms at the same time?** -{% include faq-begin.md %} -## How do I sync my connection? -1. Ensure that both the Expensify Sync Manager and QuickBooks Desktop are running. -2. On the Expensify website, navigate to **Settings** > **Policies** > **Group** > _[Policy Name]_ > **Connections** > **QuickBooks Desktop**, and click **Sync now**. -3. Wait for the syncing process to finish. Typically, this takes about 2-5 minutes, but it might take longer, depending on when you last synced and the size of your QuickBooks company file. The page will refresh automatically once syncing is complete. +When syncing Expensify to QuickBooks Desktop, we recommend waiting until the sync finishes to access either Expensify and/or QuickBooks Desktop, as performance may vary during this process. You cannot open an instance of QuickBooks Desktop while a program is syncing - this may cause QuickBooks Desktop to behave unexpectedly. -We recommend syncing at least once a week or whenever you make changes in QuickBooks Desktop that could impact how your reports export from Expensify. Changes could include adjustments to your Chart of Accounts, Vendors, Employees, Customers/Jobs, or Items. Remember, both the Sync Manager and QuickBooks Desktop need to be running for syncing or exporting to work. +## **What are the different types of accounts that can be imported from Quickbooks Desktop?** -## How do I export reports? -The Sync Manager and QuickBooks Desktop both need to be running in order to sync or export. -* **Exporting an Individual Report:** You can export reports to QuickBooks Desktop one at a time from within an individual report on the Expensify website by clicking the "Export to" button. -* **Exporting Reports in Bulk:** To export multiple reports at a time, select the reports that you'd like to export from the Reports page on the website and click the "Export to" button near the top of the page. +Here is the list of accounts from QuickBooks Desktop and how they are pulled into Expensify: -Once reports have been exported to QuickBooks Desktop successfully, you will see a green QuickBooks icon next to each report on the Reports page. You can check to see when a report was exported in the Comments section of the individual report. +| QBD account type | How it imports to Expensify | +| ------------- | ------------- | +| Accounts payable | Vendor bill or journal entry export options | +| Accounts receivable | Do not import | +| Accumulated adjustment | Do not import | +| Bank | Debit card or check export options | +| Credit card | Credit card export options | +| Equity | Do not import | +| Fixed assets | Categories | +| Income | Do not import | +| Long-term liabilities | Do not import | +| Other assets | Do not import | +| Other current assets | Categories or journal entry export options | +| Other current liabilities | Journal Entry export options if the report creator is set up as an Employee within QuickBooks | +| Other expense | All detail types except Exchange Gain or Loss import as categories; Exchange Gain or Loss does not import | +| Other income | Do not import | -## Can I export negative expenses? -Generally, you can export negative expenses to QuickBooks Desktop successfully, regardless of your option. However, please keep in mind that if you have *Check* selected as your export option, the report's total cannot be negative. +## **Why are exports showing as “Credit Card Misc.”?** + +When exporting as credit or debit card expenses, Expensify checks for an exact vendor match. If none are found, the payee will be mapped to a vendor that Expensify will automatically create and label as Credit Card Misc. or Debit Card Misc. + +If you centrally manage your company cards through domains, you can export expenses from each card to a specific account in QuickBooks: + +1. In Expensify, hover over Settings and click Domains. +2. Select the desired domain. +3. Click the **Company Cards** tab. +4. Click **Export**. + +## **How does multi-currency work with QuickBooks Desktop?** -## How does multi-currency work with QuickBooks Desktop? When using QuickBooks Desktop Multi-Currency, there are some limitations to consider based on your export options: -1. **Vendor Bills and Checks:** The currency of the vendor and the currency of the account must match, but they do not have to be in the home currency. -2. **Credit Card:** If an expense doesn't match an existing vendor in QuickBooks, it exports to the **Credit Card Misc.** vendor created by Expensify. When exporting a report in a currency other than your home currency, the transaction will be created under the vendor's currency with a 1:1 conversion. For example, a transaction in Expensify for $50 CAD will appear in QuickBooks as $50 USD. -3. **Journal Entries:** Multi-currency exports will fail because the account currency must match both the vendor currency and the home currency. + +- **Vendor Bills and Checks**: The currency of the vendor and the currency of the account must match, but they do not have to be in the home currency. +- **Credit Card**: If an expense doesn’t match an existing vendor in QuickBooks, it exports to the Credit Card Misc. vendor created by Expensify. When exporting a report in a currency other than your home currency, the transaction will be created under the vendor’s currency with a 1:1 conversion. For example, a transaction in Expensify for $50 CAD will appear in QuickBooks as $50 USD. +- **Journal Entries**: Multi-currency exports will fail because the account currency must match both the vendor currency and the home currency. diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md index 92e1e4dd841f..50e3e0971869 100644 --- a/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Connect-To-QuickBooks-Desktop.md @@ -4,59 +4,95 @@ description: Connect Expensify to QuickBooks Desktop order: 1 --- # Overview -To connect Expensify to QuickBooks Desktop, use Right Networks as the hosting platform if possible. Right Networks is a cloud-based service that was built specifically for this integration. If you need a Right Networks account, complete [this form](https://info.rightnetworks.com/partner-expensify) to start the process. +QuickBooks Desktop is an accounting package developed by Intuit. It is designed for small and medium-sized businesses to help them manage their financial and accounting tasks. You can connect Expensify to QuickBooks Desktop to make expense management seamless. -**A couple of notes before connecting QuickBooks Desktop to Expensify:** -- Make sure you're logged into QuickBooks Desktop as an admin -- Check that the company file you want to connect Expensify to is the only one open +# Connect to QuickBooks Desktop +{% include info.html %} +To connect QuickBooks Desktop to Expensify, you must log into QuickBooks Desktop as an Admin, and the company file that you want to connect to Expensify must be the only one that is open. +{% include end-info.html %} -# Connect to QuickBooks Desktop +1. In Expensify, hover over **Settings** and click on **Workspaces**. +2. Select the workspace you want to connect to QuickBooks Desktop. +3. Click the **Connections** tab. +4. Click **Connect to QuickBooks Desktop**. +5. Click Copy to copy the link, then paste the link into the computer where QuickBooks Desktop is running. + +![QuickBooks Desktop Setup pop-up link, containing the URL to paste](https://help.expensify.com/assets/images/QBO_desktop_01.png){:width="100%"} + +6. Select the version of QuickBooks Desktop that you currently have. + +![The Web Connnector Pop-up to allow you to select the type of QuickBooks Desktop you have](https://help.expensify.com/assets/images/QBO_desktop_02.png){:width="100%"} + +7. Download the Web Connector and go through the guided installation process. +8. Open the Web Connector. +9. Click on **Add an Application**. + +![The Web Connnector Pop-up where you will need to click on Add an Application](https://help.expensify.com/assets/images/QBO_desktop_03.png){:width="100%"} + +{% include info.html %} +For this step, it is key to ensure that the correct company file is open in QuickBooks Desktop and that it is the only one open. +{% include end-info.html %} + +10. In QuickBooks Desktop, select **"Yes, always allow access, even when QuickBooks is not running"** and click **Continue**. + +![The QuickBooks Desktop pop-up, where you will need to select "Yes, always allow access, even when QuickBooks is not running"](https://help.expensify.com/assets/images/QBO_desktop_04.png){:width="100%"} + +11. Click **OK**, then click **Yes**. + +![The QuickBooks Desktop pop-up, where you will need to click "Ok" then select "Yes"](https://help.expensify.com/assets/images/QBO_desktop_05.png){:width="100%"} + +12. Click **Copy** to copy the password. + +![The Web Connector pop-up, where you will need to click "Copy"](https://help.expensify.com/assets/images/QBO_desktop_06.png){:width="100%"} + +13. Paste the password into the Password field of the Web Connector and press **Enter**. + +![The Web Connector pop-up, where you will need to paste the password into the password field](https://help.expensify.com/assets/images/QBO_desktop_08.png){:width="100%"} + +14. Click **Yes** to save the password. The new connection now appears in the Web Connector. + +![The Web Connector pop-up, where you will need to click "Yes"](https://help.expensify.com/assets/images/QBO_desktop_07.png){:width="100%"} + +# FAQ + +## What are the hardware and software requirements for the QuickBooks Desktop connector? + +- Hardware requirements: You will need to ensure that the host machine meets [Intuit's recommended specifications](https://quickbooks.intuit.com/learn-support/en-us/help-article/install-products/system-requirements-quickbooks-desktop-2022/L9664spDA_US_en_US) for running QuickBooks Desktop. +- Software requirements: Windows 10 or Windows 11 with the latest service pack(s) installed. Users have successfully run the connector on older versions of Windows; however, we do not officially support this due to Microsoft's withdrawal of support for these operating systems. The web connector will not run on Mac OS. + +## What versions of QuickBooks Desktop are supported? + +Expensify’s QuickBooks Desktop integration follows [Intuit’s service discontinuation policy](https://quickbooks.intuit.com/learn-support/en-us/help-article/feature-preferences/quickbooks-desktop-service-discontinuation-policy/L17cXxlie_US_en_US) for QuickBooks Desktop and fully supports the following versions, version tiers, and special editions: + +- The latest three versions of: + + - QuickBooks Desktop (US) + - QuickBooks Desktop (Canada) + +- Version tiers: + + - Accountant + - Pro + - Pro Plus + - Premier + - Premier Plus + - Enterprise + +- Special editions: + + - Contractor edition + - Manufacturing and Wholesale edition + - Accountant edition + - Professional Services edition + - Nonprofit edition + +## Can multiple QuickBooks Desktop Connectors be installed on the same machine? + +Yes. You must have one connector per company file, but you can install multiple QuickBooks Desktop Connectors to sync multiple company files to Expensify from the same computer. + +If syncing multiple companies, make sure you’re logged in to the correct QuickBooks company file when syncing between QuickBooks and Expensify. + +## Can I export negative expenses? -## Step 1: Set up submitters in QuickBooks Desktop -- Make sure all report submitters are set up as Vendors in QuickBooks Desktop and their Expensify email is in the "Main Email" field of their Vendor record. You can do this in the vendor section of QuickBooks. -- If you want to export reports to your users' employee records instead of vendor records, select Check or Journal Entry as your reimbursable export option. -- To set up Expensify users as employees, activate QuickBooks Desktop Payroll. This module is necessary to access the Employee Profile tab, where you can enter the submitter's email addresses. - -## Step 2: Enable/install the Expensify Sync Manager -1. Navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections** -2. Click **Connect to QuickBooks Desktop** to initiate the connection - -**Option 1: Enable the Expensify Sync Manager in Right Networks (recommended)** -- For this option, **single-user mode** in QuickBooks Desktop is required. -- If you don't have an account with Right Networks, you can contact Right Networks [here](https://info.rightnetworks.com/partner-expensify) -- Once set up, you can enable the Expensify Sync Manager from the **My Account** section in Right Networks' portal - -**Option 2: Install the Expensify Sync Manager on Your Third-Party Remote Desktop.** -To download the Sync Manager to your desktop, you must contact your third-party remote desktop provider and request permission. They might have security restrictions, so it's best to communicate with them directly to avoid potential problems with the Sync Manager. Remember that the Sync Manager program file should be stored in the same location (i.e., the same drive) as your QuickBooks Desktop program. - -## Step 3: Complete the connection -1. Open QuickBooks and access the desired Company File using the QuickBooks Admin credentials (admin credentials are necessary for creating the connection) -2. Navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections** -3. Copy the Token by selecting the copy icon -4. While QuickBooks is still running, launch the Expensify Sync Manager by pasting the Token into the Sync Manager -5. Click **Save** -6. Once the Sync Manager status displays **Connected**, return to Expensify and click **Continue** - -## Step 4: Allow access -1. Return to QuickBooks where you'll see an **Application Certificate** screen - - On the first page of the Certificate screen, click **Yes, always; allow access even if QuickBooks is not running** -3. Click **Continue** -4. On the second page of the Certificate screen, choose the Admin user from the dropdown menu -5. Click **Done** -7. Return to Expensify and wait for the sync to complete - -{% include faq-begin.md %} - -## After connecting, how do I sync QuickBooks and Expensify? -1. Confirm that both the Expensify Sync Manager and QuickBooks Desktop are running -2. On the Expensify website, navigate to **Settings > Workspaces > Group > [Workspace Name] > Connections**, and click **Sync now** -3. Wait for the sync to complete - -Typically, this takes about 2-5 minutes, but it might take longer, depending on when you last synced and the size of your QuickBooks company file. The page will refresh automatically once syncing is complete. - -We recommend syncing at least once a week or whenever you make changes in QuickBooks Desktop that could impact how your reports export from Expensify. Changes could include adjustments to your Chart of Accounts, Vendors, Employees, Customers/Jobs, or Items. - -Remember, both the Sync Manager and QuickBooks Desktop need to be running for syncing or exporting to work. - -{% include faq-end.md %} +Generally, yes. However, if you have Check selected as your export option, the report’s total cannot be negative. This also applies to non-reimbursable expenses exported as debit card transactions. Because QuickBooks Desktop does not have debit card functionality, the transactions export as a non-reimbursable check, which must have a positive total amount. diff --git a/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md b/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md index 061b01b7a924..09afd2e4e7f2 100644 --- a/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md +++ b/docs/articles/expensify-classic/connections/quickbooks-desktop/Quickbooks-Desktop-Troubleshooting.md @@ -3,43 +3,89 @@ title: Quickbooks Desktop Troubleshooting description: Quickbooks Desktop Troubleshooting --- -# Sync and export errors -## Error: No Vendor Found For Email in QuickBooks -To address this issue, ensure that each submitter's email is saved as the **Main Email** in their Vendor record within QuickBooks Desktop. Here's how to resolve it: -1. Go to your Vendor section in QuickBooks. -2. Verify that the email mentioned in the error matches the **Main Email** field in the respective vendor's record. It's important to note that this comparison is case-sensitive, so ensure that capitalization matches as well. -3. If you prefer to export reports to your users' employee records instead of their vendor records, select either **Check** or **Journal Entry** as your reimbursable export option. If you are setting up Expensify users as employees, activate QuickBooks Desktop Payroll to access the Employee Profile tab where submitter email addresses need to be entered. -4. Once you've added the correct email to the vendor record, save this change, and then sync your policy before attempting to export the report again. - -## Error: Do Not Have Permission to Access Company Data File -To resolve this error, follow these steps: -1. Log into QuickBooks Desktop as an Admin in single-user mode. -2. Go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**. -3. Select the Expensify Sync Manager and click on **Properties**. -4. Ensure that **Allow this application to login automatically** is checked, and then click **OK**. Close all windows within QuickBooks. -5. If you still encounter the error after following the above steps, go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**, and remove the Expensify Sync Manager from the list. -6. Next, attempt to sync your policy again in Expensify. You'll be prompted to re-authorize the connection in QuickBooks. -7. Click **Yes, always; allow access even if QuickBooks is not running.** -8. From the dropdown, select the Admin user, and then click **Continue**. Note that selecting **Admin** here doesn't mean you always have to be logged in as an admin to use the connection; it's just required for setting up the connection. -9. Click **Done** on the pop-up window and return to Expensify, where your policy should complete the syncing process. - -## Error: The Wrong QuickBooks Company is Open. -This error suggests that the wrong company file is open in QuickBooks Desktop. To resolve this issue, follow these steps: -1. First, go through the general troubleshooting steps as outlined. -2. If you can confirm that the incorrect company file is open in QuickBooks, go to QuickBooks and select **File** > **Open or Restore Company** > _[Company Name]_ to open the correct company file. After doing this, try syncing your policy again. -3. If the correct company file is open, but you're still encountering the error, completely close QuickBooks Desktop, reopen the desired company file and then attempt to sync again. -4. If the error persists, log into QuickBooks as an admin in single-user mode. Then, go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences** and remove the Expensify Sync Manager from the list. -5. Next, try syncing your policy again in Expensify. You'll be prompted to re-authorize the connection in QuickBooks, allowing you to sync successfully. -6. If the error continues even after trying the steps above, double-check that the token you see in the Sync Manager matches the token in your connection settings. - -## Error: The Expensify Sync Manager Could Not Be Reached. -To resolve this error, follow these steps: -*Note: You must be in single-user mode to sync.* - -1. Ensure that both the Sync Manager and QuickBooks Desktop are running. -2. Confirm that the Sync Manager is installed in the correct location. It should be in the same location as your QuickBooks application. If QuickBooks is on your local desktop, the Sync Manager should be there, too. If QuickBooks is on a remote server, install the Sync Manager there. -Verify that the Sync Manager's status is **Connected**. -3. If the Sync Manager status is already **Connected**, click **Edit** and then *Save* to refresh the connection. Afterwards, try syncing your policy again. -4. If the error persists, double-check that the token you see in the Sync Manager matches the token in your connection settings. - -{% include faq-end.md %} +# The Web Connector cannot be reached + +Generally, these errors indicate that there is a connection issue, where there’s a breakdown between Expensify and QuickBooks. + +## How to resolve + +1. Make sure that the Web Connector and QuickBooks Desktop are both running. +2. Make sure that the Web Connector is installed in the same location as your QuickBooks application. For example, if QuickBooks is installed on your local desktop, the Web Connector should be too. Or if QuickBooks is installed on a remote server, the Web Connector should be installed there as well. + +If the error persists: + +1. Close the Web Connector completely (you may want to use Task Manager to do this). +2. Right-click the Web Connector icon on your desktop and select **Run as administrator**. +3. Sync your Workspace again. + +If this doesn’t work, the final troubleshooting steps should be: + +1. Quit QuickBooks Desktop, then reopen it. +2. In Expensify, hover over **Settings** and select **Workspaces**. +3. Click the workspace name that is connected to QuickBooks Desktop. +4. Click the **Connections** tab on the left. +5. Click **QuickBooks Desktop**. +6. Click **Sync Now**. +7. If this still doesn’t resolve the issue, use the link to reinstall the Web Connector. + +# Connection and/or authentication issue + +Generally, these errors indicate that there is a credentials issue. + +## How to resolve + +1. Make sure QuickBooks Desktop is open with the correct company file. This must be the same company file that you have connected to Expensify. +2. Make sure the QuickBooks Web Connector is open and the connector is online. +3. Make sure that there are no dialogue boxes open in QuickBooks that are interfering with attempts to sync or export. To resolve this, close any open windows in QuickBooks Desktop so that you only see a gray screen, then try exporting or syncing again. +4. Check that you have the correct permissions. +5. Log in to QuickBooks Desktop as an Admin (in single-user mode). +6. Go to **Edit** > **Preferences** > **Integrated Applications** > **Company Preferences**. +7. Select the Web Connector and click **Properties**. +8. Make sure that the "Allow this application to login automatically" checkbox is selected and click **OK**. +9. Close all windows in QuickBooks. + +If these general troubleshooting steps don’t work, reach out to Concierge and have the following information ready to provide: + +1. What version of QuickBooks Desktop do you have (Enterprise 2016, Pro 2014, etc.)? +2. Is your QuickBooks program installed on your computer or a remote network/drive? +3. Is your QuickBooks company file installed on your computer or a remote network/drive? +4. Is your Web Connector installed on your computer or a remote network/drive? +5. If any of the above are on a remote option, is there a company that runs that remote environment? If so, who (ie: RightNetworks, SwissNet, Cloud9, etc.)? + +# Import issue or missing categories and/or tags + +Generally, if you are having issues importing data from QuickBooks to Expensify, this indicates that the integration needs to be updated or your version of QuickBooks may not support a specific configuration. + +## How to resolve + +1. Re-sync the connection between Expensify and QuickBooks Desktop. A fresh sync can often resolve any issues, especially if you have recently updated your chart of accounts or projects, customers, or jobs in QuickBooks Desktop. +2. Check your configuration in QuickBooks Desktop. Expensify will import the chart of accounts to be utilized either as categories or export account options, while projects, customers, and tags will be imported as tags. + +If these general troubleshooting steps don’t work, reach out to Concierge with context on what is specifically missing in Expensify, as well as screenshots from your QuickBooks Desktop setup. + +# Export or "can't find category/class/location/account" issue + +Generally, when an export error occurs, we’ll share the reason in the Report Comments section at the bottom of the report. This will give you an indication of how to resolve the error. + +## How to resolve + +1. Re-sync the connection between Expensify and QuickBooks Desktop. A fresh sync can often resolve any issues, especially if you have recently updated your chart of accounts or projects, customers, or jobs in QuickBooks Desktop. +2. Re-apply coding to expenses and re-export the report. If you’ve recently synced Expensify and QuickBooks or recently made changes to your Workspace category or tags settings, you may need to re-apply coding to expenses. +3. Make sure that your current version of QuickBooks Desktop supports the selected export option. Different versions of QuickBooks Desktop support different export options and the [version that you own](https://quickbooks.intuit.com/desktop/) may not be compatible with the export type. + +If these general troubleshooting steps don’t work, reach out to Concierge with the Report ID, some context on what you’re trying to do, and a screenshot of the Expensify error message. + +# “Oops!” error when syncing or exporting + +Generally, an “Oops!” error can often be temporary or a false error. Although you will see a message pop up, there may actually not be an actual issue. + +## How to resolve + +1. Check to see if the sync or export was successful. +2. If it wasn't, please attempt to sync or export the connection again. + +If the problem persists, download the QuickBooks Desktop log file via the Web Connector (click View Logs to download them) and reach out to Concierge for further assistance. + +{% include info.html %} +If you’re using a remote server (e.g. RightNetworks), you may need to contact that support team to request your logs. +{% include end-info.html %} diff --git a/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md new file mode 100644 index 000000000000..922bf9d07056 --- /dev/null +++ b/docs/articles/expensify-classic/expensify-card/Expensify-Card-Perks.md @@ -0,0 +1,172 @@ +--- +title: Expensify Card Perks +description: An overview of all the various perks the Expensify Card offers +--- +## Expensify-specific Perks + +### Cashback +Get 1% cash back with every swipe — no minimums necessary — and 2% back if you spend $250k+/month across cards. + +This applies to USD purchases only. + +### Discounts on Monthly Expensify Bill +Get the Expensify Visa® Commercial Card and use it for at least half of your organization's monthly expenses to save 50% on your monthly Expensify bill. + +## Partner-specific Perks + +### Amazon AWS +Whether you're a two-person startup or a venture-backed company, AWS Activate provides access to the resources you need to get started on AWS quickly, including free credits, technical support, and training. All Expensify Cardholders qualify when they add their Expensify Card for AWS billing! + +**How to redeem:** Apply [here](https://aws.amazon.com/activate/portfolio-signup) using OrgID: 0qyIA (Case Sensitive). + +For more details, refer to the [AWS Activate terms and conditions](https://aws.amazon.com/activate/terms/) and the [Activate FAQs](https://aws.amazon.com/activate/faq/). + +### Stripe +Stripe’s integrated payments platform helps you build and scale your business globally, whether you're creating a subscription service, an on-demand marketplace, or an e-commerce store. + +Stripe will waive the processing fees for the first $5,000 in payments for Expensify Cardholders. + +**How to redeem:** Sign up for Stripe using your Expensify Card. + +### Lamar Advertising +Lamar offers out-of-home advertising on billboards, digital displays, airport signage, transit, and highway logo signs. Expensify Cardholders receive a minimum 10% discount on their first campaign. + +**How to redeem:** Contact Expensify’s dedicated account manager, Lisa Kane (email: lkane@lamar.com), and mention that you’re an Expensify Cardholder. + +### Carta +Simplify equity management with Carta. Expensify Cardholders receive a 20% discount on the first year and waived implementation fees. + +**How to redeem:** Sign up using your Expensify Card. + +### Pilot +Pilot specializes in bookkeeping and tax preparation for startups and e-commerce providers. When you use Pilot, you're paired with a dedicated finance expert who handles the work for you and answers your questions. + +**Offer:** 20% off the first six months of Pilot Core. + +**How to redeem:** Sign up using your Expensify Card. + +### Spotlight Reporting +Spotlight Reporting is a cloud-based reporting and forecasting tool designed by accountants, for accountants. Expensify Cardholders receive a 20% discount on their subscription for the first six months, plus one free seat for Spotlight Certification. + +**How to redeem:** Sign up using your Expensify Card. + +### Guideline +Guideline's full-service 401(k) plans make it easier and more affordable to offer your employees the retirement benefits they deserve. + +**Offer:** Three months free. + +**How to redeem:** Sign up using your Expensify Card. + +### Gusto +Gusto's platform helps businesses onboard, pay, insure, and support their teams. Expensify Cardholders receive three months of free service. + +**How to redeem:** Sign up using your Expensify Card. + +### QuickBooks Online +QuickBooks accounting software keeps your books accurate and up to date with features like invoicing, cash flow management, expense tracking, and more. + +**Offer:** 30% off QuickBooks Online for the first 12 months. + +**How to redeem:** Sign up using your Expensify Card. + +### Highfive +Highfive improves the ease and quality of in-room video conferencing. Expensify Cardholders receive 50% off the Highfive Select Starter Package and 10% off the Highfive Premium Package. + +**How to redeem:** Sign up using your Expensify Card. + +### Zendesk +Expensify Cardholders receive $436 in credits for Zendesk Suite products per month for the first year. + +**How to redeem:** +- Reach out to startups@zendesk.com with the message: "Expensify asked me to send an email regarding the Zendesk promotion.” You'll receive a promo code to use. +- Start a Zendesk trial (Suite or another plan) in USD (if your trial is not in USD, contact Zendesk). +- Click the "Buy Now" button inside your trial. +- Select your plan with monthly billing. + - The $436 monthly credit applies to up to four licenses of the Suite but can also be applied to any other monthly plan. +- Enter the promo code you receive. +- Complete the checkout process. + - After 12 monthly billing periods, your free credit will expire, and you'll be charged for the next month. + +### Xero +Xero offers accounting software with everything you need to run your business seamlessly. + +**Offer:** U.S. residents receive 50% off Xero for six months. + +**How to redeem:** Visit [this page](https://apps.xero.com/us/app/expensify?xtid=x30expensify&utm_source=expensify&utm_medium=web&utm_campaign=cardoffer) and sign up using your Expensify Card. + +### Freshworks +Boost your startup journey with customer and employee engagement solutions from Freshworks, including CRM, live chat, support, marketing automation, ITSM, and HRMS. + +**Offer:** $4,000 in credits on Freshworks products. + +**How to redeem:** Click [here](https://www.freshworks.com/partners/startup-program/expensify-card/) and fill out the form; Freshworks will automatically recognize your company as an Expensify Card customer. + +### Slack +Get 25% off your first year with Slack, enjoying premium features like unlimited messaging, apps, Slack Connect channels, group video calls, priority support, and more. + +**How to redeem:** Click [here](https://slack.com/promo/partner?remote_promo=ead919f5) to redeem the offer using your Expensify Card. + +### Deel.com +Deel simplifies onboarding international team members in 150 different countries. Expensify Cardholders receive three months free, followed by 30% off for the rest of the year. + +**How to redeem:** Click [here](https://www.deel.com/partners/expensify) and sign up using your Expensify Card. + +### Snap +Expensify Cardholders receive $1,000 in Snap credits when they spend $1,000 in Snapchat's Ads Manager. + +**How to redeem:** +- Click "Create Ad" or "Request a Call" by clicking [here](https://forbusiness.snapchat.com/). +- Enter your details to set up your account if you don't already have one. +- Add the Expensify Card as your payment option for your Snap Business account. +- Credits will automatically be placed in your account once you've reached $1,000 in spend. + +### Aircall +Aircall is a cloud-based phone system for sales and support teams. Expensify Cardholders receive two months free, with discounts ranging from $270 to $9,000+ depending on the number of users. + +**How to redeem:** +- Click [here](http://pages.aircall.io/Expensify-RewardsPartnerReferral.html) to sign up for a demo. +- Let the Aircall team know you're an Expensify customer. + +### NetSuite +NetSuite helps companies manage core business processes with a cloud-based ERP and accounting software. Expensify has a direct integration with NetSuite to synchronize data and customize expense coding. + +**Offer:** 10% off for the first year. + +**How to redeem:** +- Fill out this [Google form](https://docs.google.com/forms/d/e/1FAIpQLSeiOzLrdO-MgqeEMwEqgdQoh4SJBi42MZME9ycHp4SQjlk3bQ/viewform?usp=sf_link). +- An Expensify rep will introduce you to a NetSuite sales rep to start the process. +- Once set up, pay for your first year with NetSuite, and Expensify will send you a payment equal to 10% of your first-year contract within three months of your first NetSuite invoice. + +**Note:** This offer is only for prospective NetSuite customers. + +### PagerDuty +PagerDuty's platform integrates machine data and human intelligence to improve visibility and agility across organizations. + +**Offer:** 25% off. + +**How to redeem:** Sign up using your Expensify Card and enter the discount code EXPENSIFYPDTEAM for the Team plan or EXPENSIFYPDBUSINESS for the Business plan at checkout. + +### Typeform +Typeform makes collecting and sharing information easy and conversational, allowing you to create anything from surveys to apps without writing a single line of code. + +**Offer:** 30% off annual Premium and Professional plans. + +**How to redeem:** +- Click [here](https://try.typeform.com/expensify/?utm_source=expensify&utm_medium=referral&utm_campaign=expensify_integration&utm_content=directory) to get Typeform. +- Enter your details and set up your free account. +- Verify your email by clicking on the link Typeform sends you. +- Complete the onboarding flow within Typeform. +- Click the "Upgrade" button in your workspace. +- Select your plan. +- Enter the coupon code EXPENSIFY30 on the checkout page. +- Fill out all payment details using your Expensify Card and click "Upgrade now." + +### Intercom +Intercom offers a suite of messaging-first products to help businesses accelerate growth across the customer lifecycle. + +**Offer:** Three months of free service. + +**How to redeem:** Sign up using your Expensify Card. + +### Talkspace +Talkspace provides prescription management and personalized treatment through a network of licensed prescribers trained in mental healthcare. Expensify Cardholders receive $125 off Talk diff --git a/docs/articles/expensify-classic/workspaces/Enable-per-diem-expenses.md b/docs/articles/expensify-classic/workspaces/Enable-per-diem-expenses.md index 3ab757e75e9a..2d2f1b5afddc 100644 --- a/docs/articles/expensify-classic/workspaces/Enable-per-diem-expenses.md +++ b/docs/articles/expensify-classic/workspaces/Enable-per-diem-expenses.md @@ -14,9 +14,9 @@ To enable and set per diem rates, 4. Click the **Per Diem** tab on the left. 5. Click the Per Diem toggle to enable it. 6. Create a .csv, .txt, .xls, or .xlsx spreadsheet containing four columns: Destination, Sub-rate, Amount, and Currency. You’ll want a different row for each location that an employee may travel to, which may include states and/or countries to help account for cost differences across various locations. Here are some example templates you can use: - - [Germany multi-subrates](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fs3-us-west-1.amazonaws.com%2Fconcierge-responses-expensify-com%2Fuploads%252F1596692482998-Germany%2B-%2BPer%2BDiem.csv) - - [Sweden multi-subrates](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fs3-us-west-1.amazonaws.com%2Fconcierge-responses-expensify-com%2Fuploads%252F1604410653223-Swedish%2BPer%2BDiem%2BRates.csv) - - [South Africa single rates](https://community.expensify.com/home/leaving?allowTrusted=1&target=https%3A%2F%2Fs3-us-west-1.amazonaws.com%2Fconcierge-responses-expensify-com%2Fuploads%252F1596692413995-SA%2BPer%2BDiem%2BRates.csv) + - [Germany rates]({{site.url}}/assets/Files/Germany-per-diem.csv) + - [Sweden rates]({{site.url}}/assets/Files/Sweden-per-diem.csv) + - [South Africa single rates]({{site.url}}/assets/Files/South-Africa-per-diem.csv) 7. Click **Import from spreadsheet**. 8. Click **Upload** to select your spreadsheet. diff --git a/docs/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses.md b/docs/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses.md index c037e8fe9cd3..5754d5f2ff90 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses.md +++ b/docs/articles/new-expensify/expenses-&-payments/Approve-and-pay-expenses.md @@ -4,18 +4,23 @@ description: Approve, hold, or pay expenses submitted to you ---
-When expenses are sent to you for approval, you have the option to: -- Approve and pay the expenses. -- Hold the expenses if payment needs to be delayed or if the expenses require additional information before they can be approved. +As a workspace admin, you can set an approval workflow for the expenses submitted to you. Expenses can be, + +- Instantly submitted without needing approval. +- Submitted at a desired frequency (daily, weekly, monthly) and follow an approval workflow. + +**Setting approval workflow and submission frequencies** + +# Manually approve expense + +When someone sends an expense or a group of expenses to you for approval, you’ll receive the expense in Expensify Chat for the related workspace. Chats with new updates appear with a green dot to the right of the chat message. Concierge also sends you an email notification for the new expense. {% include info.html %} -If your workspace does not require expense approvals, or if the expense is sent to you by a friend, you will not need to approve the expense and instead can immediately pay the expense when you are ready. +If an expense is sent to you by a friend, you will not need to approve the expense. Instead, you can immediately pay the expense when you are ready. {% include end-info.html %} # Approve expenses -When someone sends an expense or a group of expenses to you for approval, you’ll receive the expense in Expensify Chat for the related workspace. Chats with new updates appear with a green dot to the right of the chat message. Concierge also sends you an email notification for the new expense. - To approve an expense, 1. Open the Expensify Chat thread for the expense. @@ -27,7 +32,7 @@ To approve an expense, - **Request changes**: You can add a comment to the expense’s chat thread in your Expensify Chat inbox to request changes to the expense details. {% include info.html %} -Admins can modify an expense, if needed. +If the transaction is pending (a common occurrence with recent company card expenses or SmartScan expenses), you’ll need to wait until the transaction posts before approving it. {% include end-info.html %} ![The approve button in an expense]({{site.url}}/assets/images/ExpensifyHelp_ApproveExpense_1.png){:width="100%"} @@ -57,12 +62,28 @@ Once the expense has been approved, you can now pay the expense. Held expenses will not be available for payment until they have been approved. {% include end-info.html %} +# Unapprove an expense + +Some details of approved expenses and reports cannot be edited. If you need to edit an expense that has been approved, admins and the last approver have the option to unapprove reports. + +1. Click the workspace logo in the top left corner. +2. Select the workspace associated with the expense report. +3. Find the approved report by searching for the submitter. +4. Click the dropdown arrow at the top of the report to view the report actions. +5. Click **Unapprove**. + +The unapproved report will return to an editable state, and the submitter will receive an email and chat notification that the expense has been unapproved. + +{% include info.html %} +Reports that have been paid cannot be unapproved. If the approved expense has already been exported to an accounting package, you’ll see a warning that unapproving an expense can cause data discrepancies and Expensify Card reconciliation issues. Ideally, you’ll want to delete the data that has already been exported to the accounting package before approving the expense again. +{% include end-info.html %} + # Pay expenses Once you’ve approved an expense—or if the expense does not require approval—you’ll be able to pay it. {% include info.html %} -To pay expenses within Expensify, you’ll need to set up your Expensify Wallet. +To pay expenses within Expensify, you’ll need to [set up your Expensify Wallet](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Set-up-your-wallet). {% include end-info.html %} To pay an expense, @@ -73,4 +94,14 @@ To pay an expense, - Click **Pay** to pay the full expense within Expensify. If the expenses contain one that has been held, the pay amount will only include the expenses that have not been held. Then you’ll select your payment method. - Click **Pay Elsewhere** to indicate that a payment has been sent using a method outside of Expensify, such as cash or a check. This will label the expense as Paid. +# FAQ + +**Why was an expense automatically approved?** + +We refer to this as **Instant Submit**. If a workspace doesn’t have Delayed Submission enabled, an expense report will automatically be submitted. + +**Why is an employee expense showing as ‘pending?’** + +An Expensify Card expense will show as pending if the merchant hasn’t posted it. This is usually the case with hotel holds, or card rental holds. A hold will normally last no more than 7-10 business days unless it’s a hotel hold, which can last 31 days. +
diff --git a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md index 6546f57073ee..1154a6a3163e 100644 --- a/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md +++ b/docs/articles/new-expensify/expenses-&-payments/Send-an-invoice.md @@ -1,12 +1,38 @@ --- -title: Send an invoice -description: Notify a customer that a payment is due +title: Send an invoice and receive payment +description: steps to send an invoice to someone and receipt payment ---
-You can send invoices directly from Expensify to notify customers that a payment is due. +Workspace admins can enable invoicing on a workspace to send invoices and receive invoice payments through Expensify. -To create and send an invoice, +# Enable invoicing + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click your profile image or icon in the bottom left menu. +2. Scroll down and click **Workspaces** in the left menu. +3. Select the workspace for which you want to enable invoicing. +4. Click **More features** in the left menu. +5. Under the Earn section, enable the Invoice toggle + +{% include end-option.html %} + +{% include option.html value="mobile" %} +1. Tap your profile image or icon in the bottom menu. +2. Tap **Workspaces**. +3. Select the workspace for which you want to enable invoicing. +4. Tap **More features**. +5. Under the Earn section, enable the Invoice toggle. + +{% include end-option.html %} + +# Send an invoice + +{% include info.html %} +Only workspace admins can send invoices. Invoices can be sent directly from Expensify to any customer, even if they do not have an Expensify account. +{% include end-info.html %} {% include selector.html values="desktop, mobile" %} @@ -28,28 +54,38 @@ To create and send an invoice, {% include end-selector.html %} -# How the customer pays an invoice - -Once an invoice is sent, the customer receives an automated email or text message to notify them of the invoice. They can use this notification to pay the invoice whenever they are ready. They will: +# Receive invoice payment -1. Click the link in the email or text notification they receive from Expensify. -2. Click **Pay**. -3. Choose **Paying as an individual** or **Paying as a business**. -4. Click **Pay Elsewhere**, which will mark the invoice as Paid. - -Currently, invoices must be paid outside of Expensify. However, the ability to make payments through Expensify is coming soon. - -![A photo of the pay button]({{site.url}}/assets/images/ExpensifyHelp-Invoice-1.png){:width="100%"} +If you have not [connected a business bank account](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Connect-a-Business-Bank-Account) to receive invoice payments, you will see an **Invoice balance** in your [Wallet](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Set-up-your-wallet). Expensify will automatically transfer these invoice payments once a business bank account is connected. # FAQs +**Why do I need to create a workspace to send an invoice?** + +A workspace is a configuration of settings related to your business. Since invoicing is considered a business feature, you must have a workspace to configure and use invoicing. + **How do I communicate with the sender/recipient about the invoice?** -You can communicate with the recipient in New Expensify. After sending an invoice, Expensify automatically creates an invoice room between the invoice sender and the payer to discuss anything related to the invoice. You can invite users to join the conversation, remove them from the room, and leave the room at any time. +Expensify will automatically notify the invoice recipient about the new invoice via email, SMS, and a mobile app notification, along with instructions on how to pay it. Daily reminders will be sent until the invoice is paid. + +Additionally, an invoice chat room will be automatically created in Expensify between the invoice sender, their workspace admins, and the payer. You can use this chat to discuss anything related to the invoice. + +**Can you export invoices between an accounting integration?** + +Yes, you can export invoices between Expensify and your connected [accounting integration](https://help.expensify.com/new-expensify/hubs/connections/). + +**Who can send and pay an invoice?** + +All workspace admins will be able to send and pay invoices. Invoices can also be paid by anyone, including recipients without an Expensify account. + +**What happens if I disable invoicing in the future?** + +When invoicing is disabled, all previously created invoice rooms and historical invoices will remain unaffected and continue to exist. However, all workspace admins will no longer have the option to send an invoice. + +**Why am I getting an error after I enter my website when connecting a business bank account?** -**Can you import and export invoices between an accounting integration?** +We can only accept a private domain website to ensure the security of your business. If you receive an error when entering your website, it is likely because the domain is not recognized as private. Make sure you are using a business email with a private domain. If you continue to experience issues, contact our support team at concierge@expensify.com for further assistance. -Yes, you can export and import invoices between Expensify and your QuickBooks Online or Xero integration.
diff --git a/docs/articles/new-expensify/expenses-&-payments/pay-an-invoice.md b/docs/articles/new-expensify/expenses-&-payments/pay-an-invoice.md new file mode 100644 index 000000000000..6221117ed962 --- /dev/null +++ b/docs/articles/new-expensify/expenses-&-payments/pay-an-invoice.md @@ -0,0 +1,56 @@ +--- +title: Pay an Invoice +description: process to pay an invoice +--- +
+ +{% include info.html %} +Anyone who receives an Expensify invoice can pay it using Expensify—even if they don’t have an Expensify account. +{% include end-info.html %} + +You'll receive an automated email or text notification when an invoice is sent to you for payment. + +To pay an invoice, + +{% include selector.html values="desktop, mobile" %} + +{% include option.html value="desktop" %} +1. Click the link in the email or text notification you receive from Expensify. +2. Click **Pay**. +3. Choose **Pay as an individual** or **Pay as a business**. +4. Click **Add Bank Account** or **Add debit or credit card** to issue payment. +{% include end-option.html %} + +You can also view all unpaid invoices by searching for the sender’s email or phone number on the left-hand side of the app. The invoices waiting for your payment will have a green dot. + +{% include option.html value="mobile" %} +1. Tap the link in the email or text notification they receive from Expensify. +2. Tap **Pay**. +3. Choose **Pay as an individual** or **Pay as a business**. +4. Tap **Add Bank Account** or **Add debit or credit card** to issue payment. +{% include end-option.html %} + +# FAQ + +**Can someone else pay an invoice besides the person who received it?** + +No, only the person who received the invoice will see the option to pay it. + +**Who can send an invoice?** + +Anyone can pay an invoice they’ve received, but only Expensify customers can send an invoice. This feature ensures that businesses using Expensify can seamlessly manage their invoice billing processes while providing flexibility for their customers to make payments. + +Expensify invoicing is designed to cater to both business-to-customer (B2C) and business-to-business (B2B) needs, making it a versatile tool for businesses of all sizes. + +**Can I pay an invoice outside of Expensify** + +You will need to work with the vendor to discuss alternative payment options. You can chat with your vendor directly at expensify.com in the designated invoice room. + +**Can I add additional payment methods?** + +You can add additional payment methods to your [Wallet](https://help.expensify.com/articles/new-expensify/expenses-&-payments/Set-up-your-wallet). Click **Account Settings** > **Wallet** > click **Add Bank Account**. + +You will be prompted to choose a payment method when paying future invoices. + + +
diff --git a/docs/assets/Files/Germany-per-diem.csv b/docs/assets/Files/Germany-per-diem.csv new file mode 100644 index 000000000000..217cc0a2fed1 --- /dev/null +++ b/docs/assets/Files/Germany-per-diem.csv @@ -0,0 +1,1417 @@ +Destination,Currency,Subrate,Amount +Afghanistan,EUR,Full Day,30 +Afghanistan,EUR,Part Day,20 +Afghanistan,EUR,Overnight,95 +Afghanistan,EUR,Breakfast,-6 +Afghanistan,EUR,Lunch,-12 +Afghanistan,EUR,Dinner,-12 +Albania,EUR,Full Day,29 +Albania,EUR,Part Day,20 +Albania,EUR,Overnight,113 +Albania,EUR,Breakfast,-5.8 +Albania,EUR,Lunch,-11.6 +Albania,EUR,Dinner,-11.6 +Algeria,EUR,Full Day,51 +Algeria,EUR,Part Day,34 +Algeria,EUR,Overnight,173 +Algeria,EUR,Breakfast,-10.2 +Algeria,EUR,Lunch,-20.4 +Algeria,EUR,Dinner,-20.4 +Andorra,EUR,Full Day,34 +Andorra,EUR,Part Day,23 +Andorra,EUR,Overnight,45 +Andorra,EUR,Breakfast,-6.8 +Andorra,EUR,Lunch,-13.6 +Andorra,EUR,Dinner,-13.6 +Angola,EUR,Full Day,77 +Angola,EUR,Part Day,52 +Angola,EUR,Overnight,265 +Angola,EUR,Breakfast,-15.4 +Angola,EUR,Lunch,-30.8 +Angola,EUR,Dinner,-30.8 +Antigua and Barbuda,EUR,Full Day,45 +Antigua and Barbuda,EUR,Part Day,30 +Antigua and Barbuda,EUR,Overnight,177 +Antigua and Barbuda,EUR,Breakfast,-9 +Antigua and Barbuda,EUR,Lunch,-18 +Antigua and Barbuda,EUR,Dinner,-18 +Argentina,EUR,Full Day,34 +Argentina,EUR,Part Day,23 +Argentina,EUR,Overnight,144 +Argentina,EUR,Breakfast,-6.8 +Argentina,EUR,Lunch,-13.6 +Argentina,EUR,Dinner,-13.6 +Armenia,EUR,Full Day,23 +Armenia,EUR,Part Day,16 +Armenia,EUR,Overnight,63 +Armenia,EUR,Breakfast,-4.6 +Armenia,EUR,Lunch,-9.2 +Armenia,EUR,Dinner,-9.2 +Australia - Canberra,EUR,Full Day,51 +Australia - Canberra,EUR,Part Day,34 +Australia - Canberra,EUR,Overnight,158 +Australia - Canberra,EUR,Breakfast,-10.2 +Australia - Canberra,EUR,Lunch,-20.4 +Australia - Canberra,EUR,Dinner,-20.4 +Australia - Sydney,EUR,Full Day,68 +Australia - Sydney,EUR,Part Day,45 +Australia - Sydney,EUR,Overnight,184 +Australia - Sydney,EUR,Breakfast,-13.6 +Australia - Sydney,EUR,Lunch,-27.2 +Australia - Sydney,EUR,Dinner,-27.2 +Australia - for the rest,EUR,Full Day,51 +Australia - for the rest,EUR,Part Day,34 +Australia - for the rest,EUR,Overnight,158 +Australia - for the rest,EUR,Breakfast,-10.2 +Australia - for the rest,EUR,Lunch,-20.4 +Australia - for the rest,EUR,Dinner,-20.4 +Austria,EUR,Full Day,40 +Austria,EUR,Part Day,27 +Austria,EUR,Overnight,108 +Austria,EUR,Breakfast,-8 +Austria,EUR,Lunch,-16 +Austria,EUR,Dinner,-16 +Azerbaijan,EUR,Full Day,30 +Azerbaijan,EUR,Part Day,20 +Azerbaijan,EUR,Overnight,72 +Azerbaijan,EUR,Breakfast,-6 +Azerbaijan,EUR,Lunch,-12 +Azerbaijan,EUR,Dinner,-12 +Bahrain,EUR,Full Day,45 +Bahrain,EUR,Part Day,30 +Bahrain,EUR,Overnight,180 +Bahrain,EUR,Breakfast,-9 +Bahrain,EUR,Lunch,-18 +Bahrain,EUR,Dinner,-18 +Bangladesh,EUR,Full Day,30 +Bangladesh,EUR,Part Day,20 +Bangladesh,EUR,Overnight,111 +Bangladesh,EUR,Breakfast,-6 +Bangladesh,EUR,Lunch,-12 +Bangladesh,EUR,Dinner,-12 +Barbados,EUR,Full Day,52 +Barbados,EUR,Part Day,35 +Barbados,EUR,Overnight,165 +Barbados,EUR,Breakfast,-10.4 +Barbados,EUR,Lunch,-20.8 +Barbados,EUR,Dinner,-20.8 +Belarus,EUR,Full Day,20 +Belarus,EUR,Part Day,13 +Belarus,EUR,Overnight,98 +Belarus,EUR,Breakfast,-4 +Belarus,EUR,Lunch,-8 +Belarus,EUR,Dinner,-8 +Belgium,EUR,Full Day,42 +Belgium,EUR,Part Day,28 +Belgium,EUR,Overnight,135 +Belgium,EUR,Breakfast,-8.4 +Belgium,EUR,Lunch,-16.8 +Belgium,EUR,Dinner,-16.8 +Benin,EUR,Full Day,40 +Benin,EUR,Part Day,27 +Benin,EUR,Overnight,101 +Benin,EUR,Breakfast,-8 +Benin,EUR,Lunch,-16 +Benin,EUR,Dinner,-16 +Bolivia,EUR,Full Day,30 +Bolivia,EUR,Part Day,20 +Bolivia,EUR,Overnight,93 +Bolivia,EUR,Breakfast,-6 +Bolivia,EUR,Lunch,-12 +Bolivia,EUR,Dinner,-12 +Bosnia and Herzegovina,EUR,Full Day,18 +Bosnia and Herzegovina,EUR,Part Day,12 +Bosnia and Herzegovina,EUR,Overnight,73 +Bosnia and Herzegovina,EUR,Breakfast,-3.6 +Bosnia and Herzegovina,EUR,Lunch,-7.2 +Bosnia and Herzegovina,EUR,Dinner,-7.2 +Botswana,EUR,Full Day,40 +Botswana,EUR,Part Day,27 +Botswana,EUR,Overnight,102 +Botswana,EUR,Breakfast,-8 +Botswana,EUR,Lunch,-16 +Botswana,EUR,Dinner,-16 +Brazil - Brasilia,EUR,Full Day,57 +Brazil - Brasilia,EUR,Part Day,38 +Brazil - Brasilia,EUR,Overnight,127 +Brazil - Brasilia,EUR,Breakfast,-11.4 +Brazil - Brasilia,EUR,Lunch,-22.8 +Brazil - Brasilia,EUR,Dinner,-22.8 +Brazil - Rio de Janeiro,EUR,Full Day,57 +Brazil - Rio de Janeiro,EUR,Part Day,38 +Brazil - Rio de Janeiro,EUR,Overnight,145 +Brazil - Rio de Janeiro,EUR,Breakfast,-11.4 +Brazil - Rio de Janeiro,EUR,Lunch,-22.8 +Brazil - Rio de Janeiro,EUR,Dinner,-22.8 +Brazil - São Paulo,EUR,Full Day,53 +Brazil - São Paulo,EUR,Part Day,36 +Brazil - São Paulo,EUR,Overnight,132 +Brazil - São Paulo,EUR,Breakfast,-10.6 +Brazil - São Paulo,EUR,Lunch,-21.2 +Brazil - São Paulo,EUR,Dinner,-21.2 +Brazil - for the rest,EUR,Full Day,51 +Brazil - for the rest,EUR,Part Day,34 +Brazil - for the rest,EUR,Overnight,84 +Brazil - for the rest,EUR,Breakfast,-10.2 +Brazil - for the rest,EUR,Lunch,-20.4 +Brazil - for the rest,EUR,Dinner,-20.4 +Brunei,EUR,Full Day,48 +Brunei,EUR,Part Day,32 +Brunei,EUR,Overnight,106 +Brunei,EUR,Breakfast,-9.6 +Brunei,EUR,Lunch,-19.2 +Brunei,EUR,Dinner,-19.2 +Bulgaria,EUR,Full Day,22 +Bulgaria,EUR,Part Day,15 +Bulgaria,EUR,Overnight,90 +Bulgaria,EUR,Breakfast,-4.4 +Bulgaria,EUR,Lunch,-8.8 +Bulgaria,EUR,Dinner,-8.8 +Burkina Faso,EUR,Full Day,44 +Burkina Faso,EUR,Part Day,29 +Burkina Faso,EUR,Overnight,84 +Burkina Faso,EUR,Breakfast,-8.8 +Burkina Faso,EUR,Lunch,-17.6 +Burkina Faso,EUR,Dinner,-17.6 +Burundi,EUR,Full Day,47 +Burundi,EUR,Part Day,32 +Burundi,EUR,Overnight,98 +Burundi,EUR,Breakfast,-9.4 +Burundi,EUR,Lunch,-18.8 +Burundi,EUR,Dinner,-18.8 +Cambodia,EUR,Full Day,38 +Cambodia,EUR,Part Day,25 +Cambodia,EUR,Overnight,94 +Cambodia,EUR,Breakfast,-7.6 +Cambodia,EUR,Lunch,-15.2 +Cambodia,EUR,Dinner,-15.2 +Cameroon,EUR,Full Day,50 +Cameroon,EUR,Part Day,33 +Cameroon,EUR,Overnight,180 +Cameroon,EUR,Breakfast,-10 +Cameroon,EUR,Lunch,-20 +Cameroon,EUR,Dinner,-20 +Canada - Ottawa,EUR,Full Day,47 +Canada - Ottawa,EUR,Part Day,32 +Canada - Ottawa,EUR,Overnight,142 +Canada - Ottawa,EUR,Breakfast,-9.4 +Canada - Ottawa,EUR,Lunch,-18.8 +Canada - Ottawa,EUR,Dinner,-18.8 +Canada - Toronto,EUR,Full Day,51 +Canada - Toronto,EUR,Part Day,34 +Canada - Toronto,EUR,Overnight,161 +Canada - Toronto,EUR,Breakfast,-10.2 +Canada - Toronto,EUR,Lunch,-20.4 +Canada - Toronto,EUR,Dinner,-20.4 +Canada - Vancouver,EUR,Full Day,50 +Canada - Vancouver,EUR,Part Day,33 +Canada - Vancouver,EUR,Overnight,140 +Canada - Vancouver,EUR,Breakfast,-10 +Canada - Vancouver,EUR,Lunch,-20 +Canada - Vancouver,EUR,Dinner,-20 +Canada - for the rest,EUR,Full Day,47 +Canada - for the rest,EUR,Part Day,32 +Canada - for the rest,EUR,Overnight,134 +Canada - for the rest,EUR,Breakfast,-9.4 +Canada - for the rest,EUR,Lunch,-18.8 +Canada - for the rest,EUR,Dinner,-18.8 +Cape Verde,EUR,Full Day,30 +Cape Verde,EUR,Part Day,20 +Cape Verde,EUR,Overnight,105 +Cape Verde,EUR,Breakfast,-6 +Cape Verde,EUR,Lunch,-12 +Cape Verde,EUR,Dinner,-12 +Central African Republic,EUR,Full Day,46 +Central African Republic,EUR,Part Day,31 +Central African Republic,EUR,Overnight,74 +Central African Republic,EUR,Breakfast,-9.2 +Central African Republic,EUR,Lunch,-18.4 +Central African Republic,EUR,Dinner,-18.4 +Chad,EUR,Full Day,64 +Chad,EUR,Part Day,43 +Chad,EUR,Overnight,163 +Chad,EUR,Breakfast,-12.8 +Chad,EUR,Lunch,-25.6 +Chad,EUR,Dinner,-25.6 +Chile,EUR,Full Day,44 +Chile,EUR,Part Day,29 +Chile,EUR,Overnight,187 +Chile,EUR,Breakfast,-8.8 +Chile,EUR,Lunch,-17.6 +Chile,EUR,Dinner,-17.6 +China - Beijing,EUR,Full Day,46 +China - Beijing,EUR,Part Day,31 +China - Beijing,EUR,Overnight,142 +China - Beijing,EUR,Breakfast,-9.2 +China - Beijing,EUR,Lunch,-18.4 +China - Beijing,EUR,Dinner,-18.4 +China - Canton,EUR,Full Day,40 +China - Canton,EUR,Part Day,27 +China - Canton,EUR,Overnight,113 +China - Canton,EUR,Breakfast,-8 +China - Canton,EUR,Lunch,-16 +China - Canton,EUR,Dinner,-16 +China - Chengdu,EUR,Full Day,35 +China - Chengdu,EUR,Part Day,24 +China - Chengdu,EUR,Overnight,105 +China - Chengdu,EUR,Breakfast,-7 +China - Chengdu,EUR,Lunch,-14 +China - Chengdu,EUR,Dinner,-14 +China - Hong Kong,EUR,Full Day,74 +China - Hong Kong,EUR,Part Day,49 +China - Hong Kong,EUR,Overnight,145 +China - Hong Kong,EUR,Breakfast,-14.8 +China - Hong Kong,EUR,Lunch,-29.6 +China - Hong Kong,EUR,Dinner,-29.6 +China - Shanghai,EUR,Full Day,50 +China - Shanghai,EUR,Part Day,33 +China - Shanghai,EUR,Overnight,128 +China - Shanghai,EUR,Breakfast,-10 +China - Shanghai,EUR,Lunch,-20 +China - Shanghai,EUR,Dinner,-20 +China - for the rest,EUR,Full Day,50 +China - for the rest,EUR,Part Day,33 +China - for the rest,EUR,Overnight,78 +China - for the rest,EUR,Breakfast,-10 +China - for the rest,EUR,Lunch,-20 +China - for the rest,EUR,Dinner,-20 +Colombia,EUR,Full Day,41 +Colombia,EUR,Part Day,28 +Colombia,EUR,Overnight,126 +Colombia,EUR,Breakfast,-8.2 +Colombia,EUR,Lunch,-16.4 +Colombia,EUR,Dinner,-16.4 +"Congo, Democratic Republic",EUR,Full Day,68 +"Congo, Democratic Republic",EUR,Part Day,45 +"Congo, Democratic Republic",EUR,Overnight,171 +"Congo, Democratic Republic",EUR,Breakfast,-13.6 +"Congo, Democratic Republic",EUR,Lunch,-27.2 +"Congo, Democratic Republic",EUR,Dinner,-27.2 +"Congo, Republic",EUR,Full Day,50 +"Congo, Republic",EUR,Part Day,33 +"Congo, Republic",EUR,Overnight,200 +"Congo, Republic",EUR,Breakfast,-10 +"Congo, Republic",EUR,Lunch,-20 +"Congo, Republic",EUR,Dinner,-20 +Costa Rica,EUR,Full Day,47 +Costa Rica,EUR,Part Day,32 +Costa Rica,EUR,Overnight,93 +Costa Rica,EUR,Breakfast,-9.4 +Costa Rica,EUR,Lunch,-18.8 +Costa Rica,EUR,Dinner,-18.8 +Côte d’Ivoire,EUR,Full Day,51 +Côte d’Ivoire,EUR,Part Day,34 +Côte d’Ivoire,EUR,Overnight,146 +Côte d’Ivoire,EUR,Breakfast,-10.2 +Côte d’Ivoire,EUR,Lunch,-20.4 +Côte d’Ivoire,EUR,Dinner,-20.4 +Croatia,EUR,Full Day,28 +Croatia,EUR,Part Day,19 +Croatia,EUR,Overnight,75 +Croatia,EUR,Breakfast,-5.6 +Croatia,EUR,Lunch,-11.2 +Croatia,EUR,Dinner,-11.2 +Cuba,EUR,Full Day,46 +Cuba,EUR,Part Day,31 +Cuba,EUR,Overnight,228 +Cuba,EUR,Breakfast,-9.2 +Cuba,EUR,Lunch,-18.4 +Cuba,EUR,Dinner,-18.4 +Cyprus,EUR,Full Day,45 +Cyprus,EUR,Part Day,30 +Cyprus,EUR,Overnight,116 +Cyprus,EUR,Breakfast,-9 +Cyprus,EUR,Lunch,-18 +Cyprus,EUR,Dinner,-18 +Czech Republic,EUR,Full Day,35 +Czech Republic,EUR,Part Day,24 +Czech Republic,EUR,Overnight,94 +Czech Republic,EUR,Breakfast,-7 +Czech Republic,EUR,Lunch,-14 +Czech Republic,EUR,Dinner,-14 +Denmark,EUR,Full Day,58 +Denmark,EUR,Part Day,39 +Denmark,EUR,Overnight,143 +Denmark,EUR,Breakfast,-11.6 +Denmark,EUR,Lunch,-23.2 +Denmark,EUR,Dinner,-23.2 +Djibouti,EUR,Full Day,65 +Djibouti,EUR,Part Day,44 +Djibouti,EUR,Overnight,305 +Djibouti,EUR,Breakfast,-13 +Djibouti,EUR,Lunch,-26 +Djibouti,EUR,Dinner,-26 +Dominica,EUR,Full Day,45 +Dominica,EUR,Part Day,30 +Dominica,EUR,Overnight,177 +Dominica,EUR,Breakfast,-9 +Dominica,EUR,Lunch,-18 +Dominica,EUR,Dinner,-18 +Dominican Republic,EUR,Full Day,45 +Dominican Republic,EUR,Part Day,30 +Dominican Republic,EUR,Overnight,147 +Dominican Republic,EUR,Breakfast,-9 +Dominican Republic,EUR,Lunch,-18 +Dominican Republic,EUR,Dinner,-18 +Ecuador,EUR,Full Day,44 +Ecuador,EUR,Part Day,29 +Ecuador,EUR,Overnight,97 +Ecuador,EUR,Breakfast,-8.8 +Ecuador,EUR,Lunch,-17.6 +Ecuador,EUR,Dinner,-17.6 +Egypt,EUR,Full Day,41 +Egypt,EUR,Part Day,28 +Egypt,EUR,Overnight,125 +Egypt,EUR,Breakfast,-8.2 +Egypt,EUR,Lunch,-16.4 +Egypt,EUR,Dinner,-16.4 +El Salvador,EUR,Full Day,44 +El Salvador,EUR,Part Day,29 +El Salvador,EUR,Overnight,119 +El Salvador,EUR,Breakfast,-8.8 +El Salvador,EUR,Lunch,-17.6 +El Salvador,EUR,Dinner,-17.6 +Equatorial Guinea,EUR,Full Day,36 +Equatorial Guinea,EUR,Part Day,24 +Equatorial Guinea,EUR,Overnight,166 +Equatorial Guinea,EUR,Breakfast,-7.2 +Equatorial Guinea,EUR,Lunch,-14.4 +Equatorial Guinea,EUR,Dinner,-14.4 +Eritrea,EUR,Full Day,50 +Eritrea,EUR,Part Day,33 +Eritrea,EUR,Overnight,91 +Eritrea,EUR,Breakfast,-10 +Eritrea,EUR,Lunch,-20 +Eritrea,EUR,Dinner,-20 +Estonia,EUR,Full Day,27 +Estonia,EUR,Part Day,18 +Estonia,EUR,Overnight,71 +Estonia,EUR,Breakfast,-5.4 +Estonia,EUR,Lunch,-10.8 +Estonia,EUR,Dinner,-10.8 +Ethiopia,EUR,Full Day,27 +Ethiopia,EUR,Part Day,18 +Ethiopia,EUR,Overnight,86 +Ethiopia,EUR,Breakfast,-5.4 +Ethiopia,EUR,Lunch,-10.8 +Ethiopia,EUR,Dinner,-10.8 +Fiji,EUR,Full Day,34 +Fiji,EUR,Part Day,23 +Fiji,EUR,Overnight,69 +Fiji,EUR,Breakfast,-6.8 +Fiji,EUR,Lunch,-13.6 +Fiji,EUR,Dinner,-13.6 +Finland,EUR,Full Day,50 +Finland,EUR,Part Day,33 +Finland,EUR,Overnight,136 +Finland,EUR,Breakfast,-10 +Finland,EUR,Lunch,-20 +Finland,EUR,Dinner,-20 +France - Lyon,EUR,Full Day,53 +France - Lyon,EUR,Part Day,36 +France - Lyon,EUR,Overnight,115 +France - Lyon,EUR,Breakfast,-10.6 +France - Lyon,EUR,Lunch,-21.2 +France - Lyon,EUR,Dinner,-21.2 +France - Marseille,EUR,Full Day,46 +France - Marseille,EUR,Part Day,31 +France - Marseille,EUR,Overnight,101 +France - Marseille,EUR,Breakfast,-9.2 +France - Marseille,EUR,Lunch,-18.4 +France - Marseille,EUR,Dinner,-18.4 +"France - Paris and Departments 92, 93 and 94",EUR,Full Day,58 +"France - Paris and Departments 92, 93 and 94",EUR,Part Day,39 +"France - Paris and Departments 92, 93 and 94",EUR,Overnight,152 +"France - Paris and Departments 92, 93 and 94",EUR,Breakfast,-11.6 +"France - Paris and Departments 92, 93 and 94",EUR,Lunch,-23.2 +"France - Paris and Departments 92, 93 and 94",EUR,Dinner,-23.2 +France - Strasbourg,EUR,Full Day,51 +France - Strasbourg,EUR,Part Day,34 +France - Strasbourg,EUR,Overnight,96 +France - Strasbourg,EUR,Breakfast,-10.2 +France - Strasbourg,EUR,Lunch,-20.4 +France - Strasbourg,EUR,Dinner,-20.4 +France - for the rest,EUR,Full Day,44 +France - for the rest,EUR,Part Day,29 +France - for the rest,EUR,Overnight,115 +France - for the rest,EUR,Breakfast,-8.8 +France - for the rest,EUR,Lunch,-17.6 +France - for the rest,EUR,Dinner,-17.6 +Gabon,EUR,Full Day,62 +Gabon,EUR,Part Day,41 +Gabon,EUR,Overnight,278 +Gabon,EUR,Breakfast,-12.4 +Gabon,EUR,Lunch,-24.8 +Gabon,EUR,Dinner,-24.8 +Gambia,EUR,Full Day,30 +Gambia,EUR,Part Day,20 +Gambia,EUR,Overnight,125 +Gambia,EUR,Breakfast,-6 +Gambia,EUR,Lunch,-12 +Gambia,EUR,Dinner,-12 +Georgia,EUR,Full Day,35 +Georgia,EUR,Part Day,24 +Georgia,EUR,Overnight,88 +Georgia,EUR,Breakfast,-7 +Georgia,EUR,Lunch,-14 +Georgia,EUR,Dinner,-14 +Ghana,EUR,Full Day,46 +Ghana,EUR,Part Day,31 +Ghana,EUR,Overnight,148 +Ghana,EUR,Breakfast,-9.2 +Ghana,EUR,Lunch,-18.4 +Ghana,EUR,Dinner,-18.4 +Greece - Athens,EUR,Full Day,46 +Greece - Athens,EUR,Part Day,31 +Greece - Athens,EUR,Overnight,132 +Greece - Athens,EUR,Breakfast,-9.2 +Greece - Athens,EUR,Lunch,-18.4 +Greece - Athens,EUR,Dinner,-18.4 +Greece - for the rest,EUR,Full Day,36 +Greece - for the rest,EUR,Part Day,24 +Greece - for the rest,EUR,Overnight,135 +Greece - for the rest,EUR,Breakfast,-7.2 +Greece - for the rest,EUR,Lunch,-14.4 +Greece - for the rest,EUR,Dinner,-14.4 +Grenada,EUR,Full Day,45 +Grenada,EUR,Part Day,30 +Grenada,EUR,Overnight,177 +Grenada,EUR,Breakfast,-9 +Grenada,EUR,Lunch,-18 +Grenada,EUR,Dinner,-18 +Guatemala,EUR,Full Day,34 +Guatemala,EUR,Part Day,23 +Guatemala,EUR,Overnight,90 +Guatemala,EUR,Breakfast,-6.8 +Guatemala,EUR,Lunch,-13.6 +Guatemala,EUR,Dinner,-13.6 +Guinea,EUR,Full Day,46 +Guinea,EUR,Part Day,31 +Guinea,EUR,Overnight,118 +Guinea,EUR,Breakfast,-9.2 +Guinea,EUR,Lunch,-18.4 +Guinea,EUR,Dinner,-18.4 +Guinea-Bissau,EUR,Full Day,24 +Guinea-Bissau,EUR,Part Day,16 +Guinea-Bissau,EUR,Overnight,86 +Guinea-Bissau,EUR,Breakfast,-4.8 +Guinea-Bissau,EUR,Lunch,-9.6 +Guinea-Bissau,EUR,Dinner,-9.6 +Guyana,EUR,Full Day,45 +Guyana,EUR,Part Day,30 +Guyana,EUR,Overnight,177 +Guyana,EUR,Breakfast,-9 +Guyana,EUR,Lunch,-18 +Guyana,EUR,Dinner,-18 +Haiti,EUR,Full Day,58 +Haiti,EUR,Part Day,39 +Haiti,EUR,Overnight,130 +Haiti,EUR,Breakfast,-11.6 +Haiti,EUR,Lunch,-23.2 +Haiti,EUR,Dinner,-23.2 +Honduras,EUR,Full Day,48 +Honduras,EUR,Part Day,32 +Honduras,EUR,Overnight,101 +Honduras,EUR,Breakfast,-9.6 +Honduras,EUR,Lunch,-19.2 +Honduras,EUR,Dinner,-19.2 +Hungary,EUR,Full Day,22 +Hungary,EUR,Part Day,15 +Hungary,EUR,Overnight,63 +Hungary,EUR,Breakfast,-4.4 +Hungary,EUR,Lunch,-8.8 +Hungary,EUR,Dinner,-8.8 +Iceland,EUR,Full Day,47 +Iceland,EUR,Part Day,32 +Iceland,EUR,Overnight,108 +Iceland,EUR,Breakfast,-9.4 +Iceland,EUR,Lunch,-18.8 +Iceland,EUR,Dinner,-18.8 +India - Chennai,EUR,Full Day,32 +India - Chennai,EUR,Part Day,21 +India - Chennai,EUR,Overnight,85 +India - Chennai,EUR,Breakfast,-6.4 +India - Chennai,EUR,Lunch,-12.8 +India - Chennai,EUR,Dinner,-12.8 +India - Kolkata,EUR,Full Day,35 +India - Kolkata,EUR,Part Day,24 +India - Kolkata,EUR,Overnight,145 +India - Kolkata,EUR,Breakfast,-7 +India - Kolkata,EUR,Lunch,-14 +India - Kolkata,EUR,Dinner,-14 +India - Mumbai,EUR,Full Day,50 +India - Mumbai,EUR,Part Day,33 +India - Mumbai,EUR,Overnight,146 +India - Mumbai,EUR,Breakfast,-10 +India - Mumbai,EUR,Lunch,-20 +India - Mumbai,EUR,Dinner,-20 +India - New Delhi,EUR,Full Day,38 +India - New Delhi,EUR,Part Day,25 +India - New Delhi,EUR,Overnight,185 +India - New Delhi,EUR,Breakfast,-7.6 +India - New Delhi,EUR,Lunch,-15.2 +India - New Delhi,EUR,Dinner,-15.2 +India - for the rest,EUR,Full Day,32 +India - for the rest,EUR,Part Day,21 +India - for the rest,EUR,Overnight,85 +India - for the rest,EUR,Breakfast,-6.4 +India - for the rest,EUR,Lunch,-12.8 +India - for the rest,EUR,Dinner,-12.8 +Indonesia,EUR,Full Day,38 +Indonesia,EUR,Part Day,25 +Indonesia,EUR,Overnight,130 +Indonesia,EUR,Breakfast,-7.6 +Indonesia,EUR,Lunch,-15.2 +Indonesia,EUR,Dinner,-15.2 +Iran,EUR,Full Day,33 +Iran,EUR,Part Day,22 +Iran,EUR,Overnight,196 +Iran,EUR,Breakfast,-6.6 +Iran,EUR,Lunch,-13.2 +Iran,EUR,Dinner,-13.2 +Ireland,EUR,Full Day,44 +Ireland,EUR,Part Day,29 +Ireland,EUR,Overnight,92 +Ireland,EUR,Breakfast,-8.8 +Ireland,EUR,Lunch,-17.6 +Ireland,EUR,Dinner,-17.6 +Israel,EUR,Full Day,56 +Israel,EUR,Part Day,37 +Israel,EUR,Overnight,191 +Israel,EUR,Breakfast,-11.2 +Israel,EUR,Lunch,-22.4 +Israel,EUR,Dinner,-22.4 +Italy - Milan,EUR,Full Day,45 +Italy - Milan,EUR,Part Day,30 +Italy - Milan,EUR,Overnight,158 +Italy - Milan,EUR,Breakfast,-9 +Italy - Milan,EUR,Lunch,-18 +Italy - Milan,EUR,Dinner,-18 +Italy - Rome,EUR,Full Day,40 +Italy - Rome,EUR,Part Day,27 +Italy - Rome,EUR,Overnight,135 +Italy - Rome,EUR,Breakfast,-8 +Italy - Rome,EUR,Lunch,-16 +Italy - Rome,EUR,Dinner,-16 +Italy - for the rest,EUR,Full Day,40 +Italy - for the rest,EUR,Part Day,27 +Italy - for the rest,EUR,Overnight,135 +Italy - for the rest,EUR,Breakfast,-8 +Italy - for the rest,EUR,Lunch,-16 +Italy - for the rest,EUR,Dinner,-16 +Jamaica,EUR,Full Day,57 +Jamaica,EUR,Part Day,38 +Jamaica,EUR,Overnight,138 +Jamaica,EUR,Breakfast,-11.4 +Jamaica,EUR,Lunch,-22.8 +Jamaica,EUR,Dinner,-22.8 +Japan - Tokyo,EUR,Full Day,66 +Japan - Tokyo,EUR,Part Day,44 +Japan - Tokyo,EUR,Overnight,233 +Japan - Tokyo,EUR,Breakfast,-13.2 +Japan - Tokyo,EUR,Lunch,-26.4 +Japan - Tokyo,EUR,Dinner,-26.4 +Japan - for the rest,EUR,Full Day,51 +Japan - for the rest,EUR,Part Day,34 +Japan - for the rest,EUR,Overnight,156 +Japan - for the rest,EUR,Breakfast,-10.2 +Japan - for the rest,EUR,Lunch,-20.4 +Japan - for the rest,EUR,Dinner,-20.4 +Jordan,EUR,Full Day,46 +Jordan,EUR,Part Day,31 +Jordan,EUR,Overnight,126 +Jordan,EUR,Breakfast,-9.2 +Jordan,EUR,Lunch,-18.4 +Jordan,EUR,Dinner,-18.4 +Kazakhstan,EUR,Full Day,45 +Kazakhstan,EUR,Part Day,30 +Kazakhstan,EUR,Overnight,111 +Kazakhstan,EUR,Breakfast,-9 +Kazakhstan,EUR,Lunch,-18 +Kazakhstan,EUR,Dinner,-18 +Kenya,EUR,Full Day,42 +Kenya,EUR,Part Day,28 +Kenya,EUR,Overnight,223 +Kenya,EUR,Breakfast,-8.4 +Kenya,EUR,Lunch,-16.8 +Kenya,EUR,Dinner,-16.8 +"Korea, Democratic People's Republic",EUR,Full Day,39 +"Korea, Democratic People's Republic",EUR,Part Day,26 +"Korea, Democratic People's Republic",EUR,Overnight,132 +"Korea, Democratic People's Republic",EUR,Breakfast,-7.8 +"Korea, Democratic People's Republic",EUR,Lunch,-15.6 +"Korea, Democratic People's Republic",EUR,Dinner,-15.6 +"Korea, People's Republic",EUR,Full Day,58 +"Korea, People's Republic",EUR,Part Day,39 +"Korea, People's Republic",EUR,Overnight,112 +"Korea, People's Republic",EUR,Breakfast,-11.6 +"Korea, People's Republic",EUR,Lunch,-23.2 +"Korea, People's Republic",EUR,Dinner,-23.2 +Kosovo,EUR,Full Day,23 +Kosovo,EUR,Part Day,16 +Kosovo,EUR,Overnight,57 +Kosovo,EUR,Breakfast,-4.6 +Kosovo,EUR,Lunch,-9.2 +Kosovo,EUR,Dinner,-9.2 +Kuwait,EUR,Full Day,42 +Kuwait,EUR,Part Day,28 +Kuwait,EUR,Overnight,185 +Kuwait,EUR,Breakfast,-8.4 +Kuwait,EUR,Lunch,-16.8 +Kuwait,EUR,Dinner,-16.8 +Kyrgyzstan,EUR,Full Day,29 +Kyrgyzstan,EUR,Part Day,20 +Kyrgyzstan,EUR,Overnight,91 +Kyrgyzstan,EUR,Breakfast,-5.8 +Kyrgyzstan,EUR,Lunch,-11.6 +Kyrgyzstan,EUR,Dinner,-11.6 +Laos,EUR,Full Day,33 +Laos,EUR,Part Day,22 +Laos,EUR,Overnight,96 +Laos,EUR,Breakfast,-6.6 +Laos,EUR,Lunch,-13.2 +Laos,EUR,Dinner,-13.2 +Latvia,EUR,Full Day,30 +Latvia,EUR,Part Day,20 +Latvia,EUR,Overnight,80 +Latvia,EUR,Breakfast,-6 +Latvia,EUR,Lunch,-12 +Latvia,EUR,Dinner,-12 +Lebanon,EUR,Full Day,59 +Lebanon,EUR,Part Day,40 +Lebanon,EUR,Overnight,123 +Lebanon,EUR,Breakfast,-11.8 +Lebanon,EUR,Lunch,-23.6 +Lebanon,EUR,Dinner,-23.6 +Lesotho,EUR,Full Day,24 +Lesotho,EUR,Part Day,16 +Lesotho,EUR,Overnight,103 +Lesotho,EUR,Breakfast,-4.8 +Lesotho,EUR,Lunch,-9.6 +Lesotho,EUR,Dinner,-9.6 +Libya,EUR,Full Day,63 +Libya,EUR,Part Day,42 +Libya,EUR,Overnight,135 +Libya,EUR,Breakfast,-12.6 +Libya,EUR,Lunch,-25.2 +Libya,EUR,Dinner,-25.2 +Liechtenstein,EUR,Full Day,53 +Liechtenstein,EUR,Part Day,36 +Liechtenstein,EUR,Overnight,180 +Liechtenstein,EUR,Breakfast,-10.6 +Liechtenstein,EUR,Lunch,-21.2 +Liechtenstein,EUR,Dinner,-21.2 +Lithuania,EUR,Full Day,24 +Lithuania,EUR,Part Day,16 +Lithuania,EUR,Overnight,68 +Lithuania,EUR,Breakfast,-4.8 +Lithuania,EUR,Lunch,-9.6 +Lithuania,EUR,Dinner,-9.6 +Luxembourg,EUR,Full Day,47 +Luxembourg,EUR,Part Day,32 +Luxembourg,EUR,Overnight,130 +Luxembourg,EUR,Breakfast,-9.4 +Luxembourg,EUR,Lunch,-18.8 +Luxembourg,EUR,Dinner,-18.8 +Macedonia,EUR,Full Day,29 +Macedonia,EUR,Part Day,20 +Macedonia,EUR,Overnight,95 +Macedonia,EUR,Breakfast,-5.8 +Macedonia,EUR,Lunch,-11.6 +Macedonia,EUR,Dinner,-11.6 +Madagascar,EUR,Full Day,34 +Madagascar,EUR,Part Day,23 +Madagascar,EUR,Overnight,87 +Madagascar,EUR,Breakfast,-6.8 +Madagascar,EUR,Lunch,-13.6 +Madagascar,EUR,Dinner,-13.6 +Malawi,EUR,Full Day,47 +Malawi,EUR,Part Day,32 +Malawi,EUR,Overnight,123 +Malawi,EUR,Breakfast,-9.4 +Malawi,EUR,Lunch,-18.8 +Malawi,EUR,Dinner,-18.8 +Malaysia,EUR,Full Day,34 +Malaysia,EUR,Part Day,23 +Malaysia,EUR,Overnight,88 +Malaysia,EUR,Breakfast,-6.8 +Malaysia,EUR,Lunch,-13.6 +Malaysia,EUR,Dinner,-13.6 +Maldives,EUR,Full Day,52 +Maldives,EUR,Part Day,35 +Maldives,EUR,Overnight,170 +Maldives,EUR,Breakfast,-10.4 +Maldives,EUR,Lunch,-20.8 +Maldives,EUR,Dinner,-20.8 +Mali,EUR,Full Day,41 +Mali,EUR,Part Day,28 +Mali,EUR,Overnight,122 +Mali,EUR,Breakfast,-8.2 +Mali,EUR,Lunch,-16.4 +Mali,EUR,Dinner,-16.4 +Malta,EUR,Full Day,45 +Malta,EUR,Part Day,30 +Malta,EUR,Overnight,112 +Malta,EUR,Breakfast,-9 +Malta,EUR,Lunch,-18 +Malta,EUR,Dinner,-18 +Marshall Islands,EUR,Full Day,63 +Marshall Islands,EUR,Part Day,42 +Marshall Islands,EUR,Overnight,102 +Marshall Islands,EUR,Breakfast,-12.6 +Marshall Islands,EUR,Lunch,-25.2 +Marshall Islands,EUR,Dinner,-25.2 +Mauritania,EUR,Full Day,39 +Mauritania,EUR,Part Day,26 +Mauritania,EUR,Overnight,105 +Mauritania,EUR,Breakfast,-7.8 +Mauritania,EUR,Lunch,-15.6 +Mauritania,EUR,Dinner,-15.6 +Mauritius,EUR,Full Day,54 +Mauritius,EUR,Part Day,36 +Mauritius,EUR,Overnight,220 +Mauritius,EUR,Breakfast,-10.8 +Mauritius,EUR,Lunch,-21.6 +Mauritius,EUR,Dinner,-21.6 +Mexico,EUR,Full Day,41 +Mexico,EUR,Part Day,28 +Mexico,EUR,Overnight,141 +Mexico,EUR,Breakfast,-8.2 +Mexico,EUR,Lunch,-16.4 +Mexico,EUR,Dinner,-16.4 +Micronesia,EUR,Full Day,33 +Micronesia,EUR,Part Day,22 +Micronesia,EUR,Overnight,116 +Micronesia,EUR,Breakfast,-6.6 +Micronesia,EUR,Lunch,-13.2 +Micronesia,EUR,Dinner,-13.2 +"Moldova, Republic",EUR,Full Day,24 +"Moldova, Republic",EUR,Part Day,16 +"Moldova, Republic",EUR,Overnight,88 +"Moldova, Republic",EUR,Breakfast,-4.8 +"Moldova, Republic",EUR,Lunch,-9.6 +"Moldova, Republic",EUR,Dinner,-9.6 +Monaco,EUR,Full Day,42 +Monaco,EUR,Part Day,28 +Monaco,EUR,Overnight,180 +Monaco,EUR,Breakfast,-8.4 +Monaco,EUR,Lunch,-16.8 +Monaco,EUR,Dinner,-16.8 +Mongolia,EUR,Full Day,27 +Mongolia,EUR,Part Day,18 +Mongolia,EUR,Overnight,92 +Mongolia,EUR,Breakfast,-5.4 +Mongolia,EUR,Lunch,-10.8 +Mongolia,EUR,Dinner,-10.8 +Montenegro,EUR,Full Day,29 +Montenegro,EUR,Part Day,20 +Montenegro,EUR,Overnight,94 +Montenegro,EUR,Breakfast,-5.8 +Montenegro,EUR,Lunch,-11.6 +Montenegro,EUR,Dinner,-11.6 +Morocco,EUR,Full Day,42 +Morocco,EUR,Part Day,28 +Morocco,EUR,Overnight,129 +Morocco,EUR,Breakfast,-8.4 +Morocco,EUR,Lunch,-16.8 +Morocco,EUR,Dinner,-16.8 +Mozambique,EUR,Full Day,38 +Mozambique,EUR,Part Day,25 +Mozambique,EUR,Overnight,146 +Mozambique,EUR,Breakfast,-7.6 +Mozambique,EUR,Lunch,-15.2 +Mozambique,EUR,Dinner,-15.2 +Myanmar,EUR,Full Day,35 +Myanmar,EUR,Part Day,24 +Myanmar,EUR,Overnight,155 +Myanmar,EUR,Breakfast,-7 +Myanmar,EUR,Lunch,-14 +Myanmar,EUR,Dinner,-14 +Namibia,EUR,Full Day,23 +Namibia,EUR,Part Day,16 +Namibia,EUR,Overnight,77 +Namibia,EUR,Breakfast,-4.6 +Namibia,EUR,Lunch,-9.2 +Namibia,EUR,Dinner,-9.2 +Nepal,EUR,Full Day,28 +Nepal,EUR,Part Day,19 +Nepal,EUR,Overnight,86 +Nepal,EUR,Breakfast,-5.6 +Nepal,EUR,Lunch,-11.2 +Nepal,EUR,Dinner,-11.2 +Netherlands,EUR,Full Day,46 +Netherlands,EUR,Part Day,31 +Netherlands,EUR,Overnight,119 +Netherlands,EUR,Breakfast,-9.2 +Netherlands,EUR,Lunch,-18.4 +Netherlands,EUR,Dinner,-18.4 +New Zealand,EUR,Full Day,56 +New Zealand,EUR,Part Day,37 +New Zealand,EUR,Overnight,153 +New Zealand,EUR,Breakfast,-11.2 +New Zealand,EUR,Lunch,-22.4 +New Zealand,EUR,Dinner,-22.4 +Nicaragua,EUR,Full Day,36 +Nicaragua,EUR,Part Day,24 +Nicaragua,EUR,Overnight,81 +Nicaragua,EUR,Breakfast,-7.2 +Nicaragua,EUR,Lunch,-14.4 +Nicaragua,EUR,Dinner,-14.4 +Niger,EUR,Full Day,41 +Niger,EUR,Part Day,28 +Niger,EUR,Overnight,89 +Niger,EUR,Breakfast,-8.2 +Niger,EUR,Lunch,-16.4 +Niger,EUR,Dinner,-16.4 +Nigeria,EUR,Full Day,63 +Nigeria,EUR,Part Day,42 +Nigeria,EUR,Overnight,255 +Nigeria,EUR,Breakfast,-12.6 +Nigeria,EUR,Lunch,-25.2 +Nigeria,EUR,Dinner,-25.2 +Norway,EUR,Full Day,80 +Norway,EUR,Part Day,53 +Norway,EUR,Overnight,182 +Norway,EUR,Breakfast,-16 +Norway,EUR,Lunch,-32 +Norway,EUR,Dinner,-32 +Oman,EUR,Full Day,60 +Oman,EUR,Part Day,40 +Oman,EUR,Overnight,200 +Oman,EUR,Breakfast,-12 +Oman,EUR,Lunch,-24 +Oman,EUR,Dinner,-24 +Pakistan - Islamabad,EUR,Full Day,30 +Pakistan - Islamabad,EUR,Part Day,20 +Pakistan - Islamabad,EUR,Overnight,165 +Pakistan - Islamabad,EUR,Breakfast,-6 +Pakistan - Islamabad,EUR,Lunch,-12 +Pakistan - Islamabad,EUR,Dinner,-12 +Pakistan - for the rest,EUR,Full Day,27 +Pakistan - for the rest,EUR,Part Day,18 +Pakistan - for the rest,EUR,Overnight,68 +Pakistan - for the rest,EUR,Breakfast,-5.4 +Pakistan - for the rest,EUR,Lunch,-10.8 +Pakistan - for the rest,EUR,Dinner,-10.8 +Palau,EUR,Full Day,51 +Palau,EUR,Part Day,34 +Palau,EUR,Overnight,179 +Palau,EUR,Breakfast,-10.2 +Palau,EUR,Lunch,-20.4 +Palau,EUR,Dinner,-20.4 +Panama,EUR,Full Day,39 +Panama,EUR,Part Day,26 +Panama,EUR,Overnight,111 +Panama,EUR,Breakfast,-7.8 +Panama,EUR,Lunch,-15.6 +Panama,EUR,Dinner,-15.6 +Papua New Guinea,EUR,Full Day,60 +Papua New Guinea,EUR,Part Day,40 +Papua New Guinea,EUR,Overnight,234 +Papua New Guinea,EUR,Breakfast,-12 +Papua New Guinea,EUR,Lunch,-24 +Papua New Guinea,EUR,Dinner,-24 +Paraguay,EUR,Full Day,38 +Paraguay,EUR,Part Day,25 +Paraguay,EUR,Overnight,108 +Paraguay,EUR,Breakfast,-7.6 +Paraguay,EUR,Lunch,-15.2 +Paraguay,EUR,Dinner,-15.2 +Peru,EUR,Full Day,30 +Peru,EUR,Part Day,20 +Peru,EUR,Overnight,93 +Peru,EUR,Breakfast,-6 +Peru,EUR,Lunch,-12 +Peru,EUR,Dinner,-12 +Philippines,EUR,Full Day,33 +Philippines,EUR,Part Day,22 +Philippines,EUR,Overnight,116 +Philippines,EUR,Breakfast,-6.6 +Philippines,EUR,Lunch,-13.2 +Philippines,EUR,Dinner,-13.2 +Poland - Danzig,EUR,Full Day,30 +Poland - Danzig,EUR,Part Day,20 +Poland - Danzig,EUR,Overnight,84 +Poland - Danzig,EUR,Breakfast,-6 +Poland - Danzig,EUR,Lunch,-12 +Poland - Danzig,EUR,Dinner,-12 +Poland - Kraków,EUR,Full Day,27 +Poland - Kraków,EUR,Part Day,18 +Poland - Kraków,EUR,Overnight,86 +Poland - Kraków,EUR,Breakfast,-5.4 +Poland - Kraków,EUR,Lunch,-10.8 +Poland - Kraków,EUR,Dinner,-10.8 +Poland - Warsaw,EUR,Full Day,29 +Poland - Warsaw,EUR,Part Day,20 +Poland - Warsaw,EUR,Overnight,109 +Poland - Warsaw,EUR,Breakfast,-5.8 +Poland - Warsaw,EUR,Lunch,-11.6 +Poland - Warsaw,EUR,Dinner,-11.6 +Poland - Wrocław,EUR,Full Day,33 +Poland - Wrocław,EUR,Part Day,22 +Poland - Wrocław,EUR,Overnight,117 +Poland - Wrocław,EUR,Breakfast,-6.6 +Poland - Wrocław,EUR,Lunch,-13.2 +Poland - Wrocław,EUR,Dinner,-13.2 +Poland - for the rest,EUR,Full Day,29 +Poland - for the rest,EUR,Part Day,20 +Poland - for the rest,EUR,Overnight,60 +Poland - for the rest,EUR,Breakfast,-5.8 +Poland - for the rest,EUR,Lunch,-11.6 +Poland - for the rest,EUR,Dinner,-11.6 +Portugal,EUR,Full Day,36 +Portugal,EUR,Part Day,24 +Portugal,EUR,Overnight,102 +Portugal,EUR,Breakfast,-7.2 +Portugal,EUR,Lunch,-14.4 +Portugal,EUR,Dinner,-14.4 +Qatar,EUR,Full Day,56 +Qatar,EUR,Part Day,37 +Qatar,EUR,Overnight,170 +Qatar,EUR,Breakfast,-11.2 +Qatar,EUR,Lunch,-22.4 +Qatar,EUR,Dinner,-22.4 +Romania - Bucharest,EUR,Full Day,32 +Romania - Bucharest,EUR,Part Day,21 +Romania - Bucharest,EUR,Overnight,100 +Romania - Bucharest,EUR,Breakfast,-6.4 +Romania - Bucharest,EUR,Lunch,-12.8 +Romania - Bucharest,EUR,Dinner,-12.8 +Romania - for the rest,EUR,Full Day,26 +Romania - for the rest,EUR,Part Day,17 +Romania - for the rest,EUR,Overnight,62 +Romania - for the rest,EUR,Breakfast,-5.2 +Romania - for the rest,EUR,Lunch,-10.4 +Romania - for the rest,EUR,Dinner,-10.4 +Russia - Moscow,EUR,Full Day,30 +Russia - Moscow,EUR,Part Day,20 +Russia - Moscow,EUR,Overnight,110 +Russia - Moscow,EUR,Breakfast,-6 +Russia - Moscow,EUR,Lunch,-12 +Russia - Moscow,EUR,Dinner,-12 +Russia - St. Petersburg,EUR,Full Day,26 +Russia - St. Petersburg,EUR,Part Day,17 +Russia - St. Petersburg,EUR,Overnight,114 +Russia - St. Petersburg,EUR,Breakfast,-5.2 +Russia - St. Petersburg,EUR,Lunch,-10.4 +Russia - St. Petersburg,EUR,Dinner,-10.4 +Russia - Yekaterinburg,EUR,Full Day,28 +Russia - Yekaterinburg,EUR,Part Day,19 +Russia - Yekaterinburg,EUR,Overnight,84 +Russia - Yekaterinburg,EUR,Breakfast,-5.6 +Russia - Yekaterinburg,EUR,Lunch,-11.2 +Russia - Yekaterinburg,EUR,Dinner,-11.2 +Russia - for the rest,EUR,Full Day,24 +Russia - for the rest,EUR,Part Day,16 +Russia - for the rest,EUR,Overnight,58 +Russia - for the rest,EUR,Breakfast,-4.8 +Russia - for the rest,EUR,Lunch,-9.6 +Russia - for the rest,EUR,Dinner,-9.6 +Rwanda,EUR,Full Day,46 +Rwanda,EUR,Part Day,31 +Rwanda,EUR,Overnight,141 +Rwanda,EUR,Breakfast,-9.2 +Rwanda,EUR,Lunch,-18.4 +Rwanda,EUR,Dinner,-18.4 +Samoa,EUR,Full Day,29 +Samoa,EUR,Part Day,20 +Samoa,EUR,Overnight,85 +Samoa,EUR,Breakfast,-5.8 +Samoa,EUR,Lunch,-11.6 +Samoa,EUR,Dinner,-11.6 +San Marino,EUR,Full Day,34 +San Marino,EUR,Part Day,23 +San Marino,EUR,Overnight,75 +San Marino,EUR,Breakfast,-6.8 +San Marino,EUR,Lunch,-13.6 +San Marino,EUR,Dinner,-13.6 +São Tomé and Príncipe,EUR,Full Day,47 +São Tomé and Príncipe,EUR,Part Day,32 +São Tomé and Príncipe,EUR,Overnight,80 +São Tomé and Príncipe,EUR,Breakfast,-9.4 +São Tomé and Príncipe,EUR,Lunch,-18.8 +São Tomé and Príncipe,EUR,Dinner,-18.8 +Saudi Arabia - Jeddah,EUR,Full Day,38 +Saudi Arabia - Jeddah,EUR,Part Day,25 +Saudi Arabia - Jeddah,EUR,Overnight,234 +Saudi Arabia - Jeddah,EUR,Breakfast,-7.6 +Saudi Arabia - Jeddah,EUR,Lunch,-15.2 +Saudi Arabia - Jeddah,EUR,Dinner,-15.2 +Saudi Arabia - Riyadh,EUR,Full Day,48 +Saudi Arabia - Riyadh,EUR,Part Day,32 +Saudi Arabia - Riyadh,EUR,Overnight,179 +Saudi Arabia - Riyadh,EUR,Breakfast,-9.6 +Saudi Arabia - Riyadh,EUR,Lunch,-19.2 +Saudi Arabia - Riyadh,EUR,Dinner,-19.2 +Saudi Arabia - for the rest,EUR,Full Day,48 +Saudi Arabia - for the rest,EUR,Part Day,32 +Saudi Arabia - for the rest,EUR,Overnight,80 +Saudi Arabia - for the rest,EUR,Breakfast,-9.6 +Saudi Arabia - for the rest,EUR,Lunch,-19.2 +Saudi Arabia - for the rest,EUR,Dinner,-19.2 +Senegal,EUR,Full Day,45 +Senegal,EUR,Part Day,30 +Senegal,EUR,Overnight,128 +Senegal,EUR,Breakfast,-9 +Senegal,EUR,Lunch,-18 +Senegal,EUR,Dinner,-18 +Serbia,EUR,Full Day,20 +Serbia,EUR,Part Day,13 +Serbia,EUR,Overnight,74 +Serbia,EUR,Breakfast,-4 +Serbia,EUR,Lunch,-8 +Serbia,EUR,Dinner,-8 +Sierra Leone,EUR,Full Day,48 +Sierra Leone,EUR,Part Day,32 +Sierra Leone,EUR,Overnight,161 +Sierra Leone,EUR,Breakfast,-9.6 +Sierra Leone,EUR,Lunch,-19.2 +Sierra Leone,EUR,Dinner,-19.2 +Singapore,EUR,Full Day,54 +Singapore,EUR,Part Day,36 +Singapore,EUR,Overnight,197 +Singapore,EUR,Breakfast,-10.8 +Singapore,EUR,Lunch,-21.6 +Singapore,EUR,Dinner,-21.6 +Slovakia,EUR,Full Day,24 +Slovakia,EUR,Part Day,16 +Slovakia,EUR,Overnight,85 +Slovakia,EUR,Breakfast,-4.8 +Slovakia,EUR,Lunch,-9.6 +Slovakia,EUR,Dinner,-9.6 +Slovenia,EUR,Full Day,33 +Slovenia,EUR,Part Day,22 +Slovenia,EUR,Overnight,95 +Slovenia,EUR,Breakfast,-6.6 +Slovenia,EUR,Lunch,-13.2 +Slovenia,EUR,Dinner,-13.2 +South Africa - Cape Town,EUR,Full Day,27 +South Africa - Cape Town,EUR,Part Day,18 +South Africa - Cape Town,EUR,Overnight,112 +South Africa - Cape Town,EUR,Breakfast,-5.4 +South Africa - Cape Town,EUR,Lunch,-10.8 +South Africa - Cape Town,EUR,Dinner,-10.8 +South Africa - Johannesburg,EUR,Full Day,29 +South Africa - Johannesburg,EUR,Part Day,20 +South Africa - Johannesburg,EUR,Overnight,124 +South Africa - Johannesburg,EUR,Breakfast,-5.8 +South Africa - Johannesburg,EUR,Lunch,-11.6 +South Africa - Johannesburg,EUR,Dinner,-11.6 +Souh Africa - for the rest,EUR,Full Day,22 +Souh Africa - for the rest,EUR,Part Day,15 +Souh Africa - for the rest,EUR,Overnight,94 +Souh Africa - for the rest,EUR,Breakfast,-4.4 +Souh Africa - for the rest,EUR,Lunch,-8.8 +Souh Africa - for the rest,EUR,Dinner,-8.8 +South Sudan,EUR,Full Day,34 +South Sudan,EUR,Part Day,23 +South Sudan,EUR,Overnight,150 +South Sudan,EUR,Breakfast,-6.8 +South Sudan,EUR,Lunch,-13.6 +South Sudan,EUR,Dinner,-13.6 +Spain - Barcelona,EUR,Full Day,34 +Spain - Barcelona,EUR,Part Day,23 +Spain - Barcelona,EUR,Overnight,118 +Spain - Barcelona,EUR,Breakfast,-6.8 +Spain - Barcelona,EUR,Lunch,-13.6 +Spain - Barcelona,EUR,Dinner,-13.6 +Spain - Canary Islands,EUR,Full Day,40 +Spain - Canary Islands,EUR,Part Day,27 +Spain - Canary Islands,EUR,Overnight,115 +Spain - Canary Islands,EUR,Breakfast,-8 +Spain - Canary Islands,EUR,Lunch,-16 +Spain - Canary Islands,EUR,Dinner,-16 +Spain - Madrid,EUR,Full Day,40 +Spain - Madrid,EUR,Part Day,27 +Spain - Madrid,EUR,Overnight,118 +Spain - Madrid,EUR,Breakfast,-8 +Spain - Madrid,EUR,Lunch,-16 +Spain - Madrid,EUR,Dinner,-16 +Spain - Palma de Mallorca,EUR,Full Day,35 +Spain - Palma de Mallorca,EUR,Part Day,24 +Spain - Palma de Mallorca,EUR,Overnight,121 +Spain - Palma de Mallorca,EUR,Breakfast,-7 +Spain - Palma de Mallorca,EUR,Lunch,-14 +Spain - Palma de Mallorca,EUR,Dinner,-14 +Spain - for the rest,EUR,Full Day,34 +Spain - for the rest,EUR,Part Day,23 +Spain - for the rest,EUR,Overnight,115 +Spain - for the rest,EUR,Breakfast,-6.8 +Spain - for the rest,EUR,Lunch,-13.6 +Spain - for the rest,EUR,Dinner,-13.6 +Sri Lanka,EUR,Full Day,42 +Sri Lanka,EUR,Part Day,28 +Sri Lanka,EUR,Overnight,100 +Sri Lanka,EUR,Breakfast,-8.4 +Sri Lanka,EUR,Lunch,-16.8 +Sri Lanka,EUR,Dinner,-16.8 +St. Kitts and Nevis,EUR,Full Day,45 +St. Kitts and Nevis,EUR,Part Day,30 +St. Kitts and Nevis,EUR,Overnight,177 +St. Kitts and Nevis,EUR,Breakfast,-9 +St. Kitts and Nevis,EUR,Lunch,-18 +St. Kitts and Nevis,EUR,Dinner,-18 +St. Lucia,EUR,Full Day,45 +St. Lucia,EUR,Part Day,30 +St. Lucia,EUR,Overnight,177 +St. Lucia,EUR,Breakfast,-9 +St. Lucia,EUR,Lunch,-18 +St. Lucia,EUR,Dinner,-18 +St. Vincent and the Grenadines,EUR,Full Day,45 +St. Vincent and the Grenadines,EUR,Part Day,30 +St. Vincent and the Grenadines,EUR,Overnight,177 +St. Vincent and the Grenadines,EUR,Breakfast,-9 +St. Vincent and the Grenadines,EUR,Lunch,-18 +St. Vincent and the Grenadines,EUR,Dinner,-18 +Sudan,EUR,Full Day,35 +Sudan,EUR,Part Day,24 +Sudan,EUR,Overnight,115 +Sudan,EUR,Breakfast,-7 +Sudan,EUR,Lunch,-14 +Sudan,EUR,Dinner,-14 +Suriname,EUR,Full Day,45 +Suriname,EUR,Part Day,30 +Suriname,EUR,Overnight,177 +Suriname,EUR,Breakfast,-9 +Suriname,EUR,Lunch,-18 +Suriname,EUR,Dinner,-18 +Sweden,EUR,Full Day,50 +Sweden,EUR,Part Day,33 +Sweden,EUR,Overnight,168 +Sweden,EUR,Breakfast,-10 +Sweden,EUR,Lunch,-20 +Sweden,EUR,Dinner,-20 +Switzerland - Geneva,EUR,Full Day,64 +Switzerland - Geneva,EUR,Part Day,43 +Switzerland - Geneva,EUR,Overnight,195 +Switzerland - Geneva,EUR,Breakfast,-12.8 +Switzerland - Geneva,EUR,Lunch,-25.6 +Switzerland - Geneva,EUR,Dinner,-25.6 +Switzerland - for the rest,EUR,Full Day,62 +Switzerland - for the rest,EUR,Part Day,41 +Switzerland - for the rest,EUR,Overnight,169 +Switzerland - for the rest,EUR,Breakfast,-12.4 +Switzerland - for the rest,EUR,Lunch,-24.8 +Switzerland - for the rest,EUR,Dinner,-24.8 +Syria,EUR,Full Day,38 +Syria,EUR,Part Day,25 +Syria,EUR,Overnight,140 +Syria,EUR,Breakfast,-7.6 +Syria,EUR,Lunch,-15.2 +Syria,EUR,Dinner,-15.2 +Taiwan,EUR,Full Day,51 +Taiwan,EUR,Part Day,34 +Taiwan,EUR,Overnight,126 +Taiwan,EUR,Breakfast,-10.2 +Taiwan,EUR,Lunch,-20.4 +Taiwan,EUR,Dinner,-20.4 +Tajikistan,EUR,Full Day,27 +Tajikistan,EUR,Part Day,18 +Tajikistan,EUR,Overnight,118 +Tajikistan,EUR,Breakfast,-5.4 +Tajikistan,EUR,Lunch,-10.8 +Tajikistan,EUR,Dinner,-10.8 +Tanzania,EUR,Full Day,47 +Tanzania,EUR,Part Day,32 +Tanzania,EUR,Overnight,201 +Tanzania,EUR,Breakfast,-9.4 +Tanzania,EUR,Lunch,-18.8 +Tanzania,EUR,Dinner,-18.8 +Thailand,EUR,Full Day,38 +Thailand,EUR,Part Day,25 +Thailand,EUR,Overnight,110 +Thailand,EUR,Breakfast,-7.6 +Thailand,EUR,Lunch,-15.2 +Thailand,EUR,Dinner,-15.2 +Togo,EUR,Full Day,35 +Togo,EUR,Part Day,24 +Togo,EUR,Overnight,108 +Togo,EUR,Breakfast,-7 +Togo,EUR,Lunch,-14 +Togo,EUR,Dinner,-14 +Tonga,EUR,Full Day,39 +Tonga,EUR,Part Day,26 +Tonga,EUR,Overnight,94 +Tonga,EUR,Breakfast,-7.8 +Tonga,EUR,Lunch,-15.6 +Tonga,EUR,Dinner,-15.6 +Trinidad and Tobago,EUR,Full Day,45 +Trinidad and Tobago,EUR,Part Day,30 +Trinidad and Tobago,EUR,Overnight,177 +Trinidad and Tobago,EUR,Breakfast,-9 +Trinidad and Tobago,EUR,Lunch,-18 +Trinidad and Tobago,EUR,Dinner,-18 +Tunisia,EUR,Full Day,40 +Tunisia,EUR,Part Day,27 +Tunisia,EUR,Overnight,115 +Tunisia,EUR,Breakfast,-8 +Tunisia,EUR,Lunch,-16 +Tunisia,EUR,Dinner,-16 +Turkey - Istanbul,EUR,Full Day,35 +Turkey - Istanbul,EUR,Part Day,24 +Turkey - Istanbul,EUR,Overnight,104 +Turkey - Istanbul,EUR,Breakfast,-7 +Turkey - Istanbul,EUR,Lunch,-14 +Turkey - Istanbul,EUR,Dinner,-14 +Turkey - Izmir,EUR,Full Day,42 +Turkey - Izmir,EUR,Part Day,28 +Turkey - Izmir,EUR,Overnight,80 +Turkey - Izmir,EUR,Breakfast,-8.4 +Turkey - Izmir,EUR,Lunch,-16.8 +Turkey - Izmir,EUR,Dinner,-16.8 +Turkey - for the rest,EUR,Full Day,40 +Turkey - for the rest,EUR,Part Day,27 +Turkey - for the rest,EUR,Overnight,78 +Turkey - for the rest,EUR,Breakfast,-8 +Turkey - for the rest,EUR,Lunch,-16 +Turkey - for the rest,EUR,Dinner,-16 +Turkmenistan,EUR,Full Day,33 +Turkmenistan,EUR,Part Day,22 +Turkmenistan,EUR,Overnight,108 +Turkmenistan,EUR,Breakfast,-6.6 +Turkmenistan,EUR,Lunch,-13.2 +Turkmenistan,EUR,Dinner,-13.2 +Uganda,EUR,Full Day,35 +Uganda,EUR,Part Day,24 +Uganda,EUR,Overnight,129 +Uganda,EUR,Breakfast,-7 +Uganda,EUR,Lunch,-14 +Uganda,EUR,Dinner,-14 +Ukraine,EUR,Full Day,32 +Ukraine,EUR,Part Day,21 +Ukraine,EUR,Overnight,98 +Ukraine,EUR,Breakfast,-6.4 +Ukraine,EUR,Lunch,-12.8 +Ukraine,EUR,Dinner,-12.8 +United Arab Emirates,EUR,Full Day,45 +United Arab Emirates,EUR,Part Day,30 +United Arab Emirates,EUR,Overnight,155 +United Arab Emirates,EUR,Breakfast,-9 +United Arab Emirates,EUR,Lunch,-18 +United Arab Emirates,EUR,Dinner,-18 +United Kingdom of Great Britain and Ireland,EUR,Full Day, +United Kingdom of Great Britain and Ireland,EUR,Part Day, +United Kingdom of Great Britain and Ireland,EUR,Overnight, +United Kingdom of Great Britain and Ireland,EUR,Breakfast,0 +United Kingdom of Great Britain and Ireland,EUR,Lunch,0 +United Kingdom of Great Britain and Ireland,EUR,Dinner,0 +United Kingdom - London,EUR,Full Day,62 +United Kingdom - London,EUR,Part Day,41 +United Kingdom - London,EUR,Overnight,224 +United Kingdom - London,EUR,Breakfast,-12.4 +United Kingdom - London,EUR,Lunch,-24.8 +United Kingdom - London,EUR,Dinner,-24.8 +United Kingdom - for the rest,EUR,Full Day,45 +United Kingdom - for the rest,EUR,Part Day,30 +United Kingdom - for the rest,EUR,Overnight,115 +United Kingdom - for the rest,EUR,Breakfast,-9 +United Kingdom - for the rest,EUR,Lunch,-18 +United Kingdom - for the rest,EUR,Dinner,-18 +United States of America - Atlanta,EUR,Full Day,62 +United States of America - Atlanta,EUR,Part Day,41 +United States of America - Atlanta,EUR,Overnight,175 +United States of America - Atlanta,EUR,Breakfast,-12.4 +United States of America - Atlanta,EUR,Lunch,-24.8 +United States of America - Atlanta,EUR,Dinner,-24.8 +United States of America - Boston,EUR,Full Day,58 +United States of America - Boston,EUR,Part Day,39 +United States of America - Boston,EUR,Overnight,265 +United States of America - Boston,EUR,Breakfast,-11.6 +United States of America - Boston,EUR,Lunch,-23.2 +United States of America - Boston,EUR,Dinner,-23.2 +United States of America - Chicago,EUR,Full Day,54 +United States of America - Chicago,EUR,Part Day,36 +United States of America - Chicago,EUR,Overnight,209 +United States of America - Chicago,EUR,Breakfast,-10.8 +United States of America - Chicago,EUR,Lunch,-21.6 +United States of America - Chicago,EUR,Dinner,-21.6 +United States of America - Houston,EUR,Full Day,63 +United States of America - Houston,EUR,Part Day,42 +United States of America - Houston,EUR,Overnight,138 +United States of America - Houston,EUR,Breakfast,-12.6 +United States of America - Houston,EUR,Lunch,-25.2 +United States of America - Houston,EUR,Dinner,-25.2 +United States of America - Los Angeles,EUR,Full Day,56 +United States of America - Los Angeles,EUR,Part Day,37 +United States of America - Los Angeles,EUR,Overnight,274 +United States of America - Los Angeles,EUR,Breakfast,-11.2 +United States of America - Los Angeles,EUR,Lunch,-22.4 +United States of America - Los Angeles,EUR,Dinner,-22.4 +United States of America - Miami,EUR,Full Day,64 +United States of America - Miami,EUR,Part Day,43 +United States of America - Miami,EUR,Overnight,151 +United States of America - Miami,EUR,Breakfast,-12.8 +United States of America - Miami,EUR,Lunch,-25.6 +United States of America - Miami,EUR,Dinner,-25.6 +United States of America - New York,EUR,Full Day,58 +United States of America - New York,EUR,Part Day,39 +United States of America - New York,EUR,Overnight,282 +United States of America - New York,EUR,Breakfast,-11.6 +United States of America - New York,EUR,Lunch,-23.2 +United States of America - New York,EUR,Dinner,-23.2 +United States of America - San Francisco,EUR,Full Day,51 +United States of America - San Francisco,EUR,Part Day,34 +United States of America - San Francisco,EUR,Overnight,314 +United States of America - San Francisco,EUR,Breakfast,-10.2 +United States of America - San Francisco,EUR,Lunch,-20.4 +United States of America - San Francisco,EUR,Dinner,-20.4 +United States of America - Washington D.C.,EUR,Full Day,62 +United States of America - Washington D.C.,EUR,Part Day,41 +United States of America - Washington D.C.,EUR,Overnight,276 +United States of America - Washington D.C.,EUR,Breakfast,-12.4 +United States of America - Washington D.C.,EUR,Lunch,-24.8 +United States of America - Washington D.C.,EUR,Dinner,-24.8 +United States of America - for the rest,EUR,Full Day,51 +United States of America - for the rest,EUR,Part Day,34 +United States of America - for the rest,EUR,Overnight,138 +United States of America - for the rest,EUR,Breakfast,-10.2 +United States of America - for the rest,EUR,Lunch,-20.4 +United States of America - for the rest,EUR,Dinner,-20.4 +Uruguay,EUR,Full Day,44 +Uruguay,EUR,Part Day,29 +Uruguay,EUR,Overnight,109 +Uruguay,EUR,Breakfast,-8.8 +Uruguay,EUR,Lunch,-17.6 +Uruguay,EUR,Dinner,-17.6 +Uzbekistan,EUR,Full Day,34 +Uzbekistan,EUR,Part Day,23 +Uzbekistan,EUR,Overnight,123 +Uzbekistan,EUR,Breakfast,-6.8 +Uzbekistan,EUR,Lunch,-13.6 +Uzbekistan,EUR,Dinner,-13.6 +Vatican City,EUR,Full Day,52 +Vatican City,EUR,Part Day,35 +Vatican City,EUR,Overnight,160 +Vatican City,EUR,Breakfast,-10.4 +Vatican City,EUR,Lunch,-20.8 +Vatican City,EUR,Dinner,-20.8 +Venezuela,EUR,Full Day,69 +Venezuela,EUR,Part Day,46 +Venezuela,EUR,Overnight,127 +Venezuela,EUR,Breakfast,-13.8 +Venezuela,EUR,Lunch,-27.6 +Venezuela,EUR,Dinner,-27.6 +Vietnam,EUR,Full Day,41 +Vietnam,EUR,Part Day,28 +Vietnam,EUR,Overnight,86 +Vietnam,EUR,Breakfast,-8.2 +Vietnam,EUR,Lunch,-16.4 +Vietnam,EUR,Dinner,-16.4 +Yemen,EUR,Full Day,24 +Yemen,EUR,Part Day,16 +Yemen,EUR,Overnight,95 +Yemen,EUR,Breakfast,-4.8 +Yemen,EUR,Lunch,-9.6 +Yemen,EUR,Dinner,-9.6 +Zambia,EUR,Full Day,36 +Zambia,EUR,Part Day,24 +Zambia,EUR,Overnight,130 +Zambia,EUR,Breakfast,-7.2 +Zambia,EUR,Lunch,-14.4 +Zambia,EUR,Dinner,-14.4 +Zimbabwe,EUR,Full Day,45 +Zimbabwe,EUR,Part Day,30 +Zimbabwe,EUR,Overnight,140 +Zimbabwe,EUR,Breakfast,-9 +Zimbabwe,EUR,Lunch,-18 +Zimbabwe,EUR,Dinner,-18 \ No newline at end of file diff --git a/docs/assets/Files/South-Africa-per-diem.csv b/docs/assets/Files/South-Africa-per-diem.csv new file mode 100644 index 000000000000..d8ed328edec6 --- /dev/null +++ b/docs/assets/Files/South-Africa-per-diem.csv @@ -0,0 +1,186 @@ +rateID,Destination,Subrate,Amount,Currency +5e6618c88c782,Albania,International,100,EUR +5e6618c88c788,Algeria,International,110,EUR +5e6618c88c78a,Angola,International,303,USD +5e6618c88c78b,Antigua and Barbuda,International,220,USD +5e6618c88c78d,Argentina,International,133,USD +5e6618c88c78e,Armenia,International,220,USD +5e6618c88c78f,Australia,International,230,AUD +5e6618c88c790,Austria,International,131,EUR +5e6618c88c792,Bahamas,International,191,USD +5e6618c88c793,Bahrain,International,36,BHD +5e6618c88c794,Bangladesh,International,79,USD +5e6618c88c795,Barbados,International,202,USD +5e6618c88c796,Belarus,International,62,EUR +5e6618c88c798,Belgium,International,146,EUR +5e6618c88c799,Belize,International,152,USD +5e6618c88c79a,Benin,International,111,EUR +5e6618c88c79b,Bolivia,International,78,USD +5e6618c88c79c,Bosnia-Herzegovina,International,75,EUR +5e6618c88c79d,Botswana,International,826,BWP +5e6618c88c79e,Brazil,International,409,BRL +5e6618c88c79f,Brunei,International,88,USD +5e6618c88c7a0,Bulgaria,International,91,EUR +5e6618c88c7a1,Burkina Faso,International,"58,790.00",XAF +5e6618c88c7a3,Burundi,International,73,EUR +5e6618c88c7a4,Cambodia,International,99,USD +5e6618c88c7a5,Cameroon,International,120,EUR +5e6618c88c7a6,Cape Verde Islands,International,65,EUR +5e6618c88c7a7,Central African Republic,International,94,EUR +5e6618c88c7a9,Chad,International,121,EUR +5e6618c88c7aa,Chile,International,106,USD +5e6618c88c7af,China (People's Republic),International,127,USD +5e6618c88c7b0,Colombia,International,94,USD +5e6618c88c7b1,Comoro Island,International,122,EUR +5e6618c88c7b2,Cook Islands,International,211,NZD +5e6618c88c7b3,Costa Rica,International,116,USD +5e6618c88c7b4,Cote D'Ivoire,International,119,EUR +5e6618c88c7b6,Croatia,International,99,EUR +5e6618c88c7b7,Cuba,International,114,USD +5e6618c88c7b8,Cyprus,International,117,EUR +5e6618c88c7b9,Czech Republic,International,90,EUR +5e6618c88c7ba,Democratic Republic of Congo,International,164,USD +5e6618c88c7bb,Denmark,International,"2,328.00",DKK +5e6618c88c7bc,Djibouti,International,99,USD +5e6618c88c7bd,Dominican Republic,International,99,USD +5e6618c88c7be,Ecuador,International,163,USD +5e6618c88c7bf,Egypt,International,873,USD +5e6618c88c7c0,El Salvador,International,98,USD +5e6618c88c7c1,Equatorial Guinea,International,166,EUR +5e6618c88c7c2,Eritrea,International,109,USD +5e6618c88c7c3,Estonia,International,92,EUR +5e6618c88c7c4,Ethiopia,International,95,USD +5e6618c88c7c5,Fiji,International,102,USD +5e6618c88c7c6,Finland,International,171,EUR +5e6618c88c7c7,France,International,129,EUR +5e6618c88c7c8,Gabon,International,160,EUR +5e6618c88c7c9,Gambia,International,74,EUR +5e6618c88c7ca,Georgia,International,95,USD +5e6618c88c7cb,Germany,International,125,EUR +5e6618c88c7cc,Ghana,International,130,USD +5e6618c88c7cd,Greece,International,138,EUR +5e6618c88c7ce,Grenada,International,151,USD +5e6618c88c7cf,Guatemala,International,114,USD +5e6618c88c7d0,Guinea,International,78,EUR +5e6618c88c7d1,Guinea Bissau,International,59,EUR +5e6618c88c7d2,Guyana,International,118,USD +5e6618c88c7d3,Haiti,International,109,USD +5e6618c88c7d4,Honduras,International,186,USD +5e6618c88c7d5,Hong Kong,International,"1,000.00",HKD +5e6618c88c7d6,Hungary,International,92,EUR +5e6618c88c7d7,Iceland,International,"25,466.00",ISK +5e6618c88c7d8,India,International,"5,852.00",INR +5e6618c88c7d9,Indonesia,International,86,USD +5e6618c88c7da,Iran,International,120,USD +5e6618c88c7db,Iraq,International,125,USD +5e6618c88c7dc,Ireland,International,139,EUR +5e6618c88c7dd,Israel,International,209,USD +5e6618c88c7e1,Italy,International,125,EUR +5e6618c88c7e3,Jamaica,International,151,USD +5e6618c88c7e4,Japan,International,"16,275.00",JPY +5e6618c88c7e5,Jordan,International,201,USD +5e6618c88c7e6,Kazakhstan,International,100,USD +5e6618c88c7e7,Kenya,International,138,USD +5e6618c88c7e8,Kiribati,International,233,AUD +5e6618c88c7e9,Kuwait (State of),International,51,KWD +5e6618c88c7ea,Kyrgyzstan,International,172,USD +5e6618c88c7eb,Laos,International,92,USD +5e6618c88c7ec,Latvia,International,150,USD +5e6618c88c7ed,Lebanon,International,158,USD +5e6618c88c7ee,Lesotho,International,750,ZAR +5e6618c88c7ef,Liberia,International,112,USD +5e6618c88c7f0,Libya,International,120,USD +5e6618c88c7f1,Lithuania,International,154,EUR +5e6618c88c7f2,Macedonia (Former Yugoslav),International,100,EUR +5e6618c88c7f3,Madagascar,International,58,EUR +5e6618c88c7f4,Malawi,International,"31,254.00",MWK +5e6618c88c7f5,Malaysia,International,382,MYR +5e6618c88c7f6,Maldives,International,202,USD +5e6618c88c7f7,Mali,International,178,EUR +5e6618c88c7f8,Malta,International,132,EUR +5e6618c88c7f9,Marshall Islands,International,255,USD +5e6618c88c7fa,Mauritania,International,97,EUR +5e6618c88c7fb,Mauritius,International,114,USD +5e6618c88c7fc,Mexico,International,"1,313.00",MXN +5e6618c88c7fd,Moldova,International,117,USD +5e6618c88c7fe,Mongolia,International,69,USD +5e6618c88c7ff,Montenegro,International,94,EUR +5e6618c88c800,Morocco,International,"1,081.00",AED +5e6618c88c801,Mozambique,International,101,USD +5e6618c88c802,Myanmar,International,123,USD +5e6618c88c803,Namibia,International,950,ZAR +5e6618c88c804,Nauru,International,278,AUD +5e6618c88c805,Nepal,International,64,USD +5e6618c88c806,Netherlands,International,122,EUR +5e6618c88c807,New Zealand,International,206,NZD +5e6618c88c808,Nicaragua,International,90,USD +5e6618c88c809,Niger,International,75,EUR +5e6618c88c80a,Nigeria,International,242,USD +5e6618c88c80b,Niue,International,252,NZD +5e6618c88c80c,Norway,International,"1,760.00",NOK +5e6618c88c80d,Oman,International,77,OMR +5e6618c88c80e,Pakistan,International,"6,235.00",PKR +5e6618c88c80f,Palau,International,252,USD +5e6618c88c810,Palestine,International,147,USD +5e6618c88c811,Panama,International,105,USD +5e6618c88c812,Papa New Guinea,International,285,PGK +5e6618c88c813,Paraguay,International,76,USD +5e6618c88c815,Peru,International,139,USD +5e6618c88c816,Philippines,International,122,USD +5e6618c88c817,Poland,International,88,EUR +5e6618c88c818,Portugal,International,87,EUR +5e6618c88c819,Puerto Rico,International,215,USD +5e6618c88c81a,Qatar,International,715,QAR +5e6618c88c81b,Republic of Congo,International,149,EUR +5e6618c88c81c,Reunion,International,164,EUR +5e6618c88c81d,Romania,International,83,EUR +5e6618c88c81e,Russia,International,330,EUR +5e6618c88c81f,Rwanda,International,102,USD +5e6618c88c823,Samoa,International,193,WST +5e6618c88c825,Sao Tome & Principe,International,160,EUR +5e6618c88c826,Saudi Arabia,International,512,SAR +5e6618c88c827,Senegal,International,113,EUR +5e6618c88c829,Serbia,International,83,EUR +5e6618c88c82a,Seychelles,International,132,EUR +5e6618c88c82c,Sierra Leone,International,90,USD +5e6618c88c83b,Slovakia,International,102,EUR +5e6618c88c83c,Slovenia,International,106,EUR +5e6618c88c83d,Solomon Islands,International,"1,107.00",SBD +5e6618c88c83e,South Korea Republic,International,"187,735.00",KRW +5e6618c88c840,South Sudan,International,146,USD +5e6618c88c841,Spain,International,112,EUR +5e6618c88c842,Sri Lanka,International,100,USD +5e6618c88c843,St. Kitts & Nevis,International,227,USD +5e6618c88c844,St. Lucia,International,215,USD +5e6618c88c845,St. Vincent & The Grenadines,International,187,USD +5e6618c88c846,Sudan,International,200,USD +5e6618c88c847,Suriname,International,107,USD +5e6618c88c848,Swaziland,International,"1,367.00",ZAR +5e6618c88c849,Sweden,International,"1,317.00",SEK +5e6618c88c84a,Switzerland,International,201,CHF +5e6618c88c84b,Syria,International,185,USD +5e6618c88c84c,Taiwan,International,"3,505.00",TWD +5e6618c88c84d,Tajikistan,International,97,USD +5e6618c88c84e,Tanzania,International,129,USD +5e6618c88c84f,Togo,International,"64,214.00",XAF +5e6618c88c850,Tonga,International,251,TOP +5e6618c88c851,Trinidad & Tobago,International,213,USD +5e6618c88c852,Tunisia,International,198,TND +5e6618c88c853,Turkey,International,101,EUR +5e6618c88c854,Turkmenistan,International,125,USD +5e6618c88c855,Tuvalu,International,339,AUD +5e6618c88c856,USA,International,215,USD +5e6618c88c857,Uganda,International,111,USD +5e6618c88c858,Ukraine,International,131,EUR +5e6618c88c859,Unit Kingdom - Fiber Lean,International,40,GBP +5e6618c88c85a,United Arab Emirates,International,699,AED +5e6618c88c85e,United Kingdom,International,102,GBP +5e6618c88c860,Uruguay,International,133,USD +5e6618c88c861,Uzbekistan,International,80,EUR +5e6618c88c863,Vanuatu,International,166,USD +5e6618c88c864,Venezuela,International,294,USD +5e6618c88c865,Vietnam,International,91,USD +5e6618c88c866,Yemen,International,94,USD +5e6618c88c867,Zambia,International,119,USD +5e6618c88c868,Zimbabwe,International,123,USD +5e6618c88c8a4,South Africa,Local,452,ZAR \ No newline at end of file diff --git a/docs/assets/Files/Sweden-per-diem.csv b/docs/assets/Files/Sweden-per-diem.csv new file mode 100644 index 000000000000..d20c0ac8e991 --- /dev/null +++ b/docs/assets/Files/Sweden-per-diem.csv @@ -0,0 +1,2037 @@ +Destination,Currency,Subrate,Amount +Albanien (2018),SEK,Normalbelopp,230 +Albanien (2018),SEK,Breakfast,-34.5 +Albanien (2018),SEK,Lunch,-80.5 +Albanien (2018),SEK,Dinner,-80.5 +Albanien (2019),SEK,Normalbelopp,242 +Albanien (2019),SEK,Breakfast,-36.3 +Albanien (2019),SEK,Lunch,-84.7 +Albanien (2019),SEK,Dinner,-84.7 +Albanien (2020),SEK,Normalbelopp,254 +Albanien (2020),SEK,Breakfast,-38.1 +Albanien (2020),SEK,Lunch,-88.9 +Albanien (2020),SEK,Dinner,-88.9 +Algeriet (2018),SEK,Normalbelopp,335 +Algeriet (2018),SEK,Breakfast,-50.25 +Algeriet (2018),SEK,Lunch,-117.25 +Algeriet (2018),SEK,Dinner,-117.25 +Algeriet (2019),SEK,Normalbelopp,362 +Algeriet (2019),SEK,Breakfast,-54.3 +Algeriet (2019),SEK,Lunch,-126.7 +Algeriet (2019),SEK,Dinner,-126.7 +Algeriet (2020),SEK,Normalbelopp,378 +Algeriet (2020),SEK,Breakfast,-56.7 +Algeriet (2020),SEK,Lunch,-132.3 +Algeriet (2020),SEK,Dinner,-132.3 +Angola (2018),SEK,Normalbelopp,937 +Angola (2018),SEK,Breakfast,-140.55 +Angola (2018),SEK,Lunch,-327.95 +Angola (2018),SEK,Dinner,-327.95 +Angola (2019),SEK,Normalbelopp,567 +Angola (2019),SEK,Breakfast,-85.05 +Angola (2019),SEK,Lunch,-198.45 +Angola (2019),SEK,Dinner,-198.45 +Angola (2020),SEK,Normalbelopp,346 +Angola (2020),SEK,Breakfast,-51.9 +Angola (2020),SEK,Lunch,-121.1 +Angola (2020),SEK,Dinner,-121.1 +Antigua och Barbuda (2018),SEK,Normalbelopp,613 +Antigua och Barbuda (2018),SEK,Breakfast,-91.95 +Antigua och Barbuda (2018),SEK,Lunch,-214.55 +Antigua och Barbuda (2018),SEK,Dinner,-214.55 +Antigua och Barbuda (2019),SEK,Normalbelopp,670 +Antigua och Barbuda (2019),SEK,Breakfast,-100.5 +Antigua och Barbuda (2019),SEK,Lunch,-234.5 +Antigua och Barbuda (2019),SEK,Dinner,-234.5 +Antigua och Barbuda (2020),SEK,Normalbelopp,697 +Antigua och Barbuda (2020),SEK,Breakfast,-104.55 +Antigua och Barbuda (2020),SEK,Lunch,-243.95 +Antigua och Barbuda (2020),SEK,Dinner,-243.95 +Argentina (2018),SEK,Normalbelopp,454 +Argentina (2018),SEK,Breakfast,-68.1 +Argentina (2018),SEK,Lunch,-158.9 +Argentina (2018),SEK,Dinner,-158.9 +Argentina (2019),SEK,Normalbelopp,276 +Argentina (2019),SEK,Breakfast,-41.4 +Argentina (2019),SEK,Lunch,-96.6 +Argentina (2019),SEK,Dinner,-96.6 +Argentina (2020),SEK,Normalbelopp,240 +Argentina (2020),SEK,Breakfast,-36 +Argentina (2020),SEK,Lunch,-84 +Argentina (2020),SEK,Dinner,-84 +Armenien (2018),SEK,Normalbelopp,313 +Armenien (2018),SEK,Breakfast,-46.95 +Armenien (2018),SEK,Lunch,-109.55 +Armenien (2018),SEK,Dinner,-109.55 +Armenien (2019),SEK,Normalbelopp,329 +Armenien (2019),SEK,Breakfast,-49.35 +Armenien (2019),SEK,Lunch,-115.15 +Armenien (2019),SEK,Dinner,-115.15 +Armenien (2020),SEK,Normalbelopp,358 +Armenien (2020),SEK,Breakfast,-53.7 +Armenien (2020),SEK,Lunch,-125.3 +Armenien (2020),SEK,Dinner,-125.3 +Australien (2018),SEK,Normalbelopp,683 +Australien (2018),SEK,Breakfast,-102.45 +Australien (2018),SEK,Lunch,-239.05 +Australien (2018),SEK,Dinner,-239.05 +Australien (2019),SEK,Normalbelopp,685 +Australien (2019),SEK,Breakfast,-102.75 +Australien (2019),SEK,Lunch,-239.75 +Australien (2019),SEK,Dinner,-239.75 +Australien (2020),SEK,Normalbelopp,697 +Australien (2020),SEK,Breakfast,-104.55 +Australien (2020),SEK,Lunch,-243.95 +Australien (2020),SEK,Dinner,-243.95 +Azerbajdzjan (2018),SEK,Normalbelopp,322 +Azerbajdzjan (2018),SEK,Breakfast,-48.3 +Azerbajdzjan (2018),SEK,Lunch,-112.7 +Azerbajdzjan (2018),SEK,Dinner,-112.7 +Azerbajdzjan (2019),SEK,Normalbelopp,345 +Azerbajdzjan (2019),SEK,Breakfast,-51.75 +Azerbajdzjan (2019),SEK,Lunch,-120.75 +Azerbajdzjan (2019),SEK,Dinner,-120.75 +Azerbajdzjan (2020),SEK,Normalbelopp,359 +Azerbajdzjan (2020),SEK,Breakfast,-53.85 +Azerbajdzjan (2020),SEK,Lunch,-125.65 +Azerbajdzjan (2020),SEK,Dinner,-125.65 +Bahamas (2018),SEK,Normalbelopp,797 +Bahamas (2018),SEK,Breakfast,-119.55 +Bahamas (2018),SEK,Lunch,-278.95 +Bahamas (2018),SEK,Dinner,-278.95 +Bahamas (2019),SEK,Normalbelopp,878 +Bahamas (2019),SEK,Breakfast,-131.7 +Bahamas (2019),SEK,Lunch,-307.3 +Bahamas (2019),SEK,Dinner,-307.3 +Bahamas (2020),SEK,Normalbelopp,953 +Bahamas (2020),SEK,Breakfast,-142.95 +Bahamas (2020),SEK,Lunch,-333.55 +Bahamas (2020),SEK,Dinner,-333.55 +Bahrain (2018),SEK,Normalbelopp,581 +Bahrain (2018),SEK,Breakfast,-87.15 +Bahrain (2018),SEK,Lunch,-203.35 +Bahrain (2018),SEK,Dinner,-203.35 +Bahrain (2019),SEK,Normalbelopp,661 +Bahrain (2019),SEK,Breakfast,-99.15 +Bahrain (2019),SEK,Lunch,-231.35 +Bahrain (2019),SEK,Dinner,-231.35 +Bahrain (2020),SEK,Normalbelopp,696 +Bahrain (2020),SEK,Breakfast,-104.4 +Bahrain (2020),SEK,Lunch,-243.6 +Bahrain (2020),SEK,Dinner,-243.6 +Bangladesh (2018),SEK,Normalbelopp,389 +Bangladesh (2018),SEK,Breakfast,-58.35 +Bangladesh (2018),SEK,Lunch,-136.15 +Bangladesh (2018),SEK,Dinner,-136.15 +Bangladesh (2019),SEK,Normalbelopp,429 +Bangladesh (2019),SEK,Breakfast,-64.35 +Bangladesh (2019),SEK,Lunch,-150.15 +Bangladesh (2019),SEK,Dinner,-150.15 +Bangladesh (2020),SEK,Normalbelopp,457 +Bangladesh (2020),SEK,Breakfast,-68.55 +Bangladesh (2020),SEK,Lunch,-159.95 +Bangladesh (2020),SEK,Dinner,-159.95 +Barbados (2018),SEK,Normalbelopp,738 +Barbados (2018),SEK,Breakfast,-110.7 +Barbados (2018),SEK,Lunch,-258.3 +Barbados (2018),SEK,Dinner,-258.3 +Barbados (2019),SEK,Normalbelopp,818 +Barbados (2019),SEK,Breakfast,-122.7 +Barbados (2019),SEK,Lunch,-286.3 +Barbados (2019),SEK,Dinner,-286.3 +Barbados (2020),SEK,Normalbelopp,893 +Barbados (2020),SEK,Breakfast,-133.95 +Barbados (2020),SEK,Lunch,-312.55 +Barbados (2020),SEK,Dinner,-312.55 +Belgien (2018),SEK,Normalbelopp,678 +Belgien (2018),SEK,Breakfast,-101.7 +Belgien (2018),SEK,Lunch,-237.3 +Belgien (2018),SEK,Dinner,-237.3 +Belgien (2019),SEK,Normalbelopp,723 +Belgien (2019),SEK,Breakfast,-108.45 +Belgien (2019),SEK,Lunch,-253.05 +Belgien (2019),SEK,Dinner,-253.05 +Belgien (2020),SEK,Normalbelopp,756 +Belgien (2020),SEK,Breakfast,-113.4 +Belgien (2020),SEK,Lunch,-264.6 +Belgien (2020),SEK,Dinner,-264.6 +Belize (2018),SEK,Normalbelopp,453 +Belize (2018),SEK,Breakfast,-67.95 +Belize (2018),SEK,Lunch,-158.55 +Belize (2018),SEK,Dinner,-158.55 +Belize (2019),SEK,Normalbelopp,485 +Belize (2019),SEK,Breakfast,-72.75 +Belize (2019),SEK,Lunch,-169.75 +Belize (2019),SEK,Dinner,-169.75 +Belize (2020),SEK,Normalbelopp,524 +Belize (2020),SEK,Breakfast,-78.6 +Belize (2020),SEK,Lunch,-183.4 +Belize (2020),SEK,Dinner,-183.4 +Benin (2018),SEK,Normalbelopp,471 +Benin (2018),SEK,Breakfast,-70.65 +Benin (2018),SEK,Lunch,-164.85 +Benin (2018),SEK,Dinner,-164.85 +Benin (2019),SEK,Normalbelopp,507 +Benin (2019),SEK,Breakfast,-76.05 +Benin (2019),SEK,Lunch,-177.45 +Benin (2019),SEK,Dinner,-177.45 +Benin (2020),SEK,Normalbelopp,523 +Benin (2020),SEK,Breakfast,-78.45 +Benin (2020),SEK,Lunch,-183.05 +Benin (2020),SEK,Dinner,-183.05 +Bolivia (2018),SEK,Normalbelopp,280 +Bolivia (2018),SEK,Breakfast,-42 +Bolivia (2018),SEK,Lunch,-98 +Bolivia (2018),SEK,Dinner,-98 +Bolivia (2019),SEK,Normalbelopp,322 +Bolivia (2019),SEK,Breakfast,-48.3 +Bolivia (2019),SEK,Lunch,-112.7 +Bolivia (2019),SEK,Dinner,-112.7 +Bolivia (2020),SEK,Normalbelopp,363 +Bolivia (2020),SEK,Breakfast,-54.45 +Bolivia (2020),SEK,Lunch,-127.05 +Bolivia (2020),SEK,Dinner,-127.05 +Bosnien-Hercegovina (2018),SEK,Normalbelopp,323 +Bosnien-Hercegovina (2018),SEK,Breakfast,-48.45 +Bosnien-Hercegovina (2018),SEK,Lunch,-113.05 +Bosnien-Hercegovina (2018),SEK,Dinner,-113.05 +Bosnien-Hercegovina (2019),SEK,Normalbelopp,352 +Bosnien-Hercegovina (2019),SEK,Breakfast,-52.8 +Bosnien-Hercegovina (2019),SEK,Lunch,-123.2 +Bosnien-Hercegovina (2019),SEK,Dinner,-123.2 +Bosnien-Hercegovina (2020),SEK,Normalbelopp,357 +Bosnien-Hercegovina (2020),SEK,Breakfast,-53.55 +Bosnien-Hercegovina (2020),SEK,Lunch,-124.95 +Bosnien-Hercegovina (2020),SEK,Dinner,-124.95 +Botswana (2018),SEK,Normalbelopp,259 +Botswana (2018),SEK,Breakfast,-38.85 +Botswana (2018),SEK,Lunch,-90.65 +Botswana (2018),SEK,Dinner,-90.65 +Botswana (2019),SEK,Normalbelopp,301 +Botswana (2019),SEK,Breakfast,-45.15 +Botswana (2019),SEK,Lunch,-105.35 +Botswana (2019),SEK,Dinner,-105.35 +Botswana (2020),SEK,Normalbelopp,357 +Botswana (2020),SEK,Breakfast,-53.55 +Botswana (2020),SEK,Lunch,-124.95 +Botswana (2020),SEK,Dinner,-124.95 +Brasilien (2018),SEK,Normalbelopp,419 +Brasilien (2018),SEK,Breakfast,-62.85 +Brasilien (2018),SEK,Lunch,-146.65 +Brasilien (2018),SEK,Dinner,-146.65 +Brasilien (2019),SEK,Normalbelopp,411 +Brasilien (2019),SEK,Breakfast,-61.65 +Brasilien (2019),SEK,Lunch,-143.85 +Brasilien (2019),SEK,Dinner,-143.85 +Brasilien (2020),SEK,Normalbelopp,424 +Brasilien (2020),SEK,Breakfast,-63.6 +Brasilien (2020),SEK,Lunch,-148.4 +Brasilien (2020),SEK,Dinner,-148.4 +Brunei Darussalam (2018),SEK,Normalbelopp,366 +Brunei Darussalam (2018),SEK,Breakfast,-54.9 +Brunei Darussalam (2018),SEK,Lunch,-128.1 +Brunei Darussalam (2018),SEK,Dinner,-128.1 +Brunei Darussalam (2019),SEK,Normalbelopp,426 +Brunei Darussalam (2019),SEK,Breakfast,-63.9 +Brunei Darussalam (2019),SEK,Lunch,-149.1 +Brunei Darussalam (2019),SEK,Dinner,-149.1 +Brunei Darussalam (2020),SEK,Normalbelopp,470 +Brunei Darussalam (2020),SEK,Breakfast,-70.5 +Brunei Darussalam (2020),SEK,Lunch,-164.5 +Brunei Darussalam (2020),SEK,Dinner,-164.5 +Bulgarien (2018),SEK,Normalbelopp,347 +Bulgarien (2018),SEK,Breakfast,-52.05 +Bulgarien (2018),SEK,Lunch,-121.45 +Bulgarien (2018),SEK,Dinner,-121.45 +Bulgarien (2019),SEK,Normalbelopp,365 +Bulgarien (2019),SEK,Breakfast,-54.75 +Bulgarien (2019),SEK,Lunch,-127.75 +Bulgarien (2019),SEK,Dinner,-127.75 +Bulgarien (2020),SEK,Normalbelopp,375 +Bulgarien (2020),SEK,Breakfast,-56.25 +Bulgarien (2020),SEK,Lunch,-131.25 +Bulgarien (2020),SEK,Dinner,-131.25 +Burkina Faso (2018),SEK,Normalbelopp,400 +Burkina Faso (2018),SEK,Breakfast,-60 +Burkina Faso (2018),SEK,Lunch,-140 +Burkina Faso (2018),SEK,Dinner,-140 +Burkina Faso (2019),SEK,Normalbelopp,411 +Burkina Faso (2019),SEK,Breakfast,-61.65 +Burkina Faso (2019),SEK,Lunch,-143.85 +Burkina Faso (2019),SEK,Dinner,-143.85 +Burkina Faso (2020),SEK,Normalbelopp,419 +Burkina Faso (2020),SEK,Breakfast,-62.85 +Burkina Faso (2020),SEK,Lunch,-146.65 +Burkina Faso (2020),SEK,Dinner,-146.65 +Burma (2018),SEK,Normalbelopp,315 +Burma (2018),SEK,Breakfast,-47.25 +Burma (2018),SEK,Lunch,-110.25 +Burma (2018),SEK,Dinner,-110.25 +Burma (2019),SEK,Normalbelopp,286 +Burma (2019),SEK,Breakfast,-42.9 +Burma (2019),SEK,Lunch,-100.1 +Burma (2019),SEK,Dinner,-100.1 +Burma (2020),SEK,Normalbelopp,318 +Burma (2020),SEK,Breakfast,-47.7 +Burma (2020),SEK,Lunch,-111.3 +Burma (2020),SEK,Dinner,-111.3 +Centralafrikanska republiken (2018),SEK,Normalbelopp,393 +Centralafrikanska republiken (2018),SEK,Breakfast,-58.95 +Centralafrikanska republiken (2018),SEK,Lunch,-137.55 +Centralafrikanska republiken (2018),SEK,Dinner,-137.55 +Centralafrikanska republiken (2019),SEK,Normalbelopp,436 +Centralafrikanska republiken (2019),SEK,Breakfast,-65.4 +Centralafrikanska republiken (2019),SEK,Lunch,-152.6 +Centralafrikanska republiken (2019),SEK,Dinner,-152.6 +Centralafrikanska republiken (2020),SEK,Normalbelopp,463 +Centralafrikanska republiken (2020),SEK,Breakfast,-69.45 +Centralafrikanska republiken (2020),SEK,Lunch,-162.05 +Centralafrikanska republiken (2020),SEK,Dinner,-162.05 +Chile (2018),SEK,Normalbelopp,424 +Chile (2018),SEK,Breakfast,-63.6 +Chile (2018),SEK,Lunch,-148.4 +Chile (2018),SEK,Dinner,-148.4 +Chile (2019),SEK,Normalbelopp,422 +Chile (2019),SEK,Breakfast,-63.3 +Chile (2019),SEK,Lunch,-147.7 +Chile (2019),SEK,Dinner,-147.7 +Chile (2020),SEK,Normalbelopp,436 +Chile (2020),SEK,Breakfast,-65.4 +Chile (2020),SEK,Lunch,-152.6 +Chile (2020),SEK,Dinner,-152.6 +Colombia (2018),SEK,Normalbelopp,329 +Colombia (2018),SEK,Breakfast,-49.35 +Colombia (2018),SEK,Lunch,-115.15 +Colombia (2018),SEK,Dinner,-115.15 +Colombia (2019),SEK,Normalbelopp,345 +Colombia (2019),SEK,Breakfast,-51.75 +Colombia (2019),SEK,Lunch,-120.75 +Colombia (2019),SEK,Dinner,-120.75 +Colombia (2020),SEK,Normalbelopp,345 +Colombia (2020),SEK,Breakfast,-51.75 +Colombia (2020),SEK,Lunch,-120.75 +Colombia (2020),SEK,Dinner,-120.75 +Costa Rica (2018),SEK,Normalbelopp,445 +Costa Rica (2018),SEK,Breakfast,-66.75 +Costa Rica (2018),SEK,Lunch,-155.75 +Costa Rica (2018),SEK,Dinner,-155.75 +Costa Rica (2019),SEK,Normalbelopp,464 +Costa Rica (2019),SEK,Breakfast,-69.6 +Costa Rica (2019),SEK,Lunch,-162.4 +Costa Rica (2019),SEK,Dinner,-162.4 +Costa Rica (2020),SEK,Normalbelopp,534 +Costa Rica (2020),SEK,Breakfast,-80.1 +Costa Rica (2020),SEK,Lunch,-186.9 +Costa Rica (2020),SEK,Dinner,-186.9 +Cypern (2018),SEK,Normalbelopp,543 +Cypern (2018),SEK,Breakfast,-81.45 +Cypern (2018),SEK,Lunch,-190.05 +Cypern (2018),SEK,Dinner,-190.05 +Cypern (2019),SEK,Normalbelopp,505 +Cypern (2019),SEK,Breakfast,-75.75 +Cypern (2019),SEK,Lunch,-176.75 +Cypern (2019),SEK,Dinner,-176.75 +Cypern (2020),SEK,Normalbelopp,605 +Cypern (2020),SEK,Breakfast,-90.75 +Cypern (2020),SEK,Lunch,-211.75 +Cypern (2020),SEK,Dinner,-211.75 +Danmark (2018),SEK,Normalbelopp,915 +Danmark (2018),SEK,Breakfast,-137.25 +Danmark (2018),SEK,Lunch,-320.25 +Danmark (2018),SEK,Dinner,-320.25 +Danmark (2019),SEK,Normalbelopp,993 +Danmark (2019),SEK,Breakfast,-148.95 +Danmark (2019),SEK,Lunch,-347.55 +Danmark (2019),SEK,Dinner,-347.55 +Danmark (2020),SEK,Normalbelopp,1016 +Danmark (2020),SEK,Breakfast,-152.4 +Danmark (2020),SEK,Lunch,-355.6 +Danmark (2020),SEK,Dinner,-355.6 +Djibouti (2018),SEK,Normalbelopp,480 +Djibouti (2018),SEK,Breakfast,-72 +Djibouti (2018),SEK,Lunch,-168 +Djibouti (2018),SEK,Dinner,-168 +Djibouti (2019),SEK,Normalbelopp,529 +Djibouti (2019),SEK,Breakfast,-79.35 +Djibouti (2019),SEK,Lunch,-185.15 +Djibouti (2019),SEK,Dinner,-185.15 +Djibouti (2020),SEK,Normalbelopp,561 +Djibouti (2020),SEK,Breakfast,-84.15 +Djibouti (2020),SEK,Lunch,-196.35 +Djibouti (2020),SEK,Dinner,-196.35 +Ecuador (2018),SEK,Normalbelopp,512 +Ecuador (2018),SEK,Breakfast,-76.8 +Ecuador (2018),SEK,Lunch,-179.2 +Ecuador (2018),SEK,Dinner,-179.2 +Ecuador (2019),SEK,Normalbelopp,562 +Ecuador (2019),SEK,Breakfast,-84.3 +Ecuador (2019),SEK,Lunch,-196.7 +Ecuador (2019),SEK,Dinner,-196.7 +Ecuador (2020),SEK,Normalbelopp,592 +Ecuador (2020),SEK,Breakfast,-88.8 +Ecuador (2020),SEK,Lunch,-207.2 +Ecuador (2020),SEK,Dinner,-207.2 +Egypten (2018),SEK,Normalbelopp,230 +Egypten (2018),SEK,Breakfast,-34.5 +Egypten (2018),SEK,Lunch,-80.5 +Egypten (2018),SEK,Dinner,-80.5 +Egypten (2019),SEK,Normalbelopp,233 +Egypten (2019),SEK,Breakfast,-34.95 +Egypten (2019),SEK,Lunch,-81.55 +Egypten (2019),SEK,Dinner,-81.55 +Egypten (2020),SEK,Normalbelopp,302 +Egypten (2020),SEK,Breakfast,-45.3 +Egypten (2020),SEK,Lunch,-105.7 +Egypten (2020),SEK,Dinner,-105.7 +El Salvador (2018),SEK,Normalbelopp,405 +El Salvador (2018),SEK,Breakfast,-60.75 +El Salvador (2018),SEK,Lunch,-141.75 +El Salvador (2018),SEK,Dinner,-141.75 +El Salvador (2019),SEK,Normalbelopp,449 +El Salvador (2019),SEK,Breakfast,-67.35 +El Salvador (2019),SEK,Lunch,-157.15 +El Salvador (2019),SEK,Dinner,-157.15 +El Salvador (2020),SEK,Normalbelopp,484 +El Salvador (2020),SEK,Breakfast,-72.6 +El Salvador (2020),SEK,Lunch,-169.4 +El Salvador (2020),SEK,Dinner,-169.4 +Elfenbenskusten (2018),SEK,Normalbelopp,512 +Elfenbenskusten (2018),SEK,Breakfast,-76.8 +Elfenbenskusten (2018),SEK,Lunch,-179.2 +Elfenbenskusten (2018),SEK,Dinner,-179.2 +Elfenbenskusten (2019),SEK,Normalbelopp,562 +Elfenbenskusten (2019),SEK,Breakfast,-84.3 +Elfenbenskusten (2019),SEK,Lunch,-196.7 +Elfenbenskusten (2019),SEK,Dinner,-196.7 +Elfenbenskusten (2020),SEK,Normalbelopp,608 +Elfenbenskusten (2020),SEK,Breakfast,-91.2 +Elfenbenskusten (2020),SEK,Lunch,-212.8 +Elfenbenskusten (2020),SEK,Dinner,-212.8 +Eritrea (2018),SEK,Normalbelopp,471 +Eritrea (2018),SEK,Breakfast,-70.65 +Eritrea (2018),SEK,Lunch,-164.85 +Eritrea (2018),SEK,Dinner,-164.85 +Eritrea (2019),SEK,Normalbelopp,515 +Eritrea (2019),SEK,Breakfast,-77.25 +Eritrea (2019),SEK,Lunch,-180.25 +Eritrea (2019),SEK,Dinner,-180.25 +Eritrea (2020),SEK,Normalbelopp,468 +Eritrea (2020),SEK,Breakfast,-70.2 +Eritrea (2020),SEK,Lunch,-163.8 +Eritrea (2020),SEK,Dinner,-163.8 +Estland (2018),SEK,Normalbelopp,459 +Estland (2018),SEK,Breakfast,-68.85 +Estland (2018),SEK,Lunch,-160.65 +Estland (2018),SEK,Dinner,-160.65 +Estland (2019),SEK,Normalbelopp,491 +Estland (2019),SEK,Breakfast,-73.65 +Estland (2019),SEK,Lunch,-171.85 +Estland (2019),SEK,Dinner,-171.85 +Estland (2020),SEK,Normalbelopp,506 +Estland (2020),SEK,Breakfast,-75.9 +Estland (2020),SEK,Lunch,-177.1 +Estland (2020),SEK,Dinner,-177.1 +Etiopien (2018),SEK,Normalbelopp,230 +Etiopien (2018),SEK,Breakfast,-34.5 +Etiopien (2018),SEK,Lunch,-80.5 +Etiopien (2018),SEK,Dinner,-80.5 +Etiopien (2019),SEK,Normalbelopp,265 +Etiopien (2019),SEK,Breakfast,-39.75 +Etiopien (2019),SEK,Lunch,-92.75 +Etiopien (2019),SEK,Dinner,-92.75 +Etiopien (2020),SEK,Normalbelopp,266 +Etiopien (2020),SEK,Breakfast,-39.9 +Etiopien (2020),SEK,Lunch,-93.1 +Etiopien (2020),SEK,Dinner,-93.1 +Filippinerna (2018),SEK,Normalbelopp,357 +Filippinerna (2018),SEK,Breakfast,-53.55 +Filippinerna (2018),SEK,Lunch,-124.95 +Filippinerna (2018),SEK,Dinner,-124.95 +Filippinerna (2019),SEK,Normalbelopp,384 +Filippinerna (2019),SEK,Breakfast,-57.6 +Filippinerna (2019),SEK,Lunch,-134.4 +Filippinerna (2019),SEK,Dinner,-134.4 +Filippinerna (2020),SEK,Normalbelopp,441 +Filippinerna (2020),SEK,Breakfast,-66.15 +Filippinerna (2020),SEK,Lunch,-154.35 +Filippinerna (2020),SEK,Dinner,-154.35 +Finland (2018),SEK,Normalbelopp,680 +Finland (2018),SEK,Breakfast,-102 +Finland (2018),SEK,Lunch,-238 +Finland (2018),SEK,Dinner,-238 +Finland (2019),SEK,Normalbelopp,729 +Finland (2019),SEK,Breakfast,-109.35 +Finland (2019),SEK,Lunch,-255.15 +Finland (2019),SEK,Dinner,-255.15 +Finland (2020),SEK,Normalbelopp,759 +Finland (2020),SEK,Breakfast,-113.85 +Finland (2020),SEK,Lunch,-265.65 +Finland (2020),SEK,Dinner,-265.65 +Förenade Arabemiraten (2018),SEK,Normalbelopp,703 +Förenade Arabemiraten (2018),SEK,Breakfast,-105.45 +Förenade Arabemiraten (2018),SEK,Lunch,-246.05 +Förenade Arabemiraten (2018),SEK,Dinner,-246.05 +Förenade Arabemiraten (2019),SEK,Normalbelopp,849 +Förenade Arabemiraten (2019),SEK,Breakfast,-127.35 +Förenade Arabemiraten (2019),SEK,Lunch,-297.15 +Förenade Arabemiraten (2019),SEK,Dinner,-297.15 +Förenade Arabemiraten (2020),SEK,Normalbelopp,899 +Förenade Arabemiraten (2020),SEK,Breakfast,-134.85 +Förenade Arabemiraten (2020),SEK,Lunch,-314.65 +Förenade Arabemiraten (2020),SEK,Dinner,-314.65 +Frankrike (2018),SEK,Normalbelopp,714 +Frankrike (2018),SEK,Breakfast,-107.1 +Frankrike (2018),SEK,Lunch,-249.9 +Frankrike (2018),SEK,Dinner,-249.9 +Frankrike (2019),SEK,Normalbelopp,760 +Frankrike (2019),SEK,Breakfast,-114 +Frankrike (2019),SEK,Lunch,-266 +Frankrike (2019),SEK,Dinner,-266 +Frankrike (2020),SEK,Normalbelopp,794 +Frankrike (2020),SEK,Breakfast,-119.1 +Frankrike (2020),SEK,Lunch,-277.9 +Frankrike (2020),SEK,Dinner,-277.9 +Gabon (2018),SEK,Normalbelopp,708 +Gabon (2018),SEK,Breakfast,-106.2 +Gabon (2018),SEK,Lunch,-247.8 +Gabon (2018),SEK,Dinner,-247.8 +Gabon (2019),SEK,Normalbelopp,742 +Gabon (2019),SEK,Breakfast,-111.3 +Gabon (2019),SEK,Lunch,-259.7 +Gabon (2019),SEK,Dinner,-259.7 +Gabon (2020),SEK,Normalbelopp,751 +Gabon (2020),SEK,Breakfast,-112.65 +Gabon (2020),SEK,Lunch,-262.85 +Gabon (2020),SEK,Dinner,-262.85 +Gambia (2018),SEK,Normalbelopp,237 +Gambia (2018),SEK,Breakfast,-35.55 +Gambia (2018),SEK,Lunch,-82.95 +Gambia (2018),SEK,Dinner,-82.95 +Gambia (2019),SEK,Normalbelopp,244 +Gambia (2019),SEK,Breakfast,-36.6 +Gambia (2019),SEK,Lunch,-85.4 +Gambia (2019),SEK,Dinner,-85.4 +Gambia (2020),SEK,Normalbelopp,260 +Gambia (2020),SEK,Breakfast,-39 +Gambia (2020),SEK,Lunch,-91 +Gambia (2020),SEK,Dinner,-91 +Georgien (2018),SEK,Normalbelopp,230 +Georgien (2018),SEK,Breakfast,-34.5 +Georgien (2018),SEK,Lunch,-80.5 +Georgien (2018),SEK,Dinner,-80.5 +Georgien (2019),SEK,Normalbelopp,235 +Georgien (2019),SEK,Breakfast,-35.25 +Georgien (2019),SEK,Lunch,-82.25 +Georgien (2019),SEK,Dinner,-82.25 +Georgien (2020),SEK,Normalbelopp,240 +Georgien (2020),SEK,Breakfast,-36 +Georgien (2020),SEK,Lunch,-84 +Georgien (2020),SEK,Dinner,-84 +Ghana (2018),SEK,Normalbelopp,404 +Ghana (2018),SEK,Breakfast,-60.6 +Ghana (2018),SEK,Lunch,-141.4 +Ghana (2018),SEK,Dinner,-141.4 +Ghana (2019),SEK,Normalbelopp,407 +Ghana (2019),SEK,Breakfast,-61.05 +Ghana (2019),SEK,Lunch,-142.45 +Ghana (2019),SEK,Dinner,-142.45 +Ghana (2020),SEK,Normalbelopp,372 +Ghana (2020),SEK,Breakfast,-55.8 +Ghana (2020),SEK,Lunch,-130.2 +Ghana (2020),SEK,Dinner,-130.2 +Grekland (2018),SEK,Normalbelopp,589 +Grekland (2018),SEK,Breakfast,-88.35 +Grekland (2018),SEK,Lunch,-206.15 +Grekland (2018),SEK,Dinner,-206.15 +Grekland (2019),SEK,Normalbelopp,625 +Grekland (2019),SEK,Breakfast,-93.75 +Grekland (2019),SEK,Lunch,-218.75 +Grekland (2019),SEK,Dinner,-218.75 +Grekland (2020),SEK,Normalbelopp,650 +Grekland (2020),SEK,Breakfast,-97.5 +Grekland (2020),SEK,Lunch,-227.5 +Grekland (2020),SEK,Dinner,-227.5 +Grenada (2018),SEK,Normalbelopp,477 +Grenada (2018),SEK,Breakfast,-71.55 +Grenada (2018),SEK,Lunch,-166.95 +Grenada (2018),SEK,Dinner,-166.95 +Grenada (2019),SEK,Normalbelopp,550 +Grenada (2019),SEK,Breakfast,-82.5 +Grenada (2019),SEK,Lunch,-192.5 +Grenada (2019),SEK,Dinner,-192.5 +Grenada (2020),SEK,Normalbelopp,581 +Grenada (2020),SEK,Breakfast,-87.15 +Grenada (2020),SEK,Lunch,-203.35 +Grenada (2020),SEK,Dinner,-203.35 +Grönland (2018),SEK,Normalbelopp,915 +Grönland (2018),SEK,Breakfast,-137.25 +Grönland (2018),SEK,Lunch,-320.25 +Grönland (2018),SEK,Dinner,-320.25 +Grönland (2019),SEK,Normalbelopp,993 +Grönland (2019),SEK,Breakfast,-148.95 +Grönland (2019),SEK,Lunch,-347.55 +Grönland (2019),SEK,Dinner,-347.55 +Grönland (2020),SEK,Normalbelopp,1016 +Grönland (2020),SEK,Breakfast,-152.4 +Grönland (2020),SEK,Lunch,-355.6 +Grönland (2020),SEK,Dinner,-355.6 +Guinea (2018),SEK,Normalbelopp,400 +Guinea (2018),SEK,Breakfast,-60 +Guinea (2018),SEK,Lunch,-140 +Guinea (2018),SEK,Dinner,-140 +Guinea (2019),SEK,Normalbelopp,444 +Guinea (2019),SEK,Breakfast,-66.6 +Guinea (2019),SEK,Lunch,-155.4 +Guinea (2019),SEK,Dinner,-155.4 +Guinea (2020),SEK,Normalbelopp,475 +Guinea (2020),SEK,Breakfast,-71.25 +Guinea (2020),SEK,Lunch,-166.25 +Guinea (2020),SEK,Dinner,-166.25 +Guyana (2018),SEK,Normalbelopp,494 +Guyana (2018),SEK,Breakfast,-74.1 +Guyana (2018),SEK,Lunch,-172.9 +Guyana (2018),SEK,Dinner,-172.9 +Guyana (2019),SEK,Normalbelopp,518 +Guyana (2019),SEK,Breakfast,-77.7 +Guyana (2019),SEK,Lunch,-181.3 +Guyana (2019),SEK,Dinner,-181.3 +Guyana (2020),SEK,Normalbelopp,548 +Guyana (2020),SEK,Breakfast,-82.2 +Guyana (2020),SEK,Lunch,-191.8 +Guyana (2020),SEK,Dinner,-191.8 +Haiti (2018),SEK,Normalbelopp,525 +Haiti (2018),SEK,Breakfast,-78.75 +Haiti (2018),SEK,Lunch,-183.75 +Haiti (2018),SEK,Dinner,-183.75 +Haiti (2019),SEK,Normalbelopp,548 +Haiti (2019),SEK,Breakfast,-82.2 +Haiti (2019),SEK,Lunch,-191.8 +Haiti (2019),SEK,Dinner,-191.8 +Haiti (2020),SEK,Normalbelopp,451 +Haiti (2020),SEK,Breakfast,-67.65 +Haiti (2020),SEK,Lunch,-157.85 +Haiti (2020),SEK,Dinner,-157.85 +Honduras (2018),SEK,Normalbelopp,312 +Honduras (2018),SEK,Breakfast,-46.8 +Honduras (2018),SEK,Lunch,-109.2 +Honduras (2018),SEK,Dinner,-109.2 +Honduras (2019),SEK,Normalbelopp,328 +Honduras (2019),SEK,Breakfast,-49.2 +Honduras (2019),SEK,Lunch,-114.8 +Honduras (2019),SEK,Dinner,-114.8 +Honduras (2020),SEK,Normalbelopp,351 +Honduras (2020),SEK,Breakfast,-52.65 +Honduras (2020),SEK,Lunch,-122.85 +Honduras (2020),SEK,Dinner,-122.85 +Hong Kong (2018),SEK,Normalbelopp,538 +Hong Kong (2018),SEK,Breakfast,-80.7 +Hong Kong (2018),SEK,Lunch,-188.3 +Hong Kong (2018),SEK,Dinner,-188.3 +Hong Kong (2019),SEK,Normalbelopp,578 +Hong Kong (2019),SEK,Breakfast,-86.7 +Hong Kong (2019),SEK,Lunch,-202.3 +Hong Kong (2019),SEK,Dinner,-202.3 +Hong Kong (2020),SEK,Normalbelopp,590 +Hong Kong (2020),SEK,Breakfast,-88.5 +Hong Kong (2020),SEK,Lunch,-206.5 +Hong Kong (2020),SEK,Dinner,-206.5 +Indien (2018),SEK,Normalbelopp,319 +Indien (2018),SEK,Breakfast,-47.85 +Indien (2018),SEK,Lunch,-111.65 +Indien (2018),SEK,Dinner,-111.65 +Indien (2019),SEK,Normalbelopp,320 +Indien (2019),SEK,Breakfast,-48 +Indien (2019),SEK,Lunch,-112 +Indien (2019),SEK,Dinner,-112 +Indien (2020),SEK,Normalbelopp,356 +Indien (2020),SEK,Breakfast,-53.4 +Indien (2020),SEK,Lunch,-124.6 +Indien (2020),SEK,Dinner,-124.6 +Indonesien (2018),SEK,Normalbelopp,358 +Indonesien (2018),SEK,Breakfast,-53.7 +Indonesien (2018),SEK,Lunch,-125.3 +Indonesien (2018),SEK,Dinner,-125.3 +Indonesien (2019),SEK,Normalbelopp,348 +Indonesien (2019),SEK,Breakfast,-52.2 +Indonesien (2019),SEK,Lunch,-121.8 +Indonesien (2019),SEK,Dinner,-121.8 +Indonesien (2020),SEK,Normalbelopp,404 +Indonesien (2020),SEK,Breakfast,-60.6 +Indonesien (2020),SEK,Lunch,-141.4 +Indonesien (2020),SEK,Dinner,-141.4 +Irak (2018),SEK,Normalbelopp,626 +Irak (2018),SEK,Breakfast,-93.9 +Irak (2018),SEK,Lunch,-219.1 +Irak (2018),SEK,Dinner,-219.1 +Irak (2019),SEK,Normalbelopp,671 +Irak (2019),SEK,Breakfast,-100.65 +Irak (2019),SEK,Lunch,-234.85 +Irak (2019),SEK,Dinner,-234.85 +Irak (2020),SEK,Normalbelopp,703 +Irak (2020),SEK,Breakfast,-105.45 +Irak (2020),SEK,Lunch,-246.05 +Irak (2020),SEK,Dinner,-246.05 +Iran (2018),SEK,Normalbelopp,410 +Iran (2018),SEK,Breakfast,-61.5 +Iran (2018),SEK,Lunch,-143.5 +Iran (2018),SEK,Dinner,-143.5 +Iran (2019),SEK,Normalbelopp,362 +Iran (2019),SEK,Breakfast,-54.3 +Iran (2019),SEK,Lunch,-126.7 +Iran (2019),SEK,Dinner,-126.7 +Iran (2020),SEK,Normalbelopp,452 +Iran (2020),SEK,Breakfast,-67.8 +Iran (2020),SEK,Lunch,-158.2 +Iran (2020),SEK,Dinner,-158.2 +Irland (2018),SEK,Normalbelopp,654 +Irland (2018),SEK,Breakfast,-98.1 +Irland (2018),SEK,Lunch,-228.9 +Irland (2018),SEK,Dinner,-228.9 +Irland (2019),SEK,Normalbelopp,703 +Irland (2019),SEK,Breakfast,-105.45 +Irland (2019),SEK,Lunch,-246.05 +Irland (2019),SEK,Dinner,-246.05 +Irland (2020),SEK,Normalbelopp,727 +Irland (2020),SEK,Breakfast,-109.05 +Irland (2020),SEK,Lunch,-254.45 +Irland (2020),SEK,Dinner,-254.45 +Island (2018),SEK,Normalbelopp,837 +Island (2018),SEK,Breakfast,-125.55 +Island (2018),SEK,Lunch,-292.95 +Island (2018),SEK,Dinner,-292.95 +Island (2019),SEK,Normalbelopp,798 +Island (2019),SEK,Breakfast,-119.7 +Island (2019),SEK,Lunch,-279.3 +Island (2019),SEK,Dinner,-279.3 +Island (2020),SEK,Normalbelopp,838 +Island (2020),SEK,Breakfast,-125.7 +Island (2020),SEK,Lunch,-293.3 +Island (2020),SEK,Dinner,-293.3 +Israel (2018),SEK,Normalbelopp,828 +Israel (2018),SEK,Breakfast,-124.2 +Israel (2018),SEK,Lunch,-289.8 +Israel (2018),SEK,Dinner,-289.8 +Israel (2019),SEK,Normalbelopp,822 +Israel (2019),SEK,Breakfast,-123.3 +Israel (2019),SEK,Lunch,-287.7 +Israel (2019),SEK,Dinner,-287.7 +Israel (2020),SEK,Normalbelopp,921 +Israel (2020),SEK,Breakfast,-138.15 +Israel (2020),SEK,Lunch,-322.35 +Israel (2020),SEK,Dinner,-322.35 +Italien (2018),SEK,Normalbelopp,540 +Italien (2018),SEK,Breakfast,-81 +Italien (2018),SEK,Lunch,-189 +Italien (2018),SEK,Dinner,-189 +Italien (2019),SEK,Normalbelopp,590 +Italien (2019),SEK,Breakfast,-88.5 +Italien (2019),SEK,Lunch,-206.5 +Italien (2019),SEK,Dinner,-206.5 +Italien (2020),SEK,Normalbelopp,627 +Italien (2020),SEK,Breakfast,-94.05 +Italien (2020),SEK,Lunch,-219.45 +Italien (2020),SEK,Dinner,-219.45 +Jamaica (2018),SEK,Normalbelopp,475 +Jamaica (2018),SEK,Breakfast,-71.25 +Jamaica (2018),SEK,Lunch,-166.25 +Jamaica (2018),SEK,Dinner,-166.25 +Jamaica (2019),SEK,Normalbelopp,539 +Jamaica (2019),SEK,Breakfast,-80.85 +Jamaica (2019),SEK,Lunch,-188.65 +Jamaica (2019),SEK,Dinner,-188.65 +Jamaica (2020),SEK,Normalbelopp,519 +Jamaica (2020),SEK,Breakfast,-77.85 +Jamaica (2020),SEK,Lunch,-181.65 +Jamaica (2020),SEK,Dinner,-181.65 +Japan (2018),SEK,Normalbelopp,503 +Japan (2018),SEK,Breakfast,-75.45 +Japan (2018),SEK,Lunch,-176.05 +Japan (2018),SEK,Dinner,-176.05 +Japan (2019),SEK,Normalbelopp,537 +Japan (2019),SEK,Breakfast,-80.55 +Japan (2019),SEK,Lunch,-187.95 +Japan (2019),SEK,Dinner,-187.95 +Japan (2020),SEK,Normalbelopp,587 +Japan (2020),SEK,Breakfast,-88.05 +Japan (2020),SEK,Lunch,-205.45 +Japan (2020),SEK,Dinner,-205.45 +Jordanien (2018),SEK,Normalbelopp,651 +Jordanien (2018),SEK,Breakfast,-97.65 +Jordanien (2018),SEK,Lunch,-227.85 +Jordanien (2018),SEK,Dinner,-227.85 +Jordanien (2019),SEK,Normalbelopp,709 +Jordanien (2019),SEK,Breakfast,-106.35 +Jordanien (2019),SEK,Lunch,-248.15 +Jordanien (2019),SEK,Dinner,-248.15 +Jordanien (2020),SEK,Normalbelopp,748 +Jordanien (2020),SEK,Breakfast,-112.2 +Jordanien (2020),SEK,Lunch,-261.8 +Jordanien (2020),SEK,Dinner,-261.8 +Kambodja (2018),SEK,Normalbelopp,380 +Kambodja (2018),SEK,Breakfast,-57 +Kambodja (2018),SEK,Lunch,-133 +Kambodja (2018),SEK,Dinner,-133 +Kambodja (2019),SEK,Normalbelopp,434 +Kambodja (2019),SEK,Breakfast,-65.1 +Kambodja (2019),SEK,Lunch,-151.9 +Kambodja (2019),SEK,Dinner,-151.9 +Kambodja (2020),SEK,Normalbelopp,485 +Kambodja (2020),SEK,Breakfast,-72.75 +Kambodja (2020),SEK,Lunch,-169.75 +Kambodja (2020),SEK,Dinner,-169.75 +Kamerun (2018),SEK,Normalbelopp,491 +Kamerun (2018),SEK,Breakfast,-73.65 +Kamerun (2018),SEK,Lunch,-171.85 +Kamerun (2018),SEK,Dinner,-171.85 +Kamerun (2019),SEK,Normalbelopp,504 +Kamerun (2019),SEK,Breakfast,-75.6 +Kamerun (2019),SEK,Lunch,-176.4 +Kamerun (2019),SEK,Dinner,-176.4 +Kamerun (2020),SEK,Normalbelopp,520 +Kamerun (2020),SEK,Breakfast,-78 +Kamerun (2020),SEK,Lunch,-182 +Kamerun (2020),SEK,Dinner,-182 +Kanada (2018),SEK,Normalbelopp,556 +Kanada (2018),SEK,Breakfast,-83.4 +Kanada (2018),SEK,Lunch,-194.6 +Kanada (2018),SEK,Dinner,-194.6 +Kanada (2019),SEK,Normalbelopp,616 +Kanada (2019),SEK,Breakfast,-92.4 +Kanada (2019),SEK,Lunch,-215.6 +Kanada (2019),SEK,Dinner,-215.6 +Kanada (2020),SEK,Normalbelopp,662 +Kanada (2020),SEK,Breakfast,-99.3 +Kanada (2020),SEK,Lunch,-231.7 +Kanada (2020),SEK,Dinner,-231.7 +Kazakstan (2018),SEK,Normalbelopp,262 +Kazakstan (2018),SEK,Breakfast,-39.3 +Kazakstan (2018),SEK,Lunch,-91.7 +Kazakstan (2018),SEK,Dinner,-91.7 +Kazakstan (2019),SEK,Normalbelopp,261 +Kazakstan (2019),SEK,Breakfast,-39.15 +Kazakstan (2019),SEK,Lunch,-91.35 +Kazakstan (2019),SEK,Dinner,-91.35 +Kazakstan (2020),SEK,Normalbelopp,257 +Kazakstan (2020),SEK,Breakfast,-38.55 +Kazakstan (2020),SEK,Lunch,-89.95 +Kazakstan (2020),SEK,Dinner,-89.95 +Kenya (2018),SEK,Normalbelopp,438 +Kenya (2018),SEK,Breakfast,-65.7 +Kenya (2018),SEK,Lunch,-153.3 +Kenya (2018),SEK,Dinner,-153.3 +Kenya (2019),SEK,Normalbelopp,488 +Kenya (2019),SEK,Breakfast,-73.2 +Kenya (2019),SEK,Lunch,-170.8 +Kenya (2019),SEK,Dinner,-170.8 +Kenya (2020),SEK,Normalbelopp,505 +Kenya (2020),SEK,Breakfast,-75.75 +Kenya (2020),SEK,Lunch,-176.75 +Kenya (2020),SEK,Dinner,-176.75 +Kina (2018),SEK,Normalbelopp,538 +Kina (2018),SEK,Breakfast,-80.7 +Kina (2018),SEK,Lunch,-188.3 +Kina (2018),SEK,Dinner,-188.3 +Kina (2019),SEK,Normalbelopp,578 +Kina (2019),SEK,Breakfast,-86.7 +Kina (2019),SEK,Lunch,-202.3 +Kina (2019),SEK,Dinner,-202.3 +Kina (2020),SEK,Normalbelopp,590 +Kina (2020),SEK,Breakfast,-88.5 +Kina (2020),SEK,Lunch,-206.5 +Kina (2020),SEK,Dinner,-206.5 +Kirgizistan (2018),SEK,Normalbelopp,230 +Kirgizistan (2018),SEK,Breakfast,-34.5 +Kirgizistan (2018),SEK,Lunch,-80.5 +Kirgizistan (2018),SEK,Dinner,-80.5 +Kirgizistan (2019),SEK,Normalbelopp,259 +Kirgizistan (2019),SEK,Breakfast,-38.85 +Kirgizistan (2019),SEK,Lunch,-90.65 +Kirgizistan (2019),SEK,Dinner,-90.65 +Kirgizistan (2020),SEK,Normalbelopp,271 +Kirgizistan (2020),SEK,Breakfast,-40.65 +Kirgizistan (2020),SEK,Lunch,-94.85 +Kirgizistan (2020),SEK,Dinner,-94.85 +Kongo (Brazzaville) (2018),SEK,Normalbelopp,500 +Kongo (Brazzaville) (2018),SEK,Breakfast,-75 +Kongo (Brazzaville) (2018),SEK,Lunch,-175 +Kongo (Brazzaville) (2018),SEK,Dinner,-175 +Kongo (Brazzaville) (2019),SEK,Normalbelopp,542 +Kongo (Brazzaville) (2019),SEK,Breakfast,-81.3 +Kongo (Brazzaville) (2019),SEK,Lunch,-189.7 +Kongo (Brazzaville) (2019),SEK,Dinner,-189.7 +Kongo (Brazzaville) (2020),SEK,Normalbelopp,560 +Kongo (Brazzaville) (2020),SEK,Breakfast,-84 +Kongo (Brazzaville) (2020),SEK,Lunch,-196 +Kongo (Brazzaville) (2020),SEK,Dinner,-196 +Kongo (Demokratiska Republiken) (2018),SEK,Normalbelopp,464 +Kongo (Demokratiska Republiken) (2018),SEK,Breakfast,-69.6 +Kongo (Demokratiska Republiken) (2018),SEK,Lunch,-162.4 +Kongo (Demokratiska Republiken) (2018),SEK,Dinner,-162.4 +Kongo (Demokratiska Republiken) (2019),SEK,Normalbelopp,638 +Kongo (Demokratiska Republiken) (2019),SEK,Breakfast,-95.7 +Kongo (Demokratiska Republiken) (2019),SEK,Lunch,-223.3 +Kongo (Demokratiska Republiken) (2019),SEK,Dinner,-223.3 +Kongo (Demokratiska Republiken) (2020),SEK,Normalbelopp,634 +Kongo (Demokratiska Republiken) (2020),SEK,Breakfast,-95.1 +Kongo (Demokratiska Republiken) (2020),SEK,Lunch,-221.9 +Kongo (Demokratiska Republiken) (2020),SEK,Dinner,-221.9 +Kosovo (2018),SEK,Normalbelopp,240 +Kosovo (2018),SEK,Breakfast,-36 +Kosovo (2018),SEK,Lunch,-84 +Kosovo (2018),SEK,Dinner,-84 +Kosovo (2019),SEK,Normalbelopp,246 +Kosovo (2019),SEK,Breakfast,-36.9 +Kosovo (2019),SEK,Lunch,-86.1 +Kosovo (2019),SEK,Dinner,-86.1 +Kosovo (2020),SEK,Normalbelopp,240 +Kosovo (2020),SEK,Breakfast,-36 +Kosovo (2020),SEK,Lunch,-84 +Kosovo (2020),SEK,Dinner,-84 +Kroatien (2018),SEK,Normalbelopp,410 +Kroatien (2018),SEK,Breakfast,-61.5 +Kroatien (2018),SEK,Lunch,-143.5 +Kroatien (2018),SEK,Dinner,-143.5 +Kroatien (2019),SEK,Normalbelopp,439 +Kroatien (2019),SEK,Breakfast,-65.85 +Kroatien (2019),SEK,Lunch,-153.65 +Kroatien (2019),SEK,Dinner,-153.65 +Kroatien (2020),SEK,Normalbelopp,448 +Kroatien (2020),SEK,Breakfast,-67.2 +Kroatien (2020),SEK,Lunch,-156.8 +Kroatien (2020),SEK,Dinner,-156.8 +Kuba (2018),SEK,Normalbelopp,379 +Kuba (2018),SEK,Breakfast,-56.85 +Kuba (2018),SEK,Lunch,-132.65 +Kuba (2018),SEK,Dinner,-132.65 +Kuba (2019),SEK,Normalbelopp,413 +Kuba (2019),SEK,Breakfast,-61.95 +Kuba (2019),SEK,Lunch,-144.55 +Kuba (2019),SEK,Dinner,-144.55 +Kuba (2020),SEK,Normalbelopp,449 +Kuba (2020),SEK,Breakfast,-67.35 +Kuba (2020),SEK,Lunch,-157.15 +Kuba (2020),SEK,Dinner,-157.15 +Kuwait (2018),SEK,Normalbelopp,677 +Kuwait (2018),SEK,Breakfast,-101.55 +Kuwait (2018),SEK,Lunch,-236.95 +Kuwait (2018),SEK,Dinner,-236.95 +Kuwait (2019),SEK,Normalbelopp,741 +Kuwait (2019),SEK,Breakfast,-111.15 +Kuwait (2019),SEK,Lunch,-259.35 +Kuwait (2019),SEK,Dinner,-259.35 +Kuwait (2020),SEK,Normalbelopp,791 +Kuwait (2020),SEK,Breakfast,-118.65 +Kuwait (2020),SEK,Lunch,-276.85 +Kuwait (2020),SEK,Dinner,-276.85 +Laos (2018),SEK,Normalbelopp,278 +Laos (2018),SEK,Breakfast,-41.7 +Laos (2018),SEK,Lunch,-97.3 +Laos (2018),SEK,Dinner,-97.3 +Laos (2019),SEK,Normalbelopp,302 +Laos (2019),SEK,Breakfast,-45.3 +Laos (2019),SEK,Lunch,-105.7 +Laos (2019),SEK,Dinner,-105.7 +Laos (2020),SEK,Normalbelopp,308 +Laos (2020),SEK,Breakfast,-46.2 +Laos (2020),SEK,Lunch,-107.8 +Laos (2020),SEK,Dinner,-107.8 +Lettland (2018),SEK,Normalbelopp,460 +Lettland (2018),SEK,Breakfast,-69 +Lettland (2018),SEK,Lunch,-161 +Lettland (2018),SEK,Dinner,-161 +Lettland (2019),SEK,Normalbelopp,505 +Lettland (2019),SEK,Breakfast,-75.75 +Lettland (2019),SEK,Lunch,-176.75 +Lettland (2019),SEK,Dinner,-176.75 +Lettland (2020),SEK,Normalbelopp,529 +Lettland (2020),SEK,Breakfast,-79.35 +Lettland (2020),SEK,Lunch,-185.15 +Lettland (2020),SEK,Dinner,-185.15 +Libanon (2018),SEK,Normalbelopp,674 +Libanon (2018),SEK,Breakfast,-101.1 +Libanon (2018),SEK,Lunch,-235.9 +Libanon (2018),SEK,Dinner,-235.9 +Libanon (2019),SEK,Normalbelopp,738 +Libanon (2019),SEK,Breakfast,-110.7 +Libanon (2019),SEK,Lunch,-258.3 +Libanon (2019),SEK,Dinner,-258.3 +Libanon (2020),SEK,Normalbelopp,766 +Libanon (2020),SEK,Breakfast,-114.9 +Libanon (2020),SEK,Lunch,-268.1 +Libanon (2020),SEK,Dinner,-268.1 +Liberia (2018),SEK,Normalbelopp,549 +Liberia (2018),SEK,Breakfast,-82.35 +Liberia (2018),SEK,Lunch,-192.15 +Liberia (2018),SEK,Dinner,-192.15 +Liberia (2019),SEK,Normalbelopp,569 +Liberia (2019),SEK,Breakfast,-85.35 +Liberia (2019),SEK,Lunch,-199.15 +Liberia (2019),SEK,Dinner,-199.15 +Liberia (2020),SEK,Normalbelopp,604 +Liberia (2020),SEK,Breakfast,-90.6 +Liberia (2020),SEK,Lunch,-211.4 +Liberia (2020),SEK,Dinner,-211.4 +Libyen (2018),SEK,Normalbelopp,584 +Libyen (2018),SEK,Breakfast,-87.6 +Libyen (2018),SEK,Lunch,-204.4 +Libyen (2018),SEK,Dinner,-204.4 +Libyen (2019),SEK,Normalbelopp,707 +Libyen (2019),SEK,Breakfast,-106.05 +Libyen (2019),SEK,Lunch,-247.45 +Libyen (2019),SEK,Dinner,-247.45 +Libyen (2020),SEK,Normalbelopp,775 +Libyen (2020),SEK,Breakfast,-116.25 +Libyen (2020),SEK,Lunch,-271.25 +Libyen (2020),SEK,Dinner,-271.25 +Liechtenstein (2018),SEK,Normalbelopp,705 +Liechtenstein (2018),SEK,Breakfast,-105.75 +Liechtenstein (2018),SEK,Lunch,-246.75 +Liechtenstein (2018),SEK,Dinner,-246.75 +Liechtenstein (2019),SEK,Normalbelopp,794 +Liechtenstein (2019),SEK,Breakfast,-119.1 +Liechtenstein (2019),SEK,Lunch,-277.9 +Liechtenstein (2019),SEK,Dinner,-277.9 +Liechtenstein (2020),SEK,Normalbelopp,914 +Liechtenstein (2020),SEK,Breakfast,-137.1 +Liechtenstein (2020),SEK,Lunch,-319.9 +Liechtenstein (2020),SEK,Dinner,-319.9 +Litauen (2018),SEK,Normalbelopp,345 +Litauen (2018),SEK,Breakfast,-51.75 +Litauen (2018),SEK,Lunch,-120.75 +Litauen (2018),SEK,Dinner,-120.75 +Litauen (2019),SEK,Normalbelopp,377 +Litauen (2019),SEK,Breakfast,-56.55 +Litauen (2019),SEK,Lunch,-131.95 +Litauen (2019),SEK,Dinner,-131.95 +Litauen (2020),SEK,Normalbelopp,398 +Litauen (2020),SEK,Breakfast,-59.7 +Litauen (2020),SEK,Lunch,-139.3 +Litauen (2020),SEK,Dinner,-139.3 +Luxemburg (2018),SEK,Normalbelopp,669 +Luxemburg (2018),SEK,Breakfast,-100.35 +Luxemburg (2018),SEK,Lunch,-234.15 +Luxemburg (2018),SEK,Dinner,-234.15 +Luxemburg (2019),SEK,Normalbelopp,735 +Luxemburg (2019),SEK,Breakfast,-110.25 +Luxemburg (2019),SEK,Lunch,-257.25 +Luxemburg (2019),SEK,Dinner,-257.25 +Luxemburg (2020),SEK,Normalbelopp,776 +Luxemburg (2020),SEK,Breakfast,-116.4 +Luxemburg (2020),SEK,Lunch,-271.6 +Luxemburg (2020),SEK,Dinner,-271.6 +Macao (2018),SEK,Normalbelopp,538 +Macao (2018),SEK,Breakfast,-80.7 +Macao (2018),SEK,Lunch,-188.3 +Macao (2018),SEK,Dinner,-188.3 +Macao (2019),SEK,Normalbelopp,578 +Macao (2019),SEK,Breakfast,-86.7 +Macao (2019),SEK,Lunch,-202.3 +Macao (2019),SEK,Dinner,-202.3 +Macao (2020),SEK,Normalbelopp,590 +Macao (2020),SEK,Breakfast,-88.5 +Macao (2020),SEK,Lunch,-206.5 +Macao (2020),SEK,Dinner,-206.5 +Madagaskar (2018),SEK,Normalbelopp,230 +Madagaskar (2018),SEK,Breakfast,-34.5 +Madagaskar (2018),SEK,Lunch,-80.5 +Madagaskar (2018),SEK,Dinner,-80.5 +Madagaskar (2019),SEK,Normalbelopp,230 +Madagaskar (2019),SEK,Breakfast,-34.5 +Madagaskar (2019),SEK,Lunch,-80.5 +Madagaskar (2019),SEK,Dinner,-80.5 +Madagaskar (2020),SEK,Normalbelopp,240 +Madagaskar (2020),SEK,Breakfast,-36 +Madagaskar (2020),SEK,Lunch,-84 +Madagaskar (2020),SEK,Dinner,-84 +Makedonien (2018),SEK,Normalbelopp,297 +Makedonien (2018),SEK,Breakfast,-44.55 +Makedonien (2018),SEK,Lunch,-103.95 +Makedonien (2018),SEK,Dinner,-103.95 +Makedonien (2019),SEK,Normalbelopp,321 +Makedonien (2019),SEK,Breakfast,-48.15 +Makedonien (2019),SEK,Lunch,-112.35 +Makedonien (2019),SEK,Dinner,-112.35 +Makedonien (2020),SEK,Normalbelopp,327 +Makedonien (2020),SEK,Breakfast,-49.05 +Makedonien (2020),SEK,Lunch,-114.45 +Makedonien (2020),SEK,Dinner,-114.45 +Malawi (2018),SEK,Normalbelopp,230 +Malawi (2018),SEK,Breakfast,-34.5 +Malawi (2018),SEK,Lunch,-80.5 +Malawi (2018),SEK,Dinner,-80.5 +Malawi (2019),SEK,Normalbelopp,281 +Malawi (2019),SEK,Breakfast,-42.15 +Malawi (2019),SEK,Lunch,-98.35 +Malawi (2019),SEK,Dinner,-98.35 +Malawi (2020),SEK,Normalbelopp,385 +Malawi (2020),SEK,Breakfast,-57.75 +Malawi (2020),SEK,Lunch,-134.75 +Malawi (2020),SEK,Dinner,-134.75 +Malaysia (2018),SEK,Normalbelopp,274 +Malaysia (2018),SEK,Breakfast,-41.1 +Malaysia (2018),SEK,Lunch,-95.9 +Malaysia (2018),SEK,Dinner,-95.9 +Malaysia (2019),SEK,Normalbelopp,307 +Malaysia (2019),SEK,Breakfast,-46.05 +Malaysia (2019),SEK,Lunch,-107.45 +Malaysia (2019),SEK,Dinner,-107.45 +Malaysia (2020),SEK,Normalbelopp,330 +Malaysia (2020),SEK,Breakfast,-49.5 +Malaysia (2020),SEK,Lunch,-115.5 +Malaysia (2020),SEK,Dinner,-115.5 +Maldiverna (2018),SEK,Normalbelopp,339 +Maldiverna (2018),SEK,Breakfast,-50.85 +Maldiverna (2018),SEK,Lunch,-118.65 +Maldiverna (2018),SEK,Dinner,-118.65 +Maldiverna (2019),SEK,Normalbelopp,393 +Maldiverna (2019),SEK,Breakfast,-58.95 +Maldiverna (2019),SEK,Lunch,-137.55 +Maldiverna (2019),SEK,Dinner,-137.55 +Maldiverna (2020),SEK,Normalbelopp,433 +Maldiverna (2020),SEK,Breakfast,-64.95 +Maldiverna (2020),SEK,Lunch,-151.55 +Maldiverna (2020),SEK,Dinner,-151.55 +Mali (2018),SEK,Normalbelopp,440 +Mali (2018),SEK,Breakfast,-66 +Mali (2018),SEK,Lunch,-154 +Mali (2018),SEK,Dinner,-154 +Mali (2019),SEK,Normalbelopp,450 +Mali (2019),SEK,Breakfast,-67.5 +Mali (2019),SEK,Lunch,-157.5 +Mali (2019),SEK,Dinner,-157.5 +Mali (2020),SEK,Normalbelopp,454 +Mali (2020),SEK,Breakfast,-68.1 +Mali (2020),SEK,Lunch,-158.9 +Mali (2020),SEK,Dinner,-158.9 +Malta (2018),SEK,Normalbelopp,480 +Malta (2018),SEK,Breakfast,-72 +Malta (2018),SEK,Lunch,-168 +Malta (2018),SEK,Dinner,-168 +Malta (2019),SEK,Normalbelopp,510 +Malta (2019),SEK,Breakfast,-76.5 +Malta (2019),SEK,Lunch,-178.5 +Malta (2019),SEK,Dinner,-178.5 +Malta (2020),SEK,Normalbelopp,538 +Malta (2020),SEK,Breakfast,-80.7 +Malta (2020),SEK,Lunch,-188.3 +Malta (2020),SEK,Dinner,-188.3 +Marocko (2018),SEK,Normalbelopp,376 +Marocko (2018),SEK,Breakfast,-56.4 +Marocko (2018),SEK,Lunch,-131.6 +Marocko (2018),SEK,Dinner,-131.6 +Marocko (2019),SEK,Normalbelopp,414 +Marocko (2019),SEK,Breakfast,-62.1 +Marocko (2019),SEK,Lunch,-144.9 +Marocko (2019),SEK,Dinner,-144.9 +Marocko (2020),SEK,Normalbelopp,446 +Marocko (2020),SEK,Breakfast,-66.9 +Marocko (2020),SEK,Lunch,-156.1 +Marocko (2020),SEK,Dinner,-156.1 +Mauretanien (2018),SEK,Normalbelopp,319 +Mauretanien (2018),SEK,Breakfast,-47.85 +Mauretanien (2018),SEK,Lunch,-111.65 +Mauretanien (2018),SEK,Dinner,-111.65 +Mauretanien (2019),SEK,Normalbelopp,340 +Mauretanien (2019),SEK,Breakfast,-51 +Mauretanien (2019),SEK,Lunch,-119 +Mauretanien (2019),SEK,Dinner,-119 +Mauretanien (2020),SEK,Normalbelopp,353 +Mauretanien (2020),SEK,Breakfast,-52.95 +Mauretanien (2020),SEK,Lunch,-123.55 +Mauretanien (2020),SEK,Dinner,-123.55 +Mauritius (2018),SEK,Normalbelopp,399 +Mauritius (2018),SEK,Breakfast,-59.85 +Mauritius (2018),SEK,Lunch,-139.65 +Mauritius (2018),SEK,Dinner,-139.65 +Mauritius (2019),SEK,Normalbelopp,456 +Mauritius (2019),SEK,Breakfast,-68.4 +Mauritius (2019),SEK,Lunch,-159.6 +Mauritius (2019),SEK,Dinner,-159.6 +Mauritius (2020),SEK,Normalbelopp,465 +Mauritius (2020),SEK,Breakfast,-69.75 +Mauritius (2020),SEK,Lunch,-162.75 +Mauritius (2020),SEK,Dinner,-162.75 +Mexiko (2018),SEK,Normalbelopp,293 +Mexiko (2018),SEK,Breakfast,-43.95 +Mexiko (2018),SEK,Lunch,-102.55 +Mexiko (2018),SEK,Dinner,-102.55 +Mexiko (2019),SEK,Normalbelopp,301 +Mexiko (2019),SEK,Breakfast,-45.15 +Mexiko (2019),SEK,Lunch,-105.35 +Mexiko (2019),SEK,Dinner,-105.35 +Mexiko (2020),SEK,Normalbelopp,367 +Mexiko (2020),SEK,Breakfast,-55.05 +Mexiko (2020),SEK,Lunch,-128.45 +Mexiko (2020),SEK,Dinner,-128.45 +Mikronesien (2018),SEK,Normalbelopp,396 +Mikronesien (2018),SEK,Breakfast,-59.4 +Mikronesien (2018),SEK,Lunch,-138.6 +Mikronesien (2018),SEK,Dinner,-138.6 +Mikronesien (2019),SEK,Normalbelopp,443 +Mikronesien (2019),SEK,Breakfast,-66.45 +Mikronesien (2019),SEK,Lunch,-155.05 +Mikronesien (2019),SEK,Dinner,-155.05 +Mikronesien (2020),SEK,Normalbelopp,464 +Mikronesien (2020),SEK,Breakfast,-69.6 +Mikronesien (2020),SEK,Lunch,-162.4 +Mikronesien (2020),SEK,Dinner,-162.4 +Mocambique (2018),SEK,Normalbelopp,255 +Mocambique (2018),SEK,Breakfast,-38.25 +Mocambique (2018),SEK,Lunch,-89.25 +Mocambique (2018),SEK,Dinner,-89.25 +Mocambique (2019),SEK,Normalbelopp,288 +Mocambique (2019),SEK,Breakfast,-43.2 +Mocambique (2019),SEK,Lunch,-100.8 +Mocambique (2019),SEK,Dinner,-100.8 +Mocambique (2020),SEK,Normalbelopp,297 +Mocambique (2020),SEK,Breakfast,-44.55 +Mocambique (2020),SEK,Lunch,-103.95 +Mocambique (2020),SEK,Dinner,-103.95 +Moldavien (2018),SEK,Normalbelopp,230 +Moldavien (2018),SEK,Breakfast,-34.5 +Moldavien (2018),SEK,Lunch,-80.5 +Moldavien (2018),SEK,Dinner,-80.5 +Moldavien (2019),SEK,Normalbelopp,248 +Moldavien (2019),SEK,Breakfast,-37.2 +Moldavien (2019),SEK,Lunch,-86.8 +Moldavien (2019),SEK,Dinner,-86.8 +Moldavien (2020),SEK,Normalbelopp,285 +Moldavien (2020),SEK,Breakfast,-42.75 +Moldavien (2020),SEK,Lunch,-99.75 +Moldavien (2020),SEK,Dinner,-99.75 +Monaco (2018),SEK,Normalbelopp,818 +Monaco (2018),SEK,Breakfast,-122.7 +Monaco (2018),SEK,Lunch,-286.3 +Monaco (2018),SEK,Dinner,-286.3 +Monaco (2019),SEK,Normalbelopp,858 +Monaco (2019),SEK,Breakfast,-128.7 +Monaco (2019),SEK,Lunch,-300.3 +Monaco (2019),SEK,Dinner,-300.3 +Monaco (2020),SEK,Normalbelopp,906 +Monaco (2020),SEK,Breakfast,-135.9 +Monaco (2020),SEK,Lunch,-317.1 +Monaco (2020),SEK,Dinner,-317.1 +Mongoliet (2018),SEK,Normalbelopp,230 +Mongoliet (2018),SEK,Breakfast,-34.5 +Mongoliet (2018),SEK,Lunch,-80.5 +Mongoliet (2018),SEK,Dinner,-80.5 +Mongoliet (2019),SEK,Normalbelopp,280 +Mongoliet (2019),SEK,Breakfast,-42 +Mongoliet (2019),SEK,Lunch,-98 +Mongoliet (2019),SEK,Dinner,-98 +Mongoliet (2020),SEK,Normalbelopp,283 +Mongoliet (2020),SEK,Breakfast,-42.45 +Mongoliet (2020),SEK,Lunch,-99.05 +Mongoliet (2020),SEK,Dinner,-99.05 +Montenegro (2018),SEK,Normalbelopp,393 +Montenegro (2018),SEK,Breakfast,-58.95 +Montenegro (2018),SEK,Lunch,-137.55 +Montenegro (2018),SEK,Dinner,-137.55 +Montenegro (2019),SEK,Normalbelopp,423 +Montenegro (2019),SEK,Breakfast,-63.45 +Montenegro (2019),SEK,Lunch,-148.05 +Montenegro (2019),SEK,Dinner,-148.05 +Montenegro (2020),SEK,Normalbelopp,391 +Montenegro (2020),SEK,Breakfast,-58.65 +Montenegro (2020),SEK,Lunch,-136.85 +Montenegro (2020),SEK,Dinner,-136.85 +Myanmar (2018),SEK,Normalbelopp,315 +Myanmar (2018),SEK,Breakfast,-47.25 +Myanmar (2018),SEK,Lunch,-110.25 +Myanmar (2018),SEK,Dinner,-110.25 +Myanmar (2019),SEK,Normalbelopp,286 +Myanmar (2019),SEK,Breakfast,-42.9 +Myanmar (2019),SEK,Lunch,-100.1 +Myanmar (2019),SEK,Dinner,-100.1 +Myanmar (2020),SEK,Normalbelopp,318 +Myanmar (2020),SEK,Breakfast,-47.7 +Myanmar (2020),SEK,Lunch,-111.3 +Myanmar (2020),SEK,Dinner,-111.3 +Nederländerna (2018),SEK,Normalbelopp,554 +Nederländerna (2018),SEK,Breakfast,-83.1 +Nederländerna (2018),SEK,Lunch,-193.9 +Nederländerna (2018),SEK,Dinner,-193.9 +Nederländerna (2019),SEK,Normalbelopp,610 +Nederländerna (2019),SEK,Breakfast,-91.5 +Nederländerna (2019),SEK,Lunch,-213.5 +Nederländerna (2019),SEK,Dinner,-213.5 +Nederländerna (2020),SEK,Normalbelopp,648 +Nederländerna (2020),SEK,Breakfast,-97.2 +Nederländerna (2020),SEK,Lunch,-226.8 +Nederländerna (2020),SEK,Dinner,-226.8 +Nederländska Antillerna (2018),SEK,Normalbelopp,490 +Nederländska Antillerna (2018),SEK,Breakfast,-73.5 +Nederländska Antillerna (2018),SEK,Lunch,-171.5 +Nederländska Antillerna (2018),SEK,Dinner,-171.5 +Nederländska Antillerna (2019),SEK,Normalbelopp,544 +Nederländska Antillerna (2019),SEK,Breakfast,-81.6 +Nederländska Antillerna (2019),SEK,Lunch,-190.4 +Nederländska Antillerna (2019),SEK,Dinner,-190.4 +Nederländska Antillerna (2020),SEK,Normalbelopp,601 +Nederländska Antillerna (2020),SEK,Breakfast,-90.15 +Nederländska Antillerna (2020),SEK,Lunch,-210.35 +Nederländska Antillerna (2020),SEK,Dinner,-210.35 +Nepal (2018),SEK,Normalbelopp,268 +Nepal (2018),SEK,Breakfast,-40.2 +Nepal (2018),SEK,Lunch,-93.8 +Nepal (2018),SEK,Dinner,-93.8 +Nepal (2019),SEK,Normalbelopp,258 +Nepal (2019),SEK,Breakfast,-38.7 +Nepal (2019),SEK,Lunch,-90.3 +Nepal (2019),SEK,Dinner,-90.3 +Nepal (2020),SEK,Normalbelopp,287 +Nepal (2020),SEK,Breakfast,-43.05 +Nepal (2020),SEK,Lunch,-100.45 +Nepal (2020),SEK,Dinner,-100.45 +Nicaragua (2018),SEK,Normalbelopp,328 +Nicaragua (2018),SEK,Breakfast,-49.2 +Nicaragua (2018),SEK,Lunch,-114.8 +Nicaragua (2018),SEK,Dinner,-114.8 +Nicaragua (2019),SEK,Normalbelopp,334 +Nicaragua (2019),SEK,Breakfast,-50.1 +Nicaragua (2019),SEK,Lunch,-116.9 +Nicaragua (2019),SEK,Dinner,-116.9 +Nicaragua (2020),SEK,Normalbelopp,367 +Nicaragua (2020),SEK,Breakfast,-55.05 +Nicaragua (2020),SEK,Lunch,-128.45 +Nicaragua (2020),SEK,Dinner,-128.45 +Niger (2018),SEK,Normalbelopp,306 +Niger (2018),SEK,Breakfast,-45.9 +Niger (2018),SEK,Lunch,-107.1 +Niger (2018),SEK,Dinner,-107.1 +Niger (2019),SEK,Normalbelopp,339 +Niger (2019),SEK,Breakfast,-50.85 +Niger (2019),SEK,Lunch,-118.65 +Niger (2019),SEK,Dinner,-118.65 +Niger (2020),SEK,Normalbelopp,354 +Niger (2020),SEK,Breakfast,-53.1 +Niger (2020),SEK,Lunch,-123.9 +Niger (2020),SEK,Dinner,-123.9 +Nigeria (2018),SEK,Normalbelopp,418 +Nigeria (2018),SEK,Breakfast,-62.7 +Nigeria (2018),SEK,Lunch,-146.3 +Nigeria (2018),SEK,Dinner,-146.3 +Nigeria (2019),SEK,Normalbelopp,501 +Nigeria (2019),SEK,Breakfast,-75.15 +Nigeria (2019),SEK,Lunch,-175.35 +Nigeria (2019),SEK,Dinner,-175.35 +Nigeria (2020),SEK,Normalbelopp,549 +Nigeria (2020),SEK,Breakfast,-82.35 +Nigeria (2020),SEK,Lunch,-192.15 +Nigeria (2020),SEK,Dinner,-192.15 +Nordmakedonien - f.d. Makedonien (2020),SEK,Normalbelopp,327 +Nordmakedonien - f.d. Makedonien (2020),SEK,Breakfast,-49.05 +Nordmakedonien - f.d. Makedonien (2020),SEK,Lunch,-114.45 +Nordmakedonien - f.d. Makedonien (2020),SEK,Dinner,-114.45 +Norge (2018),SEK,Normalbelopp,823 +Norge (2018),SEK,Breakfast,-123.45 +Norge (2018),SEK,Lunch,-288.05 +Norge (2018),SEK,Dinner,-288.05 +Norge (2019),SEK,Normalbelopp,876 +Norge (2019),SEK,Breakfast,-131.4 +Norge (2019),SEK,Lunch,-306.6 +Norge (2019),SEK,Dinner,-306.6 +Norge (2020),SEK,Normalbelopp,893 +Norge (2020),SEK,Breakfast,-133.95 +Norge (2020),SEK,Lunch,-312.55 +Norge (2020),SEK,Dinner,-312.55 +Nya Zeeland (2018),SEK,Normalbelopp,538 +Nya Zeeland (2018),SEK,Breakfast,-80.7 +Nya Zeeland (2018),SEK,Lunch,-188.3 +Nya Zeeland (2018),SEK,Dinner,-188.3 +Nya Zeeland (2019),SEK,Normalbelopp,565 +Nya Zeeland (2019),SEK,Breakfast,-84.75 +Nya Zeeland (2019),SEK,Lunch,-197.75 +Nya Zeeland (2019),SEK,Dinner,-197.75 +Nya Zeeland (2020),SEK,Normalbelopp,585 +Nya Zeeland (2020),SEK,Breakfast,-87.75 +Nya Zeeland (2020),SEK,Lunch,-204.75 +Nya Zeeland (2020),SEK,Dinner,-204.75 +Oman (2018),SEK,Normalbelopp,685 +Oman (2018),SEK,Breakfast,-102.75 +Oman (2018),SEK,Lunch,-239.75 +Oman (2018),SEK,Dinner,-239.75 +Oman (2019),SEK,Normalbelopp,748 +Oman (2019),SEK,Breakfast,-112.2 +Oman (2019),SEK,Lunch,-261.8 +Oman (2019),SEK,Dinner,-261.8 +Oman (2020),SEK,Normalbelopp,777 +Oman (2020),SEK,Breakfast,-116.55 +Oman (2020),SEK,Lunch,-271.95 +Oman (2020),SEK,Dinner,-271.95 +Österrike (2018),SEK,Normalbelopp,583 +Österrike (2018),SEK,Breakfast,-87.45 +Österrike (2018),SEK,Lunch,-204.05 +Österrike (2018),SEK,Dinner,-204.05 +Österrike (2019),SEK,Normalbelopp,616 +Österrike (2019),SEK,Breakfast,-92.4 +Österrike (2019),SEK,Lunch,-215.6 +Österrike (2019),SEK,Dinner,-215.6 +Österrike (2020),SEK,Normalbelopp,641 +Österrike (2020),SEK,Breakfast,-96.15 +Österrike (2020),SEK,Lunch,-224.35 +Österrike (2020),SEK,Dinner,-224.35 +Övriga länder och områden (2018),SEK,Normalbelopp,357 +Övriga länder och områden (2018),SEK,Breakfast,-53.55 +Övriga länder och områden (2018),SEK,Lunch,-124.95 +Övriga länder och områden (2018),SEK,Dinner,-124.95 +Övriga länder och områden (2019),SEK,Normalbelopp,369 +Övriga länder och områden (2019),SEK,Breakfast,-55.35 +Övriga länder och områden (2019),SEK,Lunch,-129.15 +Övriga länder och områden (2019),SEK,Dinner,-129.15 +Övriga länder och områden (2020),SEK,Normalbelopp,392 +Övriga länder och områden (2020),SEK,Breakfast,-58.8 +Övriga länder och områden (2020),SEK,Lunch,-137.2 +Övriga länder och områden (2020),SEK,Dinner,-137.2 +Pakistan (2018),SEK,Normalbelopp,296 +Pakistan (2018),SEK,Breakfast,-44.4 +Pakistan (2018),SEK,Lunch,-103.6 +Pakistan (2018),SEK,Dinner,-103.6 +Pakistan (2019),SEK,Normalbelopp,230 +Pakistan (2019),SEK,Breakfast,-34.5 +Pakistan (2019),SEK,Lunch,-80.5 +Pakistan (2019),SEK,Dinner,-80.5 +Pakistan (2020),SEK,Normalbelopp,240 +Pakistan (2020),SEK,Breakfast,-36 +Pakistan (2020),SEK,Lunch,-84 +Pakistan (2020),SEK,Dinner,-84 +Panama (2018),SEK,Normalbelopp,444 +Panama (2018),SEK,Breakfast,-66.6 +Panama (2018),SEK,Lunch,-155.4 +Panama (2018),SEK,Dinner,-155.4 +Panama (2019),SEK,Normalbelopp,505 +Panama (2019),SEK,Breakfast,-75.75 +Panama (2019),SEK,Lunch,-176.75 +Panama (2019),SEK,Dinner,-176.75 +Panama (2020),SEK,Normalbelopp,564 +Panama (2020),SEK,Breakfast,-84.6 +Panama (2020),SEK,Lunch,-197.4 +Panama (2020),SEK,Dinner,-197.4 +Papua Nya Guinea (2018),SEK,Normalbelopp,452 +Papua Nya Guinea (2018),SEK,Breakfast,-67.8 +Papua Nya Guinea (2018),SEK,Lunch,-158.2 +Papua Nya Guinea (2018),SEK,Dinner,-158.2 +Papua Nya Guinea (2019),SEK,Normalbelopp,487 +Papua Nya Guinea (2019),SEK,Breakfast,-73.05 +Papua Nya Guinea (2019),SEK,Lunch,-170.45 +Papua Nya Guinea (2019),SEK,Dinner,-170.45 +Papua Nya Guinea (2020),SEK,Normalbelopp,515 +Papua Nya Guinea (2020),SEK,Breakfast,-77.25 +Papua Nya Guinea (2020),SEK,Lunch,-180.25 +Papua Nya Guinea (2020),SEK,Dinner,-180.25 +Paraguay (2018),SEK,Normalbelopp,298 +Paraguay (2018),SEK,Breakfast,-44.7 +Paraguay (2018),SEK,Lunch,-104.3 +Paraguay (2018),SEK,Dinner,-104.3 +Paraguay (2019),SEK,Normalbelopp,316 +Paraguay (2019),SEK,Breakfast,-47.4 +Paraguay (2019),SEK,Lunch,-110.6 +Paraguay (2019),SEK,Dinner,-110.6 +Paraguay (2020),SEK,Normalbelopp,314 +Paraguay (2020),SEK,Breakfast,-47.1 +Paraguay (2020),SEK,Lunch,-109.9 +Paraguay (2020),SEK,Dinner,-109.9 +Peru (2018),SEK,Normalbelopp,457 +Peru (2018),SEK,Breakfast,-68.55 +Peru (2018),SEK,Lunch,-159.95 +Peru (2018),SEK,Dinner,-159.95 +Peru (2019),SEK,Normalbelopp,481 +Peru (2019),SEK,Breakfast,-72.15 +Peru (2019),SEK,Lunch,-168.35 +Peru (2019),SEK,Dinner,-168.35 +Peru (2020),SEK,Normalbelopp,511 +Peru (2020),SEK,Breakfast,-76.65 +Peru (2020),SEK,Lunch,-178.85 +Peru (2020),SEK,Dinner,-178.85 +Polen (2018),SEK,Normalbelopp,411 +Polen (2018),SEK,Breakfast,-61.65 +Polen (2018),SEK,Lunch,-143.85 +Polen (2018),SEK,Dinner,-143.85 +Polen (2019),SEK,Normalbelopp,436 +Polen (2019),SEK,Breakfast,-65.4 +Polen (2019),SEK,Lunch,-152.6 +Polen (2019),SEK,Dinner,-152.6 +Polen (2020),SEK,Normalbelopp,461 +Polen (2020),SEK,Breakfast,-69.15 +Polen (2020),SEK,Lunch,-161.35 +Polen (2020),SEK,Dinner,-161.35 +Portugal (2018),SEK,Normalbelopp,432 +Portugal (2018),SEK,Breakfast,-64.8 +Portugal (2018),SEK,Lunch,-151.2 +Portugal (2018),SEK,Dinner,-151.2 +Portugal (2019),SEK,Normalbelopp,461 +Portugal (2019),SEK,Breakfast,-69.15 +Portugal (2019),SEK,Lunch,-161.35 +Portugal (2019),SEK,Dinner,-161.35 +Portugal (2020),SEK,Normalbelopp,486 +Portugal (2020),SEK,Breakfast,-72.9 +Portugal (2020),SEK,Lunch,-170.1 +Portugal (2020),SEK,Dinner,-170.1 +Puerto Rico (2018),SEK,Normalbelopp,689 +Puerto Rico (2018),SEK,Breakfast,-103.35 +Puerto Rico (2018),SEK,Lunch,-241.15 +Puerto Rico (2018),SEK,Dinner,-241.15 +Puerto Rico (2019),SEK,Normalbelopp,762 +Puerto Rico (2019),SEK,Breakfast,-114.3 +Puerto Rico (2019),SEK,Lunch,-266.7 +Puerto Rico (2019),SEK,Dinner,-266.7 +Puerto Rico (2020),SEK,Normalbelopp,851 +Puerto Rico (2020),SEK,Breakfast,-127.65 +Puerto Rico (2020),SEK,Lunch,-297.85 +Puerto Rico (2020),SEK,Dinner,-297.85 +Qatar (2018),SEK,Normalbelopp,645 +Qatar (2018),SEK,Breakfast,-96.75 +Qatar (2018),SEK,Lunch,-225.75 +Qatar (2018),SEK,Dinner,-225.75 +Qatar (2019),SEK,Normalbelopp,716 +Qatar (2019),SEK,Breakfast,-107.4 +Qatar (2019),SEK,Lunch,-250.6 +Qatar (2019),SEK,Dinner,-250.6 +Qatar (2020),SEK,Normalbelopp,766 +Qatar (2020),SEK,Breakfast,-114.9 +Qatar (2020),SEK,Lunch,-268.1 +Qatar (2020),SEK,Dinner,-268.1 +Rumänien (2018),SEK,Normalbelopp,312 +Rumänien (2018),SEK,Breakfast,-46.8 +Rumänien (2018),SEK,Lunch,-109.2 +Rumänien (2018),SEK,Dinner,-109.2 +Rumänien (2019),SEK,Normalbelopp,332 +Rumänien (2019),SEK,Breakfast,-49.8 +Rumänien (2019),SEK,Lunch,-116.2 +Rumänien (2019),SEK,Dinner,-116.2 +Rumänien (2020),SEK,Normalbelopp,337 +Rumänien (2020),SEK,Breakfast,-50.55 +Rumänien (2020),SEK,Lunch,-117.95 +Rumänien (2020),SEK,Dinner,-117.95 +Rwanda (2018),SEK,Normalbelopp,296 +Rwanda (2018),SEK,Breakfast,-44.4 +Rwanda (2018),SEK,Lunch,-103.6 +Rwanda (2018),SEK,Dinner,-103.6 +Rwanda (2019),SEK,Normalbelopp,333 +Rwanda (2019),SEK,Breakfast,-49.95 +Rwanda (2019),SEK,Lunch,-116.55 +Rwanda (2019),SEK,Dinner,-116.55 +Rwanda (2020),SEK,Normalbelopp,332 +Rwanda (2020),SEK,Breakfast,-49.8 +Rwanda (2020),SEK,Lunch,-116.2 +Rwanda (2020),SEK,Dinner,-116.2 +Ryssland (2018),SEK,Normalbelopp,470 +Ryssland (2018),SEK,Breakfast,-70.5 +Ryssland (2018),SEK,Lunch,-164.5 +Ryssland (2018),SEK,Dinner,-164.5 +Ryssland (2019),SEK,Normalbelopp,455 +Ryssland (2019),SEK,Breakfast,-68.25 +Ryssland (2019),SEK,Lunch,-159.25 +Ryssland (2019),SEK,Dinner,-159.25 +Ryssland (2020),SEK,Normalbelopp,520 +Ryssland (2020),SEK,Breakfast,-78 +Ryssland (2020),SEK,Lunch,-182 +Ryssland (2020),SEK,Dinner,-182 +Saint Lucia (2018),SEK,Normalbelopp,524 +Saint Lucia (2018),SEK,Breakfast,-78.6 +Saint Lucia (2018),SEK,Lunch,-183.4 +Saint Lucia (2018),SEK,Dinner,-183.4 +Saint Lucia (2019),SEK,Normalbelopp,555 +Saint Lucia (2019),SEK,Breakfast,-83.25 +Saint Lucia (2019),SEK,Lunch,-194.25 +Saint Lucia (2019),SEK,Dinner,-194.25 +Saint Lucia (2020),SEK,Normalbelopp,573 +Saint Lucia (2020),SEK,Breakfast,-85.95 +Saint Lucia (2020),SEK,Lunch,-200.55 +Saint Lucia (2020),SEK,Dinner,-200.55 +Saint Vincent och Grenadinerna (2018),SEK,Normalbelopp,460 +Saint Vincent och Grenadinerna (2018),SEK,Breakfast,-69 +Saint Vincent och Grenadinerna (2018),SEK,Lunch,-161 +Saint Vincent och Grenadinerna (2018),SEK,Dinner,-161 +Saint Vincent och Grenadinerna (2019),SEK,Normalbelopp,502 +Saint Vincent och Grenadinerna (2019),SEK,Breakfast,-75.3 +Saint Vincent och Grenadinerna (2019),SEK,Lunch,-175.7 +Saint Vincent och Grenadinerna (2019),SEK,Dinner,-175.7 +Saint Vincent och Grenadinerna (2020),SEK,Normalbelopp,516 +Saint Vincent och Grenadinerna (2020),SEK,Breakfast,-77.4 +Saint Vincent och Grenadinerna (2020),SEK,Lunch,-180.6 +Saint Vincent och Grenadinerna (2020),SEK,Dinner,-180.6 +Samoa - Självständiga staten (2018),SEK,Normalbelopp,479 +Samoa - Självständiga staten (2018),SEK,Breakfast,-71.85 +Samoa - Självständiga staten (2018),SEK,Lunch,-167.65 +Samoa - Självständiga staten (2018),SEK,Dinner,-167.65 +Samoa - Självständiga staten (2019),SEK,Normalbelopp,513 +Samoa - Självständiga staten (2019),SEK,Breakfast,-76.95 +Samoa - Självständiga staten (2019),SEK,Lunch,-179.55 +Samoa - Självständiga staten (2019),SEK,Dinner,-179.55 +Samoa - Självständiga staten (2020),SEK,Normalbelopp,572 +Samoa - Självständiga staten (2020),SEK,Breakfast,-85.8 +Samoa - Självständiga staten (2020),SEK,Lunch,-200.2 +Samoa - Självständiga staten (2020),SEK,Dinner,-200.2 +San Marino (2018),SEK,Normalbelopp,540 +San Marino (2018),SEK,Breakfast,-81 +San Marino (2018),SEK,Lunch,-189 +San Marino (2018),SEK,Dinner,-189 +San Marino (2019),SEK,Normalbelopp,590 +San Marino (2019),SEK,Breakfast,-88.5 +San Marino (2019),SEK,Lunch,-206.5 +San Marino (2019),SEK,Dinner,-206.5 +San Marino (2020),SEK,Normalbelopp,627 +San Marino (2020),SEK,Breakfast,-94.05 +San Marino (2020),SEK,Lunch,-219.45 +San Marino (2020),SEK,Dinner,-219.45 +Saudiarabien (2018),SEK,Normalbelopp,568 +Saudiarabien (2018),SEK,Breakfast,-85.2 +Saudiarabien (2018),SEK,Lunch,-198.8 +Saudiarabien (2018),SEK,Dinner,-198.8 +Saudiarabien (2019),SEK,Normalbelopp,652 +Saudiarabien (2019),SEK,Breakfast,-97.8 +Saudiarabien (2019),SEK,Lunch,-228.2 +Saudiarabien (2019),SEK,Dinner,-228.2 +Saudiarabien (2020),SEK,Normalbelopp,742 +Saudiarabien (2020),SEK,Breakfast,-111.3 +Saudiarabien (2020),SEK,Lunch,-259.7 +Saudiarabien (2020),SEK,Dinner,-259.7 +Schweiz (2018),SEK,Normalbelopp,819 +Schweiz (2018),SEK,Breakfast,-122.85 +Schweiz (2018),SEK,Lunch,-286.65 +Schweiz (2018),SEK,Dinner,-286.65 +Schweiz (2019),SEK,Normalbelopp,888 +Schweiz (2019),SEK,Breakfast,-133.2 +Schweiz (2019),SEK,Lunch,-310.8 +Schweiz (2019),SEK,Dinner,-310.8 +Schweiz (2020),SEK,Normalbelopp,979 +Schweiz (2020),SEK,Breakfast,-146.85 +Schweiz (2020),SEK,Lunch,-342.65 +Schweiz (2020),SEK,Dinner,-342.65 +Senegal (2018),SEK,Normalbelopp,511 +Senegal (2018),SEK,Breakfast,-76.65 +Senegal (2018),SEK,Lunch,-178.85 +Senegal (2018),SEK,Dinner,-178.85 +Senegal (2019),SEK,Normalbelopp,539 +Senegal (2019),SEK,Breakfast,-80.85 +Senegal (2019),SEK,Lunch,-188.65 +Senegal (2019),SEK,Dinner,-188.65 +Senegal (2020),SEK,Normalbelopp,563 +Senegal (2020),SEK,Breakfast,-84.45 +Senegal (2020),SEK,Lunch,-197.05 +Senegal (2020),SEK,Dinner,-197.05 +Serbien (2018),SEK,Normalbelopp,362 +Serbien (2018),SEK,Breakfast,-54.3 +Serbien (2018),SEK,Lunch,-126.7 +Serbien (2018),SEK,Dinner,-126.7 +Serbien (2019),SEK,Normalbelopp,388 +Serbien (2019),SEK,Breakfast,-58.2 +Serbien (2019),SEK,Lunch,-135.8 +Serbien (2019),SEK,Dinner,-135.8 +Serbien (2020),SEK,Normalbelopp,395 +Serbien (2020),SEK,Breakfast,-59.25 +Serbien (2020),SEK,Lunch,-138.25 +Serbien (2020),SEK,Dinner,-138.25 +Seychellerna (2018),SEK,Normalbelopp,542 +Seychellerna (2018),SEK,Breakfast,-81.3 +Seychellerna (2018),SEK,Lunch,-189.7 +Seychellerna (2018),SEK,Dinner,-189.7 +Seychellerna (2019),SEK,Normalbelopp,620 +Seychellerna (2019),SEK,Breakfast,-93 +Seychellerna (2019),SEK,Lunch,-217 +Seychellerna (2019),SEK,Dinner,-217 +Seychellerna (2020),SEK,Normalbelopp,696 +Seychellerna (2020),SEK,Breakfast,-104.4 +Seychellerna (2020),SEK,Lunch,-243.6 +Seychellerna (2020),SEK,Dinner,-243.6 +Sierra Leone (2018),SEK,Normalbelopp,287 +Sierra Leone (2018),SEK,Breakfast,-43.05 +Sierra Leone (2018),SEK,Lunch,-100.45 +Sierra Leone (2018),SEK,Dinner,-100.45 +Sierra Leone (2019),SEK,Normalbelopp,328 +Sierra Leone (2019),SEK,Breakfast,-49.2 +Sierra Leone (2019),SEK,Lunch,-114.8 +Sierra Leone (2019),SEK,Dinner,-114.8 +Sierra Leone (2020),SEK,Normalbelopp,351 +Sierra Leone (2020),SEK,Breakfast,-52.65 +Sierra Leone (2020),SEK,Lunch,-122.85 +Sierra Leone (2020),SEK,Dinner,-122.85 +Singapore (2018),SEK,Normalbelopp,576 +Singapore (2018),SEK,Breakfast,-86.4 +Singapore (2018),SEK,Lunch,-201.6 +Singapore (2018),SEK,Dinner,-201.6 +Singapore (2019),SEK,Normalbelopp,632 +Singapore (2019),SEK,Breakfast,-94.8 +Singapore (2019),SEK,Lunch,-221.2 +Singapore (2019),SEK,Dinner,-221.2 +Singapore (2020),SEK,Normalbelopp,690 +Singapore (2020),SEK,Breakfast,-103.5 +Singapore (2020),SEK,Lunch,-241.5 +Singapore (2020),SEK,Dinner,-241.5 +Slovakien (2018),SEK,Normalbelopp,451 +Slovakien (2018),SEK,Breakfast,-67.65 +Slovakien (2018),SEK,Lunch,-157.85 +Slovakien (2018),SEK,Dinner,-157.85 +Slovakien (2019),SEK,Normalbelopp,479 +Slovakien (2019),SEK,Breakfast,-71.85 +Slovakien (2019),SEK,Lunch,-167.65 +Slovakien (2019),SEK,Dinner,-167.65 +Slovakien (2020),SEK,Normalbelopp,519 +Slovakien (2020),SEK,Breakfast,-77.85 +Slovakien (2020),SEK,Lunch,-181.65 +Slovakien (2020),SEK,Dinner,-181.65 +Slovenien (2018),SEK,Normalbelopp,463 +Slovenien (2018),SEK,Breakfast,-69.45 +Slovenien (2018),SEK,Lunch,-162.05 +Slovenien (2018),SEK,Dinner,-162.05 +Slovenien (2019),SEK,Normalbelopp,503 +Slovenien (2019),SEK,Breakfast,-75.45 +Slovenien (2019),SEK,Lunch,-176.05 +Slovenien (2019),SEK,Dinner,-176.05 +Slovenien (2020),SEK,Normalbelopp,529 +Slovenien (2020),SEK,Breakfast,-79.35 +Slovenien (2020),SEK,Lunch,-185.15 +Slovenien (2020),SEK,Dinner,-185.15 +Spanien (2018),SEK,Normalbelopp,513 +Spanien (2018),SEK,Breakfast,-76.95 +Spanien (2018),SEK,Lunch,-179.55 +Spanien (2018),SEK,Dinner,-179.55 +Spanien (2019),SEK,Normalbelopp,558 +Spanien (2019),SEK,Breakfast,-83.7 +Spanien (2019),SEK,Lunch,-195.3 +Spanien (2019),SEK,Dinner,-195.3 +Spanien (2020),SEK,Normalbelopp,615 +Spanien (2020),SEK,Breakfast,-92.25 +Spanien (2020),SEK,Lunch,-215.25 +Spanien (2020),SEK,Dinner,-215.25 +Sri Lanka (2018),SEK,Normalbelopp,283 +Sri Lanka (2018),SEK,Breakfast,-42.45 +Sri Lanka (2018),SEK,Lunch,-99.05 +Sri Lanka (2018),SEK,Dinner,-99.05 +Sri Lanka (2019),SEK,Normalbelopp,263 +Sri Lanka (2019),SEK,Breakfast,-39.45 +Sri Lanka (2019),SEK,Lunch,-92.05 +Sri Lanka (2019),SEK,Dinner,-92.05 +Sri Lanka (2020),SEK,Normalbelopp,272 +Sri Lanka (2020),SEK,Breakfast,-40.8 +Sri Lanka (2020),SEK,Lunch,-95.2 +Sri Lanka (2020),SEK,Dinner,-95.2 +Storbritannien och Nordirland (2018),SEK,Normalbelopp,655 +Storbritannien och Nordirland (2018),SEK,Breakfast,-98.25 +Storbritannien och Nordirland (2018),SEK,Lunch,-229.25 +Storbritannien och Nordirland (2018),SEK,Dinner,-229.25 +Storbritannien och Nordirland (2019),SEK,Normalbelopp,689 +Storbritannien och Nordirland (2019),SEK,Breakfast,-103.35 +Storbritannien och Nordirland (2019),SEK,Lunch,-241.15 +Storbritannien och Nordirland (2019),SEK,Dinner,-241.15 +Storbritannien och Nordirland (2020),SEK,Normalbelopp,746 +Storbritannien och Nordirland (2020),SEK,Breakfast,-111.9 +Storbritannien och Nordirland (2020),SEK,Lunch,-261.1 +Storbritannien och Nordirland (2020),SEK,Dinner,-261.1 +Sudan (2018),SEK,Normalbelopp,507 +Sudan (2018),SEK,Breakfast,-76.05 +Sudan (2018),SEK,Lunch,-177.45 +Sudan (2018),SEK,Dinner,-177.45 +Sudan (2019),SEK,Normalbelopp,230 +Sudan (2019),SEK,Breakfast,-34.5 +Sudan (2019),SEK,Lunch,-80.5 +Sudan (2019),SEK,Dinner,-80.5 +Sudan (2020),SEK,Normalbelopp,240 +Sudan (2020),SEK,Breakfast,-36 +Sudan (2020),SEK,Lunch,-84 +Sudan (2020),SEK,Dinner,-84 +Swaziland (2018),SEK,Normalbelopp,230 +Swaziland (2018),SEK,Breakfast,-34.5 +Swaziland (2018),SEK,Lunch,-80.5 +Swaziland (2018),SEK,Dinner,-80.5 +Swaziland (2019),SEK,Normalbelopp,230 +Swaziland (2019),SEK,Breakfast,-34.5 +Swaziland (2019),SEK,Lunch,-80.5 +Swaziland (2019),SEK,Dinner,-80.5 +Swaziland (2020),SEK,Normalbelopp,240 +Swaziland (2020),SEK,Breakfast,-36 +Swaziland (2020),SEK,Lunch,-84 +Swaziland (2020),SEK,Dinner,-84 +Sydafrika (2018),SEK,Normalbelopp,249 +Sydafrika (2018),SEK,Breakfast,-37.35 +Sydafrika (2018),SEK,Lunch,-87.15 +Sydafrika (2018),SEK,Dinner,-87.15 +Sydafrika (2019),SEK,Normalbelopp,262 +Sydafrika (2019),SEK,Breakfast,-39.3 +Sydafrika (2019),SEK,Lunch,-91.7 +Sydafrika (2019),SEK,Dinner,-91.7 +Sydafrika (2020),SEK,Normalbelopp,286 +Sydafrika (2020),SEK,Breakfast,-42.9 +Sydafrika (2020),SEK,Lunch,-100.1 +Sydafrika (2020),SEK,Dinner,-100.1 +Sydkorea (2018),SEK,Normalbelopp,624 +Sydkorea (2018),SEK,Breakfast,-93.6 +Sydkorea (2018),SEK,Lunch,-218.4 +Sydkorea (2018),SEK,Dinner,-218.4 +Sydkorea (2019),SEK,Normalbelopp,675 +Sydkorea (2019),SEK,Breakfast,-101.25 +Sydkorea (2019),SEK,Lunch,-236.25 +Sydkorea (2019),SEK,Dinner,-236.25 +Sydkorea (2020),SEK,Normalbelopp,696 +Sydkorea (2020),SEK,Breakfast,-104.4 +Sydkorea (2020),SEK,Lunch,-243.6 +Sydkorea (2020),SEK,Dinner,-243.6 +Taiwan (2018),SEK,Normalbelopp,465 +Taiwan (2018),SEK,Breakfast,-69.75 +Taiwan (2018),SEK,Lunch,-162.75 +Taiwan (2018),SEK,Dinner,-162.75 +Taiwan (2019),SEK,Normalbelopp,505 +Taiwan (2019),SEK,Breakfast,-75.75 +Taiwan (2019),SEK,Lunch,-176.75 +Taiwan (2019),SEK,Dinner,-176.75 +Taiwan (2020),SEK,Normalbelopp,546 +Taiwan (2020),SEK,Breakfast,-81.9 +Taiwan (2020),SEK,Lunch,-191.1 +Taiwan (2020),SEK,Dinner,-191.1 +Tanzania (2018),SEK,Normalbelopp,328 +Tanzania (2018),SEK,Breakfast,-49.2 +Tanzania (2018),SEK,Lunch,-114.8 +Tanzania (2018),SEK,Dinner,-114.8 +Tanzania (2019),SEK,Normalbelopp,349 +Tanzania (2019),SEK,Breakfast,-52.35 +Tanzania (2019),SEK,Lunch,-122.15 +Tanzania (2019),SEK,Dinner,-122.15 +Tanzania (2020),SEK,Normalbelopp,360 +Tanzania (2020),SEK,Breakfast,-54 +Tanzania (2020),SEK,Lunch,-126 +Tanzania (2020),SEK,Dinner,-126 +Thailand (2018),SEK,Normalbelopp,475 +Thailand (2018),SEK,Breakfast,-71.25 +Thailand (2018),SEK,Lunch,-166.25 +Thailand (2018),SEK,Dinner,-166.25 +Thailand (2019),SEK,Normalbelopp,525 +Thailand (2019),SEK,Breakfast,-78.75 +Thailand (2019),SEK,Lunch,-183.75 +Thailand (2019),SEK,Dinner,-183.75 +Thailand (2020),SEK,Normalbelopp,617 +Thailand (2020),SEK,Breakfast,-92.55 +Thailand (2020),SEK,Lunch,-215.95 +Thailand (2020),SEK,Dinner,-215.95 +Tjeckien (2018),SEK,Normalbelopp,388 +Tjeckien (2018),SEK,Breakfast,-58.2 +Tjeckien (2018),SEK,Lunch,-135.8 +Tjeckien (2018),SEK,Dinner,-135.8 +Tjeckien (2019),SEK,Normalbelopp,417 +Tjeckien (2019),SEK,Breakfast,-62.55 +Tjeckien (2019),SEK,Lunch,-145.95 +Tjeckien (2019),SEK,Dinner,-145.95 +Tjeckien (2020),SEK,Normalbelopp,436 +Tjeckien (2020),SEK,Breakfast,-65.4 +Tjeckien (2020),SEK,Lunch,-152.6 +Tjeckien (2020),SEK,Dinner,-152.6 +Togo (2018),SEK,Normalbelopp,438 +Togo (2018),SEK,Breakfast,-65.7 +Togo (2018),SEK,Lunch,-153.3 +Togo (2018),SEK,Dinner,-153.3 +Togo (2019),SEK,Normalbelopp,457 +Togo (2019),SEK,Breakfast,-68.55 +Togo (2019),SEK,Lunch,-159.95 +Togo (2019),SEK,Dinner,-159.95 +Togo (2020),SEK,Normalbelopp,482 +Togo (2020),SEK,Breakfast,-72.3 +Togo (2020),SEK,Lunch,-168.7 +Togo (2020),SEK,Dinner,-168.7 +Tonga (2018),SEK,Normalbelopp,380 +Tonga (2018),SEK,Breakfast,-57 +Tonga (2018),SEK,Lunch,-133 +Tonga (2018),SEK,Dinner,-133 +Tonga (2019),SEK,Normalbelopp,435 +Tonga (2019),SEK,Breakfast,-65.25 +Tonga (2019),SEK,Lunch,-152.25 +Tonga (2019),SEK,Dinner,-152.25 +Tonga (2020),SEK,Normalbelopp,455 +Tonga (2020),SEK,Breakfast,-68.25 +Tonga (2020),SEK,Lunch,-159.25 +Tonga (2020),SEK,Dinner,-159.25 +Trinidad och Tobago (2018),SEK,Normalbelopp,674 +Trinidad och Tobago (2018),SEK,Breakfast,-101.1 +Trinidad och Tobago (2018),SEK,Lunch,-235.9 +Trinidad och Tobago (2018),SEK,Dinner,-235.9 +Trinidad och Tobago (2019),SEK,Normalbelopp,728 +Trinidad och Tobago (2019),SEK,Breakfast,-109.2 +Trinidad och Tobago (2019),SEK,Lunch,-254.8 +Trinidad och Tobago (2019),SEK,Dinner,-254.8 +Trinidad och Tobago (2020),SEK,Normalbelopp,757 +Trinidad och Tobago (2020),SEK,Breakfast,-113.55 +Trinidad och Tobago (2020),SEK,Lunch,-264.95 +Trinidad och Tobago (2020),SEK,Dinner,-264.95 +Tunisien (2018),SEK,Normalbelopp,282 +Tunisien (2018),SEK,Breakfast,-42.3 +Tunisien (2018),SEK,Lunch,-98.7 +Tunisien (2018),SEK,Dinner,-98.7 +Tunisien (2019),SEK,Normalbelopp,240 +Tunisien (2019),SEK,Breakfast,-36 +Tunisien (2019),SEK,Lunch,-84 +Tunisien (2019),SEK,Dinner,-84 +Tunisien (2020),SEK,Normalbelopp,251 +Tunisien (2020),SEK,Breakfast,-37.65 +Tunisien (2020),SEK,Lunch,-87.85 +Tunisien (2020),SEK,Dinner,-87.85 +Turkiet (2018),SEK,Normalbelopp,274 +Turkiet (2018),SEK,Breakfast,-41.1 +Turkiet (2018),SEK,Lunch,-95.9 +Turkiet (2018),SEK,Dinner,-95.9 +Turkiet (2019),SEK,Normalbelopp,230 +Turkiet (2019),SEK,Breakfast,-34.5 +Turkiet (2019),SEK,Lunch,-80.5 +Turkiet (2019),SEK,Dinner,-80.5 +Turkiet (2020),SEK,Normalbelopp,251 +Turkiet (2020),SEK,Breakfast,-37.65 +Turkiet (2020),SEK,Lunch,-87.85 +Turkiet (2020),SEK,Dinner,-87.85 +Turkmenistan (2018),SEK,Normalbelopp,395 +Turkmenistan (2018),SEK,Breakfast,-59.25 +Turkmenistan (2018),SEK,Lunch,-138.25 +Turkmenistan (2018),SEK,Dinner,-138.25 +Turkmenistan (2019),SEK,Normalbelopp,473 +Turkmenistan (2019),SEK,Breakfast,-70.95 +Turkmenistan (2019),SEK,Lunch,-165.55 +Turkmenistan (2019),SEK,Dinner,-165.55 +Turkmenistan (2020),SEK,Normalbelopp,560 +Turkmenistan (2020),SEK,Breakfast,-84 +Turkmenistan (2020),SEK,Lunch,-196 +Turkmenistan (2020),SEK,Dinner,-196 +Tyskland (2018),SEK,Normalbelopp,620 +Tyskland (2018),SEK,Breakfast,-93 +Tyskland (2018),SEK,Lunch,-217 +Tyskland (2018),SEK,Dinner,-217 +Tyskland (2019),SEK,Normalbelopp,646 +Tyskland (2019),SEK,Breakfast,-96.9 +Tyskland (2019),SEK,Lunch,-226.1 +Tyskland (2019),SEK,Dinner,-226.1 +Tyskland (2020),SEK,Normalbelopp,668 +Tyskland (2020),SEK,Breakfast,-100.2 +Tyskland (2020),SEK,Lunch,-233.8 +Tyskland (2020),SEK,Dinner,-233.8 +Uganda (2018),SEK,Normalbelopp,243 +Uganda (2018),SEK,Breakfast,-36.45 +Uganda (2018),SEK,Lunch,-85.05 +Uganda (2018),SEK,Dinner,-85.05 +Uganda (2019),SEK,Normalbelopp,267 +Uganda (2019),SEK,Breakfast,-40.05 +Uganda (2019),SEK,Lunch,-93.45 +Uganda (2019),SEK,Dinner,-93.45 +Uganda (2020),SEK,Normalbelopp,321 +Uganda (2020),SEK,Breakfast,-48.15 +Uganda (2020),SEK,Lunch,-112.35 +Uganda (2020),SEK,Dinner,-112.35 +Ukraina (2018),SEK,Normalbelopp,257 +Ukraina (2018),SEK,Breakfast,-38.55 +Ukraina (2018),SEK,Lunch,-89.95 +Ukraina (2018),SEK,Dinner,-89.95 +Ukraina (2019),SEK,Normalbelopp,278 +Ukraina (2019),SEK,Breakfast,-41.7 +Ukraina (2019),SEK,Lunch,-97.3 +Ukraina (2019),SEK,Dinner,-97.3 +Ukraina (2020),SEK,Normalbelopp,356 +Ukraina (2020),SEK,Breakfast,-53.4 +Ukraina (2020),SEK,Lunch,-124.6 +Ukraina (2020),SEK,Dinner,-124.6 +Ungern (2018),SEK,Normalbelopp,393 +Ungern (2018),SEK,Breakfast,-58.95 +Ungern (2018),SEK,Lunch,-137.55 +Ungern (2018),SEK,Dinner,-137.55 +Ungern (2019),SEK,Normalbelopp,417 +Ungern (2019),SEK,Breakfast,-62.55 +Ungern (2019),SEK,Lunch,-145.95 +Ungern (2019),SEK,Dinner,-145.95 +Ungern (2020),SEK,Normalbelopp,436 +Ungern (2020),SEK,Breakfast,-65.4 +Ungern (2020),SEK,Lunch,-152.6 +Ungern (2020),SEK,Dinner,-152.6 +Uruguay (2018),SEK,Normalbelopp,503 +Uruguay (2018),SEK,Breakfast,-75.45 +Uruguay (2018),SEK,Lunch,-176.05 +Uruguay (2018),SEK,Dinner,-176.05 +Uruguay (2019),SEK,Normalbelopp,495 +Uruguay (2019),SEK,Breakfast,-74.25 +Uruguay (2019),SEK,Lunch,-173.25 +Uruguay (2019),SEK,Dinner,-173.25 +Uruguay (2020),SEK,Normalbelopp,479 +Uruguay (2020),SEK,Breakfast,-71.85 +Uruguay (2020),SEK,Lunch,-167.65 +Uruguay (2020),SEK,Dinner,-167.65 +USA (2018),SEK,Normalbelopp,689 +USA (2018),SEK,Breakfast,-103.35 +USA (2018),SEK,Lunch,-241.15 +USA (2018),SEK,Dinner,-241.15 +USA (2019),SEK,Normalbelopp,762 +USA (2019),SEK,Breakfast,-114.3 +USA (2019),SEK,Lunch,-266.7 +USA (2019),SEK,Dinner,-266.7 +USA (2020),SEK,Normalbelopp,851 +USA (2020),SEK,Breakfast,-127.65 +USA (2020),SEK,Lunch,-297.85 +USA (2020),SEK,Dinner,-297.85 +Uzbekistan (2018),SEK,Normalbelopp,230 +Uzbekistan (2018),SEK,Breakfast,-34.5 +Uzbekistan (2018),SEK,Lunch,-80.5 +Uzbekistan (2018),SEK,Dinner,-80.5 +Uzbekistan (2019),SEK,Normalbelopp,230 +Uzbekistan (2019),SEK,Breakfast,-34.5 +Uzbekistan (2019),SEK,Lunch,-80.5 +Uzbekistan (2019),SEK,Dinner,-80.5 +Uzbekistan (2020),SEK,Normalbelopp,240 +Uzbekistan (2020),SEK,Breakfast,-36 +Uzbekistan (2020),SEK,Lunch,-84 +Uzbekistan (2020),SEK,Dinner,-84 +Vanuatu (2018),SEK,Normalbelopp,587 +Vanuatu (2018),SEK,Breakfast,-88.05 +Vanuatu (2018),SEK,Lunch,-205.45 +Vanuatu (2018),SEK,Dinner,-205.45 +Vanuatu (2019),SEK,Normalbelopp,626 +Vanuatu (2019),SEK,Breakfast,-93.9 +Vanuatu (2019),SEK,Lunch,-219.1 +Vanuatu (2019),SEK,Dinner,-219.1 +Vanuatu (2020),SEK,Normalbelopp,650 +Vanuatu (2020),SEK,Breakfast,-97.5 +Vanuatu (2020),SEK,Lunch,-227.5 +Vanuatu (2020),SEK,Dinner,-227.5 +Venezuela (2020),SEK,Normalbelopp,240 +Venezuela (2020),SEK,Breakfast,-36 +Venezuela (2020),SEK,Lunch,-84 +Venezuela (2020),SEK,Dinner,-84 +Vietnam (2018),SEK,Normalbelopp,308 +Vietnam (2018),SEK,Breakfast,-46.2 +Vietnam (2018),SEK,Lunch,-107.8 +Vietnam (2018),SEK,Dinner,-107.8 +Vietnam (2019),SEK,Normalbelopp,333 +Vietnam (2019),SEK,Breakfast,-49.95 +Vietnam (2019),SEK,Lunch,-116.55 +Vietnam (2019),SEK,Dinner,-116.55 +Vietnam (2020),SEK,Normalbelopp,374 +Vietnam (2020),SEK,Breakfast,-56.1 +Vietnam (2020),SEK,Lunch,-130.9 +Vietnam (2020),SEK,Dinner,-130.9 +Vitryssland (2018),SEK,Normalbelopp,230 +Vitryssland (2018),SEK,Breakfast,-34.5 +Vitryssland (2018),SEK,Lunch,-80.5 +Vitryssland (2018),SEK,Dinner,-80.5 +Vitryssland (2019),SEK,Normalbelopp,233 +Vitryssland (2019),SEK,Breakfast,-34.95 +Vitryssland (2019),SEK,Lunch,-81.55 +Vitryssland (2019),SEK,Dinner,-81.55 +Vitryssland (2020),SEK,Normalbelopp,271 +Vitryssland (2020),SEK,Breakfast,-40.65 +Vitryssland (2020),SEK,Lunch,-94.85 +Vitryssland (2020),SEK,Dinner,-94.85 +Zambia (2018),SEK,Normalbelopp,355 +Zambia (2018),SEK,Breakfast,-53.25 +Zambia (2018),SEK,Lunch,-124.25 +Zambia (2018),SEK,Dinner,-124.25 +Zambia (2019),SEK,Normalbelopp,295 +Zambia (2019),SEK,Breakfast,-44.25 +Zambia (2019),SEK,Lunch,-103.25 +Zambia (2019),SEK,Dinner,-103.25 +Zambia (2020),SEK,Normalbelopp,260 +Zambia (2020),SEK,Breakfast,-39 +Zambia (2020),SEK,Lunch,-91 +Zambia (2020),SEK,Dinner,-91 +Zimbabwe (2018),SEK,Normalbelopp,511 +Zimbabwe (2018),SEK,Breakfast,-76.65 +Zimbabwe (2018),SEK,Lunch,-178.85 +Zimbabwe (2018),SEK,Dinner,-178.85 +Zimbabwe (2019),SEK,Normalbelopp,561 +Zimbabwe (2019),SEK,Breakfast,-84.15 +Zimbabwe (2019),SEK,Lunch,-196.35 +Zimbabwe (2019),SEK,Dinner,-196.35 +Zimbabwe (2020),SEK,Normalbelopp,487 +Zimbabwe (2020),SEK,Breakfast,-73.05 +Zimbabwe (2020),SEK,Lunch,-170.45 +Zimbabwe (2020),SEK,Dinner,-170.45 \ No newline at end of file diff --git a/docs/assets/images/QBO_desktop_01.png b/docs/assets/images/QBO_desktop_01.png new file mode 100644 index 000000000000..7d04236a9765 Binary files /dev/null and b/docs/assets/images/QBO_desktop_01.png differ diff --git a/docs/assets/images/QBO_desktop_02.png b/docs/assets/images/QBO_desktop_02.png new file mode 100644 index 000000000000..07bf2f006043 Binary files /dev/null and b/docs/assets/images/QBO_desktop_02.png differ diff --git a/docs/assets/images/QBO_desktop_03.png b/docs/assets/images/QBO_desktop_03.png new file mode 100644 index 000000000000..81a4176fdf52 Binary files /dev/null and b/docs/assets/images/QBO_desktop_03.png differ diff --git a/docs/assets/images/QBO_desktop_04.png b/docs/assets/images/QBO_desktop_04.png new file mode 100644 index 000000000000..d516a9e2c56a Binary files /dev/null and b/docs/assets/images/QBO_desktop_04.png differ diff --git a/docs/assets/images/QBO_desktop_05.png b/docs/assets/images/QBO_desktop_05.png new file mode 100644 index 000000000000..123adf897820 Binary files /dev/null and b/docs/assets/images/QBO_desktop_05.png differ diff --git a/docs/assets/images/QBO_desktop_06.png b/docs/assets/images/QBO_desktop_06.png new file mode 100644 index 000000000000..4e0dfbf4b574 Binary files /dev/null and b/docs/assets/images/QBO_desktop_06.png differ diff --git a/docs/assets/images/QBO_desktop_07.png b/docs/assets/images/QBO_desktop_07.png new file mode 100644 index 000000000000..519e0d3738f4 Binary files /dev/null and b/docs/assets/images/QBO_desktop_07.png differ diff --git a/docs/assets/images/QBO_desktop_08.png b/docs/assets/images/QBO_desktop_08.png new file mode 100644 index 000000000000..1573277c15c2 Binary files /dev/null and b/docs/assets/images/QBO_desktop_08.png differ diff --git a/docs/redirects.csv b/docs/redirects.csv index afefb662a69d..a1a8346b4789 100644 --- a/docs/redirects.csv +++ b/docs/redirects.csv @@ -419,8 +419,8 @@ https://community.expensify.com/discussion/4825/how-to-create-and-send-invoices- https://community.expensify.com/discussion/8850/how-to-enable-monthly-settlement,https://help.expensify.com/articles/expensify-classic/expensify-card/Admin-Card-Settings-and-Features#change-the-settlement-frequency https://community.expensify.com/discussion/4828/how-to-match-your-company-cards-statement-to-expensify/,https://help.expensify.com/articles/expensify-classic/expensify-card/Admin-Card-Settings-and-Features#reconciling-expenses-and-settlements https://community.expensify.com/discussion/5751/deep-dive-domain-groups-and-permissions-what-are-they-all-about,https://help.expensify.com/articles/expensify-classic/domains/Create-A-Group -https://community.expensify.com/discussion/4832/how-to-enable-saml-sso,https://help.expensify.com/articles/expensify-classic/domains/SAML-SSO https://community.expensify.com/categories/expensify-org-general-questions-and-discussions,https://www.expensify.org/ +https://community.expensify.com/discussion/4832/how-to-enable-saml-sso,https://help.expensify.com/articles/expensify-classic/domains/SAML-SSO https://community.expensify.com/discussion/4476/how-to-enable-scheduled-submit-for-a-group-policy/,https://help.expensify.com/articles/expensify-classic/reports/Automatically-submit-employee-reports https://community.expensify.com/discussion/4524/how-to-set-up-the-uber-integration/,https://help.expensify.com/articles/expensify-classic/connections/Uber https://community.expensify.com/discussion/4828/how-to-match-your-company-cards-statement-to-expensify/p1,https://help.expensify.com/articles/expensify-classic/expensify-card/Admin-Card-Settings-and-Features#reconciling-expenses-and-settlements @@ -477,7 +477,6 @@ https://community.expensify.com/discussion/5321/how-to-set-up-saml-authenticatio https://community.expensify.com/discussion/5499/deep-dive-configure-coding-for-sage-intacct,https://help.expensify.com/articles/expensify-classic/connections/sage-intacct/Configure-Sage-Intacct https://community.expensify.com/discussion/5580/deep-dive-configure-advanced-settings-for-netsuite,https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite#step-3-configure-advanced-settings https://community.expensify.com/discussion/5632/deep-dive-configure-coding-for-netsuite,https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite#configure-netsuite-integration -https://community.expensify.com/discussion/5632/deep-dive-configure-coding-for-netsuite#tax,https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite#configure-netsuite-integration https://community.expensify.com/discussion/5649/deep-dive-configure-advanced-settings-for-quickbooks-online,https://help.expensify.com/articles/expensify-classic/connections/quickbooks-online/Configure-Quickbooks-Online https://community.expensify.com/discussion/5654/deep-dive-using-expense-rules-to-vendor-match-when-exporting-to-an-accounting-package,https://help.expensify.com/articles/expensify-classic/expenses/Expense-Rules#how-can-i-use-expense-rules-to-vendor-match-when-exporting-to-an-accounting-package https://community.expensify.com/discussion/5656/deep-dive-configure-coding-for-xero/,https://help.expensify.com/articles/new-expensify/connections/xero/Connect-to-Xero#step-2-configure-import-settings @@ -490,7 +489,6 @@ https://community.expensify.com/discussion/5864/how-to-add-a-personal-bank-accou https://community.expensify.com/discussion/5941/how-to-reimburse-overseas-employees-for-us-employers/,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/bank-accounts/Enable-Global-Reimbursements https://community.expensify.com/discussion/6203/deep-dive-expensify-card-and-netsuite-auto-reconciliation-how-it-works,https://help.expensify.com/articles/expensify-classic/expensify-card/Expensify-Card-Reconciliation https://community.expensify.com/discussion/6698/faq-troubleshooting-bank-and-card-errors,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting -https://community.expensify.com/discussion/6698/faq-troubleshooting-bank-and-card-errors#account-type-not-supported,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Troubleshooting https://community.expensify.com/discussion/6827/what-s-happening-to-my-expensify-bill,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview https://community.expensify.com/discussion/6898/deep-dive-guide-to-billing,https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Overview https://community.expensify.com/discussion/7231/how-to-export-invoices-to-netsuite,https://help.expensify.com/articles/new-expensify/connections/netsuite/Connect-to-NetSuite#export-invoices-to @@ -500,11 +498,9 @@ https://community.expensify.com/discussion/7736/faq-troubleshooting-two-factor-a https://community.expensify.com/discussion/7862/introducing-expensify-cash-open-source-financial-group-chat-built-with-react-native,https://help.expensify.com/articles/expensify-classic/bank-accounts-and-payments/payments/Reimburse-Reports-Invoices-and-Bills https://community.expensify.com/discussion/7931/how-to-become-an-expensify-org-donor,https://www.expensify.org/donate https://community.expensify.com/discussion/8705/how-to-change-an-invoice-to-an-expense-report/,https://help.expensify.com/articles/expensify-classic/reports/Edit-a-report -https://community.expensify.com/discussion/8707/how-to-connect-bolt-to-your-expensify-account-using-a-work-profile/,https://bolt.eu/en/support/articles/4403357843090/ https://community.expensify.com/discussion/8803/settle-your-expensify-cards-monthly-or-daily-you-pick,https://help.expensify.com/articles/expensify-classic/expensify-card/Statements#expensify-card-settlement-frequency https://community.expensify.com/discussion/4784/how-to-alternate-option-submit-as-part-of-approval-workflow,https://help.expensify.com/articles/expensify-classic/reports/Create-a-report-approval-workflow https://community.expensify.com/discussion/4863/how-to-assign-and-unassign-company-cards,https://help.expensify.com/articles/expensify-classic/connect-credit-cards/company-cards/Commercial-Card-Feeds#how-to-assign-company-cards -https://community.expensify.com/discussion/4901/how-to-connect-locomote-to-your-expensify-account,https://www.locomote.com/marketplace/expensify-expense-management https://community.expensify.com/discussion/4902/how-to-expense-grab-airport-mobile-orders-with-expensify,https://help.expensify.com/articles/expensify-classic/connections/Additional-Travel-Integrations#how-to-connect-to-grab https://community.expensify.com/discussion/4953/how-to-forward-hoteltonight-expenses-to-your-expensify-account,https://help.expensify.com/articles/expensify-classic/connections/Additional-Travel-Integrations#how-to-connect-to-hoteltonight https://community.expensify.com/discussion/5042/faq-expensierror-xro052-expenses-are-not-categorized-with-a-xero-account,https://help.expensify.com/articles/expensify-classic/connections/xero/Xero-Troubleshooting diff --git a/ios/NewExpensify.xcodeproj/project.pbxproj b/ios/NewExpensify.xcodeproj/project.pbxproj index ac9c1b318aad..bd301e478ada 100644 --- a/ios/NewExpensify.xcodeproj/project.pbxproj +++ b/ios/NewExpensify.xcodeproj/project.pbxproj @@ -1015,11 +1015,7 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -1822,11 +1818,7 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -1894,11 +1886,7 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; @@ -1976,11 +1964,7 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -2125,11 +2109,7 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; @@ -2266,11 +2246,7 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; @@ -2405,11 +2381,7 @@ "$(inherited)", "-DRN_FABRIC_ENABLED", ); - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = ""; PRODUCT_NAME = ""; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index d67a06a0cad7..4b272838832b 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 9.0.23 + 9.0.24 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 9.0.23.0 + 9.0.24.1 FullStory OrgId diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index d808278550be..fff7dd6f3d55 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 9.0.23 + 9.0.24 CFBundleSignature ???? CFBundleVersion - 9.0.23.0 + 9.0.24.1 diff --git a/ios/NotificationServiceExtension/Info.plist b/ios/NotificationServiceExtension/Info.plist index 7fb5a7c657c6..94f999e972c4 100644 --- a/ios/NotificationServiceExtension/Info.plist +++ b/ios/NotificationServiceExtension/Info.plist @@ -11,9 +11,9 @@ CFBundleName $(PRODUCT_NAME) CFBundleShortVersionString - 9.0.23 + 9.0.24 CFBundleVersion - 9.0.23.0 + 9.0.24.1 NSExtension NSExtensionPointIdentifier diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0b1847863a3f..1a1a21aba7a3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1343,7 +1343,27 @@ PODS: - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - Yoga - - react-native-pager-view (6.2.3): + - react-native-pager-view (6.3.4): + - glog + - hermes-engine + - RCT-Folly (= 2022.05.16.00) + - RCTRequired + - RCTTypeSafety + - React-Codegen + - React-Core + - React-debug + - React-Fabric + - React-graphics + - React-ImageManager + - react-native-pager-view/common (= 6.3.4) + - React-NativeModulesApple + - React-RCTFabric + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - Yoga + - react-native-pager-view/common (6.3.4): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1871,7 +1891,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.116): + - RNLiveMarkdown (0.1.117): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1889,9 +1909,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.116) + - RNLiveMarkdown/common (= 0.1.117) - Yoga - - RNLiveMarkdown/common (0.1.116): + - RNLiveMarkdown/common (0.1.117): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2568,7 +2588,7 @@ SPEC CHECKSUMS: react-native-keyboard-controller: 47c01b0741ae5fc84e53cf282e61cfa5c2edb19b react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: 02d31de0e08ab043d48f2a1a8baade109d7b6ca5 - react-native-pager-view: ccd4bbf9fc7effaf8f91f8dae43389844d9ef9fa + react-native-pager-view: 770eee116657fca3d30329b2e8aa5a282a9dc722 react-native-pdf: 762369633665ec02ac227aefe2f4558b92475c23 react-native-performance: fb21ff0c9bd7a10789c69d948f25b0067d29f7a9 react-native-plaid-link-sdk: ba40d1b13cca4b946974fafd9ae278e0fb697d87 @@ -2614,7 +2634,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 19b5b73960ca70a47582c9376bb20e6dc52f021e + RNLiveMarkdown: 54e6a7dfd3e92fdb1d2dab1b64ee8a56d56acd91 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c RNPermissions: d2392b754e67bc14491f5b12588bef2864e783f3 diff --git a/package-lock.json b/package-lock.json index 47ed9331a4bc..92723d366795 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,19 +1,19 @@ { "name": "new.expensify", - "version": "9.0.23-0", + "version": "9.0.24-1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "9.0.23-0", + "version": "9.0.24-1", "hasInstallScript": true, "license": "MIT", "dependencies": { "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "^0.1.115", + "@expensify/react-native-live-markdown": "0.1.117", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -103,7 +103,7 @@ "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", "react-native-onyx": "2.0.64", - "react-native-pager-view": "6.2.3", + "react-native-pager-view": "6.3.4", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.10.0", @@ -234,6 +234,7 @@ "jest-cli": "29.4.1", "jest-environment-jsdom": "^29.4.1", "jest-transformer-svg": "^2.0.1", + "link": "^2.1.1", "memfs": "^4.6.0", "onchange": "^7.1.0", "openai": "^4.47.2", @@ -3952,9 +3953,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.116", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.116.tgz", - "integrity": "sha512-+l3SarKLyHUaeRWl+FU/Hrjk0iz5Caeps+aBfQgR5T1XQITDYtpSAruy4ZCw0R0wC6l7c44z8tDU7g5Biniq+A==", + "version": "0.1.117", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.117.tgz", + "integrity": "sha512-MMs8U7HRNilTc5PaCODpWL89/+fo61Np1tUBjVaiA4QQw2h5Qta8V5/YexUA4wG29M0N7gcGkxapVhfUoEB0vQ==", "workspaces": [ "parser", "example", @@ -32548,6 +32549,18 @@ "version": "1.2.4", "license": "MIT" }, + "node_modules/link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/link/-/link-2.1.1.tgz", + "integrity": "sha512-NV3AUVYBovJ6eVQcTeRoPnZSxzt2LOijNd+ugEZKRy/XeQlpTRhVRkuDv5kOlXwMAUx30vfUc7asRFb9RT65yg==", + "dev": true, + "bin": { + "link": "dist/cli.js" + }, + "funding": { + "url": "https://github.com/privatenumber/link?sponsor=1" + } + }, "node_modules/load-json-file": { "version": "1.1.0", "license": "MIT", @@ -37574,9 +37587,9 @@ } }, "node_modules/react-native-pager-view": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.2.3.tgz", - "integrity": "sha512-dqVpXWFtPNfD3D2QQQr8BP+ullS5MhjRJuF8Z/qml4QTILcrWaW8F5iAxKkQR3Jl0ikcEryG/+SQlNcwlo0Ggg==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.3.4.tgz", + "integrity": "sha512-4PEQd52EOwWcfiFJZ4VGSlY+GZfumvUzNbAozsMEgJaLvOHOMMl+Arfsc0txgtGdS49uEiHFLLZthZQzxx/Mog==", "peerDependencies": { "react": "*", "react-native": "*" diff --git a/package.json b/package.json index 179e3162c214..549a5fb85e75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "9.0.23-0", + "version": "9.0.24-1", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -69,7 +69,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "^0.1.115", + "@expensify/react-native-live-markdown": "0.1.117", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -159,7 +159,7 @@ "react-native-localize": "^2.2.6", "react-native-modal": "^13.0.0", "react-native-onyx": "2.0.64", - "react-native-pager-view": "6.2.3", + "react-native-pager-view": "6.3.4", "react-native-pdf": "6.7.3", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.10.0", @@ -290,6 +290,7 @@ "jest-cli": "29.4.1", "jest-environment-jsdom": "^29.4.1", "jest-transformer-svg": "^2.0.1", + "link": "^2.1.1", "memfs": "^4.6.0", "onchange": "^7.1.0", "openai": "^4.47.2", diff --git a/patches/link+2.1.1.patch b/patches/link+2.1.1.patch new file mode 100644 index 000000000000..ae5abc87400a --- /dev/null +++ b/patches/link+2.1.1.patch @@ -0,0 +1,4513 @@ +diff --git a/node_modules/link/README.md b/node_modules/link/README.md +index d7ff505..bc868c4 100644 +--- a/node_modules/link/README.md ++++ b/node_modules/link/README.md +@@ -1,4 +1,11 @@ +-# npx link ++

++ ++

++

++ npx link ++
++ ++

+ + A safer and enhanced version of [`npm link`](https://docs.npmjs.com/cli/v8/commands/npm-link). + +@@ -13,14 +20,10 @@ Why is `npm link` unsafe? Read the [blog post](https://hirok.io/posts/avoid-npm- +
+ +

+- +- +- +- +- Premium sponsor banner +- +- ++ ++ +

++

Already a sponsor? Join the discussion in the Development repo!

+ + ## Terminology + +@@ -120,6 +123,37 @@ _Publish mode_ helps replicate the production environment in your development se + + > **Note:** If the _Dependency package_ emits new files, you'll need to re-run `npx link publish ` to create new hard links. + ++#### Watch mode ++ ++To automatically re-link a package in publish mode whenever a change is made, use the `--watch` flag: ++ ++```sh ++npx link publish --watch ++``` ++ ++Hard links do not support directories, so files must be individually linked. _Watch mode_ addresses this limitation by automatically linking new files added to the _Dependency package_ so they appear in the _Consuming package_. ++ ++##### Watch mode caveats ++ ++A fundamental limitation of _watch mode_ is that it doesn't have a reliable way to know when the rebuild of your local _Dependency package_ is complete. ++ ++The **recommended** way to address this limitation is using the `--litmus` flag. This flag specifies a filepath relative to the _Dependency package_ that `link` can check to see if a build is completed. ++For example, you might create an empty file in your local dependency called `.build_complete`, and augment it's build system to delete that file at the start of builds, and re-create it at the end of builds. Then pass `npx link --watch --litmus '.build_complete'` ++ ++``` ++"build:watch": "nodemon --watch src --ext .ts,.tsx,.css --exec \"rm -f .build_complete && yarn prepare && yarn pack && touch .build_complete\"", ++``` ++ ++If for some reason you can't do that, you'll have to fall back on some heuristics: ++ ++| Flag | Description | Default | ++|------|------------------------------------------------------------------------------------------------------------------------------------------|---------| ++| `--delay` or `-d` | The amount of time (in ms) after a change is observed to refresh the packlist for your _Dependency package_ | 2000 | ++| `--interval` or `-i` | The amount of time (in ms) to poll the _Dependency package_ to see if all expected files are present (i.e: build is complete) | 500 | ++| `--maxBuildTime` or `-m` | The maximum amount of time (in ms) to poll the _Dependency package_ to see if all expected files are present (i.e: build is complete) | 30000 | ++ ++Note that there is an edge case with _watch mode_ that may not work as expected if you don't use the `--litmus` flag; if you intentionally delete a file in the _Dependency package_, you may have to wait `--maxBuildTime` before the links are refreshed. ++ + ### Configuration file + + Create a `link.config.json` (or `link.config.js`) configuration file at the root of the _Consuming package_ to automatically setup links to multiple _Dependency packages_. +@@ -166,18 +200,6 @@ Enable with the `--deep` flag or `deepLink` property in `link.config.json`. + npx link --deep + ``` + +-
+- +-

+- +- +- +- +- Premium sponsor banner +- +- +-

+- + ## FAQ + + ### Why should I use `npx link` over `npm link`? +@@ -213,23 +235,6 @@ $ npx link + + ## Sponsors + +-

+- +- +- +- +- Premium sponsor banner +- +- +- +- +- +- +- Premium sponsor banner +- +- +-

+- +

+ + +diff --git a/node_modules/link/dist/cli.js b/node_modules/link/dist/cli.js +index ae69549..5687188 100755 +--- a/node_modules/link/dist/cli.js ++++ b/node_modules/link/dist/cli.js +@@ -1,111 +1,4284 @@ + #!/usr/bin/env node +-"use strict";var O=require("fs/promises"),Ot=require("tty"),A=require("path"),fi=require("module"),ru=require("fs"),kt=require("events"),Fi=typeof document<"u"?document.currentScript:null;const jt="known-flag",xt="unknown-flag",Tt="argument",{stringify:ne}=JSON,Rt=/\B([A-Z])/g,Pt=u=>u.replace(Rt,"-$1").toLowerCase(),{hasOwnProperty:Lt}=Object.prototype,ie=(u,e)=>Lt.call(u,e),Mt=u=>Array.isArray(u),ou=u=>typeof u=="function"?[u,!1]:Mt(u)?[u[0],!0]:ou(u.type),Nt=(u,e)=>u===Boolean?e!=="false":e,It=(u,e)=>typeof e=="boolean"?e:u===Number&&e===""?Number.NaN:u(e),_t=/[\s.:=]/,Wt=u=>{const e=`Flag name ${ne(u)}`;if(u.length===0)throw new Error(`${e} cannot be empty`);if(u.length===1)throw new Error(`${e} must be longer than a character`);const t=u.match(_t);if(t)throw new Error(`${e} cannot contain ${ne(t?.[0])}`)},Gt=u=>{const e={},t=(D,n)=>{if(ie(e,D))throw new Error(`Duplicate flags named ${ne(D)}`);e[D]=n};for(const D in u){if(!ie(u,D))continue;Wt(D);const n=u[D],i=[[],...ou(n),n];t(D,i);const s=Pt(D);if(D!==s&&t(s,i),"alias"in n&&typeof n.alias=="string"){const{alias:o}=n,a=`Flag alias ${ne(o)} for flag ${ne(D)}`;if(o.length===0)throw new Error(`${a} cannot be empty`);if(o.length>1)throw new Error(`${a} must be a single character`);t(o,i)}}return e},qt=(u,e)=>{const t={};for(const D in u){if(!ie(u,D))continue;const[n,,i,s]=e[D];if(n.length===0&&"default"in s){let{default:o}=s;typeof o=="function"&&(o=o()),t[D]=o}else t[D]=i?n:n.pop()}return t},ge="--",zt=/[.:=]/,Jt=/^-{1,2}\w/,Vt=u=>{if(!Jt.test(u))return;const e=!u.startsWith(ge);let t=u.slice(e?1:2),D;const n=t.match(zt);if(n){const{index:i}=n;D=t.slice(i+1),t=t.slice(0,i)}return[t,D,e]},Ut=(u,{onFlag:e,onArgument:t})=>{let D;const n=(i,s)=>{if(typeof D!="function")return!0;D(i,s),D=void 0};for(let i=0;i{for(const[t,D,n]of e.reverse()){if(D){const i=u[t];let s=i.slice(0,D);if(n||(s+=i.slice(D+1)),s!=="-"){u[t]=s;continue}}u.splice(t,1)}},Ht=(u,e=process.argv.slice(2),{ignore:t}={})=>{const D=[],n=Gt(u),i={},s=[];return s[ge]=[],Ut(e,{onFlag(o,a,h){const f=ie(n,o);if(!t?.(f?jt:xt,o,a)){if(f){const[g,F]=n[o],d=Nt(F,a),m=(w,S)=>{D.push(h),S&&D.push(S),g.push(It(F,w||""))};return d===void 0?m:m(d)}ie(i,o)||(i[o]=[]),i[o].push(a===void 0?!0:a),D.push(h)}},onArgument(o,a,h){t?.(Tt,e[a[0]])||(s.push(...o),h?(s[ge]=o,e.splice(a[0])):D.push(a))}}),Zt(e,D),{flags:qt(u,n),unknownFlags:i,_:s}};var Xt=Object.create,me=Object.defineProperty,Yt=Object.defineProperties,Kt=Object.getOwnPropertyDescriptor,Qt=Object.getOwnPropertyDescriptors,eD=Object.getOwnPropertyNames,au=Object.getOwnPropertySymbols,uD=Object.getPrototypeOf,lu=Object.prototype.hasOwnProperty,tD=Object.prototype.propertyIsEnumerable,cu=(u,e,t)=>e in u?me(u,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):u[e]=t,Be=(u,e)=>{for(var t in e||(e={}))lu.call(e,t)&&cu(u,t,e[t]);if(au)for(var t of au(e))tD.call(e,t)&&cu(u,t,e[t]);return u},Re=(u,e)=>Yt(u,Qt(e)),DD=u=>me(u,"__esModule",{value:!0}),nD=(u,e)=>()=>(u&&(e=u(u=0)),e),iD=(u,e)=>()=>(e||u((e={exports:{}}).exports,e),e.exports),sD=(u,e,t,D)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of eD(e))!lu.call(u,n)&&n!=="default"&&me(u,n,{get:()=>e[n],enumerable:!(D=Kt(e,n))||D.enumerable});return u},rD=(u,e)=>sD(DD(me(u!=null?Xt(uD(u)):{},"default",{value:u,enumerable:!0})),u),x=nD(()=>{}),oD=iD((u,e)=>{x(),e.exports=function(){return/\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|(?:\uD83E\uDDD1\uD83C\uDFFF\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFC-\uDFFF])|\uD83D\uDC68(?:\uD83C\uDFFB(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|[\u2695\u2696\u2708]\uFE0F|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))?|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])\uFE0F|\u200D(?:(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D[\uDC66\uDC67])|\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC)?|(?:\uD83D\uDC69(?:\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC69(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83E\uDDD1(?:\u200D(?:\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDE36\u200D\uD83C\uDF2B|\uD83C\uDFF3\uFE0F\u200D\u26A7|\uD83D\uDC3B\u200D\u2744|(?:(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\uD83C\uDFF4\u200D\u2620|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])\u200D[\u2640\u2642]|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u2600-\u2604\u260E\u2611\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26B0\u26B1\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0\u26F1\u26F4\u26F7\u26F8\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u3030\u303D\u3297\u3299]|\uD83C[\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]|\uD83D[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3])\uFE0F|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDE35\u200D\uD83D\uDCAB|\uD83D\uDE2E\u200D\uD83D\uDCA8|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83E\uDDD1(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83D\uDC69(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83D\uDC08\u200D\u2B1B|\u2764\uFE0F\u200D(?:\uD83D\uDD25|\uD83E\uDE79)|\uD83D\uDC41\uFE0F|\uD83C\uDFF3\uFE0F|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|[#\*0-9]\uFE0F\u20E3|\u2764\uFE0F|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|\uD83C\uDFF4|(?:[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270C\u270D]|\uD83D[\uDD74\uDD90])(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC08\uDC15\uDC3B\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE2E\uDE35\uDE36\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5]|\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD]|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF]|[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0D\uDD0E\uDD10-\uDD17\uDD1D\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78\uDD7A-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCB\uDDD0\uDDE0-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6]|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDD77\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g}});x(),x(),x();var aD=u=>{var e,t,D;let n=(e=process.stdout.columns)!=null?e:Number.POSITIVE_INFINITY;return typeof u=="function"&&(u=u(n)),u||(u={}),Array.isArray(u)?{columns:u,stdoutColumns:n}:{columns:(t=u.columns)!=null?t:[],stdoutColumns:(D=u.stdoutColumns)!=null?D:n}};x(),x(),x(),x(),x();function lD({onlyFirst:u=!1}={}){let e=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(e,u?void 0:"g")}function hu(u){if(typeof u!="string")throw new TypeError(`Expected a \`string\`, got \`${typeof u}\``);return u.replace(lD(),"")}x();function cD(u){return Number.isInteger(u)?u>=4352&&(u<=4447||u===9001||u===9002||11904<=u&&u<=12871&&u!==12351||12880<=u&&u<=19903||19968<=u&&u<=42182||43360<=u&&u<=43388||44032<=u&&u<=55203||63744<=u&&u<=64255||65040<=u&&u<=65049||65072<=u&&u<=65131||65281<=u&&u<=65376||65504<=u&&u<=65510||110592<=u&&u<=110593||127488<=u&&u<=127569||131072<=u&&u<=262141):!1}var hD=rD(oD());function V(u){if(typeof u!="string"||u.length===0||(u=hu(u),u.length===0))return 0;u=u.replace((0,hD.default)()," ");let e=0;for(let t=0;t=127&&D<=159||D>=768&&D<=879||(D>65535&&t++,e+=cD(D)?2:1)}return e}var pu=u=>Math.max(...u.split(` +-`).map(V)),pD=u=>{let e=[];for(let t of u){let{length:D}=t,n=D-e.length;for(let i=0;ie[i]&&(e[i]=s)}}return e};x();var fu=/^\d+%$/,Fu={width:"auto",align:"left",contentWidth:0,paddingLeft:0,paddingRight:0,paddingTop:0,paddingBottom:0,horizontalPadding:0,paddingLeftString:"",paddingRightString:""},fD=(u,e)=>{var t;let D=[];for(let n=0;n=e){let a=s-e,h=Math.ceil(t.paddingLeft/n*a),f=a-h;t.paddingLeft-=h,t.paddingRight-=f,t.horizontalPadding=t.paddingLeft+t.paddingRight}t.paddingLeftString=t.paddingLeft?" ".repeat(t.paddingLeft):"",t.paddingRightString=t.paddingRight?" ".repeat(t.paddingRight):"";let o=e-t.horizontalPadding;t.width=Math.max(Math.min(t.width,o),i)}}var du=()=>Object.assign([],{columns:0});function dD(u,e){let t=[du()],[D]=t;for(let n of u){let i=n.width+n.horizontalPadding;D.columns+i>e&&(D=du(),t.push(D)),D.push(n),D.columns+=i}for(let n of t){let i=n.reduce((F,d)=>F+d.width+d.horizontalPadding,0),s=e-i;if(s===0)continue;let o=n.filter(F=>"autoOverflow"in F),a=o.filter(F=>F.autoOverflow>0),h=a.reduce((F,d)=>F+d.autoOverflow,0),f=Math.min(h,s);for(let F of a){let d=Math.floor(F.autoOverflow/h*f);F.width+=d,s-=d}let g=Math.floor(s/o.length);for(let F=0;Fe=>`\x1B[${e+u}m`,Eu=(u=0)=>e=>`\x1B[${38+u};5;${e}m`,gu=(u=0)=>(e,t,D)=>`\x1B[${38+u};2;${e};${t};${D}m`;function ED(){let u=new Map,e={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};e.color.gray=e.color.blackBright,e.bgColor.bgGray=e.bgColor.bgBlackBright,e.color.grey=e.color.blackBright,e.bgColor.bgGrey=e.bgColor.bgBlackBright;for(let[t,D]of Object.entries(e)){for(let[n,i]of Object.entries(D))e[n]={open:`\x1B[${i[0]}m`,close:`\x1B[${i[1]}m`},D[n]=e[n],u.set(i[0],i[1]);Object.defineProperty(e,t,{value:D,enumerable:!1})}return Object.defineProperty(e,"codes",{value:u,enumerable:!1}),e.color.close="\x1B[39m",e.bgColor.close="\x1B[49m",e.color.ansi=Cu(),e.color.ansi256=Eu(),e.color.ansi16m=gu(),e.bgColor.ansi=Cu(Pe),e.bgColor.ansi256=Eu(Pe),e.bgColor.ansi16m=gu(Pe),Object.defineProperties(e,{rgbToAnsi256:{value:(t,D,n)=>t===D&&D===n?t<8?16:t>248?231:Math.round((t-8)/247*24)+232:16+36*Math.round(t/255*5)+6*Math.round(D/255*5)+Math.round(n/255*5),enumerable:!1},hexToRgb:{value:t=>{let D=/(?[a-f\d]{6}|[a-f\d]{3})/i.exec(t.toString(16));if(!D)return[0,0,0];let{colorString:n}=D.groups;n.length===3&&(n=n.split("").map(s=>s+s).join(""));let i=Number.parseInt(n,16);return[i>>16&255,i>>8&255,i&255]},enumerable:!1},hexToAnsi256:{value:t=>e.rgbToAnsi256(...e.hexToRgb(t)),enumerable:!1},ansi256ToAnsi:{value:t=>{if(t<8)return 30+t;if(t<16)return 90+(t-8);let D,n,i;if(t>=232)D=((t-232)*10+8)/255,n=D,i=D;else{t-=16;let a=t%36;D=Math.floor(t/36)/5,n=Math.floor(a/6)/5,i=a%6/5}let s=Math.max(D,n,i)*2;if(s===0)return 30;let o=30+(Math.round(i)<<2|Math.round(n)<<1|Math.round(D));return s===2&&(o+=60),o},enumerable:!1},rgbToAnsi:{value:(t,D,n)=>e.ansi256ToAnsi(e.rgbToAnsi256(t,D,n)),enumerable:!1},hexToAnsi:{value:t=>e.ansi256ToAnsi(e.hexToAnsi256(t)),enumerable:!1}}),e}var gD=ED(),mD=gD,we=new Set(["\x1B","\x9B"]),BD=39,Le="\x07",mu="[",wD="]",Bu="m",Me=`${wD}8;;`,wu=u=>`${we.values().next().value}${mu}${u}${Bu}`,yu=u=>`${we.values().next().value}${Me}${u}${Le}`,yD=u=>u.split(" ").map(e=>V(e)),Ne=(u,e,t)=>{let D=[...e],n=!1,i=!1,s=V(hu(u[u.length-1]));for(let[o,a]of D.entries()){let h=V(a);if(s+h<=t?u[u.length-1]+=a:(u.push(a),s=0),we.has(a)&&(n=!0,i=D.slice(o+1).join("").startsWith(Me)),n){i?a===Le&&(n=!1,i=!1):a===Bu&&(n=!1);continue}s+=h,s===t&&o0&&u.length>1&&(u[u.length-2]+=u.pop())},$D=u=>{let e=u.split(" "),t=e.length;for(;t>0&&!(V(e[t-1])>0);)t--;return t===e.length?u:e.slice(0,t).join(" ")+e.slice(t).join("")},AD=(u,e,t={})=>{if(t.trim!==!1&&u.trim()==="")return"";let D="",n,i,s=yD(u),o=[""];for(let[h,f]of u.split(" ").entries()){t.trim!==!1&&(o[o.length-1]=o[o.length-1].trimStart());let g=V(o[o.length-1]);if(h!==0&&(g>=e&&(t.wordWrap===!1||t.trim===!1)&&(o.push(""),g=0),(g>0||t.trim===!1)&&(o[o.length-1]+=" ",g++)),t.hard&&s[h]>e){let F=e-g,d=1+Math.floor((s[h]-F-1)/e);Math.floor((s[h]-1)/e)e&&g>0&&s[h]>0){if(t.wordWrap===!1&&ge&&t.wordWrap===!1){Ne(o,f,e);continue}o[o.length-1]+=f}t.trim!==!1&&(o=o.map(h=>$D(h)));let a=[...o.join(` +-`)];for(let[h,f]of a.entries()){if(D+=f,we.has(f)){let{groups:F}=new RegExp(`(?:\\${mu}(?\\d+)m|\\${Me}(?.*)${Le})`).exec(a.slice(h).join(""))||{groups:{}};if(F.code!==void 0){let d=Number.parseFloat(F.code);n=d===BD?void 0:d}else F.uri!==void 0&&(i=F.uri.length===0?void 0:F.uri)}let g=mD.codes.get(Number(n));a[h+1]===` +-`?(i&&(D+=yu("")),n&&g&&(D+=wu(g))):f===` +-`&&(n&&g&&(D+=wu(n)),i&&(D+=yu(i)))}return D};function bD(u,e,t){return String(u).normalize().replace(/\r\n/g,` ++'use strict'; ++ ++var fs$1 = require('fs/promises'); ++var N$1 = require('tty'); ++var path$1 = require('path'); ++var module$1 = require('module'); ++var path$2 = require('node:path'); ++var fs$2 = require('node:fs/promises'); ++var require$$0 = require('fs'); ++var require$$2 = require('events'); ++ ++var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null; ++const V$2="known-flag",k$2="unknown-flag",C$1="argument",{stringify:h}=JSON,O$1=/\B([A-Z])/g,v$1=t=>t.replace(O$1,"-$1").toLowerCase(),{hasOwnProperty:D$1}=Object.prototype,w$2=(t,n)=>D$1.call(t,n),L$2=t=>Array.isArray(t),b$2=t=>typeof t=="function"?[t,!1]:L$2(t)?[t[0],!0]:b$2(t.type),d$2=(t,n)=>t===Boolean?n!=="false":n,m$1=(t,n)=>typeof n=="boolean"?n:t===Number&&n===""?Number.NaN:t(n),R$2=/[\s.:=]/,B=t=>{const n=`Flag name ${h(t)}`;if(t.length===0)throw new Error(`${n} cannot be empty`);if(t.length===1)throw new Error(`${n} must be longer than a character`);const r=t.match(R$2);if(r)throw new Error(`${n} cannot contain ${h(r?.[0])}`)},K$1=t=>{const n={},r=(e,o)=>{if(w$2(n,e))throw new Error(`Duplicate flags named ${h(e)}`);n[e]=o;};for(const e in t){if(!w$2(t,e))continue;B(e);const o=t[e],s=[[],...b$2(o),o];r(e,s);const i=v$1(e);if(e!==i&&r(i,s),"alias"in o&&typeof o.alias=="string"){const{alias:a}=o,l=`Flag alias ${h(a)} for flag ${h(e)}`;if(a.length===0)throw new Error(`${l} cannot be empty`);if(a.length>1)throw new Error(`${l} must be a single character`);r(a,s);}}return n},_$2=(t,n)=>{const r={};for(const e in t){if(!w$2(t,e))continue;const[o,,s,i]=n[e];if(o.length===0&&"default"in i){let{default:a}=i;typeof a=="function"&&(a=a()),r[e]=a;}else r[e]=s?o:o.pop();}return r},F$1="--",G$2=/[.:=]/,T$2=/^-{1,2}\w/,N=t=>{if(!T$2.test(t))return;const n=!t.startsWith(F$1);let r=t.slice(n?1:2),e;const o=r.match(G$2);if(o){const{index:s}=o;e=r.slice(s+1),r=r.slice(0,s);}return [r,e,n]},$$1=(t,{onFlag:n,onArgument:r})=>{let e;const o=(s,i)=>{if(typeof e!="function")return !0;e(s,i),e=void 0;};for(let s=0;s{for(const[r,e,o]of n.reverse()){if(e){const s=t[r];let i=s.slice(0,e);if(o||(i+=s.slice(e+1)),i!=="-"){t[r]=i;continue}}t.splice(r,1);}},U$2=(t,n=process.argv.slice(2),{ignore:r}={})=>{const e=[],o=K$1(t),s={},i=[];return i[F$1]=[],$$1(n,{onFlag(a,l,f){const g=w$2(o,a);if(!r?.(g?V$2:k$2,a,l)){if(g){const[c,u]=o[a],y=d$2(u,l),p=(P,A)=>{e.push(f),A&&e.push(A),c.push(m$1(u,P||""));};return y===void 0?p:p(y)}w$2(s,a)||(s[a]=[]),s[a].push(l===void 0?!0:l),e.push(f);}},onArgument(a,l,f){r?.(C$1,n[l[0]])||(i.push(...a),f?(i[F$1]=a,n.splice(l[0])):e.push(l));}}),E(n,e),{flags:_$2(t,o),unknownFlags:s,_:i}}; ++ ++var DD=Object.create;var m=Object.defineProperty,uD=Object.defineProperties,FD=Object.getOwnPropertyDescriptor,CD=Object.getOwnPropertyDescriptors,tD=Object.getOwnPropertyNames,I$1=Object.getOwnPropertySymbols,ED=Object.getPrototypeOf,L$1=Object.prototype.hasOwnProperty,eD=Object.prototype.propertyIsEnumerable;var W$1=(D,F,u)=>F in D?m(D,F,{enumerable:!0,configurable:!0,writable:!0,value:u}):D[F]=u,p=(D,F)=>{for(var u in F||(F={}))L$1.call(F,u)&&W$1(D,u,F[u]);if(I$1)for(var u of I$1(F))eD.call(F,u)&&W$1(D,u,F[u]);return D},c=(D,F)=>uD(D,CD(F)),nD=D=>m(D,"__esModule",{value:!0});var rD=(D,F)=>()=>(D&&(F=D(D=0)),F);var iD=(D,F)=>()=>(F||D((F={exports:{}}).exports,F),F.exports);var oD=(D,F,u,C)=>{if(F&&typeof F=="object"||typeof F=="function")for(let t of tD(F))!L$1.call(D,t)&&(t!=="default")&&m(D,t,{get:()=>F[t],enumerable:!(C=FD(F,t))||C.enumerable});return D},BD=(D,F)=>oD(nD(m(D!=null?DD(ED(D)):{},"default",{value:D,enumerable:!0})),D);var i=rD(()=>{});var $=iD((LD,N)=>{i();N.exports=function(){return /\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|(?:\uD83E\uDDD1\uD83C\uDFFF\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFC-\uDFFF])|\uD83D\uDC68(?:\uD83C\uDFFB(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|[\u2695\u2696\u2708]\uFE0F|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))?|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFF]))|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])\uFE0F|\u200D(?:(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D[\uDC66\uDC67])|\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC)?|(?:\uD83D\uDC69(?:\uD83C\uDFFB\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|(?:\uD83C[\uDFFC-\uDFFF])\u200D\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC69(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83E\uDDD1(?:\u200D(?:\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF7C\uDF84\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|\uD83D\uDE36\u200D\uD83C\uDF2B|\uD83C\uDFF3\uFE0F\u200D\u26A7|\uD83D\uDC3B\u200D\u2744|(?:(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\uD83C\uDFF4\u200D\u2620|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])\u200D[\u2640\u2642]|[\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u2328\u23CF\u23ED-\u23EF\u23F1\u23F2\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB\u25FC\u2600-\u2604\u260E\u2611\u2618\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u2692\u2694-\u2697\u2699\u269B\u269C\u26A0\u26A7\u26B0\u26B1\u26C8\u26CF\u26D1\u26D3\u26E9\u26F0\u26F1\u26F4\u26F7\u26F8\u2702\u2708\u2709\u270F\u2712\u2714\u2716\u271D\u2721\u2733\u2734\u2744\u2747\u2763\u27A1\u2934\u2935\u2B05-\u2B07\u3030\u303D\u3297\u3299]|\uD83C[\uDD70\uDD71\uDD7E\uDD7F\uDE02\uDE37\uDF21\uDF24-\uDF2C\uDF36\uDF7D\uDF96\uDF97\uDF99-\uDF9B\uDF9E\uDF9F\uDFCD\uDFCE\uDFD4-\uDFDF\uDFF5\uDFF7]|\uD83D[\uDC3F\uDCFD\uDD49\uDD4A\uDD6F\uDD70\uDD73\uDD76-\uDD79\uDD87\uDD8A-\uDD8D\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA\uDECB\uDECD-\uDECF\uDEE0-\uDEE5\uDEE9\uDEF0\uDEF3])\uFE0F|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDE35\u200D\uD83D\uDCAB|\uD83D\uDE2E\u200D\uD83D\uDCA8|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83E\uDDD1(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83D\uDC69(?:\uD83C\uDFFF|\uD83C\uDFFE|\uD83C\uDFFD|\uD83C\uDFFC|\uD83C\uDFFB)?|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83D\uDC08\u200D\u2B1B|\u2764\uFE0F\u200D(?:\uD83D\uDD25|\uD83E\uDE79)|\uD83D\uDC41\uFE0F|\uD83C\uDFF3\uFE0F|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|[#\*0-9]\uFE0F\u20E3|\u2764\uFE0F|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|\uD83C\uDFF4|(?:[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270C\u270D]|\uD83D[\uDD74\uDD90])(?:\uFE0F|\uD83C[\uDFFB-\uDFFF])|[\u270A\u270B]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC08\uDC15\uDC3B\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDC8F\uDC91\uDCAA\uDD7A\uDD95\uDD96\uDE2E\uDE35\uDE36\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD34\uDD36\uDD77\uDDB5\uDDB6\uDDBB\uDDD2\uDDD3\uDDD5]|\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC70\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD35\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD4\uDDD6-\uDDDD]|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF]|[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF84\uDF86-\uDF93\uDFA0-\uDFC1\uDFC5\uDFC6\uDFC8\uDFC9\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC07\uDC09-\uDC14\uDC16-\uDC3A\uDC3C-\uDC3E\uDC40\uDC44\uDC45\uDC51-\uDC65\uDC6A\uDC79-\uDC7B\uDC7D-\uDC80\uDC84\uDC88-\uDC8E\uDC90\uDC92-\uDCA9\uDCAB-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDDA4\uDDFB-\uDE2D\uDE2F-\uDE34\uDE37-\uDE44\uDE48-\uDE4A\uDE80-\uDEA2\uDEA4-\uDEB3\uDEB7-\uDEBF\uDEC1-\uDEC5\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0D\uDD0E\uDD10-\uDD17\uDD1D\uDD20-\uDD25\uDD27-\uDD2F\uDD3A\uDD3F-\uDD45\uDD47-\uDD76\uDD78\uDD7A-\uDDB4\uDDB7\uDDBA\uDDBC-\uDDCB\uDDD0\uDDE0-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6]|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5-\uDED7\uDEEB\uDEEC\uDEF4-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26A7\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5-\uDED7\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFC\uDFE0-\uDFEB]|\uD83E[\uDD0C-\uDD3A\uDD3C-\uDD45\uDD47-\uDD78\uDD7A-\uDDCB\uDDCD-\uDDFF\uDE70-\uDE74\uDE78-\uDE7A\uDE80-\uDE86\uDE90-\uDEA8\uDEB0-\uDEB6\uDEC0-\uDEC2\uDED0-\uDED6])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0C\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDD77\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g};});i();i();i();var v=D=>{var u,C,t;let F=(u=process.stdout.columns)!=null?u:Number.POSITIVE_INFINITY;return typeof D=="function"&&(D=D(F)),D||(D={}),Array.isArray(D)?{columns:D,stdoutColumns:F}:{columns:(C=D.columns)!=null?C:[],stdoutColumns:(t=D.stdoutColumns)!=null?t:F}};i();i();i();i();i();function w$1({onlyFirst:D=!1}={}){let F=["[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)","(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"].join("|");return new RegExp(F,D?void 0:"g")}function d$1(D){if(typeof D!="string")throw new TypeError(`Expected a \`string\`, got \`${typeof D}\``);return D.replace(w$1(),"")}i();function y$1(D){return Number.isInteger(D)?D>=4352&&(D<=4447||D===9001||D===9002||11904<=D&&D<=12871&&D!==12351||12880<=D&&D<=19903||19968<=D&&D<=42182||43360<=D&&D<=43388||44032<=D&&D<=55203||63744<=D&&D<=64255||65040<=D&&D<=65049||65072<=D&&D<=65131||65281<=D&&D<=65376||65504<=D&&D<=65510||110592<=D&&D<=110593||127488<=D&&D<=127569||131072<=D&&D<=262141):!1}var j=BD($());function g(D){if(typeof D!="string"||D.length===0||(D=d$1(D),D.length===0))return 0;D=D.replace((0, j.default)()," ");let F=0;for(let u=0;u=127&&C<=159||C>=768&&C<=879||(C>65535&&u++,F+=y$1(C)?2:1);}return F}var b$1=D=>Math.max(...D.split(` ++`).map(g));var k$1=D=>{let F=[];for(let u of D){let{length:C}=u,t=C-F.length;for(let E=0;EF[E]&&(F[E]=e);}}return F};i();var _$1=/^\d+%$/,z$1={width:"auto",align:"left",contentWidth:0,paddingLeft:0,paddingRight:0,paddingTop:0,paddingBottom:0,horizontalPadding:0,paddingLeftString:"",paddingRightString:""},sD=(D,F)=>{var C;let u=[];for(let t=0;t=F){let n=e-F,o=Math.ceil(u.paddingLeft/t*n),B=n-o;u.paddingLeft-=o,u.paddingRight-=B,u.horizontalPadding=u.paddingLeft+u.paddingRight;}u.paddingLeftString=u.paddingLeft?" ".repeat(u.paddingLeft):"",u.paddingRightString=u.paddingRight?" ".repeat(u.paddingRight):"";let r=F-u.horizontalPadding;u.width=Math.max(Math.min(u.width,r),E);}}var G$1=()=>Object.assign([],{columns:0});function lD(D,F){let u=[G$1()],[C]=u;for(let t of D){let E=t.width+t.horizontalPadding;C.columns+E>F&&(C=G$1(),u.push(C)),C.push(t),C.columns+=E;}for(let t of u){let E=t.reduce((s,l)=>s+l.width+l.horizontalPadding,0),e=F-E;if(e===0)continue;let r=t.filter(s=>"autoOverflow"in s),n=r.filter(s=>s.autoOverflow>0),o=n.reduce((s,l)=>s+l.autoOverflow,0),B=Math.min(o,e);for(let s of n){let l=Math.floor(s.autoOverflow/o*B);s.width+=l,e-=l;}let a=Math.floor(e/r.length);for(let s=0;sF=>`[${F+D}m`,V$1=(D=0)=>F=>`[${38+D};5;${F}m`,Y=(D=0)=>(F,u,C)=>`[${38+D};2;${F};${u};${C}m`;function AD(){let D=new Map,F={modifier:{reset:[0,0],bold:[1,22],dim:[2,22],italic:[3,23],underline:[4,24],overline:[53,55],inverse:[7,27],hidden:[8,28],strikethrough:[9,29]},color:{black:[30,39],red:[31,39],green:[32,39],yellow:[33,39],blue:[34,39],magenta:[35,39],cyan:[36,39],white:[37,39],blackBright:[90,39],redBright:[91,39],greenBright:[92,39],yellowBright:[93,39],blueBright:[94,39],magentaBright:[95,39],cyanBright:[96,39],whiteBright:[97,39]},bgColor:{bgBlack:[40,49],bgRed:[41,49],bgGreen:[42,49],bgYellow:[43,49],bgBlue:[44,49],bgMagenta:[45,49],bgCyan:[46,49],bgWhite:[47,49],bgBlackBright:[100,49],bgRedBright:[101,49],bgGreenBright:[102,49],bgYellowBright:[103,49],bgBlueBright:[104,49],bgMagentaBright:[105,49],bgCyanBright:[106,49],bgWhiteBright:[107,49]}};F.color.gray=F.color.blackBright,F.bgColor.bgGray=F.bgColor.bgBlackBright,F.color.grey=F.color.blackBright,F.bgColor.bgGrey=F.bgColor.bgBlackBright;for(let[u,C]of Object.entries(F)){for(let[t,E]of Object.entries(C))F[t]={open:`[${E[0]}m`,close:`[${E[1]}m`},C[t]=F[t],D.set(E[0],E[1]);Object.defineProperty(F,u,{value:C,enumerable:!1});}return Object.defineProperty(F,"codes",{value:D,enumerable:!1}),F.color.close="",F.bgColor.close="",F.color.ansi=U$1(),F.color.ansi256=V$1(),F.color.ansi16m=Y(),F.bgColor.ansi=U$1(O),F.bgColor.ansi256=V$1(O),F.bgColor.ansi16m=Y(O),Object.defineProperties(F,{rgbToAnsi256:{value:(u,C,t)=>u===C&&C===t?u<8?16:u>248?231:Math.round((u-8)/247*24)+232:16+36*Math.round(u/255*5)+6*Math.round(C/255*5)+Math.round(t/255*5),enumerable:!1},hexToRgb:{value:u=>{let C=/(?[a-f\d]{6}|[a-f\d]{3})/i.exec(u.toString(16));if(!C)return [0,0,0];let{colorString:t}=C.groups;t.length===3&&(t=t.split("").map(e=>e+e).join(""));let E=Number.parseInt(t,16);return [E>>16&255,E>>8&255,E&255]},enumerable:!1},hexToAnsi256:{value:u=>F.rgbToAnsi256(...F.hexToRgb(u)),enumerable:!1},ansi256ToAnsi:{value:u=>{if(u<8)return 30+u;if(u<16)return 90+(u-8);let C,t,E;if(u>=232)C=((u-232)*10+8)/255,t=C,E=C;else {u-=16;let n=u%36;C=Math.floor(u/36)/5,t=Math.floor(n/6)/5,E=n%6/5;}let e=Math.max(C,t,E)*2;if(e===0)return 30;let r=30+(Math.round(E)<<2|Math.round(t)<<1|Math.round(C));return e===2&&(r+=60),r},enumerable:!1},rgbToAnsi:{value:(u,C,t)=>F.ansi256ToAnsi(F.rgbToAnsi256(u,C,t)),enumerable:!1},hexToAnsi:{value:u=>F.ansi256ToAnsi(F.hexToAnsi256(u)),enumerable:!1}}),F}var fD=AD(),K=fD;var x$1=new Set(["","\x9B"]),gD=39,R$1="\x07",q$1="[",pD="]",H$1="m",M$1=`${pD}8;;`,J$1=D=>`${x$1.values().next().value}${q$1}${D}${H$1}`,Q=D=>`${x$1.values().next().value}${M$1}${D}${R$1}`,hD=D=>D.split(" ").map(F=>g(F)),S$1=(D,F,u)=>{let C=[...F],t=!1,E=!1,e=g(d$1(D[D.length-1]));for(let[r,n]of C.entries()){let o=g(n);if(e+o<=u?D[D.length-1]+=n:(D.push(n),e=0),x$1.has(n)&&(t=!0,E=C.slice(r+1).join("").startsWith(M$1)),t){E?n===R$1&&(t=!1,E=!1):n===H$1&&(t=!1);continue}e+=o,e===u&&r0&&D.length>1&&(D[D.length-2]+=D.pop());},cD=D=>{let F=D.split(" "),u=F.length;for(;u>0&&!(g(F[u-1])>0);)u--;return u===F.length?D:F.slice(0,u).join(" ")+F.slice(u).join("")},dD=(D,F,u={})=>{if(u.trim!==!1&&D.trim()==="")return "";let C="",t,E,e=hD(D),r=[""];for(let[o,B]of D.split(" ").entries()){u.trim!==!1&&(r[r.length-1]=r[r.length-1].trimStart());let a=g(r[r.length-1]);if(o!==0&&(a>=F&&(u.wordWrap===!1||u.trim===!1)&&(r.push(""),a=0),(a>0||u.trim===!1)&&(r[r.length-1]+=" ",a++)),u.hard&&e[o]>F){let s=F-a,l=1+Math.floor((e[o]-s-1)/F);Math.floor((e[o]-1)/F)F&&a>0&&e[o]>0){if(u.wordWrap===!1&&aF&&u.wordWrap===!1){S$1(r,B,F);continue}r[r.length-1]+=B;}u.trim!==!1&&(r=r.map(o=>cD(o)));let n=[...r.join(` ++`)];for(let[o,B]of n.entries()){if(C+=B,x$1.has(B)){let{groups:s}=new RegExp(`(?:\\${q$1}(?\\d+)m|\\${M$1}(?.*)${R$1})`).exec(n.slice(o).join(""))||{groups:{}};if(s.code!==void 0){let l=Number.parseFloat(s.code);t=l===gD?void 0:l;}else s.uri!==void 0&&(E=s.uri.length===0?void 0:s.uri);}let a=K.codes.get(Number(t));n[o+1]===` ++`?(E&&(C+=Q("")),t&&a&&(C+=J$1(a))):B===` ++`&&(t&&a&&(C+=J$1(t)),E&&(C+=Q(E)));}return C};function T$1(D,F,u){return String(D).normalize().replace(/\r\n/g,` + `).split(` +-`).map(D=>AD(D,e,t)).join(` +-`)}var $u=u=>Array.from({length:u}).fill("");function vD(u,e){let t=[],D=0;for(let n of u){let i=0,s=n.map(a=>{var h;let f=(h=e[D])!=null?h:"";D+=1,a.preprocess&&(f=a.preprocess(f)),pu(f)>a.width&&(f=bD(f,a.width,{hard:!0}));let g=f.split(` +-`);if(a.postprocess){let{postprocess:F}=a;g=g.map((d,m)=>F.call(a,d,m))}return a.paddingTop&&g.unshift(...$u(a.paddingTop)),a.paddingBottom&&g.push(...$u(a.paddingBottom)),g.length>i&&(i=g.length),Re(Be({},a),{lines:g})}),o=[];for(let a=0;a{var g;let F=(g=f.lines[a])!=null?g:"",d=Number.isFinite(f.width)?" ".repeat(f.width-V(F)):"",m=f.paddingLeftString;return f.align==="right"&&(m+=d),m+=F,f.align==="left"&&(m+=d),m+f.paddingRightString}).join("");o.push(h)}t.push(o.join(` +-`))}return t.join(` +-`)}function SD(u,e){if(!u||u.length===0)return"";let t=pD(u),D=t.length;if(D===0)return"";let{stdoutColumns:n,columns:i}=aD(e);if(i.length>D)throw new Error(`${i.length} columns defined, but only ${D} columns found`);let s=CD(n,i,t);return u.map(o=>vD(s,o)).join(` +-`)}x();var OD=["<",">","=",">=","<="];function kD(u){if(!OD.includes(u))throw new TypeError(`Invalid breakpoint operator: ${u}`)}function jD(u){let e=Object.keys(u).map(t=>{let[D,n]=t.split(" ");kD(D);let i=Number.parseInt(n,10);if(Number.isNaN(i))throw new TypeError(`Invalid breakpoint value: ${n}`);let s=u[t];return{operator:D,breakpoint:i,value:s}}).sort((t,D)=>D.breakpoint-t.breakpoint);return t=>{var D;return(D=e.find(({operator:n,breakpoint:i})=>n==="="&&t===i||n===">"&&t>i||n==="<"&&t="&&t>=i||n==="<="&&t<=i))==null?void 0:D.value}}const xD=u=>u.replace(/[\W_]([a-z\d])?/gi,(e,t)=>t?t.toUpperCase():""),TD=u=>u.replace(/\B([A-Z])/g,"-$1").toLowerCase(),RD={"> 80":[{width:"content-width",paddingLeft:2,paddingRight:8},{width:"auto"}],"> 40":[{width:"auto",paddingLeft:2,paddingRight:8,preprocess:u=>u.trim()},{width:"100%",paddingLeft:2,paddingBottom:1}],"> 0":{stdoutColumns:1e3,columns:[{width:"content-width",paddingLeft:2,paddingRight:8},{width:"content-width"}]}};function PD(u){let e=!1;return{type:"table",data:{tableData:Object.keys(u).sort((t,D)=>t.localeCompare(D)).map(t=>{const D=u[t],n="alias"in D;return n&&(e=!0),{name:t,flag:D,flagFormatted:`--${TD(t)}`,aliasesEnabled:e,aliasFormatted:n?`-${D.alias}`:void 0}}).map(t=>(t.aliasesEnabled=e,[{type:"flagName",data:t},{type:"flagDescription",data:t}])),tableBreakpoints:RD}}}const Au=u=>!u||(u.version??(u.help?u.help.version:void 0)),bu=u=>{const e="parent"in u&&u.parent?.name;return(e?`${e} `:"")+u.name};function LD(u){const e=[];u.name&&e.push(bu(u));const t=Au(u)??("parent"in u&&Au(u.parent));if(t&&e.push(`v${t}`),e.length!==0)return{id:"name",type:"text",data:`${e.join(" ")} +-`}}function MD(u){const{help:e}=u;if(!(!e||!e.description))return{id:"description",type:"text",data:`${e.description} +-`}}function ND(u){const e=u.help||{};if("usage"in e)return e.usage?{id:"usage",type:"section",data:{title:"Usage:",body:Array.isArray(e.usage)?e.usage.join(` +-`):e.usage}}:void 0;if(u.name){const t=[],D=[bu(u)];if(u.flags&&Object.keys(u.flags).length>0&&D.push("[flags...]"),u.parameters&&u.parameters.length>0){const{parameters:n}=u,i=n.indexOf("--"),s=i>-1&&n.slice(i+1).some(o=>o.startsWith("<"));D.push(n.map(o=>o!=="--"?o:s?"--":"[--]").join(" "))}if(D.length>1&&t.push(D.join(" ")),"commands"in u&&u.commands?.length&&t.push(`${u.name} `),t.length>0)return{id:"usage",type:"section",data:{title:"Usage:",body:t.join(` +-`)}}}}function ID(u){return!("commands"in u)||!u.commands?.length?void 0:{id:"commands",type:"section",data:{title:"Commands:",body:{type:"table",data:{tableData:u.commands.map(e=>[e.options.name,e.options.help?e.options.help.description:""]),tableOptions:[{width:"content-width",paddingLeft:2,paddingRight:8}]}},indentBody:0}}}function _D(u){if(!(!u.flags||Object.keys(u.flags).length===0))return{id:"flags",type:"section",data:{title:"Flags:",body:PD(u.flags),indentBody:0}}}function WD(u){const{help:e}=u;if(!e||!e.examples||e.examples.length===0)return;let{examples:t}=e;if(Array.isArray(t)&&(t=t.join(` +-`)),t)return{id:"examples",type:"section",data:{title:"Examples:",body:t}}}function GD(u){if(!("alias"in u)||!u.alias)return;const{alias:e}=u;return{id:"aliases",type:"section",data:{title:"Aliases:",body:Array.isArray(e)?e.join(", "):e}}}const qD=u=>[LD,MD,ND,ID,_D,WD,GD].map(e=>e(u)).filter(Boolean),zD=Ot.WriteStream.prototype.hasColors();class JD{text(e){return e}bold(e){return zD?`\x1B[1m${e}\x1B[22m`:e.toLocaleUpperCase()}indentText({text:e,spaces:t}){return e.replace(/^/gm," ".repeat(t))}heading(e){return this.bold(e)}section({title:e,body:t,indentBody:D=2}){return`${(e?`${this.heading(e)} +-`:"")+(t?this.indentText({text:this.render(t),spaces:D}):"")} +-`}table({tableData:e,tableOptions:t,tableBreakpoints:D}){return SD(e.map(n=>n.map(i=>this.render(i))),D?jD(D):t)}flagParameter(e){return e===Boolean?"":e===String?"":e===Number?"":Array.isArray(e)?this.flagParameter(e[0]):""}flagOperator(e){return" "}flagName(e){const{flag:t,flagFormatted:D,aliasesEnabled:n,aliasFormatted:i}=e;let s="";if(i?s+=`${i}, `:n&&(s+=" "),s+=D,"placeholder"in t&&typeof t.placeholder=="string")s+=`${this.flagOperator(e)}${t.placeholder}`;else{const o=this.flagParameter("type"in t?t.type:t);o&&(s+=`${this.flagOperator(e)}${o}`)}return s}flagDefault(e){return JSON.stringify(e)}flagDescription({flag:e}){let t="description"in e?e.description??"":"";if("default"in e){let{default:D}=e;typeof D=="function"&&(D=D()),D&&(t+=` (default: ${this.flagDefault(D)})`)}return t}render(e){if(typeof e=="string")return e;if(Array.isArray(e))return e.map(t=>this.render(t)).join(` +-`);if("type"in e&&this[e.type]){const t=this[e.type];if(typeof t=="function")return t.call(this,e.data)}throw new Error(`Invalid node type: ${JSON.stringify(e)}`)}}const Ie=/^[\w.-]+$/,{stringify:I}=JSON,VD=/[|\\{}()[\]^$+*?.]/;function _e(u){const e=[];let t,D;for(const n of u){if(D)throw new Error(`Invalid parameter: Spread parameter ${I(D)} must be last`);const i=n[0],s=n[n.length-1];let o;if(i==="<"&&s===">"&&(o=!0,t))throw new Error(`Invalid parameter: Required parameter ${I(n)} cannot come after optional parameter ${I(t)}`);if(i==="["&&s==="]"&&(o=!1,t=n),o===void 0)throw new Error(`Invalid parameter: ${I(n)}. Must be wrapped in <> (required parameter) or [] (optional parameter)`);let a=n.slice(1,-1);const h=a.slice(-3)==="...";h&&(D=n,a=a.slice(0,-3));const f=a.match(VD);if(f)throw new Error(`Invalid parameter: ${I(n)}. Invalid character found ${I(f[0])}`);e.push({name:a,required:o,spread:h})}return e}function We(u,e,t,D){for(let n=0;n{console.log(e.version)};if(i&&a.flags.version===!0)return h(),process.exit(0);const f=new JD,g=o&&s?.render?s.render:m=>f.render(m),F=m=>{const w=qD({...e,...m?{help:m}:{},flags:n});console.log(g(w,f))};if(o&&a.flags.help===!0)return F(),process.exit(0);if(e.parameters){let{parameters:m}=e,w=a._;const S=m.indexOf("--"),R=m.slice(S+1),M=Object.create(null);if(S>-1&&R.length>0){m=m.slice(0,S);const G=a._["--"];w=w.slice(0,-G.length||void 0),We(M,_e(m),w,F),We(M,_e(R),G,F)}else We(M,_e(m),w,F);Object.assign(a._,M)}const d={...a,showVersion:h,showHelp:F};return typeof t=="function"&&t(d),{command:u,...d}}function ZD(u,e){const t=new Map;for(const D of e){const n=[D.options.name],{alias:i}=D.options;i&&(Array.isArray(i)?n.push(...i):n.push(i));for(const s of n){if(t.has(s))throw new Error(`Duplicate command name found: ${I(s)}`);t.set(s,D)}}return t.get(u)}function HD(u,e,t=process.argv.slice(2)){if(!u)throw new Error("Options is required");if("name"in u&&(!u.name||!Ie.test(u.name)))throw new Error(`Invalid script name: ${I(u.name)}`);const D=t[0];if(u.commands&&Ie.test(D)){const n=ZD(D,u.commands);if(n)return vu(n.options.name,{...n.options,parent:u},n.callback,t.slice(1))}return vu(void 0,u,e,t)}function XD(u,e){if(!u)throw new Error("Command options are required");const{name:t}=u;if(u.name===void 0)throw new Error("Command name is required");if(!Ie.test(t))throw new Error(`Invalid command name ${JSON.stringify(t)}. Command names must be one word.`);return{options:u,callback:e}}function ye(){}function Su(){return typeof WeakMap<"u"?new WeakMap:YD()}function YD(){return{add:ye,delete:ye,get:ye,set:ye,has:function(u){return!1}}}var KD=Object.prototype.hasOwnProperty,Ge=function(u,e){return KD.call(u,e)};function qe(u,e){for(var t in e)Ge(e,t)&&(u[t]=e[t]);return u}var QD=/^[ \t]*(?:\r\n|\r|\n)/,en=/(?:\r\n|\r|\n)[ \t]*$/,un=/^(?:[\r\n]|$)/,tn=/(?:\r\n|\r|\n)([ \t]*)(?:[^ \t\r\n]|$)/,Dn=/^[ \t]*[\r\n][ \t\r\n]*$/;function Ou(u,e,t){var D=0,n=u[0].match(tn);n&&(D=n[1].length);var i="(\\r\\n|\\r|\\n).{0,"+D+"}",s=new RegExp(i,"g");e&&(u=u.slice(1));var o=t.newline,a=t.trimLeadingNewline,h=t.trimTrailingNewline,f=typeof o=="string",g=u.length,F=u.map(function(d,m){return d=d.replace(s,"$1"),m===0&&a&&(d=d.replace(QD,"")),m===g-1&&h&&(d=d.replace(en,"")),f&&(d=d.replace(/\r\n|\n|\r/g,function(w){return o})),d});return F}function nn(u,e){for(var t="",D=0,n=u.length;Di in H.process.env)?U=!0:U=process.stdout.isTTY,U&&(process.platform==="win32"||n&&(n==="truecolor"||n==="24bit")?se=3:D&&(D.endsWith("-256color")||D.endsWith("256"))?se=2:se=1)}let ju={enabled:U,supportLevel:se};function X(u,e,t=1){const D=`\x1B[${u}m`,n=`\x1B[${e}m`,i=new RegExp(`\\x1b\\[${e}m`,"g");return s=>ju.enabled&&ju.supportLevel>=t?D+(""+s).replace(i,D)+n:""+s}const rn=X(1,22),ze=X(2,22),xu=X(31,39),Tu=X(32,39),Je=X(35,39),re=X(36,39),oe=u=>O.access(u).then(()=>!0,()=>!1);var on=require;const Ru=async u=>{const e=await O.readFile(u,"utf8");return JSON.parse(e)},an="link.config.json",Pu="link.config.js",Lu=async u=>{const e=A.join(u,an);if(await oe(e))try{return Ru(e)}catch(D){throw new Error(`Failed to parse config JSON ${e}: ${D.message}`)}const t=A.join(u,Pu);if(await oe(t))try{return on(t)}catch(D){throw new Error(`Failed to load config file ${Pu}: ${D.message}`)}};var Mu=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Nu(u){return u&&u.__esModule&&Object.prototype.hasOwnProperty.call(u,"default")?u.default:u}var $e={};$e.replaceDollarWithPercentPair=_u,$e.convertToSetCommand=Iu,$e.convertToSetCommands=cn;function Iu(u,e){var t="";return u=u||"",u=u.trim(),e=e||"",e=e.trim(),u&&e&&e.length>0&&(t="@SET "+u+"="+_u(e)+`\r +-`),t}function ln(u){var e={};return u.map(function(t){var D=t.split("=");e[D[0]]=D[1]}),e}function cn(u){var e=ln(u.split(" ")),t="";return Object.keys(e).forEach(function(D){t+=Iu(D,e[D])}),t}function _u(u){var e=/\$\{?([^$@#?\- \t{}:]+)\}?/g,t="",D=0;do{var n=e.exec(u);if(n){var i=u.substring(D,n.index)||"";t+=i+"%"+n[1]+"%",D=e.lastIndex}}while(e.lastIndex>0);return t+=u.slice(D),t}const{chmod:Ve,mkdir:hn,readFile:pn,stat:Wu,unlink:fn,writeFile:Ue}=O,{dirname:Gu,relative:Fn}=A,dn=$e,Cn=/^#!\s*(?:\/usr\/bin\/env\s+(?:-S\s+)?((?:[^ \t=]+=[^ \t=]+\s+)*))?([^ \t]+)(.*)$/,En=(u,e)=>Wu(u).then(()=>He(u,e),()=>{}),Ze=u=>fn(u).catch(()=>{}),He=(u,e)=>Wu(u).then(()=>gn(u,e)),gn=(u,e)=>Promise.all([Ze(e),Ze(e+".cmd"),Ze(e+".ps1")]).then(()=>mn(u,e)),mn=(u,e)=>hn(Gu(e),{recursive:!0}).then(()=>pn(u,"utf8")).then(t=>{const n=t.trim().split(/\r*\n/)[0].match(Cn);if(!n)return Xe(u,e);const i=n[1]||"",s=n[2],o=n[3]||"";return Xe(u,e,s,o,i)},t=>Xe(u,e)),Xe=(u,e,t,D,n)=>{let i=Fn(Gu(e),u),s=i.split("/").join("\\"),o,a=t&&t.split("\\").join("/"),h,f=a&&`"${a}$exe"`,g;i=i.split("\\").join("/"),D=D||"",n=n||"",t?(o=`"%dp0%\\${t}.exe"`,h=`"$basedir/${t}"`,g=`"$basedir/${t}$exe"`,s=`"%dp0%\\${s}"`,i=`"$basedir/${i}"`):(t=`"%dp0%\\${s}"`,a=`"$basedir/${i}"`,f=a,D="",s="",i="");const F=`@ECHO off\r +-GOTO start\r +-:find_dp0\r +-SET dp0=%~dp0\r +-EXIT /b\r +-:start\r +-SETLOCAL\r +-CALL :find_dp0\r +-`;let d;if(o){h=h.trim(),D=D.trim();const S=dn.convertToSetCommands(n);d=F+S+`\r +-IF EXIST ${o} (\r +- SET "_prog=${o.replace(/(^")|("$)/g,"")}"\r +-) ELSE (\r +- SET "_prog=${t.replace(/(^")|("$)/g,"")}"\r +- SET PATHEXT=%PATHEXT:;.JS;=;%\r +-)\r +-\r +-endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" ${D} ${s} %*\r +-`}else d=`${F}${t} ${D} ${s} %*\r +-`;let m=`#!/bin/sh +-`;m=m+`basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')") +- +-case \`uname\` in +- *CYGWIN*|*MINGW*|*MSYS*) basedir=\`cygpath -w "$basedir"\`;; +-esac +- +-`,h?m=m+`if [ -x ${h} ]; then +- exec ${n}${h} ${D} ${i} "$@" +-else +- exec ${n}${a} ${D} ${i} "$@" +-fi +-`:m=m+`exec ${a} ${D} ${i} "$@" +-`;let w=`#!/usr/bin/env pwsh +-$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent +- +-$exe="" +-if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { +- # Fix case when both the Windows and Linux builds of Node +- # are installed in the same directory +- $exe=".exe" +-} +-`;return h?w=w+`$ret=0 +-if (Test-Path ${g}) { +- # Support pipeline input +- if ($MyInvocation.ExpectingInput) { +- $input | & ${g} ${D} ${i} $args ++`).map(C=>dD(C,F,u)).join(` ++`)}var X=D=>Array.from({length:D}).fill("");function P(D,F){let u=[],C=0;for(let t of D){let E=0,e=t.map(n=>{var a;let o=(a=F[C])!=null?a:"";C+=1,n.preprocess&&(o=n.preprocess(o)),b$1(o)>n.width&&(o=T$1(o,n.width,{hard:!0}));let B=o.split(` ++`);if(n.postprocess){let{postprocess:s}=n;B=B.map((l,h)=>s.call(n,l,h));}return n.paddingTop&&B.unshift(...X(n.paddingTop)),n.paddingBottom&&B.push(...X(n.paddingBottom)),B.length>E&&(E=B.length),c(p({},n),{lines:B})}),r=[];for(let n=0;n{var h;let a=(h=B.lines[n])!=null?h:"",s=Number.isFinite(B.width)?" ".repeat(B.width-g(a)):"",l=B.paddingLeftString;return B.align==="right"&&(l+=s),l+=a,B.align==="left"&&(l+=s),l+B.paddingRightString}).join("");r.push(o);}u.push(r.join(` ++`));}return u.join(` ++`)}function mD(D,F){if(!D||D.length===0)return "";let u=k$1(D),C=u.length;if(C===0)return "";let{stdoutColumns:t,columns:E}=v(F);if(E.length>C)throw new Error(`${E.length} columns defined, but only ${C} columns found`);let e=Z$1(t,E,u);return D.map(r=>P(e,r)).join(` ++`)}i();var bD=["<",">","=",">=","<="];function xD(D){if(!bD.includes(D))throw new TypeError(`Invalid breakpoint operator: ${D}`)}function wD(D){let F=Object.keys(D).map(u=>{let[C,t]=u.split(" ");xD(C);let E=Number.parseInt(t,10);if(Number.isNaN(E))throw new TypeError(`Invalid breakpoint value: ${t}`);let e=D[u];return {operator:C,breakpoint:E,value:e}}).sort((u,C)=>C.breakpoint-u.breakpoint);return u=>{var C;return (C=F.find(({operator:t,breakpoint:E})=>t==="="&&u===E||t===">"&&u>E||t==="<"&&u="&&u>=E||t==="<="&&u<=E))==null?void 0:C.value}} ++ ++const S=t=>t.replace(/[\W_]([a-z\d])?/gi,(e,r)=>r?r.toUpperCase():""),q=t=>t.replace(/\B([A-Z])/g,"-$1").toLowerCase(),I={"> 80":[{width:"content-width",paddingLeft:2,paddingRight:8},{width:"auto"}],"> 40":[{width:"auto",paddingLeft:2,paddingRight:8,preprocess:t=>t.trim()},{width:"100%",paddingLeft:2,paddingBottom:1}],"> 0":{stdoutColumns:1e3,columns:[{width:"content-width",paddingLeft:2,paddingRight:8},{width:"content-width"}]}};function D(t){let e=!1;return {type:"table",data:{tableData:Object.keys(t).sort((a,i)=>a.localeCompare(i)).map(a=>{const i=t[a],s="alias"in i;return s&&(e=!0),{name:a,flag:i,flagFormatted:`--${q(a)}`,aliasesEnabled:e,aliasFormatted:s?`-${i.alias}`:void 0}}).map(a=>(a.aliasesEnabled=e,[{type:"flagName",data:a},{type:"flagDescription",data:a}])),tableBreakpoints:I}}}const A=t=>!t||(t.version??(t.help?t.help.version:void 0)),C=t=>{const e="parent"in t&&t.parent?.name;return (e?`${e} `:"")+t.name};function R(t){const e=[];t.name&&e.push(C(t));const r=A(t)??("parent"in t&&A(t.parent));if(r&&e.push(`v${r}`),e.length!==0)return {id:"name",type:"text",data:`${e.join(" ")} ++`}}function L(t){const{help:e}=t;if(!(!e||!e.description))return {id:"description",type:"text",data:`${e.description} ++`}}function T(t){const e=t.help||{};if("usage"in e)return e.usage?{id:"usage",type:"section",data:{title:"Usage:",body:Array.isArray(e.usage)?e.usage.join(` ++`):e.usage}}:void 0;if(t.name){const r=[],n=[C(t)];if(t.flags&&Object.keys(t.flags).length>0&&n.push("[flags...]"),t.parameters&&t.parameters.length>0){const{parameters:a}=t,i=a.indexOf("--"),s=i>-1&&a.slice(i+1).some(o=>o.startsWith("<"));n.push(a.map(o=>o!=="--"?o:s?"--":"[--]").join(" "));}if(n.length>1&&r.push(n.join(" ")),"commands"in t&&t.commands?.length&&r.push(`${t.name} `),r.length>0)return {id:"usage",type:"section",data:{title:"Usage:",body:r.join(` ++`)}}}}function _(t){return !("commands"in t)||!t.commands?.length?void 0:{id:"commands",type:"section",data:{title:"Commands:",body:{type:"table",data:{tableData:t.commands.map(n=>[n.options.name,n.options.help?n.options.help.description:""]),tableOptions:[{width:"content-width",paddingLeft:2,paddingRight:8}]}},indentBody:0}}}function k(t){if(!(!t.flags||Object.keys(t.flags).length===0))return {id:"flags",type:"section",data:{title:"Flags:",body:D(t.flags),indentBody:0}}}function F(t){const{help:e}=t;if(!e||!e.examples||e.examples.length===0)return;let{examples:r}=e;if(Array.isArray(r)&&(r=r.join(` ++`)),r)return {id:"examples",type:"section",data:{title:"Examples:",body:r}}}function H(t){if(!("alias"in t)||!t.alias)return;const{alias:e}=t;return {id:"aliases",type:"section",data:{title:"Aliases:",body:Array.isArray(e)?e.join(", "):e}}}const U=t=>[R,L,T,_,k,F,H].map(e=>e(t)).filter(Boolean),J=N$1.WriteStream.prototype.hasColors();class M{text(e){return e}bold(e){return J?`\x1B[1m${e}\x1B[22m`:e.toLocaleUpperCase()}indentText({text:e,spaces:r}){return e.replace(/^/gm," ".repeat(r))}heading(e){return this.bold(e)}section({title:e,body:r,indentBody:n=2}){return `${(e?`${this.heading(e)} ++`:"")+(r?this.indentText({text:this.render(r),spaces:n}):"")} ++`}table({tableData:e,tableOptions:r,tableBreakpoints:n}){return mD(e.map(a=>a.map(i=>this.render(i))),n?wD(n):r)}flagParameter(e){return e===Boolean?"":e===String?"":e===Number?"":Array.isArray(e)?this.flagParameter(e[0]):""}flagOperator(e){return " "}flagName(e){const{flag:r,flagFormatted:n,aliasesEnabled:a,aliasFormatted:i}=e;let s="";if(i?s+=`${i}, `:a&&(s+=" "),s+=n,"placeholder"in r&&typeof r.placeholder=="string")s+=`${this.flagOperator(e)}${r.placeholder}`;else {const o=this.flagParameter("type"in r?r.type:r);o&&(s+=`${this.flagOperator(e)}${o}`);}return s}flagDefault(e){return JSON.stringify(e)}flagDescription({flag:e}){let r="description"in e?e.description??"":"";if("default"in e){let{default:n}=e;typeof n=="function"&&(n=n()),n&&(r+=` (default: ${this.flagDefault(n)})`);}return r}render(e){if(typeof e=="string")return e;if(Array.isArray(e))return e.map(r=>this.render(r)).join(` ++`);if("type"in e&&this[e.type]){const r=this[e.type];if(typeof r=="function")return r.call(this,e.data)}throw new Error(`Invalid node type: ${JSON.stringify(e)}`)}}const y=/^[\w.-]+$/,{stringify:d}=JSON,V=/[|\\{}()[\]^$+*?.]/;function w(t){const e=[];let r,n;for(const a of t){if(n)throw new Error(`Invalid parameter: Spread parameter ${d(n)} must be last`);const i=a[0],s=a[a.length-1];let o;if(i==="<"&&s===">"&&(o=!0,r))throw new Error(`Invalid parameter: Required parameter ${d(a)} cannot come after optional parameter ${d(r)}`);if(i==="["&&s==="]"&&(o=!1,r=a),o===void 0)throw new Error(`Invalid parameter: ${d(a)}. Must be wrapped in <> (required parameter) or [] (optional parameter)`);let l=a.slice(1,-1);const f=l.slice(-3)==="...";f&&(n=a,l=l.slice(0,-3));const p=l.match(V);if(p)throw new Error(`Invalid parameter: ${d(a)}. Invalid character found ${d(p[0])}`);e.push({name:l,required:o,spread:f});}return e}function b(t,e,r,n){for(let a=0;a{console.log(e.version);};if(i&&l.flags.version===!0)return f(),process.exit(0);const p=new M,O=o&&s?.render?s.render:c=>p.render(c),u=c=>{const m=U({...e,...c?{help:c}:{},flags:a});console.log(O(m,p));};if(o&&l.flags.help===!0)return u(),process.exit(0);if(e.parameters){let{parameters:c}=e,m=l._;const g=c.indexOf("--"),v=c.slice(g+1),h=Object.create(null);if(g>-1&&v.length>0){c=c.slice(0,g);const E=l._["--"];m=m.slice(0,-E.length||void 0),b(h,w(c),m,u),b(h,w(v),E,u);}else b(h,w(c),m,u);Object.assign(l._,h);}const $={...l,showVersion:f,showHelp:u};return typeof r=="function"&&r($),{command:t,...$}}function z(t,e){const r=new Map;for(const n of e){const a=[n.options.name],{alias:i}=n.options;i&&(Array.isArray(i)?a.push(...i):a.push(i));for(const s of a){if(r.has(s))throw new Error(`Duplicate command name found: ${d(s)}`);r.set(s,n);}}return r.get(t)}function Z(t,e,r=process.argv.slice(2)){if(!t)throw new Error("Options is required");if("name"in t&&(!t.name||!y.test(t.name)))throw new Error(`Invalid script name: ${d(t.name)}`);const n=r[0];if(t.commands&&y.test(n)){const a=z(n,t.commands);if(a)return x(a.options.name,{...a.options,parent:t},a.callback,r.slice(1))}return x(void 0,t,e,r)}function G(t,e){if(!t)throw new Error("Command options are required");const{name:r}=t;if(t.name===void 0)throw new Error("Command name is required");if(!y.test(r))throw new Error(`Invalid command name ${JSON.stringify(r)}. Command names must be one word.`);return {options:t,callback:e}} ++ ++// In the absence of a WeakSet or WeakMap implementation, don't break, but don't cache either. ++function noop() { ++} ++function createWeakMap() { ++ if (typeof WeakMap !== "undefined") { ++ return new WeakMap(); ++ } ++ else { ++ return fakeSetOrMap(); ++ } ++} ++/** ++ * Creates and returns a no-op implementation of a WeakMap / WeakSet that never stores anything. ++ */ ++function fakeSetOrMap() { ++ return { ++ add: noop, ++ delete: noop, ++ get: noop, ++ set: noop, ++ has: function (k) { ++ return false; ++ }, ++ }; ++} ++// Safe hasOwnProperty ++var hop = Object.prototype.hasOwnProperty; ++var has = function (obj, prop) { ++ return hop.call(obj, prop); ++}; ++// Copy all own enumerable properties from source to target ++function extend(target, source) { ++ for (var prop in source) { ++ if (has(source, prop)) { ++ target[prop] = source[prop]; ++ } ++ } ++ return target; ++} ++var reLeadingNewline = /^[ \t]*(?:\r\n|\r|\n)/; ++var reTrailingNewline = /(?:\r\n|\r|\n)[ \t]*$/; ++var reStartsWithNewlineOrIsEmpty = /^(?:[\r\n]|$)/; ++var reDetectIndentation = /(?:\r\n|\r|\n)([ \t]*)(?:[^ \t\r\n]|$)/; ++var reOnlyWhitespaceWithAtLeastOneNewline = /^[ \t]*[\r\n][ \t\r\n]*$/; ++function _outdentArray(strings, firstInterpolatedValueSetsIndentationLevel, options) { ++ // If first interpolated value is a reference to outdent, ++ // determine indentation level from the indentation of the interpolated value. ++ var indentationLevel = 0; ++ var match = strings[0].match(reDetectIndentation); ++ if (match) { ++ indentationLevel = match[1].length; ++ } ++ var reSource = "(\\r\\n|\\r|\\n).{0," + indentationLevel + "}"; ++ var reMatchIndent = new RegExp(reSource, "g"); ++ if (firstInterpolatedValueSetsIndentationLevel) { ++ strings = strings.slice(1); ++ } ++ var newline = options.newline, trimLeadingNewline = options.trimLeadingNewline, trimTrailingNewline = options.trimTrailingNewline; ++ var normalizeNewlines = typeof newline === "string"; ++ var l = strings.length; ++ var outdentedStrings = strings.map(function (v, i) { ++ // Remove leading indentation from all lines ++ v = v.replace(reMatchIndent, "$1"); ++ // Trim a leading newline from the first string ++ if (i === 0 && trimLeadingNewline) { ++ v = v.replace(reLeadingNewline, ""); ++ } ++ // Trim a trailing newline from the last string ++ if (i === l - 1 && trimTrailingNewline) { ++ v = v.replace(reTrailingNewline, ""); ++ } ++ // Normalize newlines ++ if (normalizeNewlines) { ++ v = v.replace(/\r\n|\n|\r/g, function (_) { return newline; }); ++ } ++ return v; ++ }); ++ return outdentedStrings; ++} ++function concatStringsAndValues(strings, values) { ++ var ret = ""; ++ for (var i = 0, l = strings.length; i < l; i++) { ++ ret += strings[i]; ++ if (i < l - 1) { ++ ret += values[i]; ++ } ++ } ++ return ret; ++} ++function isTemplateStringsArray(v) { ++ return has(v, "raw") && has(v, "length"); ++} ++/** ++ * It is assumed that opts will not change. If this is a problem, clone your options object and pass the clone to ++ * makeInstance ++ * @param options ++ * @return {outdent} ++ */ ++function createInstance(options) { ++ /** Cache of pre-processed template literal arrays */ ++ var arrayAutoIndentCache = createWeakMap(); ++ /** ++ * Cache of pre-processed template literal arrays, where first interpolated value is a reference to outdent, ++ * before interpolated values are injected. ++ */ ++ var arrayFirstInterpSetsIndentCache = createWeakMap(); ++ function outdent(stringsOrOptions) { ++ var values = []; ++ for (var _i = 1; _i < arguments.length; _i++) { ++ values[_i - 1] = arguments[_i]; ++ } ++ /* tslint:enable:no-shadowed-variable */ ++ if (isTemplateStringsArray(stringsOrOptions)) { ++ var strings = stringsOrOptions; ++ // Is first interpolated value a reference to outdent, alone on its own line, without any preceding non-whitespace? ++ var firstInterpolatedValueSetsIndentationLevel = (values[0] === outdent || values[0] === defaultOutdent) && ++ reOnlyWhitespaceWithAtLeastOneNewline.test(strings[0]) && ++ reStartsWithNewlineOrIsEmpty.test(strings[1]); ++ // Perform outdentation ++ var cache = firstInterpolatedValueSetsIndentationLevel ++ ? arrayFirstInterpSetsIndentCache ++ : arrayAutoIndentCache; ++ var renderedArray = cache.get(strings); ++ if (!renderedArray) { ++ renderedArray = _outdentArray(strings, firstInterpolatedValueSetsIndentationLevel, options); ++ cache.set(strings, renderedArray); ++ } ++ /** If no interpolated values, skip concatenation step */ ++ if (values.length === 0) { ++ return renderedArray[0]; ++ } ++ /** Concatenate string literals with interpolated values */ ++ var rendered = concatStringsAndValues(renderedArray, firstInterpolatedValueSetsIndentationLevel ? values.slice(1) : values); ++ return rendered; ++ } ++ else { ++ // Create and return a new instance of outdent with the given options ++ return createInstance(extend(extend({}, options), stringsOrOptions || {})); ++ } ++ } ++ var fullOutdent = extend(outdent, { ++ string: function (str) { ++ return _outdentArray([str], false, options)[0]; ++ }, ++ }); ++ return fullOutdent; ++} ++var defaultOutdent = createInstance({ ++ trimLeadingNewline: true, ++ trimTrailingNewline: true, ++}); ++if (typeof module !== "undefined") { ++ // In webpack harmony-modules environments, module.exports is read-only, ++ // so we fail gracefully. ++ try { ++ module.exports = defaultOutdent; ++ Object.defineProperty(defaultOutdent, "__esModule", { value: true }); ++ defaultOutdent.default = defaultOutdent; ++ defaultOutdent.outdent = defaultOutdent; ++ } ++ catch (e) { } ++} ++ ++let enabled = true; ++// Support both browser and node environments ++const globalVar = typeof self !== 'undefined' ++ ? self ++ : typeof window !== 'undefined' ++ ? window ++ : typeof global !== 'undefined' ++ ? global ++ : {}; ++/** ++ * Detect how much colors the current terminal supports ++ */ ++let supportLevel = 0 /* none */; ++if (globalVar.process && globalVar.process.env && globalVar.process.stdout) { ++ const { FORCE_COLOR, NODE_DISABLE_COLORS, NO_COLOR, TERM, COLORTERM } = globalVar.process.env; ++ if (NODE_DISABLE_COLORS || NO_COLOR || FORCE_COLOR === '0') { ++ enabled = false; ++ } ++ else if (FORCE_COLOR === '1' || ++ FORCE_COLOR === '2' || ++ FORCE_COLOR === '3') { ++ enabled = true; ++ } ++ else if (TERM === 'dumb') { ++ enabled = false; ++ } ++ else if ('CI' in globalVar.process.env && ++ [ ++ 'TRAVIS', ++ 'CIRCLECI', ++ 'APPVEYOR', ++ 'GITLAB_CI', ++ 'GITHUB_ACTIONS', ++ 'BUILDKITE', ++ 'DRONE', ++ ].some(vendor => vendor in globalVar.process.env)) { ++ enabled = true; ++ } ++ else { ++ enabled = process.stdout.isTTY; ++ } ++ if (enabled) { ++ // Windows supports 24bit True Colors since Windows 10 revision #14931, ++ // see https://devblogs.microsoft.com/commandline/24-bit-color-in-the-windows-console/ ++ if (process.platform === 'win32') { ++ supportLevel = 3 /* trueColor */; ++ } ++ else { ++ if (COLORTERM && (COLORTERM === 'truecolor' || COLORTERM === '24bit')) { ++ supportLevel = 3 /* trueColor */; ++ } ++ else if (TERM && (TERM.endsWith('-256color') || TERM.endsWith('256'))) { ++ supportLevel = 2 /* ansi256 */; ++ } ++ else { ++ supportLevel = 1 /* ansi */; ++ } ++ } ++ } ++} ++let options = { ++ enabled, ++ supportLevel, ++}; ++function kolorist(start, end, level = 1 /* ansi */) { ++ const open = `\x1b[${start}m`; ++ const close = `\x1b[${end}m`; ++ const regex = new RegExp(`\\x1b\\[${end}m`, 'g'); ++ return (str) => { ++ return options.enabled && options.supportLevel >= level ++ ? open + ('' + str).replace(regex, open) + close ++ : '' + str; ++ }; ++} ++const bold = kolorist(1, 22); ++const dim = kolorist(2, 22); ++const red = kolorist(31, 39); ++const green = kolorist(32, 39); ++const yellow = kolorist(33, 39); ++const magenta = kolorist(35, 39); ++const cyan = kolorist(36, 39); ++ ++const fsExists = (path) => fs$1.access(path).then( ++ () => true, ++ () => false ++); ++ ++var require$1 = ( ++ false ++ ? /* @__PURE__ */ module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('cli.js', document.baseURI).href))) ++ : require ++ ); ++ ++const readJsonFile = async (filePath) => { ++ const jsonString = await fs$1.readFile(filePath, "utf8"); ++ return JSON.parse(jsonString); ++}; ++ ++const configJsonFile = "link.config.json"; ++const configJsFile = "link.config.js"; ++const loadConfig = async (packageDirectory) => { ++ const configJsonPath = path$1.join(packageDirectory, configJsonFile); ++ if (await fsExists(configJsonPath)) { ++ try { ++ return readJsonFile(configJsonPath); ++ } catch (error) { ++ throw new Error(`Failed to parse config JSON ${configJsonPath}: ${error.message}`); ++ } ++ } ++ const configJsPath = path$1.join(packageDirectory, configJsFile); ++ if (await fsExists(configJsPath)) { ++ try { ++ return require$1(configJsPath); ++ } catch (error) { ++ throw new Error(`Failed to load config file ${configJsFile}: ${error.message}`); ++ } ++ } ++}; ++ ++var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; ++ ++function getDefaultExportFromCjs (x) { ++ return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; ++} ++ ++var toBatchSyntax$1 = {}; ++ ++toBatchSyntax$1.replaceDollarWithPercentPair = replaceDollarWithPercentPair; ++toBatchSyntax$1.convertToSetCommand = convertToSetCommand; ++toBatchSyntax$1.convertToSetCommands = convertToSetCommands; ++ ++function convertToSetCommand (key, value) { ++ var line = ''; ++ key = key || ''; ++ key = key.trim(); ++ value = value || ''; ++ value = value.trim(); ++ if (key && value && value.length > 0) { ++ line = '@SET ' + key + '=' + replaceDollarWithPercentPair(value) + '\r\n'; ++ } ++ return line ++} ++ ++function extractVariableValuePairs (declarations) { ++ var pairs = {}; ++ declarations.map(function (declaration) { ++ var split = declaration.split('='); ++ pairs[split[0]] = split[1]; ++ }); ++ return pairs ++} ++ ++function convertToSetCommands (variableString) { ++ var variableValuePairs = extractVariableValuePairs(variableString.split(' ')); ++ var variableDeclarationsAsBatch = ''; ++ Object.keys(variableValuePairs).forEach(function (key) { ++ variableDeclarationsAsBatch += convertToSetCommand(key, variableValuePairs[key]); ++ }); ++ return variableDeclarationsAsBatch ++} ++ ++function replaceDollarWithPercentPair (value) { ++ var dollarExpressions = /\$\{?([^$@#?\- \t{}:]+)\}?/g; ++ var result = ''; ++ var startIndex = 0; ++ do { ++ var match = dollarExpressions.exec(value); ++ if (match) { ++ var betweenMatches = value.substring(startIndex, match.index) || ''; ++ result += betweenMatches + '%' + match[1] + '%'; ++ startIndex = dollarExpressions.lastIndex; ++ } ++ } while (dollarExpressions.lastIndex > 0) ++ result += value.slice(startIndex); ++ return result ++} ++ ++// On windows, create a .cmd file. ++// Read the #! in the file to see what it uses. The vast majority ++// of the time, this will be either: ++// "#!/usr/bin/env " ++// or: ++// "#! " ++// ++// Write a binroot/pkg.bin + ".cmd" file that has this line in it: ++// @ %dp0% %* ++ ++const { ++ chmod, ++ mkdir, ++ readFile: readFile$1, ++ stat, ++ unlink, ++ writeFile, ++} = fs$1; ++ ++const { dirname: dirname$1, relative: relative$1 } = path$1; ++const toBatchSyntax = toBatchSyntax$1; ++// linting disabled because this regex is really long ++// eslint-disable-next-line max-len ++const shebangExpr = /^#!\s*(?:\/usr\/bin\/env\s+(?:-S\s+)?((?:[^ \t=]+=[^ \t=]+\s+)*))?([^ \t]+)(.*)$/; ++ ++const cmdShimIfExists = (from, to) => ++ stat(from).then(() => cmdShim(from, to), () => {}); ++ ++// Try to unlink, but ignore errors. ++// Any problems will surface later. ++const rm = path => unlink(path).catch(() => {}); ++ ++const cmdShim = (from, to) => ++ stat(from).then(() => cmdShim_(from, to)); ++ ++const cmdShim_ = (from, to) => Promise.all([ ++ rm(to), ++ rm(to + '.cmd'), ++ rm(to + '.ps1'), ++]).then(() => writeShim(from, to)); ++ ++const writeShim = (from, to) => ++ // make a cmd file and a sh script ++ // First, check if the bin is a #! of some sort. ++ // If not, then assume it's something that'll be compiled, or some other ++ // sort of script, and just call it directly. ++ mkdir(dirname$1(to), { recursive: true }) ++ .then(() => readFile$1(from, 'utf8')) ++ .then(data => { ++ const firstLine = data.trim().split(/\r*\n/)[0]; ++ const shebang = firstLine.match(shebangExpr); ++ if (!shebang) { ++ return writeShim_(from, to) ++ } ++ const vars = shebang[1] || ''; ++ const prog = shebang[2]; ++ const args = shebang[3] || ''; ++ return writeShim_(from, to, prog, args, vars) ++ }, () => writeShim_(from, to)); ++ ++const writeShim_ = (from, to, prog, args, variables) => { ++ let shTarget = relative$1(dirname$1(to), from); ++ let target = shTarget.split('/').join('\\'); ++ let longProg; ++ let shProg = prog && prog.split('\\').join('/'); ++ let shLongProg; ++ let pwshProg = shProg && `"${shProg}$exe"`; ++ let pwshLongProg; ++ shTarget = shTarget.split('\\').join('/'); ++ args = args || ''; ++ variables = variables || ''; ++ if (!prog) { ++ prog = `"%dp0%\\${target}"`; ++ shProg = `"$basedir/${shTarget}"`; ++ pwshProg = shProg; ++ args = ''; ++ target = ''; ++ shTarget = ''; + } else { +- & ${g} ${D} ${i} $args ++ longProg = `"%dp0%\\${prog}.exe"`; ++ shLongProg = `"$basedir/${prog}"`; ++ pwshLongProg = `"$basedir/${prog}$exe"`; ++ target = `"%dp0%\\${target}"`; ++ shTarget = `"$basedir/${shTarget}"`; ++ } ++ ++ // Subroutine trick to fix https://github.com/npm/cmd-shim/issues/10 ++ // and https://github.com/npm/cli/issues/969 ++ const head = '@ECHO off\r\n' + ++ 'GOTO start\r\n' + ++ ':find_dp0\r\n' + ++ 'SET dp0=%~dp0\r\n' + ++ 'EXIT /b\r\n' + ++ ':start\r\n' + ++ 'SETLOCAL\r\n' + ++ 'CALL :find_dp0\r\n'; ++ ++ let cmd; ++ if (longProg) { ++ shLongProg = shLongProg.trim(); ++ args = args.trim(); ++ const variablesBatch = toBatchSyntax.convertToSetCommands(variables); ++ cmd = head ++ + variablesBatch ++ + '\r\n' ++ + `IF EXIST ${longProg} (\r\n` ++ + ` SET "_prog=${longProg.replace(/(^")|("$)/g, '')}"\r\n` ++ + ') ELSE (\r\n' ++ + ` SET "_prog=${prog.replace(/(^")|("$)/g, '')}"\r\n` ++ + ' SET PATHEXT=%PATHEXT:;.JS;=;%\r\n' ++ + ')\r\n' ++ + '\r\n' ++ // prevent "Terminate Batch Job? (Y/n)" message ++ // https://github.com/npm/cli/issues/969#issuecomment-737496588 ++ + 'endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & ' ++ + `"%_prog%" ${args} ${target} %*\r\n`; ++ } else { ++ cmd = `${head}${prog} ${args} ${target} %*\r\n`; ++ } ++ ++ // #!/bin/sh ++ // basedir=`dirname "$0"` ++ // ++ // case `uname` in ++ // *CYGWIN*|*MINGW*|*MSYS*) ++ // if command -v cygpath > /dev/null 2>&1; then ++ // basedir=`cygpath -w "$basedir"` ++ // fi ++ // ;; ++ // esac ++ // ++ // if [ -x "$basedir/node.exe" ]; then ++ // exec "$basedir/node.exe" "$basedir/node_modules/npm/bin/npm-cli.js" "$@" ++ // else ++ // exec node "$basedir/node_modules/npm/bin/npm-cli.js" "$@" ++ // fi ++ ++ let sh = '#!/bin/sh\n'; ++ ++ sh = sh ++ + `basedir=$(dirname "$(echo "$0" | sed -e 's,\\\\,/,g')")\n` ++ + '\n' ++ + 'case `uname` in\n' ++ + ' *CYGWIN*|*MINGW*|*MSYS*)\n' ++ + ' if command -v cygpath > /dev/null 2>&1; then\n' ++ + ' basedir=`cygpath -w "$basedir"`\n' ++ + ' fi\n' ++ + ' ;;\n' ++ + 'esac\n' ++ + '\n'; ++ ++ if (shLongProg) { ++ sh = sh ++ + `if [ -x ${shLongProg} ]; then\n` ++ + ` exec ${variables}${shLongProg} ${args} ${shTarget} "$@"\n` ++ + 'else \n' ++ + ` exec ${variables}${shProg} ${args} ${shTarget} "$@"\n` ++ + 'fi\n'; ++ } else { ++ sh = sh ++ + `exec ${shProg} ${args} ${shTarget} "$@"\n`; ++ } ++ ++ // #!/usr/bin/env pwsh ++ // $basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent ++ // ++ // $ret=0 ++ // $exe = "" ++ // if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) { ++ // # Fix case when both the Windows and Linux builds of Node ++ // # are installed in the same directory ++ // $exe = ".exe" ++ // } ++ // if (Test-Path "$basedir/node") { ++ // # Suport pipeline input ++ // if ($MyInvocation.ExpectingInput) { ++ // input | & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args ++ // } else { ++ // & "$basedir/node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args ++ // } ++ // $ret=$LASTEXITCODE ++ // } else { ++ // # Support pipeline input ++ // if ($MyInvocation.ExpectingInput) { ++ // $input | & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args ++ // } else { ++ // & "node$exe" "$basedir/node_modules/npm/bin/npm-cli.js" $args ++ // } ++ // $ret=$LASTEXITCODE ++ // } ++ // exit $ret ++ let pwsh = '#!/usr/bin/env pwsh\n' ++ + '$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent\n' ++ + '\n' ++ + '$exe=""\n' ++ + 'if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {\n' ++ + ' # Fix case when both the Windows and Linux builds of Node\n' ++ + ' # are installed in the same directory\n' ++ + ' $exe=".exe"\n' ++ + '}\n'; ++ if (shLongProg) { ++ pwsh = pwsh ++ + '$ret=0\n' ++ + `if (Test-Path ${pwshLongProg}) {\n` ++ + ' # Support pipeline input\n' ++ + ' if ($MyInvocation.ExpectingInput) {\n' ++ + ` $input | & ${pwshLongProg} ${args} ${shTarget} $args\n` ++ + ' } else {\n' ++ + ` & ${pwshLongProg} ${args} ${shTarget} $args\n` ++ + ' }\n' ++ + ' $ret=$LASTEXITCODE\n' ++ + '} else {\n' ++ + ' # Support pipeline input\n' ++ + ' if ($MyInvocation.ExpectingInput) {\n' ++ + ` $input | & ${pwshProg} ${args} ${shTarget} $args\n` ++ + ' } else {\n' ++ + ` & ${pwshProg} ${args} ${shTarget} $args\n` ++ + ' }\n' ++ + ' $ret=$LASTEXITCODE\n' ++ + '}\n' ++ + 'exit $ret\n'; ++ } else { ++ pwsh = pwsh ++ + '# Support pipeline input\n' ++ + 'if ($MyInvocation.ExpectingInput) {\n' ++ + ` $input | & ${pwshProg} ${args} ${shTarget} $args\n` ++ + '} else {\n' ++ + ` & ${pwshProg} ${args} ${shTarget} $args\n` ++ + '}\n' ++ + 'exit $LASTEXITCODE\n'; ++ } ++ ++ return Promise.all([ ++ writeFile(to + '.ps1', pwsh, 'utf8'), ++ writeFile(to + '.cmd', cmd, 'utf8'), ++ writeFile(to, sh, 'utf8'), ++ ]).then(() => chmodShim(to)) ++}; ++ ++const chmodShim = to => Promise.all([ ++ chmod(to, 0o755), ++ chmod(to + '.cmd', 0o755), ++ chmod(to + '.ps1', 0o755), ++]); ++ ++var lib$2 = cmdShim; ++cmdShim.ifExists = cmdShimIfExists; ++ ++var cmdShim$1 = /*@__PURE__*/getDefaultExportFromCjs(lib$2); ++ ++const readPackageJson = async (packagePath) => { ++ const packageJsonPath = path$1.join(packagePath, "package.json"); ++ const packageJsonExists = await fsExists(packageJsonPath); ++ if (!packageJsonExists) { ++ throw new Error(`package.json not found in ${packagePath}`); ++ } ++ const packageJson = await readJsonFile(packageJsonPath); ++ if (!packageJson.name) { ++ throw new Error(`package.json must contain a name: ${packageJsonPath}`); ++ } ++ return packageJson; ++}; ++ ++const symlink = async (targetPath, symlinkPath, type) => { ++ const stats = await fs$1.lstat(symlinkPath).catch(() => null); ++ if (stats) { ++ if (stats.isSymbolicLink()) { ++ const symlinkRealpath = await fs$1.realpath(symlinkPath).catch(() => null); ++ if (targetPath === symlinkRealpath) { ++ return; ++ } ++ } ++ await fs$1.rm(symlinkPath, { ++ recursive: true ++ }); ++ } ++ await fs$1.symlink( ++ targetPath, ++ symlinkPath, ++ type ++ ); ++}; ++const symlinkBinary = async (binaryPath, linkPath) => { ++ await symlink(binaryPath, linkPath); ++ await fs$1.chmod(linkPath, 493); ++}; ++const hardlink = async (sourcePath, hardlinkPath) => { ++ if (await fsExists(hardlinkPath)) { ++ const [ ++ existingStat, ++ sourceStat ++ ] = await Promise.all([ ++ fs$1.stat(hardlinkPath), ++ fs$1.stat(sourcePath) ++ ]); ++ if (existingStat.ino === sourceStat.ino) { ++ return; ++ } ++ await fs$1.rm(hardlinkPath, { ++ recursive: true ++ }); ++ } ++ await fs$1.link(sourcePath, hardlinkPath); ++}; ++ ++const linkBinaries = async (linkPackagePath, nodeModulesPath, { ++ name, ++ bin ++}, linkFunction) => { ++ if (!bin) { ++ return []; ++ } ++ if (name?.startsWith("@")) { ++ [, name] = name.split("/"); ++ } ++ const binDirectoryPath = path$1.join(nodeModulesPath, ".bin"); ++ await fs$1.mkdir(binDirectoryPath, { ++ recursive: true ++ }); ++ if (typeof bin === "string") { ++ await linkFunction( ++ path$1.resolve(linkPackagePath, bin), ++ path$1.join(binDirectoryPath, name) ++ ); ++ return; ++ } ++ await Promise.all( ++ Object.entries(bin).map( ++ async ([binaryName, binaryPath]) => await linkFunction( ++ path$1.resolve(linkPackagePath, binaryPath), ++ path$1.join(binDirectoryPath, binaryName) ++ ) ++ ) ++ ); ++}; ++ ++const nodeModulesDirectory = "node_modules"; ++const symlinkPackage = async (basePackagePath, linkPackagePath) => { ++ const absoluteLinkPackagePath = path$1.resolve(basePackagePath, linkPackagePath); ++ const packageJson = await readPackageJson(absoluteLinkPackagePath); ++ const nodeModulesPath = path$1.join(basePackagePath, nodeModulesDirectory); ++ const symlinkPath = path$1.join(nodeModulesPath, packageJson.name); ++ const symlinkDirectory = path$1.dirname(symlinkPath); ++ await fs$1.mkdir(symlinkDirectory, { ++ recursive: true ++ }); ++ const targetPath = path$1.relative(symlinkDirectory, absoluteLinkPackagePath); ++ await symlink( ++ targetPath, ++ symlinkPath, ++ /** ++ * On Windows, 'dir' requires admin privileges so use 'junction' instead ++ * ++ * npm also uses junction: ++ * https://github.com/npm/cli/blob/v9.9.3/workspaces/arborist/lib/arborist/reify.js#L738 ++ */ ++ "junction" ++ ); ++ await linkBinaries( ++ absoluteLinkPackagePath, ++ nodeModulesPath, ++ packageJson, ++ process.platform === "win32" ? cmdShim$1 : symlinkBinary ++ ); ++ return { ++ name: packageJson.name, ++ path: symlinkPath, ++ target: targetPath ++ }; ++}; ++ ++const linkPackage = async (basePackagePath, linkPackagePath, options) => { ++ const absoluteLinkPackagePath = path$1.resolve(basePackagePath, linkPackagePath); ++ const pathExists = await fsExists(absoluteLinkPackagePath); ++ if (!pathExists) { ++ console.warn(red("\u2716"), `Package path does not exist: ${linkPackagePath}`); ++ process.exitCode = 1; ++ return; ++ } ++ try { ++ const link = await symlinkPackage( ++ basePackagePath, ++ linkPackagePath ++ ); ++ console.log(green("\u2714"), `Symlinked ${magenta(link.name)}:`, cyan(link.path), "\u2192", cyan(link.target)); ++ } catch (error) { ++ console.warn(red("\u2716"), "Failed to symlink", cyan(linkPackagePath), "with error:", error.message); ++ process.exitCode = 1; ++ return; ++ } ++ if (options.deep) { ++ const config = await loadConfig(absoluteLinkPackagePath); ++ if (config) { ++ await linkFromConfig( ++ absoluteLinkPackagePath, ++ config, ++ options ++ ); ++ } ++ } ++}; ++const linkFromConfig = async (basePackagePath, config, options) => { ++ if (!config.packages) { ++ return; ++ } ++ const newOptions = { ++ deep: options.deep ?? config.deepLink ?? false ++ }; ++ await Promise.all( ++ config.packages.map( ++ async (linkPackagePath) => await linkPackage( ++ basePackagePath, ++ linkPackagePath, ++ newOptions ++ ) ++ ) ++ ); ++}; ++ ++var debounce$2 = {exports: {}}; ++ ++function debounce(function_, wait = 100, options = {}) { ++ if (typeof function_ !== 'function') { ++ throw new TypeError(`Expected the first parameter to be a function, got \`${typeof function_}\`.`); ++ } ++ ++ if (wait < 0) { ++ throw new RangeError('`wait` must not be negative.'); ++ } ++ ++ // TODO: Deprecate the boolean parameter at some point. ++ const {immediate} = typeof options === 'boolean' ? {immediate: options} : options; ++ ++ let storedContext; ++ let storedArguments; ++ let timeoutId; ++ let timestamp; ++ let result; ++ ++ function run() { ++ const callContext = storedContext; ++ const callArguments = storedArguments; ++ storedContext = undefined; ++ storedArguments = undefined; ++ result = function_.apply(callContext, callArguments); ++ return result; ++ } ++ ++ function later() { ++ const last = Date.now() - timestamp; ++ ++ if (last < wait && last >= 0) { ++ timeoutId = setTimeout(later, wait - last); ++ } else { ++ timeoutId = undefined; ++ ++ if (!immediate) { ++ result = run(); ++ } ++ } ++ } ++ ++ const debounced = function (...arguments_) { ++ if (storedContext && this !== storedContext) { ++ throw new Error('Debounced method called with different contexts.'); ++ } ++ ++ storedContext = this; // eslint-disable-line unicorn/no-this-assignment ++ storedArguments = arguments_; ++ timestamp = Date.now(); ++ ++ const callNow = immediate && !timeoutId; ++ ++ if (!timeoutId) { ++ timeoutId = setTimeout(later, wait); ++ } ++ ++ if (callNow) { ++ result = run(); ++ } ++ ++ return result; ++ }; ++ ++ debounced.clear = () => { ++ if (!timeoutId) { ++ return; ++ } ++ ++ clearTimeout(timeoutId); ++ timeoutId = undefined; ++ }; ++ ++ debounced.flush = () => { ++ if (!timeoutId) { ++ return; ++ } ++ ++ debounced.trigger(); ++ }; ++ ++ debounced.trigger = () => { ++ result = run(); ++ ++ debounced.clear(); ++ }; ++ ++ return debounced; ++} ++ ++// Adds compatibility for ES modules ++debounce$2.exports.debounce = debounce; ++ ++debounce$2.exports = debounce; ++ ++var debounceExports = debounce$2.exports; ++var debounce$1 = /*@__PURE__*/getDefaultExportFromCjs(debounceExports); ++ ++const pDebounce = (fn, wait, options = {}) => { ++ if (!Number.isFinite(wait)) { ++ throw new TypeError('Expected `wait` to be a finite number'); ++ } ++ ++ let leadingValue; ++ let timeout; ++ let resolveList = []; ++ ++ return function (...arguments_) { ++ return new Promise(resolve => { ++ const shouldCallNow = options.before && !timeout; ++ ++ clearTimeout(timeout); ++ ++ timeout = setTimeout(() => { ++ timeout = null; ++ ++ const result = options.before ? leadingValue : fn.apply(this, arguments_); ++ ++ for (resolve of resolveList) { ++ resolve(result); ++ } ++ ++ resolveList = []; ++ }, wait); ++ ++ if (shouldCallNow) { ++ leadingValue = fn.apply(this, arguments_); ++ resolve(leadingValue); ++ } else { ++ resolveList.push(resolve); ++ } ++ }); ++ }; ++}; ++ ++pDebounce.promise = function_ => { ++ let currentPromise; ++ ++ return async function (...arguments_) { ++ if (currentPromise) { ++ return currentPromise; ++ } ++ ++ try { ++ currentPromise = function_.apply(this, arguments_); ++ return await currentPromise; ++ } finally { ++ currentPromise = undefined; ++ } ++ }; ++}; ++ ++var globToRegexp = function (glob, opts) { ++ if (typeof glob !== 'string') { ++ throw new TypeError('Expected a string'); ++ } ++ ++ var str = String(glob); ++ ++ // The regexp we are building, as a string. ++ var reStr = ""; ++ ++ // Whether we are matching so called "extended" globs (like bash) and should ++ // support single character matching, matching ranges of characters, group ++ // matching, etc. ++ var extended = opts ? !!opts.extended : false; ++ ++ // When globstar is _false_ (default), '/foo/*' is translated a regexp like ++ // '^\/foo\/.*$' which will match any string beginning with '/foo/' ++ // When globstar is _true_, '/foo/*' is translated to regexp like ++ // '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT ++ // which does not have a '/' to the right of it. ++ // E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but ++ // these will not '/foo/bar/baz', '/foo/bar/baz.txt' ++ // Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when ++ // globstar is _false_ ++ var globstar = opts ? !!opts.globstar : false; ++ ++ // If we are doing extended matching, this boolean is true when we are inside ++ // a group (eg {*.html,*.js}), and false otherwise. ++ var inGroup = false; ++ ++ // RegExp flags (eg "i" ) to pass in to RegExp constructor. ++ var flags = opts && typeof( opts.flags ) === "string" ? opts.flags : ""; ++ ++ var c; ++ for (var i = 0, len = str.length; i < len; i++) { ++ c = str[i]; ++ ++ switch (c) { ++ case "/": ++ case "$": ++ case "^": ++ case "+": ++ case ".": ++ case "(": ++ case ")": ++ case "=": ++ case "!": ++ case "|": ++ reStr += "\\" + c; ++ break; ++ ++ case "?": ++ if (extended) { ++ reStr += "."; ++ break; ++ } ++ ++ case "[": ++ case "]": ++ if (extended) { ++ reStr += c; ++ break; ++ } ++ ++ case "{": ++ if (extended) { ++ inGroup = true; ++ reStr += "("; ++ break; ++ } ++ ++ case "}": ++ if (extended) { ++ inGroup = false; ++ reStr += ")"; ++ break; ++ } ++ ++ case ",": ++ if (inGroup) { ++ reStr += "|"; ++ break; ++ } ++ reStr += "\\" + c; ++ break; ++ ++ case "*": ++ // Move over all consecutive "*"'s. ++ // Also store the previous and next characters ++ var prevChar = str[i - 1]; ++ var starCount = 1; ++ while(str[i + 1] === "*") { ++ starCount++; ++ i++; ++ } ++ var nextChar = str[i + 1]; ++ ++ if (!globstar) { ++ // globstar is disabled, so treat any number of "*" as one ++ reStr += ".*"; ++ } else { ++ // globstar is enabled, so determine if this is a globstar segment ++ var isGlobstar = starCount > 1 // multiple "*"'s ++ && (prevChar === "/" || prevChar === undefined) // from the start of the segment ++ && (nextChar === "/" || nextChar === undefined); // to the end of the segment ++ ++ if (isGlobstar) { ++ // it's a globstar, so match zero or more path segments ++ reStr += "((?:[^/]*(?:\/|$))*)"; ++ i++; // move over the "/" ++ } else { ++ // it's not a globstar, so only match one path segment ++ reStr += "([^/]*)"; ++ } ++ } ++ break; ++ ++ default: ++ reStr += c; ++ } + } +- $ret=$LASTEXITCODE +-} else { +- # Support pipeline input +- if ($MyInvocation.ExpectingInput) { +- $input | & ${f} ${D} ${i} $args ++ ++ // When regexp 'g' flag is specified don't ++ // constrain the regular expression with ^ & $ ++ if (!flags || !~flags.indexOf('g')) { ++ reStr = "^" + reStr + "$"; ++ } ++ ++ return new RegExp(reStr, flags); ++}; ++ ++var globToRegexp$1 = /*@__PURE__*/getDefaultExportFromCjs(globToRegexp); ++ ++var commonjs = {}; ++ ++var balancedMatch = balanced$1; ++function balanced$1(a, b, str) { ++ if (a instanceof RegExp) a = maybeMatch(a, str); ++ if (b instanceof RegExp) b = maybeMatch(b, str); ++ ++ var r = range(a, b, str); ++ ++ return r && { ++ start: r[0], ++ end: r[1], ++ pre: str.slice(0, r[0]), ++ body: str.slice(r[0] + a.length, r[1]), ++ post: str.slice(r[1] + b.length) ++ }; ++} ++ ++function maybeMatch(reg, str) { ++ var m = str.match(reg); ++ return m ? m[0] : null; ++} ++ ++balanced$1.range = range; ++function range(a, b, str) { ++ var begs, beg, left, right, result; ++ var ai = str.indexOf(a); ++ var bi = str.indexOf(b, ai + 1); ++ var i = ai; ++ ++ if (ai >= 0 && bi > 0) { ++ if(a===b) { ++ return [ai, bi]; ++ } ++ begs = []; ++ left = str.length; ++ ++ while (i >= 0 && !result) { ++ if (i == ai) { ++ begs.push(i); ++ ai = str.indexOf(a, i + 1); ++ } else if (begs.length == 1) { ++ result = [ begs.pop(), bi ]; ++ } else { ++ beg = begs.pop(); ++ if (beg < left) { ++ left = beg; ++ right = bi; ++ } ++ ++ bi = str.indexOf(b, i + 1); ++ } ++ ++ i = ai < bi && ai >= 0 ? ai : bi; ++ } ++ ++ if (begs.length) { ++ result = [ left, right ]; ++ } ++ } ++ ++ return result; ++} ++ ++var balanced = balancedMatch; ++ ++var braceExpansion = expandTop; ++ ++var escSlash = '\0SLASH'+Math.random()+'\0'; ++var escOpen = '\0OPEN'+Math.random()+'\0'; ++var escClose = '\0CLOSE'+Math.random()+'\0'; ++var escComma = '\0COMMA'+Math.random()+'\0'; ++var escPeriod = '\0PERIOD'+Math.random()+'\0'; ++ ++function numeric(str) { ++ return parseInt(str, 10) == str ++ ? parseInt(str, 10) ++ : str.charCodeAt(0); ++} ++ ++function escapeBraces(str) { ++ return str.split('\\\\').join(escSlash) ++ .split('\\{').join(escOpen) ++ .split('\\}').join(escClose) ++ .split('\\,').join(escComma) ++ .split('\\.').join(escPeriod); ++} ++ ++function unescapeBraces(str) { ++ return str.split(escSlash).join('\\') ++ .split(escOpen).join('{') ++ .split(escClose).join('}') ++ .split(escComma).join(',') ++ .split(escPeriod).join('.'); ++} ++ ++ ++// Basically just str.split(","), but handling cases ++// where we have nested braced sections, which should be ++// treated as individual members, like {a,{b,c},d} ++function parseCommaParts(str) { ++ if (!str) ++ return ['']; ++ ++ var parts = []; ++ var m = balanced('{', '}', str); ++ ++ if (!m) ++ return str.split(','); ++ ++ var pre = m.pre; ++ var body = m.body; ++ var post = m.post; ++ var p = pre.split(','); ++ ++ p[p.length-1] += '{' + body + '}'; ++ var postParts = parseCommaParts(post); ++ if (post.length) { ++ p[p.length-1] += postParts.shift(); ++ p.push.apply(p, postParts); ++ } ++ ++ parts.push.apply(parts, p); ++ ++ return parts; ++} ++ ++function expandTop(str) { ++ if (!str) ++ return []; ++ ++ // I don't know why Bash 4.3 does this, but it does. ++ // Anything starting with {} will have the first two bytes preserved ++ // but *only* at the top level, so {},a}b will not expand to anything, ++ // but a{},b}c will be expanded to [a}c,abc]. ++ // One could argue that this is a bug in Bash, but since the goal of ++ // this module is to match Bash's rules, we escape a leading {} ++ if (str.substr(0, 2) === '{}') { ++ str = '\\{\\}' + str.substr(2); ++ } ++ ++ return expand(escapeBraces(str), true).map(unescapeBraces); ++} ++ ++function embrace(str) { ++ return '{' + str + '}'; ++} ++function isPadded(el) { ++ return /^-?0\d/.test(el); ++} ++ ++function lte(i, y) { ++ return i <= y; ++} ++function gte(i, y) { ++ return i >= y; ++} ++ ++function expand(str, isTop) { ++ var expansions = []; ++ ++ var m = balanced('{', '}', str); ++ if (!m) return [str]; ++ ++ // no need to expand pre, since it is guaranteed to be free of brace-sets ++ var pre = m.pre; ++ var post = m.post.length ++ ? expand(m.post, false) ++ : ['']; ++ ++ if (/\$$/.test(m.pre)) { ++ for (var k = 0; k < post.length; k++) { ++ var expansion = pre+ '{' + m.body + '}' + post[k]; ++ expansions.push(expansion); ++ } + } else { +- & ${f} ${D} ${i} $args +- } +- $ret=$LASTEXITCODE +-} +-exit $ret +-`:w=w+`# Support pipeline input +-if ($MyInvocation.ExpectingInput) { +- $input | & ${f} ${D} ${i} $args +-} else { +- & ${f} ${D} ${i} $args +-} +-exit $LASTEXITCODE +-`,Promise.all([Ue(e+".ps1",w,"utf8"),Ue(e+".cmd",d,"utf8"),Ue(e,m,"utf8")]).then(()=>Bn(e))},Bn=u=>Promise.all([Ve(u,493),Ve(u+".cmd",493),Ve(u+".ps1",493)]);var wn=He;He.ifExists=En;var yn=Nu(wn);const qu=async u=>{const e=A.join(u,"package.json");if(!await oe(e))throw new Error(`package.json not found in ${u}`);const D=await Ru(e);if(!D.name)throw new Error(`package.json must contain a name: ${e}`);return D},zu=async(u,e,t)=>{const D=await O.lstat(e).catch(()=>null);if(D){if(D.isSymbolicLink()){const n=await O.realpath(e).catch(()=>null);if(u===n)return}await O.rm(e,{recursive:!0})}await O.symlink(u,e,t)},$n=async(u,e)=>{await zu(u,e),await O.chmod(e,493)},An=async(u,e)=>{await oe(e)&&await O.rm(e,{recursive:!0}),await O.link(u,e)},bn=async(u,e,{name:t,bin:D},n)=>{if(!D)return[];t?.startsWith("@")&&([,t]=t.split("/"));const i=A.join(e,".bin");if(await O.mkdir(i,{recursive:!0}),typeof D=="string"){await n(A.resolve(u,D),A.join(i,t));return}await Promise.all(Object.entries(D).map(async([s,o])=>await n(A.resolve(u,o),A.join(i,s))))},vn="node_modules",Sn=async(u,e)=>{const t=A.resolve(u,e),D=await qu(t),n=A.join(u,vn),i=A.join(n,D.name),s=A.dirname(i);await O.mkdir(s,{recursive:!0});const o=A.relative(s,t);return await zu(o,i,"junction"),await bn(t,n,D,process.platform==="win32"?yn:$n),{name:D.name,path:i,target:o}},Ju=async(u,e,t)=>{const D=A.resolve(u,e);if(!await oe(D)){console.warn(xu("\u2716"),`Package path does not exist: ${e}`),process.exitCode=1;return}try{const i=await Sn(u,e);console.log(Tu("\u2714"),`Symlinked ${Je(i.name)}:`,re(i.path),"\u2192",re(i.target))}catch(i){console.warn(xu("\u2716"),"Failed to symlink",re(e),"with error:",i.message),process.exitCode=1;return}if(t.deep){const i=await Lu(D);i&&await Vu(D,i,t)}},Vu=async(u,e,t)=>{if(!e.packages)return;const D={deep:t.deep??e.deepLink??!1};await Promise.all(e.packages.map(async n=>await Ju(u,n,D)))};var Uu={},On=Zu;function Zu(u,e,t){u instanceof RegExp&&(u=Hu(u,t)),e instanceof RegExp&&(e=Hu(e,t));var D=Xu(u,e,t);return D&&{start:D[0],end:D[1],pre:t.slice(0,D[0]),body:t.slice(D[0]+u.length,D[1]),post:t.slice(D[1]+e.length)}}function Hu(u,e){var t=e.match(u);return t?t[0]:null}Zu.range=Xu;function Xu(u,e,t){var D,n,i,s,o,a=t.indexOf(u),h=t.indexOf(e,a+1),f=a;if(a>=0&&h>0){if(u===e)return[a,h];for(D=[],i=t.length;f>=0&&!o;)f==a?(D.push(f),a=t.indexOf(u,f+1)):D.length==1?o=[D.pop(),h]:(n=D.pop(),n=0?a:h;D.length&&(o=[i,s])}return o}var Yu=On,kn=Tn,Ku="\0SLASH"+Math.random()+"\0",Qu="\0OPEN"+Math.random()+"\0",Ye="\0CLOSE"+Math.random()+"\0",et="\0COMMA"+Math.random()+"\0",ut="\0PERIOD"+Math.random()+"\0";function Ke(u){return parseInt(u,10)==u?parseInt(u,10):u.charCodeAt(0)}function jn(u){return u.split("\\\\").join(Ku).split("\\{").join(Qu).split("\\}").join(Ye).split("\\,").join(et).split("\\.").join(ut)}function xn(u){return u.split(Ku).join("\\").split(Qu).join("{").split(Ye).join("}").split(et).join(",").split(ut).join(".")}function tt(u){if(!u)return[""];var e=[],t=Yu("{","}",u);if(!t)return u.split(",");var D=t.pre,n=t.body,i=t.post,s=D.split(",");s[s.length-1]+="{"+n+"}";var o=tt(i);return i.length&&(s[s.length-1]+=o.shift(),s.push.apply(s,o)),e.push.apply(e,s),e}function Tn(u){return u?(u.substr(0,2)==="{}"&&(u="\\{\\}"+u.substr(2)),ae(jn(u),!0).map(xn)):[]}function Rn(u){return"{"+u+"}"}function Pn(u){return/^-?0\d/.test(u)}function Ln(u,e){return u<=e}function Mn(u,e){return u>=e}function ae(u,e){var t=[],D=Yu("{","}",u);if(!D)return[u];var n=D.pre,i=D.post.length?ae(D.post,!1):[""];if(/\$$/.test(D.pre))for(var s=0;s=0;if(!f&&!g)return D.post.match(/,.*\}/)?(u=D.pre+"{"+D.body+Ye+D.post,ae(u)):[u];var F;if(f)F=D.body.split(/\.\./);else if(F=tt(D.body),F.length===1&&(F=ae(F[0],!1).map(Rn),F.length===1))return i.map(function(Ee){return D.pre+F[0]+Ee});var d;if(f){var m=Ke(F[0]),w=Ke(F[1]),S=Math.max(F[0].length,F[1].length),R=F.length==3?Math.abs(Ke(F[2])):1,M=Ln,G=w0){var Ce=new Array(de+1).join("0");J<0?P="-"+Ce+P.slice(1):P=Ce+P}}d.push(P)}}else{d=[];for(var _=0;_{if(typeof u!="string")throw new TypeError("invalid pattern");if(u.length>Nn)throw new TypeError("pattern is too long")};Ae.assertValidPattern=In;var le={},be={};Object.defineProperty(be,"__esModule",{value:!0}),be.parseClass=void 0;const _n={"[:alnum:]":["\\p{L}\\p{Nl}\\p{Nd}",!0],"[:alpha:]":["\\p{L}\\p{Nl}",!0],"[:ascii:]":["\\x00-\\x7f",!1],"[:blank:]":["\\p{Zs}\\t",!0],"[:cntrl:]":["\\p{Cc}",!0],"[:digit:]":["\\p{Nd}",!0],"[:graph:]":["\\p{Z}\\p{C}",!0,!0],"[:lower:]":["\\p{Ll}",!0],"[:print:]":["\\p{C}",!0],"[:punct:]":["\\p{P}",!0],"[:space:]":["\\p{Z}\\t\\r\\n\\v\\f",!0],"[:upper:]":["\\p{Lu}",!0],"[:word:]":["\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}",!0],"[:xdigit:]":["A-Fa-f0-9",!1]},ce=u=>u.replace(/[[\]\\-]/g,"\\$&"),Wn=u=>u.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),Dt=u=>u.join(""),Gn=(u,e)=>{const t=e;if(u.charAt(t)!=="[")throw new Error("not in a brace expression");const D=[],n=[];let i=t+1,s=!1,o=!1,a=!1,h=!1,f=t,g="";e:for(;ig?D.push(ce(g)+"-"+ce(w)):w===g&&D.push(ce(w)),g="",i++;continue}if(u.startsWith("-]",i+1)){D.push(ce(w+"-")),i+=2;continue}if(u.startsWith("-",i+1)){g=w,i+=2;continue}D.push(ce(w)),i++}if(fe?u.replace(/\[([^\/\\])\]/g,"$1"):u.replace(/((?!\\).|^)\[([^\/\\])\]/g,"$1$2").replace(/\\([^\/])/g,"$1");Y.unescape=qn,Object.defineProperty(le,"__esModule",{value:!0}),le.AST=void 0;const zn=be,ve=Y,Jn=new Set(["!","?","+","*","@"]),nt=u=>Jn.has(u),Vn="(?!(?:^|/)\\.\\.?(?:$|/))",Se="(?!\\.)",Un=new Set(["[","."]),Zn=new Set(["..","."]),Hn=new Set("().*{}+?[]^$\\!"),Xn=u=>u.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&"),Qe="[^/]",it=Qe+"*?",st=Qe+"+?";class T{type;#t;#D;#n=!1;#e=[];#u;#s;#o;#r=!1;#i;#a;#c=!1;constructor(e,t,D={}){this.type=e,e&&(this.#D=!0),this.#u=t,this.#t=this.#u?this.#u.#t:this,this.#i=this.#t===this?D:this.#t.#i,this.#o=this.#t===this?[]:this.#t.#o,e==="!"&&!this.#t.#r&&this.#o.push(this),this.#s=this.#u?this.#u.#e.length:0}get hasMagic(){if(this.#D!==void 0)return this.#D;for(const e of this.#e)if(typeof e!="string"&&(e.type||e.hasMagic))return this.#D=!0;return this.#D}toString(){return this.#a!==void 0?this.#a:this.type?this.#a=this.type+"("+this.#e.map(e=>String(e)).join("|")+")":this.#a=this.#e.map(e=>String(e)).join("")}#p(){if(this!==this.#t)throw new Error("should only call on root");if(this.#r)return this;this.toString(),this.#r=!0;let e;for(;e=this.#o.pop();){if(e.type!=="!")continue;let t=e,D=t.#u;for(;D;){for(let n=t.#s+1;!D.type&&ntypeof t=="string"?t:t.toJSON()):[this.type,...this.#e.map(t=>t.toJSON())];return this.isStart()&&!this.type&&e.unshift([]),this.isEnd()&&(this===this.#t||this.#t.#r&&this.#u?.type==="!")&&e.push({}),e}isStart(){if(this.#t===this)return!0;if(!this.#u?.isStart())return!1;if(this.#s===0)return!0;const e=this.#u;for(let t=0;t{const[m,w,S,R]=typeof d=="string"?T.#f(d,this.#D,a):d.toRegExpSource(e);return this.#D=this.#D||S,this.#n=this.#n||R,m}).join("");let f="";if(this.isStart()&&typeof this.#e[0]=="string"&&!(this.#e.length===1&&Zn.has(this.#e[0]))){const m=Un,w=t&&m.has(h.charAt(0))||h.startsWith("\\.")&&m.has(h.charAt(2))||h.startsWith("\\.\\.")&&m.has(h.charAt(4)),S=!t&&!e&&m.has(h.charAt(0));f=w?Vn:S?Se:""}let g="";return this.isEnd()&&this.#t.#r&&this.#u?.type==="!"&&(g="(?:$|\\/)"),[f+h+g,(0,ve.unescape)(h),this.#D=!!this.#D,this.#n]}const D=this.type==="*"||this.type==="+",n=this.type==="!"?"(?:(?!(?:":"(?:";let i=this.#h(t);if(this.isStart()&&this.isEnd()&&!i&&this.type!=="!"){const a=this.toString();return this.#e=[a],this.type=null,this.#D=void 0,[a,(0,ve.unescape)(this.toString()),!1,!1]}let s=!D||e||t||!Se?"":this.#h(!0);s===i&&(s=""),s&&(i=`(?:${i})(?:${s})*?`);let o="";if(this.type==="!"&&this.#c)o=(this.isStart()&&!t?Se:"")+st;else{const a=this.type==="!"?"))"+(this.isStart()&&!t&&!e?Se:"")+it+")":this.type==="@"?")":this.type==="?"?")?":this.type==="+"&&s?")":this.type==="*"&&s?")?":`)${this.type}`;o=n+i+a}return[o,(0,ve.unescape)(i),this.#D=!!this.#D,this.#n]}#h(e){return this.#e.map(t=>{if(typeof t=="string")throw new Error("string type in extglob ast??");const[D,n,i,s]=t.toRegExpSource(e);return this.#n=this.#n||s,D}).filter(t=>!(this.isStart()&&this.isEnd())||!!t).join("|")}static#f(e,t,D=!1){let n=!1,i="",s=!1;for(let o=0;oe?u.replace(/[?*()[\]]/g,"[$&]"):u.replace(/[?*()[\]\\]/g,"\\$&");he.escape=Yn,function(u){var e=Mu&&Mu.__importDefault||function(C){return C&&C.__esModule?C:{default:C}};Object.defineProperty(u,"__esModule",{value:!0}),u.unescape=u.escape=u.AST=u.Minimatch=u.match=u.makeRe=u.braceExpand=u.defaults=u.filter=u.GLOBSTAR=u.sep=u.minimatch=void 0;const t=e(kn),D=Ae,n=le,i=he,s=Y,o=(C,r,l={})=>((0,D.assertValidPattern)(r),!l.nocomment&&r.charAt(0)==="#"?!1:new ue(r,l).match(C));u.minimatch=o;const a=/^\*+([^+@!?\*\[\(]*)$/,h=C=>r=>!r.startsWith(".")&&r.endsWith(C),f=C=>r=>r.endsWith(C),g=C=>(C=C.toLowerCase(),r=>!r.startsWith(".")&&r.toLowerCase().endsWith(C)),F=C=>(C=C.toLowerCase(),r=>r.toLowerCase().endsWith(C)),d=/^\*+\.\*+$/,m=C=>!C.startsWith(".")&&C.includes("."),w=C=>C!=="."&&C!==".."&&C.includes("."),S=/^\.\*+$/,R=C=>C!=="."&&C!==".."&&C.startsWith("."),M=/^\*+$/,G=C=>C.length!==0&&!C.startsWith("."),Te=C=>C.length!==0&&C!=="."&&C!=="..",J=/^\?+([^+@!?\*\[\(]*)?$/,P=([C,r=""])=>{const l=Ee([C]);return r?(r=r.toLowerCase(),c=>l(c)&&c.toLowerCase().endsWith(r)):l},de=([C,r=""])=>{const l=uu([C]);return r?(r=r.toLowerCase(),c=>l(c)&&c.toLowerCase().endsWith(r)):l},Ce=([C,r=""])=>{const l=uu([C]);return r?c=>l(c)&&c.endsWith(r):l},_=([C,r=""])=>{const l=Ee([C]);return r?c=>l(c)&&c.endsWith(r):l},Ee=([C])=>{const r=C.length;return l=>l.length===r&&!l.startsWith(".")},uu=([C])=>{const r=C.length;return l=>l.length===r&&l!=="."&&l!==".."},tu=typeof process=="object"&&process?typeof process.env=="object"&&process.env&&process.env.__MINIMATCH_TESTING_PLATFORM__||process.platform:"posix",Du={win32:{sep:"\\"},posix:{sep:"/"}};u.sep=tu==="win32"?Du.win32.sep:Du.posix.sep,u.minimatch.sep=u.sep,u.GLOBSTAR=Symbol("globstar **"),u.minimatch.GLOBSTAR=u.GLOBSTAR;const ft="[^/]"+"*?",Ft="(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?",dt="(?:(?!(?:\\/|^)\\.).)*?",Ct=(C,r={})=>l=>(0,u.minimatch)(l,C,r);u.filter=Ct,u.minimatch.filter=u.filter;const L=(C,r={})=>Object.assign({},C,r),Et=C=>{if(!C||typeof C!="object"||!Object.keys(C).length)return u.minimatch;const r=u.minimatch;return Object.assign((c,p,E={})=>r(c,p,L(C,E)),{Minimatch:class extends r.Minimatch{constructor(p,E={}){super(p,L(C,E))}static defaults(p){return r.defaults(L(C,p)).Minimatch}},AST:class extends r.AST{constructor(p,E,B={}){super(p,E,L(C,B))}static fromGlob(p,E={}){return r.AST.fromGlob(p,L(C,E))}},unescape:(c,p={})=>r.unescape(c,L(C,p)),escape:(c,p={})=>r.escape(c,L(C,p)),filter:(c,p={})=>r.filter(c,L(C,p)),defaults:c=>r.defaults(L(C,c)),makeRe:(c,p={})=>r.makeRe(c,L(C,p)),braceExpand:(c,p={})=>r.braceExpand(c,L(C,p)),match:(c,p,E={})=>r.match(c,p,L(C,E)),sep:r.sep,GLOBSTAR:u.GLOBSTAR})};u.defaults=Et,u.minimatch.defaults=u.defaults;const gt=(C,r={})=>((0,D.assertValidPattern)(C),r.nobrace||!/\{(?:(?!\{).)*\}/.test(C)?[C]:(0,t.default)(C));u.braceExpand=gt,u.minimatch.braceExpand=u.braceExpand;const mt=(C,r={})=>new ue(C,r).makeRe();u.makeRe=mt,u.minimatch.makeRe=u.makeRe;const Bt=(C,r,l={})=>{const c=new ue(r,l);return C=C.filter(p=>c.match(p)),c.options.nonull&&!C.length&&C.push(r),C};u.match=Bt,u.minimatch.match=u.match;const nu=/[?*]|[+@!]\(.*?\)|\[|\]/,wt=C=>C.replace(/[-[\]{}()*+?.,\\^$|#\s]/g,"\\$&");class ue{options;set;pattern;windowsPathsNoEscape;nonegate;negate;comment;empty;preserveMultipleSlashes;partial;globSet;globParts;nocase;isWindows;platform;windowsNoMagicRoot;regexp;constructor(r,l={}){(0,D.assertValidPattern)(r),l=l||{},this.options=l,this.pattern=r,this.platform=l.platform||tu,this.isWindows=this.platform==="win32",this.windowsPathsNoEscape=!!l.windowsPathsNoEscape||l.allowWindowsEscape===!1,this.windowsPathsNoEscape&&(this.pattern=this.pattern.replace(/\\/g,"/")),this.preserveMultipleSlashes=!!l.preserveMultipleSlashes,this.regexp=null,this.negate=!1,this.nonegate=!!l.nonegate,this.comment=!1,this.empty=!1,this.partial=!!l.partial,this.nocase=!!this.options.nocase,this.windowsNoMagicRoot=l.windowsNoMagicRoot!==void 0?l.windowsNoMagicRoot:!!(this.isWindows&&this.nocase),this.globSet=[],this.globParts=[],this.set=[],this.make()}hasMagic(){if(this.options.magicalBraces&&this.set.length>1)return!0;for(const r of this.set)for(const l of r)if(typeof l!="string")return!0;return!1}debug(...r){}make(){const r=this.pattern,l=this.options;if(!l.nocomment&&r.charAt(0)==="#"){this.comment=!0;return}if(!r){this.empty=!0;return}this.parseNegate(),this.globSet=[...new Set(this.braceExpand())],l.debug&&(this.debug=(...E)=>console.error(...E)),this.debug(this.pattern,this.globSet);const c=this.globSet.map(E=>this.slashSplit(E));this.globParts=this.preprocess(c),this.debug(this.pattern,this.globParts);let p=this.globParts.map((E,B,y)=>{if(this.isWindows&&this.windowsNoMagicRoot){const b=E[0]===""&&E[1]===""&&(E[2]==="?"||!nu.test(E[2]))&&!nu.test(E[3]),v=/^[a-z]:/i.test(E[0]);if(b)return[...E.slice(0,4),...E.slice(4).map($=>this.parse($))];if(v)return[E[0],...E.slice(1).map($=>this.parse($))]}return E.map(b=>this.parse(b))});if(this.debug(this.pattern,p),this.set=p.filter(E=>E.indexOf(!1)===-1),this.isWindows)for(let E=0;E=2?(r=this.firstPhasePreProcess(r),r=this.secondPhasePreProcess(r)):l>=1?r=this.levelOneOptimize(r):r=this.adjascentGlobstarOptimize(r),r}adjascentGlobstarOptimize(r){return r.map(l=>{let c=-1;for(;(c=l.indexOf("**",c+1))!==-1;){let p=c;for(;l[p+1]==="**";)p++;p!==c&&l.splice(c,p-c)}return l})}levelOneOptimize(r){return r.map(l=>(l=l.reduce((c,p)=>{const E=c[c.length-1];return p==="**"&&E==="**"?c:p===".."&&E&&E!==".."&&E!=="."&&E!=="**"?(c.pop(),c):(c.push(p),c)},[]),l.length===0?[""]:l))}levelTwoFileOptimize(r){Array.isArray(r)||(r=this.slashSplit(r));let l=!1;do{if(l=!1,!this.preserveMultipleSlashes){for(let p=1;pp&&c.splice(p+1,B-p);let y=c[p+1];const b=c[p+2],v=c[p+3];if(y!==".."||!b||b==="."||b===".."||!v||v==="."||v==="..")continue;l=!0,c.splice(p,1);const $=c.slice(0);$[p]="**",r.push($),p--}if(!this.preserveMultipleSlashes){for(let B=1;Bl.length)}partsMatch(r,l,c=!1){let p=0,E=0,B=[],y="";for(;pDe?l=l.slice(Z):De>Z&&(r=r.slice(De)))}}const{optimizationLevel:E=1}=this.options;E>=2&&(r=this.levelTwoFileOptimize(r)),this.debug("matchOne",this,{file:r,pattern:l}),this.debug("matchOne",r.length,l.length);for(var B=0,y=0,b=r.length,v=l.length;B>> no match, partial?`,r,j,l,N),j===b))}let q;if(typeof $=="string"?(q=k===$,this.debug("string match",$,k,q)):(q=$.test(k),this.debug("pattern match",$,k,q)),!q)return!1}if(B===b&&y===v)return!0;if(B===b)return c;if(y===v)return B===b-1&&r[B]==="";throw new Error("wtf?")}braceExpand(){return(0,u.braceExpand)(this.pattern,this.options)}parse(r){(0,D.assertValidPattern)(r);const l=this.options;if(r==="**")return u.GLOBSTAR;if(r==="")return"";let c,p=null;(c=r.match(M))?p=l.dot?Te:G:(c=r.match(a))?p=(l.nocase?l.dot?F:g:l.dot?f:h)(c[1]):(c=r.match(J))?p=(l.nocase?l.dot?de:P:l.dot?Ce:_)(c):(c=r.match(d))?p=l.dot?w:m:(c=r.match(S))&&(p=R);const E=n.AST.fromGlob(r,this.options).toMMPattern();return p?Object.assign(E,{test:p}):E}makeRe(){if(this.regexp||this.regexp===!1)return this.regexp;const r=this.set;if(!r.length)return this.regexp=!1,this.regexp;const l=this.options,c=l.noglobstar?ft:l.dot?Ft:dt,p=new Set(l.nocase?["i"]:[]);let E=r.map(b=>{const v=b.map($=>{if($ instanceof RegExp)for(const k of $.flags.split(""))p.add(k);return typeof $=="string"?wt($):$===u.GLOBSTAR?u.GLOBSTAR:$._src});return v.forEach(($,k)=>{const j=v[k+1],N=v[k-1];$!==u.GLOBSTAR||N===u.GLOBSTAR||(N===void 0?j!==void 0&&j!==u.GLOBSTAR?v[k+1]="(?:\\/|"+c+"\\/)?"+j:v[k]=c:j===void 0?v[k-1]=N+"(?:\\/|"+c+")?":j!==u.GLOBSTAR&&(v[k-1]=N+"(?:\\/|\\/"+c+"\\/)"+j,v[k+1]=u.GLOBSTAR))}),v.filter($=>$!==u.GLOBSTAR).join("/")}).join("|");const[B,y]=r.length>1?["(?:",")"]:["",""];E="^"+B+E+y+"$",this.negate&&(E="^(?!"+E+").+$");try{this.regexp=new RegExp(E,[...p].join(""))}catch{this.regexp=!1}return this.regexp}slashSplit(r){return this.preserveMultipleSlashes?r.split("/"):this.isWindows&&/^\/\/[^\/]+/.test(r)?["",...r.split(/\/+/)]:r.split(/\/+/)}match(r,l=this.partial){if(this.debug("match",r,this.pattern),this.comment)return!1;if(this.empty)return r==="";if(r==="/"&&l)return!0;const c=this.options;this.isWindows&&(r=r.split("\\").join("/"));const p=this.slashSplit(r);this.debug(this.pattern,"split",p);const E=this.set;this.debug(this.pattern,"set",E);let B=p[p.length-1];if(!B)for(let y=p.length-2;!B&&y>=0;y--)B=p[y];for(let y=0;y/^@/.test(n)?`./${n}`:n).sort(this.sort),this.result=t),e==="error"&&this.parent?D=this.parent.emit("error",t):D=super.emit(e,t)),D}start(){return z.readdir(this.path,(e,t)=>e?this.emit("error",e):this.onReaddir(t)),this}isIgnoreFile(e){return e!=="."&&e!==".."&&this.ignoreFiles.indexOf(e)!==-1}onReaddir(e){this.entries=e,e.length===0?(this.includeEmpty&&this.result.add(this.path.slice(this.root.length+1)),this.emit("done",this.result)):this.entries.some(D=>this.isIgnoreFile(D))?this.addIgnoreFiles():this.filterEntries()}addIgnoreFiles(){const e=this.entries.filter(n=>this.isIgnoreFile(n));let t=e.length;const D=n=>{--t===0&&this.filterEntries()};e.forEach(n=>this.addIgnoreFile(n,D))}addIgnoreFile(e,t){const D=eu.resolve(this.path,e);z.readFile(D,"utf8",(n,i)=>n?this.emit("error",n):this.onReadIgnoreFile(e,i,t))}onReadIgnoreFile(e,t,D){const n={matchBase:!0,dot:!0,flipNegate:!0,nocase:!0},i=t.split(/\r?\n/).filter(s=>!/^#|^$/.test(s.trim())).map(s=>new Qn(s.trim(),n));this.ignoreRules[e]=i,D()}filterEntries(){const e=this.entries.map(D=>{const n=this.filterEntry(D),i=this.filterEntry(D,!0);return n||i?[D,n,i]:!1}).filter(D=>D);let t=e.length;if(t===0)this.emit("done",this.result);else{const D=n=>{--t===0&&this.emit("done",this.result)};e.forEach(n=>{const i=n[0],s=n[1],o=n[2];this.stat({entry:i,file:s,dir:o},D)})}}onstat({st:e,entry:t,file:D,dir:n,isSymbolicLink:i},s){const o=this.path+"/"+t;e.isDirectory()?n?this.walker(t,{isSymbolicLink:i},s):s():(D&&this.result.add(o.slice(this.root.length+1)),s())}stat({entry:e,file:t,dir:D},n){const i=this.path+"/"+e;z.lstat(i,(s,o)=>{if(s)this.emit("error",s);else{const a=o.isSymbolicLink();this.follow&&a?z.stat(i,(h,f)=>{h?this.emit("error",h):this.onstat({st:f,entry:e,file:t,dir:D,isSymbolicLink:a},n)}):this.onstat({st:o,entry:e,file:t,dir:D,isSymbolicLink:a},n)}})}walkerOpt(e,t){return{path:this.path+"/"+e,parent:this,ignoreFiles:this.ignoreFiles,follow:this.follow,includeEmpty:this.includeEmpty,...t}}walker(e,t,D){new fe(this.walkerOpt(e,t)).on("done",D).start()}filterEntry(e,t){let D=!0;if(this.parent&&this.parent.filterEntry){var n=this.basename+"/"+e;D=this.parent.filterEntry(n,t)}return this.ignoreFiles.forEach(i=>{this.ignoreRules[i]&&this.ignoreRules[i].forEach(s=>{s.negate!==D&&(s.match("/"+e)||s.match(e)||t&&(s.match("/"+e+"/")||s.match(e+"/"))||t&&s.negate&&(s.match("/"+e,!0)||s.match(e,!0)))&&(D=s.negate)})}),D}}class xe extends fe{start(){return this.onReaddir(z.readdirSync(this.path)),this}addIgnoreFile(e,t){const D=eu.resolve(this.path,e);this.onReadIgnoreFile(e,z.readFileSync(D,"utf8"),t)}stat({entry:e,file:t,dir:D},n){const i=this.path+"/"+e;let s=z.lstatSync(i);const o=s.isSymbolicLink();this.follow&&o&&(s=z.statSync(i)),this.onstat({st:s,entry:e,file:t,dir:D,isSymbolicLink:o},n)}walker(e,t,D){new xe(this.walkerOpt(e,t)).start(),D()}}const Oe=(u,e)=>{const t=new Promise((D,n)=>{new fe(u).on("done",D).on("error",n).start()});return e?t.then(D=>e(null,D),e):t},ei=u=>new xe(u).start().result;var ui=Oe;Oe.sync=ei,Oe.Walker=fe,Oe.WalkerSync=xe;const{Walker:ti}=ui,{lstatSync:Di,readFileSync:ni}=ru,{basename:rt,dirname:ii,extname:ot,join:K,relative:Q,resolve:si,sep:ri}=A,pe=Symbol("npm-packlist.rules.default"),ee=Symbol("npm-packlist.rules.strict"),oi=u=>/\*/.test(u),ai=[".npmignore",".gitignore","**/.git","**/.svn","**/.hg","**/CVS","**/.git/**","**/.svn/**","**/.hg/**","**/CVS/**","/.lock-wscript","/.wafpickle-*","/build/config.gypi","npm-debug.log","**/.npmrc",".*.swp",".DS_Store","**/.DS_Store/**","._*","**/._*/**","*.orig","/archived-packages/**"],at=["/.git"],ke=["!/readme{,.*[^~$]}","!/copying{,.*[^~$]}","!/license{,.*[^~$]}","!/licence{,.*[^~$]}"],li=[/^!.*readme/i,/^!.*copying/i,/^!.*licen[sc]e/i],je=u=>u.split("\\").join("/"),lt=(u,e,t=[])=>{for(const s of[".npmignore",".gitignore"])try{const o=ni(K(u,s),{encoding:"utf8"});t.push(o);break}catch(o){if(o.code!=="ENOENT")throw o}if(!e)return t;const D=e.split(ri,1)[0],n=K(u,D),i=Q(n,K(u,e));return lt(n,i,t)};class Fe extends ti{constructor(e,t){const D={...t,includeEmpty:!1,follow:!1,path:si(t?.path||e.path).replace(/\\/g,"/"),ignoreFiles:t?.ignoreFiles||[pe,"package.json",".npmignore",".gitignore",ee]};super(D),this.isPackage=D.isPackage,this.seen=D.seen||new Set,this.tree=e,this.requiredFiles=D.requiredFiles||[];const n=[];if(D.prefix&&D.workspaces){const i=je(D.path),s=je(D.prefix),o=D.workspaces.map(a=>je(a));if(i!==s&&o.includes(i)){const a=Q(D.prefix,ii(D.path));n.push(...lt(D.prefix,a))}else i===s&&n.push(...o.map(a=>je(Q(D.path,a))))}this.injectRules(pe,[...ai,...n]),this.isPackage||this.injectRules(ee,[...at,...ke,...this.requiredFiles.map(i=>`!${i}`)])}addIgnoreFile(e,t){return e!=="package.json"||!this.isPackage?super.addIgnoreFile(e,t):this.processPackage(t)}emit(e,t){return e!=="done"||!this.isPackage?super.emit(e,t):(this.gatherBundles().then(()=>{super.emit("done",this.result)}),!0)}filterEntries(){return this.ignoreRules["package.json"]?(this.ignoreRules[".npmignore"]=null,this.ignoreRules[".gitignore"]=null):this.ignoreRules[".npmignore"]&&(this.ignoreRules[".gitignore"]=null),super.filterEntries()}onstat(e,t){return!e.st.isFile()&&!e.st.isDirectory()?t():super.onstat(e,t)}stat(e,t){return oi(e.entry)?t():super.stat(e,t)}walkerOpt(e,t){let D=null;if(this.tree.workspaces){const n=[...this.tree.workspaces.values()].map(s=>s.replace(/\\/g,"/")),i=K(this.path,e).replace(/\\/g,"/");n.includes(i)&&(D=[pe,"package.json",".npmignore",".gitignore",ee])}else D=[pe,".npmignore",".gitignore",ee];return{...super.walkerOpt(e,t),ignoreFiles:D,requiredFiles:this.requiredFiles.map(n=>Q(n,e)===".."?Q(e,n).replace(/\\/g,"/"):!1).filter(Boolean)}}walker(e,t,D){new Fe(this.tree,this.walkerOpt(e,t)).on("done",D).start()}sort(e,t){const D=ot(e).toLowerCase(),n=ot(t).toLowerCase(),i=rt(e).toLowerCase(),s=rt(t).toLowerCase();return D.localeCompare(n,"en")||i.localeCompare(s,"en")||e.localeCompare(t,"en")}injectRules(e,t,D=()=>{}){this.onReadIgnoreFile(e,`${t.join(` +-`)} +-`,D)}processPackage(e){const{bin:t,browser:D,files:n,main:i}=this.tree.package,s=[],o=[...at,...ke,"!/package.json","/.git","/node_modules","/package-lock.json","/yarn.lock","/pnpm-lock.yaml"];if(n){for(let a of n){a.startsWith("/")?a=a.slice(1):a.startsWith("./")?a=a.slice(2):a.endsWith("/*")&&(a=a.slice(0,-2));const h=`!${a}`;this.excludeNonRoot(a);try{const f=Di(K(this.path,a.replace(/^!+/,"")).replace(/\\/g,"/"));f.isFile()?(o.unshift(h),this.requiredFiles.push(a)):f.isDirectory()&&(s.push(h),s.push(`${h}/**`))}catch{s.push(h)}}this.injectRules("package.json",["*",...s])}if(D&&o.push(`!/${D}`),i&&o.push(`!/${i}`),t)for(const a in t)o.push(`!/${t[a]}`);this.injectRules(ee,o,e)}excludeNonRoot(e){const t=li.find(D=>D.test(e));if(t){const D=ke.findIndex(n=>t.test(n));ke.splice(D,1)}}async gatherBundles(){if(this.seen.has(this.tree))return;this.seen.add(this.tree);let e;if(this.tree.isProjectRoot){const{bundleDependencies:t}=this.tree.package;e=t||[]}else{const{dependencies:t,optionalDependencies:D}=this.tree.package;e=Object.keys(t||{}).concat(Object.keys(D||{}))}for(const t of e){const D=this.tree.edgesOut.get(t);if(!D||D.peer||D.dev)continue;const n=this.tree.edgesOut.get(t).to;if(!n)continue;const i=n.path,s=n.target,o={path:i,isPackage:!0,ignoreFiles:[],seen:this.seen};n.isLink&&o.ignoreFiles.push(pe),o.ignoreFiles.push("package.json"),n.isLink&&(o.ignoreFiles.push(".npmignore"),o.ignoreFiles.push(".gitignore")),o.ignoreFiles.push(ee);const a=new Fe(s,o),h=await new Promise((g,F)=>{a.on("error",F),a.on("done",g),a.start()}),f=Q(this.root,a.path);for(const g of h)this.result.add(K(f,g).replace(/\\/g,"/"))}}}const ct=(u,e,t)=>{typeof e=="function"&&(t=e,e={});const D=new Promise((n,i)=>{new Fe(u,{...e,isPackage:!0}).on("done",n).on("error",i).start()});return t?D.then(n=>t(null,n),t):D};var ci=ct;ct.Walker=Fe;var ht=Nu(ci);const hi=async(u,e)=>{const t=A.resolve(u,e),D=await qu(t),n=A.join(u,"node_modules/"),i=A.join(n,D.name);if((await O.stat(i).catch(()=>null))?.isDirectory()){const o=await O.realpath(i);if(o.startsWith(n)){const a=new Map,[h,f]=await Promise.all([ht({path:o,package:D,edgesOut:a}),ht({path:t,package:D,edgesOut:a})]);console.log(`Symlinking ${Je(D.name)}:`),await Promise.all(f.map(async g=>{const F=A.join(t,g),d=A.join(i,g);await O.mkdir(A.dirname(d),{recursive:!0}),await An(F,d);const m=h.indexOf(g);m>-1&&h.splice(m,1),console.log(` ${Tu("\u2714")}`,re(A.relative(u,d)),"\u2192",re(A.relative(u,F)))})),await Promise.all(h.map(async g=>{const F=A.join(i,g);await O.rm(F)}));return}}console.error(W` +- Error: Package ${Je(D.name)} is not set up +- +- ${rn("Setup instructions")} +- 1. In the Dependency package, create a tarball: +- ${ze("$ npm pack")} +- +- 2. In the Consuming package, install the tarball and link the Dependency: +- ${ze("$ npm install --no-save ")} +- ${ze("$ npx link publish ")} +- +- 3. Start developing! +- +- Learn more: https://npmjs.com/link +- `)};var pt={command:XD({name:"publish",parameters:[""],flags:{},help:{description:"Link a package to simulate an environment similar to `npm install`"}}),handler:async(u,e)=>{e.length>0&&await Promise.all(e.map(t=>hi(u,t)))}};(async()=>{const u=HD({name:"link",parameters:["[package paths...]"],flags:{deep:{type:Boolean,alias:"d",description:"Run `npx link` on dependencies if they have a link.config.json"}},help:{description:"A better `npm link` -- symlink local dependencies to the current project",render:(t,D)=>(t[0].data=`npx link +-`,t.splice(2,0,{type:"section",data:{title:"Website",body:"https://www.npmjs.com/package/link"}}),D.render(t))},commands:[pt.command]}),e=await O.realpath(process.cwd());if(u.command)u.command==="publish"&&await pt.handler(e,u._);else{const{packagePaths:t}=u._;if(t.length>0){await Promise.all(t.map(n=>Ju(e,n,u.flags)));return}const D=await Lu(e);if(!D){console.warn(W` ++ var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); ++ var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); ++ var isSequence = isNumericSequence || isAlphaSequence; ++ var isOptions = m.body.indexOf(',') >= 0; ++ if (!isSequence && !isOptions) { ++ // {a},b} ++ if (m.post.match(/,.*\}/)) { ++ str = m.pre + '{' + m.body + escClose + m.post; ++ return expand(str); ++ } ++ return [str]; ++ } ++ ++ var n; ++ if (isSequence) { ++ n = m.body.split(/\.\./); ++ } else { ++ n = parseCommaParts(m.body); ++ if (n.length === 1) { ++ // x{{a,b}}y ==> x{a}y x{b}y ++ n = expand(n[0], false).map(embrace); ++ if (n.length === 1) { ++ return post.map(function(p) { ++ return m.pre + n[0] + p; ++ }); ++ } ++ } ++ } ++ ++ // at this point, n is the parts, and we know it's not a comma set ++ // with a single entry. ++ var N; ++ ++ if (isSequence) { ++ var x = numeric(n[0]); ++ var y = numeric(n[1]); ++ var width = Math.max(n[0].length, n[1].length); ++ var incr = n.length == 3 ++ ? Math.abs(numeric(n[2])) ++ : 1; ++ var test = lte; ++ var reverse = y < x; ++ if (reverse) { ++ incr *= -1; ++ test = gte; ++ } ++ var pad = n.some(isPadded); ++ ++ N = []; ++ ++ for (var i = x; test(i, y); i += incr) { ++ var c; ++ if (isAlphaSequence) { ++ c = String.fromCharCode(i); ++ if (c === '\\') ++ c = ''; ++ } else { ++ c = String(i); ++ if (pad) { ++ var need = width - c.length; ++ if (need > 0) { ++ var z = new Array(need + 1).join('0'); ++ if (i < 0) ++ c = '-' + z + c.slice(1); ++ else ++ c = z + c; ++ } ++ } ++ } ++ N.push(c); ++ } ++ } else { ++ N = []; ++ ++ for (var j = 0; j < n.length; j++) { ++ N.push.apply(N, expand(n[j], false)); ++ } ++ } ++ ++ for (var j = 0; j < N.length; j++) { ++ for (var k = 0; k < post.length; k++) { ++ var expansion = pre + N[j] + post[k]; ++ if (!isTop || isSequence || expansion) ++ expansions.push(expansion); ++ } ++ } ++ } ++ ++ return expansions; ++} ++ ++var assertValidPattern$1 = {}; ++ ++Object.defineProperty(assertValidPattern$1, "__esModule", { value: true }); ++assertValidPattern$1.assertValidPattern = void 0; ++const MAX_PATTERN_LENGTH = 1024 * 64; ++const assertValidPattern = (pattern) => { ++ if (typeof pattern !== 'string') { ++ throw new TypeError('invalid pattern'); ++ } ++ if (pattern.length > MAX_PATTERN_LENGTH) { ++ throw new TypeError('pattern is too long'); ++ } ++}; ++assertValidPattern$1.assertValidPattern = assertValidPattern; ++ ++var ast = {}; ++ ++var braceExpressions = {}; ++ ++// translate the various posix character classes into unicode properties ++// this works across all unicode locales ++Object.defineProperty(braceExpressions, "__esModule", { value: true }); ++braceExpressions.parseClass = void 0; ++// { : [, /u flag required, negated] ++const posixClasses = { ++ '[:alnum:]': ['\\p{L}\\p{Nl}\\p{Nd}', true], ++ '[:alpha:]': ['\\p{L}\\p{Nl}', true], ++ '[:ascii:]': ['\\x' + '00-\\x' + '7f', false], ++ '[:blank:]': ['\\p{Zs}\\t', true], ++ '[:cntrl:]': ['\\p{Cc}', true], ++ '[:digit:]': ['\\p{Nd}', true], ++ '[:graph:]': ['\\p{Z}\\p{C}', true, true], ++ '[:lower:]': ['\\p{Ll}', true], ++ '[:print:]': ['\\p{C}', true], ++ '[:punct:]': ['\\p{P}', true], ++ '[:space:]': ['\\p{Z}\\t\\r\\n\\v\\f', true], ++ '[:upper:]': ['\\p{Lu}', true], ++ '[:word:]': ['\\p{L}\\p{Nl}\\p{Nd}\\p{Pc}', true], ++ '[:xdigit:]': ['A-Fa-f0-9', false], ++}; ++// only need to escape a few things inside of brace expressions ++// escapes: [ \ ] - ++const braceEscape = (s) => s.replace(/[[\]\\-]/g, '\\$&'); ++// escape all regexp magic characters ++const regexpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); ++// everything has already been escaped, we just have to join ++const rangesToString = (ranges) => ranges.join(''); ++// takes a glob string at a posix brace expression, and returns ++// an equivalent regular expression source, and boolean indicating ++// whether the /u flag needs to be applied, and the number of chars ++// consumed to parse the character class. ++// This also removes out of order ranges, and returns ($.) if the ++// entire class just no good. ++const parseClass = (glob, position) => { ++ const pos = position; ++ /* c8 ignore start */ ++ if (glob.charAt(pos) !== '[') { ++ throw new Error('not in a brace expression'); ++ } ++ /* c8 ignore stop */ ++ const ranges = []; ++ const negs = []; ++ let i = pos + 1; ++ let sawStart = false; ++ let uflag = false; ++ let escaping = false; ++ let negate = false; ++ let endPos = pos; ++ let rangeStart = ''; ++ WHILE: while (i < glob.length) { ++ const c = glob.charAt(i); ++ if ((c === '!' || c === '^') && i === pos + 1) { ++ negate = true; ++ i++; ++ continue; ++ } ++ if (c === ']' && sawStart && !escaping) { ++ endPos = i + 1; ++ break; ++ } ++ sawStart = true; ++ if (c === '\\') { ++ if (!escaping) { ++ escaping = true; ++ i++; ++ continue; ++ } ++ // escaped \ char, fall through and treat like normal char ++ } ++ if (c === '[' && !escaping) { ++ // either a posix class, a collation equivalent, or just a [ ++ for (const [cls, [unip, u, neg]] of Object.entries(posixClasses)) { ++ if (glob.startsWith(cls, i)) { ++ // invalid, [a-[] is fine, but not [a-[:alpha]] ++ if (rangeStart) { ++ return ['$.', false, glob.length - pos, true]; ++ } ++ i += cls.length; ++ if (neg) ++ negs.push(unip); ++ else ++ ranges.push(unip); ++ uflag = uflag || u; ++ continue WHILE; ++ } ++ } ++ } ++ // now it's just a normal character, effectively ++ escaping = false; ++ if (rangeStart) { ++ // throw this range away if it's not valid, but others ++ // can still match. ++ if (c > rangeStart) { ++ ranges.push(braceEscape(rangeStart) + '-' + braceEscape(c)); ++ } ++ else if (c === rangeStart) { ++ ranges.push(braceEscape(c)); ++ } ++ rangeStart = ''; ++ i++; ++ continue; ++ } ++ // now might be the start of a range. ++ // can be either c-d or c-] or c] or c] at this point ++ if (glob.startsWith('-]', i + 1)) { ++ ranges.push(braceEscape(c + '-')); ++ i += 2; ++ continue; ++ } ++ if (glob.startsWith('-', i + 1)) { ++ rangeStart = c; ++ i += 2; ++ continue; ++ } ++ // not the start of a range, just a single character ++ ranges.push(braceEscape(c)); ++ i++; ++ } ++ if (endPos < i) { ++ // didn't see the end of the class, not a valid class, ++ // but might still be valid as a literal match. ++ return ['', false, 0, false]; ++ } ++ // if we got no ranges and no negates, then we have a range that ++ // cannot possibly match anything, and that poisons the whole glob ++ if (!ranges.length && !negs.length) { ++ return ['$.', false, glob.length - pos, true]; ++ } ++ // if we got one positive range, and it's a single character, then that's ++ // not actually a magic pattern, it's just that one literal character. ++ // we should not treat that as "magic", we should just return the literal ++ // character. [_] is a perfectly valid way to escape glob magic chars. ++ if (negs.length === 0 && ++ ranges.length === 1 && ++ /^\\?.$/.test(ranges[0]) && ++ !negate) { ++ const r = ranges[0].length === 2 ? ranges[0].slice(-1) : ranges[0]; ++ return [regexpEscape(r), false, endPos - pos, false]; ++ } ++ const sranges = '[' + (negate ? '^' : '') + rangesToString(ranges) + ']'; ++ const snegs = '[' + (negate ? '' : '^') + rangesToString(negs) + ']'; ++ const comb = ranges.length && negs.length ++ ? '(' + sranges + '|' + snegs + ')' ++ : ranges.length ++ ? sranges ++ : snegs; ++ return [comb, uflag, endPos - pos, true]; ++}; ++braceExpressions.parseClass = parseClass; ++ ++var _unescape = {}; ++ ++Object.defineProperty(_unescape, "__esModule", { value: true }); ++_unescape.unescape = void 0; ++/** ++ * Un-escape a string that has been escaped with {@link escape}. ++ * ++ * If the {@link windowsPathsNoEscape} option is used, then square-brace ++ * escapes are removed, but not backslash escapes. For example, it will turn ++ * the string `'[*]'` into `*`, but it will not turn `'\\*'` into `'*'`, ++ * becuase `\` is a path separator in `windowsPathsNoEscape` mode. ++ * ++ * When `windowsPathsNoEscape` is not set, then both brace escapes and ++ * backslash escapes are removed. ++ * ++ * Slashes (and backslashes in `windowsPathsNoEscape` mode) cannot be escaped ++ * or unescaped. ++ */ ++const unescape = (s, { windowsPathsNoEscape = false, } = {}) => { ++ return windowsPathsNoEscape ++ ? s.replace(/\[([^\/\\])\]/g, '$1') ++ : s.replace(/((?!\\).|^)\[([^\/\\])\]/g, '$1$2').replace(/\\([^\/])/g, '$1'); ++}; ++_unescape.unescape = unescape; ++ ++// parse a single path portion ++Object.defineProperty(ast, "__esModule", { value: true }); ++ast.AST = void 0; ++const brace_expressions_js_1 = braceExpressions; ++const unescape_js_1 = _unescape; ++const types = new Set(['!', '?', '+', '*', '@']); ++const isExtglobType = (c) => types.has(c); ++// Patterns that get prepended to bind to the start of either the ++// entire string, or just a single path portion, to prevent dots ++// and/or traversal patterns, when needed. ++// Exts don't need the ^ or / bit, because the root binds that already. ++const startNoTraversal = '(?!(?:^|/)\\.\\.?(?:$|/))'; ++const startNoDot = '(?!\\.)'; ++// characters that indicate a start of pattern needs the "no dots" bit, ++// because a dot *might* be matched. ( is not in the list, because in ++// the case of a child extglob, it will handle the prevention itself. ++const addPatternStart = new Set(['[', '.']); ++// cases where traversal is A-OK, no dot prevention needed ++const justDots = new Set(['..', '.']); ++const reSpecials = new Set('().*{}+?[]^$\\!'); ++const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); ++// any single thing other than / ++const qmark = '[^/]'; ++// * => any number of characters ++const star = qmark + '*?'; ++// use + when we need to ensure that *something* matches, because the * is ++// the only thing in the path portion. ++const starNoEmpty = qmark + '+?'; ++// remove the \ chars that we added if we end up doing a nonmagic compare ++// const deslash = (s: string) => s.replace(/\\(.)/g, '$1') ++class AST { ++ type; ++ #root; ++ #hasMagic; ++ #uflag = false; ++ #parts = []; ++ #parent; ++ #parentIndex; ++ #negs; ++ #filledNegs = false; ++ #options; ++ #toString; ++ // set to true if it's an extglob with no children ++ // (which really means one child of '') ++ #emptyExt = false; ++ constructor(type, parent, options = {}) { ++ this.type = type; ++ // extglobs are inherently magical ++ if (type) ++ this.#hasMagic = true; ++ this.#parent = parent; ++ this.#root = this.#parent ? this.#parent.#root : this; ++ this.#options = this.#root === this ? options : this.#root.#options; ++ this.#negs = this.#root === this ? [] : this.#root.#negs; ++ if (type === '!' && !this.#root.#filledNegs) ++ this.#negs.push(this); ++ this.#parentIndex = this.#parent ? this.#parent.#parts.length : 0; ++ } ++ get hasMagic() { ++ /* c8 ignore start */ ++ if (this.#hasMagic !== undefined) ++ return this.#hasMagic; ++ /* c8 ignore stop */ ++ for (const p of this.#parts) { ++ if (typeof p === 'string') ++ continue; ++ if (p.type || p.hasMagic) ++ return (this.#hasMagic = true); ++ } ++ // note: will be undefined until we generate the regexp src and find out ++ return this.#hasMagic; ++ } ++ // reconstructs the pattern ++ toString() { ++ if (this.#toString !== undefined) ++ return this.#toString; ++ if (!this.type) { ++ return (this.#toString = this.#parts.map(p => String(p)).join('')); ++ } ++ else { ++ return (this.#toString = ++ this.type + '(' + this.#parts.map(p => String(p)).join('|') + ')'); ++ } ++ } ++ #fillNegs() { ++ /* c8 ignore start */ ++ if (this !== this.#root) ++ throw new Error('should only call on root'); ++ if (this.#filledNegs) ++ return this; ++ /* c8 ignore stop */ ++ // call toString() once to fill this out ++ this.toString(); ++ this.#filledNegs = true; ++ let n; ++ while ((n = this.#negs.pop())) { ++ if (n.type !== '!') ++ continue; ++ // walk up the tree, appending everthing that comes AFTER parentIndex ++ let p = n; ++ let pp = p.#parent; ++ while (pp) { ++ for (let i = p.#parentIndex + 1; !pp.type && i < pp.#parts.length; i++) { ++ for (const part of n.#parts) { ++ /* c8 ignore start */ ++ if (typeof part === 'string') { ++ throw new Error('string part in extglob AST??'); ++ } ++ /* c8 ignore stop */ ++ part.copyIn(pp.#parts[i]); ++ } ++ } ++ p = pp; ++ pp = p.#parent; ++ } ++ } ++ return this; ++ } ++ push(...parts) { ++ for (const p of parts) { ++ if (p === '') ++ continue; ++ /* c8 ignore start */ ++ if (typeof p !== 'string' && !(p instanceof AST && p.#parent === this)) { ++ throw new Error('invalid part: ' + p); ++ } ++ /* c8 ignore stop */ ++ this.#parts.push(p); ++ } ++ } ++ toJSON() { ++ const ret = this.type === null ++ ? this.#parts.slice().map(p => (typeof p === 'string' ? p : p.toJSON())) ++ : [this.type, ...this.#parts.map(p => p.toJSON())]; ++ if (this.isStart() && !this.type) ++ ret.unshift([]); ++ if (this.isEnd() && ++ (this === this.#root || ++ (this.#root.#filledNegs && this.#parent?.type === '!'))) { ++ ret.push({}); ++ } ++ return ret; ++ } ++ isStart() { ++ if (this.#root === this) ++ return true; ++ // if (this.type) return !!this.#parent?.isStart() ++ if (!this.#parent?.isStart()) ++ return false; ++ if (this.#parentIndex === 0) ++ return true; ++ // if everything AHEAD of this is a negation, then it's still the "start" ++ const p = this.#parent; ++ for (let i = 0; i < this.#parentIndex; i++) { ++ const pp = p.#parts[i]; ++ if (!(pp instanceof AST && pp.type === '!')) { ++ return false; ++ } ++ } ++ return true; ++ } ++ isEnd() { ++ if (this.#root === this) ++ return true; ++ if (this.#parent?.type === '!') ++ return true; ++ if (!this.#parent?.isEnd()) ++ return false; ++ if (!this.type) ++ return this.#parent?.isEnd(); ++ // if not root, it'll always have a parent ++ /* c8 ignore start */ ++ const pl = this.#parent ? this.#parent.#parts.length : 0; ++ /* c8 ignore stop */ ++ return this.#parentIndex === pl - 1; ++ } ++ copyIn(part) { ++ if (typeof part === 'string') ++ this.push(part); ++ else ++ this.push(part.clone(this)); ++ } ++ clone(parent) { ++ const c = new AST(this.type, parent); ++ for (const p of this.#parts) { ++ c.copyIn(p); ++ } ++ return c; ++ } ++ static #parseAST(str, ast, pos, opt) { ++ let escaping = false; ++ let inBrace = false; ++ let braceStart = -1; ++ let braceNeg = false; ++ if (ast.type === null) { ++ // outside of a extglob, append until we find a start ++ let i = pos; ++ let acc = ''; ++ while (i < str.length) { ++ const c = str.charAt(i++); ++ // still accumulate escapes at this point, but we do ignore ++ // starts that are escaped ++ if (escaping || c === '\\') { ++ escaping = !escaping; ++ acc += c; ++ continue; ++ } ++ if (inBrace) { ++ if (i === braceStart + 1) { ++ if (c === '^' || c === '!') { ++ braceNeg = true; ++ } ++ } ++ else if (c === ']' && !(i === braceStart + 2 && braceNeg)) { ++ inBrace = false; ++ } ++ acc += c; ++ continue; ++ } ++ else if (c === '[') { ++ inBrace = true; ++ braceStart = i; ++ braceNeg = false; ++ acc += c; ++ continue; ++ } ++ if (!opt.noext && isExtglobType(c) && str.charAt(i) === '(') { ++ ast.push(acc); ++ acc = ''; ++ const ext = new AST(c, ast); ++ i = AST.#parseAST(str, ext, i, opt); ++ ast.push(ext); ++ continue; ++ } ++ acc += c; ++ } ++ ast.push(acc); ++ return i; ++ } ++ // some kind of extglob, pos is at the ( ++ // find the next | or ) ++ let i = pos + 1; ++ let part = new AST(null, ast); ++ const parts = []; ++ let acc = ''; ++ while (i < str.length) { ++ const c = str.charAt(i++); ++ // still accumulate escapes at this point, but we do ignore ++ // starts that are escaped ++ if (escaping || c === '\\') { ++ escaping = !escaping; ++ acc += c; ++ continue; ++ } ++ if (inBrace) { ++ if (i === braceStart + 1) { ++ if (c === '^' || c === '!') { ++ braceNeg = true; ++ } ++ } ++ else if (c === ']' && !(i === braceStart + 2 && braceNeg)) { ++ inBrace = false; ++ } ++ acc += c; ++ continue; ++ } ++ else if (c === '[') { ++ inBrace = true; ++ braceStart = i; ++ braceNeg = false; ++ acc += c; ++ continue; ++ } ++ if (isExtglobType(c) && str.charAt(i) === '(') { ++ part.push(acc); ++ acc = ''; ++ const ext = new AST(c, part); ++ part.push(ext); ++ i = AST.#parseAST(str, ext, i, opt); ++ continue; ++ } ++ if (c === '|') { ++ part.push(acc); ++ acc = ''; ++ parts.push(part); ++ part = new AST(null, ast); ++ continue; ++ } ++ if (c === ')') { ++ if (acc === '' && ast.#parts.length === 0) { ++ ast.#emptyExt = true; ++ } ++ part.push(acc); ++ acc = ''; ++ ast.push(...parts, part); ++ return i; ++ } ++ acc += c; ++ } ++ // unfinished extglob ++ // if we got here, it was a malformed extglob! not an extglob, but ++ // maybe something else in there. ++ ast.type = null; ++ ast.#hasMagic = undefined; ++ ast.#parts = [str.substring(pos - 1)]; ++ return i; ++ } ++ static fromGlob(pattern, options = {}) { ++ const ast = new AST(null, undefined, options); ++ AST.#parseAST(pattern, ast, 0, options); ++ return ast; ++ } ++ // returns the regular expression if there's magic, or the unescaped ++ // string if not. ++ toMMPattern() { ++ // should only be called on root ++ /* c8 ignore start */ ++ if (this !== this.#root) ++ return this.#root.toMMPattern(); ++ /* c8 ignore stop */ ++ const glob = this.toString(); ++ const [re, body, hasMagic, uflag] = this.toRegExpSource(); ++ // if we're in nocase mode, and not nocaseMagicOnly, then we do ++ // still need a regular expression if we have to case-insensitively ++ // match capital/lowercase characters. ++ const anyMagic = hasMagic || ++ this.#hasMagic || ++ (this.#options.nocase && ++ !this.#options.nocaseMagicOnly && ++ glob.toUpperCase() !== glob.toLowerCase()); ++ if (!anyMagic) { ++ return body; ++ } ++ const flags = (this.#options.nocase ? 'i' : '') + (uflag ? 'u' : ''); ++ return Object.assign(new RegExp(`^${re}$`, flags), { ++ _src: re, ++ _glob: glob, ++ }); ++ } ++ get options() { ++ return this.#options; ++ } ++ // returns the string match, the regexp source, whether there's magic ++ // in the regexp (so a regular expression is required) and whether or ++ // not the uflag is needed for the regular expression (for posix classes) ++ // TODO: instead of injecting the start/end at this point, just return ++ // the BODY of the regexp, along with the start/end portions suitable ++ // for binding the start/end in either a joined full-path makeRe context ++ // (where we bind to (^|/), or a standalone matchPart context (where ++ // we bind to ^, and not /). Otherwise slashes get duped! ++ // ++ // In part-matching mode, the start is: ++ // - if not isStart: nothing ++ // - if traversal possible, but not allowed: ^(?!\.\.?$) ++ // - if dots allowed or not possible: ^ ++ // - if dots possible and not allowed: ^(?!\.) ++ // end is: ++ // - if not isEnd(): nothing ++ // - else: $ ++ // ++ // In full-path matching mode, we put the slash at the START of the ++ // pattern, so start is: ++ // - if first pattern: same as part-matching mode ++ // - if not isStart(): nothing ++ // - if traversal possible, but not allowed: /(?!\.\.?(?:$|/)) ++ // - if dots allowed or not possible: / ++ // - if dots possible and not allowed: /(?!\.) ++ // end is: ++ // - if last pattern, same as part-matching mode ++ // - else nothing ++ // ++ // Always put the (?:$|/) on negated tails, though, because that has to be ++ // there to bind the end of the negated pattern portion, and it's easier to ++ // just stick it in now rather than try to inject it later in the middle of ++ // the pattern. ++ // ++ // We can just always return the same end, and leave it up to the caller ++ // to know whether it's going to be used joined or in parts. ++ // And, if the start is adjusted slightly, can do the same there: ++ // - if not isStart: nothing ++ // - if traversal possible, but not allowed: (?:/|^)(?!\.\.?$) ++ // - if dots allowed or not possible: (?:/|^) ++ // - if dots possible and not allowed: (?:/|^)(?!\.) ++ // ++ // But it's better to have a simpler binding without a conditional, for ++ // performance, so probably better to return both start options. ++ // ++ // Then the caller just ignores the end if it's not the first pattern, ++ // and the start always gets applied. ++ // ++ // But that's always going to be $ if it's the ending pattern, or nothing, ++ // so the caller can just attach $ at the end of the pattern when building. ++ // ++ // So the todo is: ++ // - better detect what kind of start is needed ++ // - return both flavors of starting pattern ++ // - attach $ at the end of the pattern when creating the actual RegExp ++ // ++ // Ah, but wait, no, that all only applies to the root when the first pattern ++ // is not an extglob. If the first pattern IS an extglob, then we need all ++ // that dot prevention biz to live in the extglob portions, because eg ++ // +(*|.x*) can match .xy but not .yx. ++ // ++ // So, return the two flavors if it's #root and the first child is not an ++ // AST, otherwise leave it to the child AST to handle it, and there, ++ // use the (?:^|/) style of start binding. ++ // ++ // Even simplified further: ++ // - Since the start for a join is eg /(?!\.) and the start for a part ++ // is ^(?!\.), we can just prepend (?!\.) to the pattern (either root ++ // or start or whatever) and prepend ^ or / at the Regexp construction. ++ toRegExpSource(allowDot) { ++ const dot = allowDot ?? !!this.#options.dot; ++ if (this.#root === this) ++ this.#fillNegs(); ++ if (!this.type) { ++ const noEmpty = this.isStart() && this.isEnd(); ++ const src = this.#parts ++ .map(p => { ++ const [re, _, hasMagic, uflag] = typeof p === 'string' ++ ? AST.#parseGlob(p, this.#hasMagic, noEmpty) ++ : p.toRegExpSource(allowDot); ++ this.#hasMagic = this.#hasMagic || hasMagic; ++ this.#uflag = this.#uflag || uflag; ++ return re; ++ }) ++ .join(''); ++ let start = ''; ++ if (this.isStart()) { ++ if (typeof this.#parts[0] === 'string') { ++ // this is the string that will match the start of the pattern, ++ // so we need to protect against dots and such. ++ // '.' and '..' cannot match unless the pattern is that exactly, ++ // even if it starts with . or dot:true is set. ++ const dotTravAllowed = this.#parts.length === 1 && justDots.has(this.#parts[0]); ++ if (!dotTravAllowed) { ++ const aps = addPatternStart; ++ // check if we have a possibility of matching . or .., ++ // and prevent that. ++ const needNoTrav = ++ // dots are allowed, and the pattern starts with [ or . ++ (dot && aps.has(src.charAt(0))) || ++ // the pattern starts with \., and then [ or . ++ (src.startsWith('\\.') && aps.has(src.charAt(2))) || ++ // the pattern starts with \.\., and then [ or . ++ (src.startsWith('\\.\\.') && aps.has(src.charAt(4))); ++ // no need to prevent dots if it can't match a dot, or if a ++ // sub-pattern will be preventing it anyway. ++ const needNoDot = !dot && !allowDot && aps.has(src.charAt(0)); ++ start = needNoTrav ? startNoTraversal : needNoDot ? startNoDot : ''; ++ } ++ } ++ } ++ // append the "end of path portion" pattern to negation tails ++ let end = ''; ++ if (this.isEnd() && ++ this.#root.#filledNegs && ++ this.#parent?.type === '!') { ++ end = '(?:$|\\/)'; ++ } ++ const final = start + src + end; ++ return [ ++ final, ++ (0, unescape_js_1.unescape)(src), ++ (this.#hasMagic = !!this.#hasMagic), ++ this.#uflag, ++ ]; ++ } ++ // We need to calculate the body *twice* if it's a repeat pattern ++ // at the start, once in nodot mode, then again in dot mode, so a ++ // pattern like *(?) can match 'x.y' ++ const repeated = this.type === '*' || this.type === '+'; ++ // some kind of extglob ++ const start = this.type === '!' ? '(?:(?!(?:' : '(?:'; ++ let body = this.#partsToRegExp(dot); ++ if (this.isStart() && this.isEnd() && !body && this.type !== '!') { ++ // invalid extglob, has to at least be *something* present, if it's ++ // the entire path portion. ++ const s = this.toString(); ++ this.#parts = [s]; ++ this.type = null; ++ this.#hasMagic = undefined; ++ return [s, (0, unescape_js_1.unescape)(this.toString()), false, false]; ++ } ++ // XXX abstract out this map method ++ let bodyDotAllowed = !repeated || allowDot || dot || !startNoDot ++ ? '' ++ : this.#partsToRegExp(true); ++ if (bodyDotAllowed === body) { ++ bodyDotAllowed = ''; ++ } ++ if (bodyDotAllowed) { ++ body = `(?:${body})(?:${bodyDotAllowed})*?`; ++ } ++ // an empty !() is exactly equivalent to a starNoEmpty ++ let final = ''; ++ if (this.type === '!' && this.#emptyExt) { ++ final = (this.isStart() && !dot ? startNoDot : '') + starNoEmpty; ++ } ++ else { ++ const close = this.type === '!' ++ ? // !() must match something,but !(x) can match '' ++ '))' + ++ (this.isStart() && !dot && !allowDot ? startNoDot : '') + ++ star + ++ ')' ++ : this.type === '@' ++ ? ')' ++ : this.type === '?' ++ ? ')?' ++ : this.type === '+' && bodyDotAllowed ++ ? ')' ++ : this.type === '*' && bodyDotAllowed ++ ? `)?` ++ : `)${this.type}`; ++ final = start + body + close; ++ } ++ return [ ++ final, ++ (0, unescape_js_1.unescape)(body), ++ (this.#hasMagic = !!this.#hasMagic), ++ this.#uflag, ++ ]; ++ } ++ #partsToRegExp(dot) { ++ return this.#parts ++ .map(p => { ++ // extglob ASTs should only contain parent ASTs ++ /* c8 ignore start */ ++ if (typeof p === 'string') { ++ throw new Error('string type in extglob ast??'); ++ } ++ /* c8 ignore stop */ ++ // can ignore hasMagic, because extglobs are already always magic ++ const [re, _, _hasMagic, uflag] = p.toRegExpSource(dot); ++ this.#uflag = this.#uflag || uflag; ++ return re; ++ }) ++ .filter(p => !(this.isStart() && this.isEnd()) || !!p) ++ .join('|'); ++ } ++ static #parseGlob(glob, hasMagic, noEmpty = false) { ++ let escaping = false; ++ let re = ''; ++ let uflag = false; ++ for (let i = 0; i < glob.length; i++) { ++ const c = glob.charAt(i); ++ if (escaping) { ++ escaping = false; ++ re += (reSpecials.has(c) ? '\\' : '') + c; ++ continue; ++ } ++ if (c === '\\') { ++ if (i === glob.length - 1) { ++ re += '\\\\'; ++ } ++ else { ++ escaping = true; ++ } ++ continue; ++ } ++ if (c === '[') { ++ const [src, needUflag, consumed, magic] = (0, brace_expressions_js_1.parseClass)(glob, i); ++ if (consumed) { ++ re += src; ++ uflag = uflag || needUflag; ++ i += consumed - 1; ++ hasMagic = hasMagic || magic; ++ continue; ++ } ++ } ++ if (c === '*') { ++ if (noEmpty && glob === '*') ++ re += starNoEmpty; ++ else ++ re += star; ++ hasMagic = true; ++ continue; ++ } ++ if (c === '?') { ++ re += qmark; ++ hasMagic = true; ++ continue; ++ } ++ re += regExpEscape(c); ++ } ++ return [re, (0, unescape_js_1.unescape)(glob), !!hasMagic, uflag]; ++ } ++} ++ast.AST = AST; ++ ++var _escape = {}; ++ ++Object.defineProperty(_escape, "__esModule", { value: true }); ++_escape.escape = void 0; ++/** ++ * Escape all magic characters in a glob pattern. ++ * ++ * If the {@link windowsPathsNoEscape | GlobOptions.windowsPathsNoEscape} ++ * option is used, then characters are escaped by wrapping in `[]`, because ++ * a magic character wrapped in a character class can only be satisfied by ++ * that exact character. In this mode, `\` is _not_ escaped, because it is ++ * not interpreted as a magic character, but instead as a path separator. ++ */ ++const escape = (s, { windowsPathsNoEscape = false, } = {}) => { ++ // don't need to escape +@! because we escape the parens ++ // that make those magic, and escaping ! as [!] isn't valid, ++ // because [!]] is a valid glob class meaning not ']'. ++ return windowsPathsNoEscape ++ ? s.replace(/[?*()[\]]/g, '[$&]') ++ : s.replace(/[?*()[\]\\]/g, '\\$&'); ++}; ++_escape.escape = escape; ++ ++(function (exports) { ++ var __importDefault = (commonjsGlobal && commonjsGlobal.__importDefault) || function (mod) { ++ return (mod && mod.__esModule) ? mod : { "default": mod }; ++ }; ++ Object.defineProperty(exports, "__esModule", { value: true }); ++ exports.unescape = exports.escape = exports.AST = exports.Minimatch = exports.match = exports.makeRe = exports.braceExpand = exports.defaults = exports.filter = exports.GLOBSTAR = exports.sep = exports.minimatch = void 0; ++ const brace_expansion_1 = __importDefault(braceExpansion); ++ const assert_valid_pattern_js_1 = assertValidPattern$1; ++ const ast_js_1 = ast; ++ const escape_js_1 = _escape; ++ const unescape_js_1 = _unescape; ++ const minimatch = (p, pattern, options = {}) => { ++ (0, assert_valid_pattern_js_1.assertValidPattern)(pattern); ++ // shortcut: comments match nothing. ++ if (!options.nocomment && pattern.charAt(0) === '#') { ++ return false; ++ } ++ return new Minimatch(pattern, options).match(p); ++ }; ++ exports.minimatch = minimatch; ++ // Optimized checking for the most common glob patterns. ++ const starDotExtRE = /^\*+([^+@!?\*\[\(]*)$/; ++ const starDotExtTest = (ext) => (f) => !f.startsWith('.') && f.endsWith(ext); ++ const starDotExtTestDot = (ext) => (f) => f.endsWith(ext); ++ const starDotExtTestNocase = (ext) => { ++ ext = ext.toLowerCase(); ++ return (f) => !f.startsWith('.') && f.toLowerCase().endsWith(ext); ++ }; ++ const starDotExtTestNocaseDot = (ext) => { ++ ext = ext.toLowerCase(); ++ return (f) => f.toLowerCase().endsWith(ext); ++ }; ++ const starDotStarRE = /^\*+\.\*+$/; ++ const starDotStarTest = (f) => !f.startsWith('.') && f.includes('.'); ++ const starDotStarTestDot = (f) => f !== '.' && f !== '..' && f.includes('.'); ++ const dotStarRE = /^\.\*+$/; ++ const dotStarTest = (f) => f !== '.' && f !== '..' && f.startsWith('.'); ++ const starRE = /^\*+$/; ++ const starTest = (f) => f.length !== 0 && !f.startsWith('.'); ++ const starTestDot = (f) => f.length !== 0 && f !== '.' && f !== '..'; ++ const qmarksRE = /^\?+([^+@!?\*\[\(]*)?$/; ++ const qmarksTestNocase = ([$0, ext = '']) => { ++ const noext = qmarksTestNoExt([$0]); ++ if (!ext) ++ return noext; ++ ext = ext.toLowerCase(); ++ return (f) => noext(f) && f.toLowerCase().endsWith(ext); ++ }; ++ const qmarksTestNocaseDot = ([$0, ext = '']) => { ++ const noext = qmarksTestNoExtDot([$0]); ++ if (!ext) ++ return noext; ++ ext = ext.toLowerCase(); ++ return (f) => noext(f) && f.toLowerCase().endsWith(ext); ++ }; ++ const qmarksTestDot = ([$0, ext = '']) => { ++ const noext = qmarksTestNoExtDot([$0]); ++ return !ext ? noext : (f) => noext(f) && f.endsWith(ext); ++ }; ++ const qmarksTest = ([$0, ext = '']) => { ++ const noext = qmarksTestNoExt([$0]); ++ return !ext ? noext : (f) => noext(f) && f.endsWith(ext); ++ }; ++ const qmarksTestNoExt = ([$0]) => { ++ const len = $0.length; ++ return (f) => f.length === len && !f.startsWith('.'); ++ }; ++ const qmarksTestNoExtDot = ([$0]) => { ++ const len = $0.length; ++ return (f) => f.length === len && f !== '.' && f !== '..'; ++ }; ++ /* c8 ignore start */ ++ const defaultPlatform = (typeof process === 'object' && process ++ ? (typeof process.env === 'object' && ++ process.env && ++ process.env.__MINIMATCH_TESTING_PLATFORM__) || ++ process.platform ++ : 'posix'); ++ const path = { ++ win32: { sep: '\\' }, ++ posix: { sep: '/' }, ++ }; ++ /* c8 ignore stop */ ++ exports.sep = defaultPlatform === 'win32' ? path.win32.sep : path.posix.sep; ++ exports.minimatch.sep = exports.sep; ++ exports.GLOBSTAR = Symbol('globstar **'); ++ exports.minimatch.GLOBSTAR = exports.GLOBSTAR; ++ // any single thing other than / ++ // don't need to escape / when using new RegExp() ++ const qmark = '[^/]'; ++ // * => any number of characters ++ const star = qmark + '*?'; ++ // ** when dots are allowed. Anything goes, except .. and . ++ // not (^ or / followed by one or two dots followed by $ or /), ++ // followed by anything, any number of times. ++ const twoStarDot = '(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?'; ++ // not a ^ or / followed by a dot, ++ // followed by anything, any number of times. ++ const twoStarNoDot = '(?:(?!(?:\\/|^)\\.).)*?'; ++ const filter = (pattern, options = {}) => (p) => (0, exports.minimatch)(p, pattern, options); ++ exports.filter = filter; ++ exports.minimatch.filter = exports.filter; ++ const ext = (a, b = {}) => Object.assign({}, a, b); ++ const defaults = (def) => { ++ if (!def || typeof def !== 'object' || !Object.keys(def).length) { ++ return exports.minimatch; ++ } ++ const orig = exports.minimatch; ++ const m = (p, pattern, options = {}) => orig(p, pattern, ext(def, options)); ++ return Object.assign(m, { ++ Minimatch: class Minimatch extends orig.Minimatch { ++ constructor(pattern, options = {}) { ++ super(pattern, ext(def, options)); ++ } ++ static defaults(options) { ++ return orig.defaults(ext(def, options)).Minimatch; ++ } ++ }, ++ AST: class AST extends orig.AST { ++ /* c8 ignore start */ ++ constructor(type, parent, options = {}) { ++ super(type, parent, ext(def, options)); ++ } ++ /* c8 ignore stop */ ++ static fromGlob(pattern, options = {}) { ++ return orig.AST.fromGlob(pattern, ext(def, options)); ++ } ++ }, ++ unescape: (s, options = {}) => orig.unescape(s, ext(def, options)), ++ escape: (s, options = {}) => orig.escape(s, ext(def, options)), ++ filter: (pattern, options = {}) => orig.filter(pattern, ext(def, options)), ++ defaults: (options) => orig.defaults(ext(def, options)), ++ makeRe: (pattern, options = {}) => orig.makeRe(pattern, ext(def, options)), ++ braceExpand: (pattern, options = {}) => orig.braceExpand(pattern, ext(def, options)), ++ match: (list, pattern, options = {}) => orig.match(list, pattern, ext(def, options)), ++ sep: orig.sep, ++ GLOBSTAR: exports.GLOBSTAR, ++ }); ++ }; ++ exports.defaults = defaults; ++ exports.minimatch.defaults = exports.defaults; ++ // Brace expansion: ++ // a{b,c}d -> abd acd ++ // a{b,}c -> abc ac ++ // a{0..3}d -> a0d a1d a2d a3d ++ // a{b,c{d,e}f}g -> abg acdfg acefg ++ // a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg ++ // ++ // Invalid sets are not expanded. ++ // a{2..}b -> a{2..}b ++ // a{b}c -> a{b}c ++ const braceExpand = (pattern, options = {}) => { ++ (0, assert_valid_pattern_js_1.assertValidPattern)(pattern); ++ // Thanks to Yeting Li for ++ // improving this regexp to avoid a ReDOS vulnerability. ++ if (options.nobrace || !/\{(?:(?!\{).)*\}/.test(pattern)) { ++ // shortcut. no need to expand. ++ return [pattern]; ++ } ++ return (0, brace_expansion_1.default)(pattern); ++ }; ++ exports.braceExpand = braceExpand; ++ exports.minimatch.braceExpand = exports.braceExpand; ++ // parse a component of the expanded set. ++ // At this point, no pattern may contain "/" in it ++ // so we're going to return a 2d array, where each entry is the full ++ // pattern, split on '/', and then turned into a regular expression. ++ // A regexp is made at the end which joins each array with an ++ // escaped /, and another full one which joins each regexp with |. ++ // ++ // Following the lead of Bash 4.1, note that "**" only has special meaning ++ // when it is the *only* thing in a path portion. Otherwise, any series ++ // of * is equivalent to a single *. Globstar behavior is enabled by ++ // default, and can be disabled by setting options.noglobstar. ++ const makeRe = (pattern, options = {}) => new Minimatch(pattern, options).makeRe(); ++ exports.makeRe = makeRe; ++ exports.minimatch.makeRe = exports.makeRe; ++ const match = (list, pattern, options = {}) => { ++ const mm = new Minimatch(pattern, options); ++ list = list.filter(f => mm.match(f)); ++ if (mm.options.nonull && !list.length) { ++ list.push(pattern); ++ } ++ return list; ++ }; ++ exports.match = match; ++ exports.minimatch.match = exports.match; ++ // replace stuff like \* with * ++ const globMagic = /[?*]|[+@!]\(.*?\)|\[|\]/; ++ const regExpEscape = (s) => s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); ++ class Minimatch { ++ options; ++ set; ++ pattern; ++ windowsPathsNoEscape; ++ nonegate; ++ negate; ++ comment; ++ empty; ++ preserveMultipleSlashes; ++ partial; ++ globSet; ++ globParts; ++ nocase; ++ isWindows; ++ platform; ++ windowsNoMagicRoot; ++ regexp; ++ constructor(pattern, options = {}) { ++ (0, assert_valid_pattern_js_1.assertValidPattern)(pattern); ++ options = options || {}; ++ this.options = options; ++ this.pattern = pattern; ++ this.platform = options.platform || defaultPlatform; ++ this.isWindows = this.platform === 'win32'; ++ this.windowsPathsNoEscape = ++ !!options.windowsPathsNoEscape || options.allowWindowsEscape === false; ++ if (this.windowsPathsNoEscape) { ++ this.pattern = this.pattern.replace(/\\/g, '/'); ++ } ++ this.preserveMultipleSlashes = !!options.preserveMultipleSlashes; ++ this.regexp = null; ++ this.negate = false; ++ this.nonegate = !!options.nonegate; ++ this.comment = false; ++ this.empty = false; ++ this.partial = !!options.partial; ++ this.nocase = !!this.options.nocase; ++ this.windowsNoMagicRoot = ++ options.windowsNoMagicRoot !== undefined ++ ? options.windowsNoMagicRoot ++ : !!(this.isWindows && this.nocase); ++ this.globSet = []; ++ this.globParts = []; ++ this.set = []; ++ // make the set of regexps etc. ++ this.make(); ++ } ++ hasMagic() { ++ if (this.options.magicalBraces && this.set.length > 1) { ++ return true; ++ } ++ for (const pattern of this.set) { ++ for (const part of pattern) { ++ if (typeof part !== 'string') ++ return true; ++ } ++ } ++ return false; ++ } ++ debug(..._) { } ++ make() { ++ const pattern = this.pattern; ++ const options = this.options; ++ // empty patterns and comments match nothing. ++ if (!options.nocomment && pattern.charAt(0) === '#') { ++ this.comment = true; ++ return; ++ } ++ if (!pattern) { ++ this.empty = true; ++ return; ++ } ++ // step 1: figure out negation, etc. ++ this.parseNegate(); ++ // step 2: expand braces ++ this.globSet = [...new Set(this.braceExpand())]; ++ if (options.debug) { ++ this.debug = (...args) => console.error(...args); ++ } ++ this.debug(this.pattern, this.globSet); ++ // step 3: now we have a set, so turn each one into a series of ++ // path-portion matching patterns. ++ // These will be regexps, except in the case of "**", which is ++ // set to the GLOBSTAR object for globstar behavior, ++ // and will not contain any / characters ++ // ++ // First, we preprocess to make the glob pattern sets a bit simpler ++ // and deduped. There are some perf-killing patterns that can cause ++ // problems with a glob walk, but we can simplify them down a bit. ++ const rawGlobParts = this.globSet.map(s => this.slashSplit(s)); ++ this.globParts = this.preprocess(rawGlobParts); ++ this.debug(this.pattern, this.globParts); ++ // glob --> regexps ++ let set = this.globParts.map((s, _, __) => { ++ if (this.isWindows && this.windowsNoMagicRoot) { ++ // check if it's a drive or unc path. ++ const isUNC = s[0] === '' && ++ s[1] === '' && ++ (s[2] === '?' || !globMagic.test(s[2])) && ++ !globMagic.test(s[3]); ++ const isDrive = /^[a-z]:/i.test(s[0]); ++ if (isUNC) { ++ return [...s.slice(0, 4), ...s.slice(4).map(ss => this.parse(ss))]; ++ } ++ else if (isDrive) { ++ return [s[0], ...s.slice(1).map(ss => this.parse(ss))]; ++ } ++ } ++ return s.map(ss => this.parse(ss)); ++ }); ++ this.debug(this.pattern, set); ++ // filter out everything that didn't compile properly. ++ this.set = set.filter(s => s.indexOf(false) === -1); ++ // do not treat the ? in UNC paths as magic ++ if (this.isWindows) { ++ for (let i = 0; i < this.set.length; i++) { ++ const p = this.set[i]; ++ if (p[0] === '' && ++ p[1] === '' && ++ this.globParts[i][2] === '?' && ++ typeof p[3] === 'string' && ++ /^[a-z]:$/i.test(p[3])) { ++ p[2] = '?'; ++ } ++ } ++ } ++ this.debug(this.pattern, this.set); ++ } ++ // various transforms to equivalent pattern sets that are ++ // faster to process in a filesystem walk. The goal is to ++ // eliminate what we can, and push all ** patterns as far ++ // to the right as possible, even if it increases the number ++ // of patterns that we have to process. ++ preprocess(globParts) { ++ // if we're not in globstar mode, then turn all ** into * ++ if (this.options.noglobstar) { ++ for (let i = 0; i < globParts.length; i++) { ++ for (let j = 0; j < globParts[i].length; j++) { ++ if (globParts[i][j] === '**') { ++ globParts[i][j] = '*'; ++ } ++ } ++ } ++ } ++ const { optimizationLevel = 1 } = this.options; ++ if (optimizationLevel >= 2) { ++ // aggressive optimization for the purpose of fs walking ++ globParts = this.firstPhasePreProcess(globParts); ++ globParts = this.secondPhasePreProcess(globParts); ++ } ++ else if (optimizationLevel >= 1) { ++ // just basic optimizations to remove some .. parts ++ globParts = this.levelOneOptimize(globParts); ++ } ++ else { ++ // just collapse multiple ** portions into one ++ globParts = this.adjascentGlobstarOptimize(globParts); ++ } ++ return globParts; ++ } ++ // just get rid of adjascent ** portions ++ adjascentGlobstarOptimize(globParts) { ++ return globParts.map(parts => { ++ let gs = -1; ++ while (-1 !== (gs = parts.indexOf('**', gs + 1))) { ++ let i = gs; ++ while (parts[i + 1] === '**') { ++ i++; ++ } ++ if (i !== gs) { ++ parts.splice(gs, i - gs); ++ } ++ } ++ return parts; ++ }); ++ } ++ // get rid of adjascent ** and resolve .. portions ++ levelOneOptimize(globParts) { ++ return globParts.map(parts => { ++ parts = parts.reduce((set, part) => { ++ const prev = set[set.length - 1]; ++ if (part === '**' && prev === '**') { ++ return set; ++ } ++ if (part === '..') { ++ if (prev && prev !== '..' && prev !== '.' && prev !== '**') { ++ set.pop(); ++ return set; ++ } ++ } ++ set.push(part); ++ return set; ++ }, []); ++ return parts.length === 0 ? [''] : parts; ++ }); ++ } ++ levelTwoFileOptimize(parts) { ++ if (!Array.isArray(parts)) { ++ parts = this.slashSplit(parts); ++ } ++ let didSomething = false; ++ do { ++ didSomething = false; ++ //

// -> 
/
++	            if (!this.preserveMultipleSlashes) {
++	                for (let i = 1; i < parts.length - 1; i++) {
++	                    const p = parts[i];
++	                    // don't squeeze out UNC patterns
++	                    if (i === 1 && p === '' && parts[0] === '')
++	                        continue;
++	                    if (p === '.' || p === '') {
++	                        didSomething = true;
++	                        parts.splice(i, 1);
++	                        i--;
++	                    }
++	                }
++	                if (parts[0] === '.' &&
++	                    parts.length === 2 &&
++	                    (parts[1] === '.' || parts[1] === '')) {
++	                    didSomething = true;
++	                    parts.pop();
++	                }
++	            }
++	            // 
/

/../ ->

/
++	            let dd = 0;
++	            while (-1 !== (dd = parts.indexOf('..', dd + 1))) {
++	                const p = parts[dd - 1];
++	                if (p && p !== '.' && p !== '..' && p !== '**') {
++	                    didSomething = true;
++	                    parts.splice(dd - 1, 2);
++	                    dd -= 2;
++	                }
++	            }
++	        } while (didSomething);
++	        return parts.length === 0 ? [''] : parts;
++	    }
++	    // First phase: single-pattern processing
++	    // 
 is 1 or more portions
++	    //  is 1 or more portions
++	    // 

is any portion other than ., .., '', or ** ++ // is . or '' ++ // ++ // **/.. is *brutal* for filesystem walking performance, because ++ // it effectively resets the recursive walk each time it occurs, ++ // and ** cannot be reduced out by a .. pattern part like a regexp ++ // or most strings (other than .., ., and '') can be. ++ // ++ //

/**/../

/

/ -> {

/../

/

/,

/**/

/

/} ++ //

// -> 
/
++	    // 
/

/../ ->

/
++	    // **/**/ -> **/
++	    //
++	    // **/*/ -> */**/ <== not valid because ** doesn't follow
++	    // this WOULD be allowed if ** did follow symlinks, or * didn't
++	    firstPhasePreProcess(globParts) {
++	        let didSomething = false;
++	        do {
++	            didSomething = false;
++	            // 
/**/../

/

/ -> {

/../

/

/,

/**/

/

/} ++ for (let parts of globParts) { ++ let gs = -1; ++ while (-1 !== (gs = parts.indexOf('**', gs + 1))) { ++ let gss = gs; ++ while (parts[gss + 1] === '**') { ++ //

/**/**/ -> 
/**/
++	                        gss++;
++	                    }
++	                    // eg, if gs is 2 and gss is 4, that means we have 3 **
++	                    // parts, and can remove 2 of them.
++	                    if (gss > gs) {
++	                        parts.splice(gs + 1, gss - gs);
++	                    }
++	                    let next = parts[gs + 1];
++	                    const p = parts[gs + 2];
++	                    const p2 = parts[gs + 3];
++	                    if (next !== '..')
++	                        continue;
++	                    if (!p ||
++	                        p === '.' ||
++	                        p === '..' ||
++	                        !p2 ||
++	                        p2 === '.' ||
++	                        p2 === '..') {
++	                        continue;
++	                    }
++	                    didSomething = true;
++	                    // edit parts in place, and push the new one
++	                    parts.splice(gs, 1);
++	                    const other = parts.slice(0);
++	                    other[gs] = '**';
++	                    globParts.push(other);
++	                    gs--;
++	                }
++	                // 
// -> 
/
++	                if (!this.preserveMultipleSlashes) {
++	                    for (let i = 1; i < parts.length - 1; i++) {
++	                        const p = parts[i];
++	                        // don't squeeze out UNC patterns
++	                        if (i === 1 && p === '' && parts[0] === '')
++	                            continue;
++	                        if (p === '.' || p === '') {
++	                            didSomething = true;
++	                            parts.splice(i, 1);
++	                            i--;
++	                        }
++	                    }
++	                    if (parts[0] === '.' &&
++	                        parts.length === 2 &&
++	                        (parts[1] === '.' || parts[1] === '')) {
++	                        didSomething = true;
++	                        parts.pop();
++	                    }
++	                }
++	                // 
/

/../ ->

/
++	                let dd = 0;
++	                while (-1 !== (dd = parts.indexOf('..', dd + 1))) {
++	                    const p = parts[dd - 1];
++	                    if (p && p !== '.' && p !== '..' && p !== '**') {
++	                        didSomething = true;
++	                        const needDot = dd === 1 && parts[dd + 1] === '**';
++	                        const splin = needDot ? ['.'] : [];
++	                        parts.splice(dd - 1, 2, ...splin);
++	                        if (parts.length === 0)
++	                            parts.push('');
++	                        dd -= 2;
++	                    }
++	                }
++	            }
++	        } while (didSomething);
++	        return globParts;
++	    }
++	    // second phase: multi-pattern dedupes
++	    // {
/*/,
/

/} ->

/*/
++	    // {
/,
/} -> 
/
++	    // {
/**/,
/} -> 
/**/
++	    //
++	    // {
/**/,
/**/

/} ->

/**/
++	    // ^-- not valid because ** doens't follow symlinks
++	    secondPhasePreProcess(globParts) {
++	        for (let i = 0; i < globParts.length - 1; i++) {
++	            for (let j = i + 1; j < globParts.length; j++) {
++	                const matched = this.partsMatch(globParts[i], globParts[j], !this.preserveMultipleSlashes);
++	                if (matched) {
++	                    globParts[i] = [];
++	                    globParts[j] = matched;
++	                    break;
++	                }
++	            }
++	        }
++	        return globParts.filter(gs => gs.length);
++	    }
++	    partsMatch(a, b, emptyGSMatch = false) {
++	        let ai = 0;
++	        let bi = 0;
++	        let result = [];
++	        let which = '';
++	        while (ai < a.length && bi < b.length) {
++	            if (a[ai] === b[bi]) {
++	                result.push(which === 'b' ? b[bi] : a[ai]);
++	                ai++;
++	                bi++;
++	            }
++	            else if (emptyGSMatch && a[ai] === '**' && b[bi] === a[ai + 1]) {
++	                result.push(a[ai]);
++	                ai++;
++	            }
++	            else if (emptyGSMatch && b[bi] === '**' && a[ai] === b[bi + 1]) {
++	                result.push(b[bi]);
++	                bi++;
++	            }
++	            else if (a[ai] === '*' &&
++	                b[bi] &&
++	                (this.options.dot || !b[bi].startsWith('.')) &&
++	                b[bi] !== '**') {
++	                if (which === 'b')
++	                    return false;
++	                which = 'a';
++	                result.push(a[ai]);
++	                ai++;
++	                bi++;
++	            }
++	            else if (b[bi] === '*' &&
++	                a[ai] &&
++	                (this.options.dot || !a[ai].startsWith('.')) &&
++	                a[ai] !== '**') {
++	                if (which === 'a')
++	                    return false;
++	                which = 'b';
++	                result.push(b[bi]);
++	                ai++;
++	                bi++;
++	            }
++	            else {
++	                return false;
++	            }
++	        }
++	        // if we fall out of the loop, it means they two are identical
++	        // as long as their lengths match
++	        return a.length === b.length && result;
++	    }
++	    parseNegate() {
++	        if (this.nonegate)
++	            return;
++	        const pattern = this.pattern;
++	        let negate = false;
++	        let negateOffset = 0;
++	        for (let i = 0; i < pattern.length && pattern.charAt(i) === '!'; i++) {
++	            negate = !negate;
++	            negateOffset++;
++	        }
++	        if (negateOffset)
++	            this.pattern = pattern.slice(negateOffset);
++	        this.negate = negate;
++	    }
++	    // set partial to true to test if, for example,
++	    // "/a/b" matches the start of "/*/b/*/d"
++	    // Partial means, if you run out of file before you run
++	    // out of pattern, then that's fine, as long as all
++	    // the parts match.
++	    matchOne(file, pattern, partial = false) {
++	        const options = this.options;
++	        // UNC paths like //?/X:/... can match X:/... and vice versa
++	        // Drive letters in absolute drive or unc paths are always compared
++	        // case-insensitively.
++	        if (this.isWindows) {
++	            const fileDrive = typeof file[0] === 'string' && /^[a-z]:$/i.test(file[0]);
++	            const fileUNC = !fileDrive &&
++	                file[0] === '' &&
++	                file[1] === '' &&
++	                file[2] === '?' &&
++	                /^[a-z]:$/i.test(file[3]);
++	            const patternDrive = typeof pattern[0] === 'string' && /^[a-z]:$/i.test(pattern[0]);
++	            const patternUNC = !patternDrive &&
++	                pattern[0] === '' &&
++	                pattern[1] === '' &&
++	                pattern[2] === '?' &&
++	                typeof pattern[3] === 'string' &&
++	                /^[a-z]:$/i.test(pattern[3]);
++	            const fdi = fileUNC ? 3 : fileDrive ? 0 : undefined;
++	            const pdi = patternUNC ? 3 : patternDrive ? 0 : undefined;
++	            if (typeof fdi === 'number' && typeof pdi === 'number') {
++	                const [fd, pd] = [file[fdi], pattern[pdi]];
++	                if (fd.toLowerCase() === pd.toLowerCase()) {
++	                    pattern[pdi] = fd;
++	                    if (pdi > fdi) {
++	                        pattern = pattern.slice(pdi);
++	                    }
++	                    else if (fdi > pdi) {
++	                        file = file.slice(fdi);
++	                    }
++	                }
++	            }
++	        }
++	        // resolve and reduce . and .. portions in the file as well.
++	        // dont' need to do the second phase, because it's only one string[]
++	        const { optimizationLevel = 1 } = this.options;
++	        if (optimizationLevel >= 2) {
++	            file = this.levelTwoFileOptimize(file);
++	        }
++	        this.debug('matchOne', this, { file, pattern });
++	        this.debug('matchOne', file.length, pattern.length);
++	        for (var fi = 0, pi = 0, fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) {
++	            this.debug('matchOne loop');
++	            var p = pattern[pi];
++	            var f = file[fi];
++	            this.debug(pattern, p, f);
++	            // should be impossible.
++	            // some invalid regexp stuff in the set.
++	            /* c8 ignore start */
++	            if (p === false) {
++	                return false;
++	            }
++	            /* c8 ignore stop */
++	            if (p === exports.GLOBSTAR) {
++	                this.debug('GLOBSTAR', [pattern, p, f]);
++	                // "**"
++	                // a/**/b/**/c would match the following:
++	                // a/b/x/y/z/c
++	                // a/x/y/z/b/c
++	                // a/b/x/b/x/c
++	                // a/b/c
++	                // To do this, take the rest of the pattern after
++	                // the **, and see if it would match the file remainder.
++	                // If so, return success.
++	                // If not, the ** "swallows" a segment, and try again.
++	                // This is recursively awful.
++	                //
++	                // a/**/b/**/c matching a/b/x/y/z/c
++	                // - a matches a
++	                // - doublestar
++	                //   - matchOne(b/x/y/z/c, b/**/c)
++	                //     - b matches b
++	                //     - doublestar
++	                //       - matchOne(x/y/z/c, c) -> no
++	                //       - matchOne(y/z/c, c) -> no
++	                //       - matchOne(z/c, c) -> no
++	                //       - matchOne(c, c) yes, hit
++	                var fr = fi;
++	                var pr = pi + 1;
++	                if (pr === pl) {
++	                    this.debug('** at the end');
++	                    // a ** at the end will just swallow the rest.
++	                    // We have found a match.
++	                    // however, it will not swallow /.x, unless
++	                    // options.dot is set.
++	                    // . and .. are *never* matched by **, for explosively
++	                    // exponential reasons.
++	                    for (; fi < fl; fi++) {
++	                        if (file[fi] === '.' ||
++	                            file[fi] === '..' ||
++	                            (!options.dot && file[fi].charAt(0) === '.'))
++	                            return false;
++	                    }
++	                    return true;
++	                }
++	                // ok, let's see if we can swallow whatever we can.
++	                while (fr < fl) {
++	                    var swallowee = file[fr];
++	                    this.debug('\nglobstar while', file, fr, pattern, pr, swallowee);
++	                    // XXX remove this slice.  Just pass the start index.
++	                    if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) {
++	                        this.debug('globstar found match!', fr, fl, swallowee);
++	                        // found a match.
++	                        return true;
++	                    }
++	                    else {
++	                        // can't swallow "." or ".." ever.
++	                        // can only swallow ".foo" when explicitly asked.
++	                        if (swallowee === '.' ||
++	                            swallowee === '..' ||
++	                            (!options.dot && swallowee.charAt(0) === '.')) {
++	                            this.debug('dot detected!', file, fr, pattern, pr);
++	                            break;
++	                        }
++	                        // ** swallows a segment, and continue.
++	                        this.debug('globstar swallow a segment, and continue');
++	                        fr++;
++	                    }
++	                }
++	                // no match was found.
++	                // However, in partial mode, we can't say this is necessarily over.
++	                /* c8 ignore start */
++	                if (partial) {
++	                    // ran out of file
++	                    this.debug('\n>>> no match, partial?', file, fr, pattern, pr);
++	                    if (fr === fl) {
++	                        return true;
++	                    }
++	                }
++	                /* c8 ignore stop */
++	                return false;
++	            }
++	            // something other than **
++	            // non-magic patterns just have to match exactly
++	            // patterns with magic have been turned into regexps.
++	            let hit;
++	            if (typeof p === 'string') {
++	                hit = f === p;
++	                this.debug('string match', p, f, hit);
++	            }
++	            else {
++	                hit = p.test(f);
++	                this.debug('pattern match', p, f, hit);
++	            }
++	            if (!hit)
++	                return false;
++	        }
++	        // Note: ending in / means that we'll get a final ""
++	        // at the end of the pattern.  This can only match a
++	        // corresponding "" at the end of the file.
++	        // If the file ends in /, then it can only match a
++	        // a pattern that ends in /, unless the pattern just
++	        // doesn't have any more for it. But, a/b/ should *not*
++	        // match "a/b/*", even though "" matches against the
++	        // [^/]*? pattern, except in partial mode, where it might
++	        // simply not be reached yet.
++	        // However, a/b/ should still satisfy a/*
++	        // now either we fell off the end of the pattern, or we're done.
++	        if (fi === fl && pi === pl) {
++	            // ran out of pattern and filename at the same time.
++	            // an exact hit!
++	            return true;
++	        }
++	        else if (fi === fl) {
++	            // ran out of file, but still had pattern left.
++	            // this is ok if we're doing the match as part of
++	            // a glob fs traversal.
++	            return partial;
++	        }
++	        else if (pi === pl) {
++	            // ran out of pattern, still have file left.
++	            // this is only acceptable if we're on the very last
++	            // empty segment of a file with a trailing slash.
++	            // a/* should match a/b/
++	            return fi === fl - 1 && file[fi] === '';
++	            /* c8 ignore start */
++	        }
++	        else {
++	            // should be unreachable.
++	            throw new Error('wtf?');
++	        }
++	        /* c8 ignore stop */
++	    }
++	    braceExpand() {
++	        return (0, exports.braceExpand)(this.pattern, this.options);
++	    }
++	    parse(pattern) {
++	        (0, assert_valid_pattern_js_1.assertValidPattern)(pattern);
++	        const options = this.options;
++	        // shortcuts
++	        if (pattern === '**')
++	            return exports.GLOBSTAR;
++	        if (pattern === '')
++	            return '';
++	        // far and away, the most common glob pattern parts are
++	        // *, *.*, and *.  Add a fast check method for those.
++	        let m;
++	        let fastTest = null;
++	        if ((m = pattern.match(starRE))) {
++	            fastTest = options.dot ? starTestDot : starTest;
++	        }
++	        else if ((m = pattern.match(starDotExtRE))) {
++	            fastTest = (options.nocase
++	                ? options.dot
++	                    ? starDotExtTestNocaseDot
++	                    : starDotExtTestNocase
++	                : options.dot
++	                    ? starDotExtTestDot
++	                    : starDotExtTest)(m[1]);
++	        }
++	        else if ((m = pattern.match(qmarksRE))) {
++	            fastTest = (options.nocase
++	                ? options.dot
++	                    ? qmarksTestNocaseDot
++	                    : qmarksTestNocase
++	                : options.dot
++	                    ? qmarksTestDot
++	                    : qmarksTest)(m);
++	        }
++	        else if ((m = pattern.match(starDotStarRE))) {
++	            fastTest = options.dot ? starDotStarTestDot : starDotStarTest;
++	        }
++	        else if ((m = pattern.match(dotStarRE))) {
++	            fastTest = dotStarTest;
++	        }
++	        const re = ast_js_1.AST.fromGlob(pattern, this.options).toMMPattern();
++	        if (fastTest && typeof re === 'object') {
++	            // Avoids overriding in frozen environments
++	            Reflect.defineProperty(re, 'test', { value: fastTest });
++	        }
++	        return re;
++	    }
++	    makeRe() {
++	        if (this.regexp || this.regexp === false)
++	            return this.regexp;
++	        // at this point, this.set is a 2d array of partial
++	        // pattern strings, or "**".
++	        //
++	        // It's better to use .match().  This function shouldn't
++	        // be used, really, but it's pretty convenient sometimes,
++	        // when you just want to work with a regex.
++	        const set = this.set;
++	        if (!set.length) {
++	            this.regexp = false;
++	            return this.regexp;
++	        }
++	        const options = this.options;
++	        const twoStar = options.noglobstar
++	            ? star
++	            : options.dot
++	                ? twoStarDot
++	                : twoStarNoDot;
++	        const flags = new Set(options.nocase ? ['i'] : []);
++	        // regexpify non-globstar patterns
++	        // if ** is only item, then we just do one twoStar
++	        // if ** is first, and there are more, prepend (\/|twoStar\/)? to next
++	        // if ** is last, append (\/twoStar|) to previous
++	        // if ** is in the middle, append (\/|\/twoStar\/) to previous
++	        // then filter out GLOBSTAR symbols
++	        let re = set
++	            .map(pattern => {
++	            const pp = pattern.map(p => {
++	                if (p instanceof RegExp) {
++	                    for (const f of p.flags.split(''))
++	                        flags.add(f);
++	                }
++	                return typeof p === 'string'
++	                    ? regExpEscape(p)
++	                    : p === exports.GLOBSTAR
++	                        ? exports.GLOBSTAR
++	                        : p._src;
++	            });
++	            pp.forEach((p, i) => {
++	                const next = pp[i + 1];
++	                const prev = pp[i - 1];
++	                if (p !== exports.GLOBSTAR || prev === exports.GLOBSTAR) {
++	                    return;
++	                }
++	                if (prev === undefined) {
++	                    if (next !== undefined && next !== exports.GLOBSTAR) {
++	                        pp[i + 1] = '(?:\\/|' + twoStar + '\\/)?' + next;
++	                    }
++	                    else {
++	                        pp[i] = twoStar;
++	                    }
++	                }
++	                else if (next === undefined) {
++	                    pp[i - 1] = prev + '(?:\\/|' + twoStar + ')?';
++	                }
++	                else if (next !== exports.GLOBSTAR) {
++	                    pp[i - 1] = prev + '(?:\\/|\\/' + twoStar + '\\/)' + next;
++	                    pp[i + 1] = exports.GLOBSTAR;
++	                }
++	            });
++	            return pp.filter(p => p !== exports.GLOBSTAR).join('/');
++	        })
++	            .join('|');
++	        // need to wrap in parens if we had more than one thing with |,
++	        // otherwise only the first will be anchored to ^ and the last to $
++	        const [open, close] = set.length > 1 ? ['(?:', ')'] : ['', ''];
++	        // must match entire pattern
++	        // ending in a * or ** will make it less strict.
++	        re = '^' + open + re + close + '$';
++	        // can match anything, as long as it's not this.
++	        if (this.negate)
++	            re = '^(?!' + re + ').+$';
++	        try {
++	            this.regexp = new RegExp(re, [...flags].join(''));
++	            /* c8 ignore start */
++	        }
++	        catch (ex) {
++	            // should be impossible
++	            this.regexp = false;
++	        }
++	        /* c8 ignore stop */
++	        return this.regexp;
++	    }
++	    slashSplit(p) {
++	        // if p starts with // on windows, we preserve that
++	        // so that UNC paths aren't broken.  Otherwise, any number of
++	        // / characters are coalesced into one, unless
++	        // preserveMultipleSlashes is set to true.
++	        if (this.preserveMultipleSlashes) {
++	            return p.split('/');
++	        }
++	        else if (this.isWindows && /^\/\/[^\/]+/.test(p)) {
++	            // add an extra '' for the one we lose
++	            return ['', ...p.split(/\/+/)];
++	        }
++	        else {
++	            return p.split(/\/+/);
++	        }
++	    }
++	    match(f, partial = this.partial) {
++	        this.debug('match', f, this.pattern);
++	        // short-circuit in the case of busted things.
++	        // comments, etc.
++	        if (this.comment) {
++	            return false;
++	        }
++	        if (this.empty) {
++	            return f === '';
++	        }
++	        if (f === '/' && partial) {
++	            return true;
++	        }
++	        const options = this.options;
++	        // windows: need to use /, not \
++	        if (this.isWindows) {
++	            f = f.split('\\').join('/');
++	        }
++	        // treat the test path as a set of pathparts.
++	        const ff = this.slashSplit(f);
++	        this.debug(this.pattern, 'split', ff);
++	        // just ONE of the pattern sets in this.set needs to match
++	        // in order for it to be valid.  If negating, then just one
++	        // match means that we have failed.
++	        // Either way, return on the first hit.
++	        const set = this.set;
++	        this.debug(this.pattern, 'set', set);
++	        // Find the basename of the path by looking for the last non-empty segment
++	        let filename = ff[ff.length - 1];
++	        if (!filename) {
++	            for (let i = ff.length - 2; !filename && i >= 0; i--) {
++	                filename = ff[i];
++	            }
++	        }
++	        for (let i = 0; i < set.length; i++) {
++	            const pattern = set[i];
++	            let file = ff;
++	            if (options.matchBase && pattern.length === 1) {
++	                file = [filename];
++	            }
++	            const hit = this.matchOne(file, pattern, partial);
++	            if (hit) {
++	                if (options.flipNegate) {
++	                    return true;
++	                }
++	                return !this.negate;
++	            }
++	        }
++	        // didn't get any hits.  this is success if it's a negative
++	        // pattern, failure otherwise.
++	        if (options.flipNegate) {
++	            return false;
++	        }
++	        return this.negate;
++	    }
++	    static defaults(def) {
++	        return exports.minimatch.defaults(def).Minimatch;
++	    }
++	}
++	exports.Minimatch = Minimatch;
++	/* c8 ignore start */
++	var ast_js_2 = ast;
++	Object.defineProperty(exports, "AST", { enumerable: true, get: function () { return ast_js_2.AST; } });
++	var escape_js_2 = _escape;
++	Object.defineProperty(exports, "escape", { enumerable: true, get: function () { return escape_js_2.escape; } });
++	var unescape_js_2 = _unescape;
++	Object.defineProperty(exports, "unescape", { enumerable: true, get: function () { return unescape_js_2.unescape; } });
++	/* c8 ignore stop */
++	exports.minimatch.AST = ast_js_1.AST;
++	exports.minimatch.Minimatch = Minimatch;
++	exports.minimatch.escape = escape_js_1.escape;
++	exports.minimatch.unescape = unescape_js_1.unescape;
++	
++} (commonjs));
++
++const fs = require$$0;
++const path = path$1;
++const EE = require$$2.EventEmitter;
++const Minimatch = commonjs.Minimatch;
++
++class Walker extends EE {
++  constructor (opts) {
++    opts = opts || {};
++    super(opts);
++    // set to true if this.path is a symlink, whether follow is true or not
++    this.isSymbolicLink = opts.isSymbolicLink;
++    this.path = opts.path || process.cwd();
++    this.basename = path.basename(this.path);
++    this.ignoreFiles = opts.ignoreFiles || ['.ignore'];
++    this.ignoreRules = {};
++    this.parent = opts.parent || null;
++    this.includeEmpty = !!opts.includeEmpty;
++    this.root = this.parent ? this.parent.root : this.path;
++    this.follow = !!opts.follow;
++    this.result = this.parent ? this.parent.result : new Set();
++    this.entries = null;
++    this.sawError = false;
++    this.exact = opts.exact;
++  }
++
++  sort (a, b) {
++    return a.localeCompare(b, 'en')
++  }
++
++  emit (ev, data) {
++    let ret = false;
++    if (!(this.sawError && ev === 'error')) {
++      if (ev === 'error') {
++        this.sawError = true;
++      } else if (ev === 'done' && !this.parent) {
++        data = Array.from(data)
++          .map(e => /^@/.test(e) ? `./${e}` : e).sort(this.sort);
++        this.result = data;
++      }
++
++      if (ev === 'error' && this.parent) {
++        ret = this.parent.emit('error', data);
++      } else {
++        ret = super.emit(ev, data);
++      }
++    }
++    return ret
++  }
++
++  start () {
++    fs.readdir(this.path, (er, entries) =>
++      er ? this.emit('error', er) : this.onReaddir(entries));
++    return this
++  }
++
++  isIgnoreFile (e) {
++    return e !== '.' &&
++      e !== '..' &&
++      this.ignoreFiles.indexOf(e) !== -1
++  }
++
++  onReaddir (entries) {
++    this.entries = entries;
++    if (entries.length === 0) {
++      if (this.includeEmpty) {
++        this.result.add(this.path.slice(this.root.length + 1));
++      }
++      this.emit('done', this.result);
++    } else {
++      const hasIg = this.entries.some(e =>
++        this.isIgnoreFile(e));
++
++      if (hasIg) {
++        this.addIgnoreFiles();
++      } else {
++        this.filterEntries();
++      }
++    }
++  }
++
++  addIgnoreFiles () {
++    const newIg = this.entries
++      .filter(e => this.isIgnoreFile(e));
++
++    let igCount = newIg.length;
++    const then = () => {
++      if (--igCount === 0) {
++        this.filterEntries();
++      }
++    };
++
++    newIg.forEach(e => this.addIgnoreFile(e, then));
++  }
++
++  addIgnoreFile (file, then) {
++    const ig = path.resolve(this.path, file);
++    fs.readFile(ig, 'utf8', (er, data) =>
++      er ? this.emit('error', er) : this.onReadIgnoreFile(file, data, then));
++  }
++
++  onReadIgnoreFile (file, data, then) {
++    const mmopt = {
++      matchBase: true,
++      dot: true,
++      flipNegate: true,
++      nocase: true,
++    };
++    const rules = data.split(/\r?\n/)
++      .filter(line => !/^#|^$/.test(line.trim()))
++      .map(rule => {
++        return new Minimatch(rule.trim(), mmopt)
++      });
++
++    this.ignoreRules[file] = rules;
++
++    then();
++  }
++
++  filterEntries () {
++    // at this point we either have ignore rules, or just inheriting
++    // this exclusion is at the point where we know the list of
++    // entries in the dir, but don't know what they are.  since
++    // some of them *might* be directories, we have to run the
++    // match in dir-mode as well, so that we'll pick up partials
++    // of files that will be included later.  Anything included
++    // at this point will be checked again later once we know
++    // what it is.
++    const filtered = this.entries.map(entry => {
++      // at this point, we don't know if it's a dir or not.
++      const passFile = this.filterEntry(entry);
++      const passDir = this.filterEntry(entry, true);
++      return (passFile || passDir) ? [entry, passFile, passDir] : false
++    }).filter(e => e);
++
++    // now we stat them all
++    // if it's a dir, and passes as a dir, then recurse
++    // if it's not a dir, but passes as a file, add to set
++    let entryCount = filtered.length;
++    if (entryCount === 0) {
++      this.emit('done', this.result);
++    } else {
++      const then = () => {
++        if (--entryCount === 0) {
++          this.emit('done', this.result);
++        }
++      };
++      filtered.forEach(filt => {
++        const entry = filt[0];
++        const file = filt[1];
++        const dir = filt[2];
++        this.stat({ entry, file, dir }, then);
++      });
++    }
++  }
++
++  onstat ({ st, entry, file, dir, isSymbolicLink }, then) {
++    const abs = this.path + '/' + entry;
++    if (!st.isDirectory()) {
++      if (file) {
++        this.result.add(abs.slice(this.root.length + 1));
++      }
++      then();
++    } else {
++      // is a directory
++      if (dir) {
++        this.walker(entry, { isSymbolicLink, exact: file || this.filterEntry(entry + '/') }, then);
++      } else {
++        then();
++      }
++    }
++  }
++
++  stat ({ entry, file, dir }, then) {
++    const abs = this.path + '/' + entry;
++    fs.lstat(abs, (lstatErr, lstatResult) => {
++      if (lstatErr) {
++        this.emit('error', lstatErr);
++      } else {
++        const isSymbolicLink = lstatResult.isSymbolicLink();
++        if (this.follow && isSymbolicLink) {
++          fs.stat(abs, (statErr, statResult) => {
++            if (statErr) {
++              this.emit('error', statErr);
++            } else {
++              this.onstat({ st: statResult, entry, file, dir, isSymbolicLink }, then);
++            }
++          });
++        } else {
++          this.onstat({ st: lstatResult, entry, file, dir, isSymbolicLink }, then);
++        }
++      }
++    });
++  }
++
++  walkerOpt (entry, opts) {
++    return {
++      path: this.path + '/' + entry,
++      parent: this,
++      ignoreFiles: this.ignoreFiles,
++      follow: this.follow,
++      includeEmpty: this.includeEmpty,
++      ...opts,
++    }
++  }
++
++  walker (entry, opts, then) {
++    new Walker(this.walkerOpt(entry, opts)).on('done', then).start();
++  }
++
++  filterEntry (entry, partial, entryBasename) {
++    let included = true;
++
++    // this = /a/b/c
++    // entry = d
++    // parent /a/b sees c/d
++    if (this.parent && this.parent.filterEntry) {
++      const parentEntry = this.basename + '/' + entry;
++      const parentBasename = entryBasename || entry;
++      included = this.parent.filterEntry(parentEntry, partial, parentBasename);
++      if (!included && !this.exact) {
++        return false
++      }
++    }
++
++    this.ignoreFiles.forEach(f => {
++      if (this.ignoreRules[f]) {
++        this.ignoreRules[f].forEach(rule => {
++          // negation means inclusion
++          // so if it's negated, and already included, no need to check
++          // likewise if it's neither negated nor included
++          if (rule.negate !== included) {
++            const isRelativeRule = entryBasename && rule.globParts.some(part =>
++              part.length <= (part.slice(-1)[0] ? 1 : 2)
++            );
++
++            // first, match against /foo/bar
++            // then, against foo/bar
++            // then, in the case of partials, match with a /
++            //   then, if also the rule is relative, match against basename
++            const match = rule.match('/' + entry) ||
++              rule.match(entry) ||
++              !!partial && (
++                rule.match('/' + entry + '/') ||
++                rule.match(entry + '/') ||
++                rule.negate && (
++                  rule.match('/' + entry, true) ||
++                  rule.match(entry, true)) ||
++                isRelativeRule && (
++                  rule.match('/' + entryBasename + '/') ||
++                  rule.match(entryBasename + '/') ||
++                  rule.negate && (
++                    rule.match('/' + entryBasename, true) ||
++                    rule.match(entryBasename, true))));
++
++            if (match) {
++              included = rule.negate;
++            }
++          }
++        });
++      }
++    });
++
++    return included
++  }
++}
++
++class WalkerSync extends Walker {
++  start () {
++    this.onReaddir(fs.readdirSync(this.path));
++    return this
++  }
++
++  addIgnoreFile (file, then) {
++    const ig = path.resolve(this.path, file);
++    this.onReadIgnoreFile(file, fs.readFileSync(ig, 'utf8'), then);
++  }
++
++  stat ({ entry, file, dir }, then) {
++    const abs = this.path + '/' + entry;
++    let st = fs.lstatSync(abs);
++    const isSymbolicLink = st.isSymbolicLink();
++    if (this.follow && isSymbolicLink) {
++      st = fs.statSync(abs);
++    }
++
++    // console.error('STAT SYNC', {st, entry, file, dir, isSymbolicLink, then})
++    this.onstat({ st, entry, file, dir, isSymbolicLink }, then);
++  }
++
++  walker (entry, opts, then) {
++    new WalkerSync(this.walkerOpt(entry, opts)).start();
++    then();
++  }
++}
++
++const walk$1 = (opts, callback) => {
++  const p = new Promise((resolve, reject) => {
++    new Walker(opts).on('done', resolve).on('error', reject).start();
++  });
++  return callback ? p.then(res => callback(null, res), callback) : p
++};
++
++const walkSync = opts => new WalkerSync(opts).start().result;
++
++var lib$1 = walk$1;
++walk$1.sync = walkSync;
++walk$1.Walker = Walker;
++walk$1.WalkerSync = WalkerSync;
++
++const { Walker: IgnoreWalker } = lib$1;
++const { lstatSync: lstat, readFileSync: readFile } = require$$0;
++const { basename, dirname, extname, join, relative, resolve, sep } = path$1;
++
++// symbols used to represent synthetic rule sets
++const defaultRules = Symbol('npm-packlist.rules.default');
++const strictRules = Symbol('npm-packlist.rules.strict');
++
++// There may be others, but :?|<> are handled by node-tar
++const nameIsBadForWindows = file => /\*/.test(file);
++
++// these are the default rules that are applied to everything except for non-link bundled deps
++const defaults = [
++  '.npmignore',
++  '.gitignore',
++  '**/.git',
++  '**/.svn',
++  '**/.hg',
++  '**/CVS',
++  '**/.git/**',
++  '**/.svn/**',
++  '**/.hg/**',
++  '**/CVS/**',
++  '/.lock-wscript',
++  '/.wafpickle-*',
++  '/build/config.gypi',
++  'npm-debug.log',
++  '**/.npmrc',
++  '.*.swp',
++  '.DS_Store',
++  '**/.DS_Store/**',
++  '._*',
++  '**/._*/**',
++  '*.orig',
++  '/archived-packages/**',
++];
++
++const strictDefaults = [
++  // these are forcibly excluded
++  '/.git',
++];
++
++const normalizePath = (path) => path.split('\\').join('/');
++
++const readOutOfTreeIgnoreFiles = (root, rel, result = []) => {
++  for (const file of ['.npmignore', '.gitignore']) {
++    try {
++      const ignoreContent = readFile(join(root, file), { encoding: 'utf8' });
++      result.push(ignoreContent);
++      // break the loop immediately after reading, this allows us to prioritize
++      // the .npmignore and discard the .gitignore if one is present
++      break
++    } catch (err) {
++      // we ignore ENOENT errors completely because we don't care if the file doesn't exist
++      // but we throw everything else because failing to read a file that does exist is
++      // something that the user likely wants to know about
++      // istanbul ignore next -- we do not need to test a thrown error
++      if (err.code !== 'ENOENT') {
++        throw err
++      }
++    }
++  }
++
++  if (!rel) {
++    return result
++  }
++
++  const firstRel = rel.split(sep, 1)[0];
++  const newRoot = join(root, firstRel);
++  const newRel = relative(newRoot, join(root, rel));
++
++  return readOutOfTreeIgnoreFiles(newRoot, newRel, result)
++};
++
++class PackWalker extends IgnoreWalker {
++  constructor (tree, opts) {
++    const options = {
++      ...opts,
++      includeEmpty: false,
++      follow: false,
++      // we path.resolve() here because ignore-walk doesn't do it and we want full paths
++      path: resolve(opts?.path || tree.path).replace(/\\/g, '/'),
++      ignoreFiles: opts?.ignoreFiles || [
++        defaultRules,
++        'package.json',
++        '.npmignore',
++        '.gitignore',
++        strictRules,
++      ],
++    };
++
++    super(options);
++    this.isPackage = options.isPackage;
++    this.seen = options.seen || new Set();
++    this.tree = tree;
++    this.requiredFiles = options.requiredFiles || [];
++
++    const additionalDefaults = [];
++    if (options.prefix && options.workspaces) {
++      const path = normalizePath(options.path);
++      const prefix = normalizePath(options.prefix);
++      const workspaces = options.workspaces.map((ws) => normalizePath(ws));
++
++      // istanbul ignore else - this does nothing unless we need it to
++      if (path !== prefix && workspaces.includes(path)) {
++        // if path and prefix are not the same directory, and workspaces has path in it
++        // then we know path is a workspace directory. in order to not drop ignore rules
++        // from directories between the workspaces root (prefix) and the workspace itself
++        // (path) we need to find and read those now
++        const relpath = relative(options.prefix, dirname(options.path));
++        additionalDefaults.push(...readOutOfTreeIgnoreFiles(options.prefix, relpath));
++      } else if (path === prefix) {
++        // on the other hand, if the path and prefix are the same, then we ignore workspaces
++        // so that we don't pack a workspace as part of the root project. append them as
++        // normalized relative paths from the root
++        additionalDefaults.push(...workspaces.map((w) => normalizePath(relative(options.path, w))));
++      }
++    }
++
++    // go ahead and inject the default rules now
++    this.injectRules(defaultRules, [...defaults, ...additionalDefaults]);
++
++    if (!this.isPackage) {
++      // if this instance is not a package, then place some strict default rules, and append
++      // known required files for this directory
++      this.injectRules(strictRules, [
++        ...strictDefaults,
++        ...this.requiredFiles.map((file) => `!${file}`),
++      ]);
++    }
++  }
++
++  // overridden method: we intercept the reading of the package.json file here so that we can
++  // process it into both the package.json file rules as well as the strictRules synthetic rule set
++  addIgnoreFile (file, callback) {
++    // if we're adding anything other than package.json, then let ignore-walk handle it
++    if (file !== 'package.json' || !this.isPackage) {
++      return super.addIgnoreFile(file, callback)
++    }
++
++    return this.processPackage(callback)
++  }
++
++  // overridden method: if we're done, but we're a package, then we also need to evaluate bundles
++  // before we actually emit our done event
++  emit (ev, data) {
++    if (ev !== 'done' || !this.isPackage) {
++      return super.emit(ev, data)
++    }
++
++    // we intentionally delay the done event while keeping the function sync here
++    // eslint-disable-next-line promise/catch-or-return, promise/always-return
++    this.gatherBundles().then(() => {
++      super.emit('done', this.result);
++    });
++    return true
++  }
++
++  // overridden method: before actually filtering, we make sure that we've removed the rules for
++  // files that should no longer take effect due to our order of precedence
++  filterEntries () {
++    if (this.ignoreRules['package.json']) {
++      // package.json means no .npmignore or .gitignore
++      this.ignoreRules['.npmignore'] = null;
++      this.ignoreRules['.gitignore'] = null;
++    } else if (this.ignoreRules['.npmignore']) {
++      // .npmignore means no .gitignore
++      this.ignoreRules['.gitignore'] = null;
++    }
++
++    return super.filterEntries()
++  }
++
++  // overridden method: we never want to include anything that isn't a file or directory
++  onstat (opts, callback) {
++    if (!opts.st.isFile() && !opts.st.isDirectory()) {
++      return callback()
++    }
++
++    return super.onstat(opts, callback)
++  }
++
++  // overridden method: we want to refuse to pack files that are invalid, node-tar protects us from
++  // a lot of them but not all
++  stat (opts, callback) {
++    if (nameIsBadForWindows(opts.entry)) {
++      return callback()
++    }
++
++    return super.stat(opts, callback)
++  }
++
++  // overridden method: this is called to create options for a child walker when we step
++  // in to a normal child directory (this will never be a bundle). the default method here
++  // copies the root's `ignoreFiles` value, but we don't want to respect package.json for
++  // subdirectories, so we override it with a list that intentionally omits package.json
++  walkerOpt (entry, opts) {
++    let ignoreFiles = null;
++
++    // however, if we have a tree, and we have workspaces, and the directory we're about
++    // to step into is a workspace, then we _do_ want to respect its package.json
++    if (this.tree.workspaces) {
++      const workspaceDirs = [...this.tree.workspaces.values()]
++        .map((dir) => dir.replace(/\\/g, '/'));
++
++      const entryPath = join(this.path, entry).replace(/\\/g, '/');
++      if (workspaceDirs.includes(entryPath)) {
++        ignoreFiles = [
++          defaultRules,
++          'package.json',
++          '.npmignore',
++          '.gitignore',
++          strictRules,
++        ];
++      }
++    } else {
++      ignoreFiles = [
++        defaultRules,
++        '.npmignore',
++        '.gitignore',
++        strictRules,
++      ];
++    }
++
++    return {
++      ...super.walkerOpt(entry, opts),
++      ignoreFiles,
++      // we map over our own requiredFiles and pass ones that are within this entry
++      requiredFiles: this.requiredFiles
++        .map((file) => {
++          if (relative(file, entry) === '..') {
++            return relative(entry, file).replace(/\\/g, '/')
++          }
++          return false
++        })
++        .filter(Boolean),
++    }
++  }
++
++  // overridden method: we want child walkers to be instances of this class, not ignore-walk
++  walker (entry, opts, callback) {
++    new PackWalker(this.tree, this.walkerOpt(entry, opts)).on('done', callback).start();
++  }
++
++  // overridden method: we use a custom sort method to help compressibility
++  sort (a, b) {
++    // optimize for compressibility
++    // extname, then basename, then locale alphabetically
++    // https://twitter.com/isntitvacant/status/1131094910923231232
++    const exta = extname(a).toLowerCase();
++    const extb = extname(b).toLowerCase();
++    const basea = basename(a).toLowerCase();
++    const baseb = basename(b).toLowerCase();
++
++    return exta.localeCompare(extb, 'en') ||
++      basea.localeCompare(baseb, 'en') ||
++      a.localeCompare(b, 'en')
++  }
++
++  // convenience method: this joins the given rules with newlines, appends a trailing newline,
++  // and calls the internal onReadIgnoreFile method
++  injectRules (filename, rules, callback = () => {}) {
++    this.onReadIgnoreFile(filename, `${rules.join('\n')}\n`, callback);
++  }
++
++  // custom method: this is called by addIgnoreFile when we find a package.json, it uses the
++  // arborist tree to pull both default rules and strict rules for the package
++  processPackage (callback) {
++    const {
++      bin,
++      browser,
++      files,
++      main,
++    } = this.tree.package;
++
++    // rules in these arrays are inverted since they are patterns we want to _not_ ignore
++    const ignores = [];
++    const strict = [
++      ...strictDefaults,
++      '!/package.json',
++      '!/readme{,.*[^~$]}',
++      '!/copying{,.*[^~$]}',
++      '!/license{,.*[^~$]}',
++      '!/licence{,.*[^~$]}',
++      '/.git',
++      '/node_modules',
++      '.npmrc',
++      '/package-lock.json',
++      '/yarn.lock',
++      '/pnpm-lock.yaml',
++    ];
++
++    // if we have a files array in our package, we need to pull rules from it
++    if (files) {
++      for (let file of files) {
++        // invert the rule because these are things we want to include
++        if (file.startsWith('./')) {
++          file = file.slice(1);
++        }
++        if (file.endsWith('/*')) {
++          file += '*';
++        }
++        const inverse = `!${file}`;
++        try {
++          // if an entry in the files array is a specific file, then we need to include it as a
++          // strict requirement for this package. if it's a directory or a pattern, it's a default
++          // pattern instead. this is ugly, but we have to stat to find out if it's a file
++          const stat = lstat(join(this.path, file.replace(/^!+/, '')).replace(/\\/g, '/'));
++          // if we have a file and we know that, it's strictly required
++          if (stat.isFile()) {
++            strict.unshift(inverse);
++            this.requiredFiles.push(file.startsWith('/') ? file.slice(1) : file);
++          } else if (stat.isDirectory()) {
++            // otherwise, it's a default ignore, and since we got here we know it's not a pattern
++            // so we include the directory contents
++            ignores.push(inverse);
++            ignores.push(`${inverse}/**`);
++          }
++          // if the thing exists, but is neither a file or a directory, we don't want it at all
++        } catch (err) {
++          // if lstat throws, then we assume we're looking at a pattern and treat it as a default
++          ignores.push(inverse);
++        }
++      }
++
++      // we prepend a '*' to exclude everything, followed by our inverted file rules
++      // which now mean to include those
++      this.injectRules('package.json', ['*', ...ignores]);
++    }
++
++    // browser is required
++    if (browser) {
++      strict.push(`!/${browser}`);
++    }
++
++    // main is required
++    if (main) {
++      strict.push(`!/${main}`);
++    }
++
++    // each bin is required
++    if (bin) {
++      for (const key in bin) {
++        strict.push(`!/${bin[key]}`);
++      }
++    }
++
++    // and now we add all of the strict rules to our synthetic file
++    this.injectRules(strictRules, strict, callback);
++  }
++
++  // custom method: after we've finished gathering the files for the root package, we call this
++  // before emitting the 'done' event in order to gather all of the files for bundled deps
++  async gatherBundles () {
++    if (this.seen.has(this.tree)) {
++      return
++    }
++
++    // add this node to our seen tracker
++    this.seen.add(this.tree);
++
++    // if we're the project root, then we look at our bundleDependencies, otherwise we got here
++    // because we're a bundled dependency of the root, which means we need to include all prod
++    // and optional dependencies in the bundle
++    let toBundle;
++    if (this.tree.isProjectRoot) {
++      const { bundleDependencies } = this.tree.package;
++      toBundle = bundleDependencies || [];
++    } else {
++      const { dependencies, optionalDependencies } = this.tree.package;
++      toBundle = Object.keys(dependencies || {}).concat(Object.keys(optionalDependencies || {}));
++    }
++
++    for (const dep of toBundle) {
++      const edge = this.tree.edgesOut.get(dep);
++      // no edgeOut = missing node, so skip it. we can't pack it if it's not here
++      // we also refuse to pack peer dependencies and dev dependencies
++      if (!edge || edge.peer || edge.dev) {
++        continue
++      }
++
++      // get a reference to the node we're bundling
++      const node = this.tree.edgesOut.get(dep).to;
++      // if there's no node, this is most likely an optional dependency that hasn't been
++      // installed. just skip it.
++      if (!node) {
++        continue
++      }
++      // we use node.path for the path because we want the location the node was linked to,
++      // not where it actually lives on disk
++      const path = node.path;
++      // but link nodes don't have edgesOut, so we need to pass in the target of the node
++      // in order to make sure we correctly traverse its dependencies
++      const tree = node.target;
++
++      // and start building options to be passed to the walker for this package
++      const walkerOpts = {
++        path,
++        isPackage: true,
++        ignoreFiles: [],
++        seen: this.seen, // pass through seen so we can prevent infinite circular loops
++      };
++
++      // if our node is a link, we apply defaultRules. we don't do this for regular bundled
++      // deps because their .npmignore and .gitignore files are excluded by default and may
++      // override defaults
++      if (node.isLink) {
++        walkerOpts.ignoreFiles.push(defaultRules);
++      }
++
++      // _all_ nodes will follow package.json rules from their package root
++      walkerOpts.ignoreFiles.push('package.json');
++
++      // only link nodes will obey .npmignore or .gitignore
++      if (node.isLink) {
++        walkerOpts.ignoreFiles.push('.npmignore');
++        walkerOpts.ignoreFiles.push('.gitignore');
++      }
++
++      // _all_ nodes follow strict rules
++      walkerOpts.ignoreFiles.push(strictRules);
++
++      // create a walker for this dependency and gather its results
++      const walker = new PackWalker(tree, walkerOpts);
++      const bundled = await new Promise((pResolve, pReject) => {
++        walker.on('error', pReject);
++        walker.on('done', pResolve);
++        walker.start();
++      });
++
++      // now we make sure we have our paths correct from the root, and accumulate everything into
++      // our own result set to deduplicate
++      const relativeFrom = relative(this.root, walker.path);
++      for (const file of bundled) {
++        this.result.add(join(relativeFrom, file).replace(/\\/g, '/'));
++      }
++    }
++  }
++}
++
++const walk = (tree, options, callback) => {
++  if (typeof options === 'function') {
++    callback = options;
++    options = {};
++  }
++  const p = new Promise((pResolve, pReject) => {
++    new PackWalker(tree, { ...options, isPackage: true })
++      .on('done', pResolve).on('error', pReject).start();
++  });
++  return callback ? p.then(res => callback(null, res), callback) : p
++};
++
++var lib = walk;
++walk.Walker = PackWalker;
++
++var packlist = /*@__PURE__*/getDefaultExportFromCjs(lib);
++
++const edgesOut = /* @__PURE__ */ new Map();
++const getNpmPacklist = (absoluteLinkPackagePath, packageJson) => packlist({
++  path: absoluteLinkPackagePath,
++  package: packageJson,
++  // @ts-expect-error outdated types
++  edgesOut
++});
++
++const cwd = process.cwd();
++const cwdPath = (filePath) => path$2.relative(cwd, filePath);
++
++const getPrettyTime = () => (/* @__PURE__ */ new Date()).toLocaleTimeString(
++  void 0,
++  {
++    hour: "numeric",
++    minute: "numeric",
++    second: "numeric",
++    hour12: true
++  }
++);
++
++const waitFor = (test, interval, maxTimeout, errorMessage) => new Promise(async (resolve, reject) => {
++  const startTime = Date.now();
++  let attempts = 0;
++  const maxAttempts = Math.floor(maxTimeout / interval);
++  const attempt = async () => {
++    attempts++;
++    try {
++      const result = await test();
++      if (result) {
++        return resolve();
++      }
++      throw new Error();
++    } catch (error) {
++      const numAttemptsRemaining = maxAttempts - attempts;
++      console.error(red(`  \u{1F615} Error: ${errorMessage}`), " retrying in", yellow(`${interval}ms`), ".", yellow(`${numAttemptsRemaining} attempts remaining`));
++    }
++    if (Date.now() - startTime >= maxTimeout) {
++      console.error(red(`  \u{1F635} Error: ${errorMessage}. Giving up after ${maxAttempts} attempts`));
++      return reject();
++    }
++    setTimeout(attempt, interval);
++  };
++  await attempt();
++});
++
++const hardlinkPackage = async (linkPath, absoluteLinkPackagePath, packageJson, publishFilesPromise = getNpmPacklist(
++  absoluteLinkPackagePath,
++  packageJson
++), interval = 500, maxBuildTime = 3e4) => {
++  const [oldPublishFiles, publishFiles] = await Promise.all([
++    getNpmPacklist(
++      linkPath,
++      /**
++       * This is evaluated in the context of the new package.json since that
++       * defines which files belong to the package.
++       */
++      packageJson
++    ),
++    publishFilesPromise
++  ]);
++  console.log(`Linking ${magenta(packageJson.name)} in publish mode:`);
++  await Promise.all(publishFiles.map(async (file) => {
++    const sourcePath = path$2.join(absoluteLinkPackagePath, file);
++    await waitFor(
++      async () => await fsExists(sourcePath),
++      interval,
++      maxBuildTime,
++      ""
++    );
++  }));
++  await Promise.all(
++    publishFiles.map(async (file) => {
++      const sourcePath = path$2.join(absoluteLinkPackagePath, file);
++      const targetPath = path$2.join(linkPath, file);
++      await fs$2.mkdir(
++        path$2.dirname(targetPath),
++        { recursive: true }
++      );
++      try {
++        await hardlink(sourcePath, targetPath);
++      } catch (error) {
++        console.warn(
++          `  ${red("\u2716 Failed to link")}`,
++          cyan(cwdPath(targetPath)),
++          "\u2192",
++          cyan(cwdPath(sourcePath)),
++          error.message ?? error
++        );
++        return;
++      }
++      const fileIndex = oldPublishFiles.indexOf(file);
++      if (fileIndex > -1) {
++        oldPublishFiles.splice(fileIndex, 1);
++      }
++      console.log(
++        `  ${green("\u2714")}`,
++        cyan(cwdPath(targetPath)),
++        "\u2192",
++        cyan(cwdPath(sourcePath))
++      );
++    })
++  );
++  await Promise.all(
++    oldPublishFiles.map(async (file) => {
++      console.log(cyan(`  \u{1F6AE} ${file} no longer in publish list, deleting it. If you did not intend to do this, something probably went wrong. See https://github.com/privatenumber/link?tab=readme-ov-file#publish-mode`));
++      await fs$2.rm(path$2.join(linkPath, file), {
++        force: true
++      });
++    })
++  );
++};
++
++const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
++
++const isValidSetup = async (linkPath, expectedPrefix) => {
++  const linkPathStat = await fs$2.stat(linkPath).catch(() => null);
++  if (!linkPathStat?.isDirectory()) {
++    return false;
++  }
++  const linkPathReal = await fs$2.realpath(linkPath);
++  return linkPathReal.startsWith(expectedPrefix);
++};
++const linkPublishMode = async (basePackagePath, linkPackagePath, watchMode, litmus, delay = 2e3, interval = 500, maxBuildTime = 3e4) => {
++  const absoluteLinkPackagePath = path$2.resolve(basePackagePath, linkPackagePath);
++  const packageJson = await readPackageJson(absoluteLinkPackagePath);
++  const expectedPrefix = path$2.join(basePackagePath, "node_modules/");
++  const linkPath = path$2.join(expectedPrefix, packageJson.name);
++  if (!await isValidSetup(linkPath, expectedPrefix)) {
++    console.error(
++      defaultOutdent`
++			Error: Package ${magenta(packageJson.name)} is not set up
++	
++			${bold("Setup instructions")}
++			1. In the Dependency package, create a tarball:
++			  ${dim("$ npm pack")}
++	
++			2. In the Consuming package, install the tarball and link the Dependency:
++			  ${dim("$ npm install --no-save ")}
++			  ${dim("$ npx link publish ")}
++	
++			3. Start developing!
++	
++			Learn more: https://npmjs.com/link
++			`
++    );
++    return;
++  }
++  const debouncedSleepForDelay = pDebounce(sleep, delay);
++  const debouncedHardlinkPackage = debounce$1(hardlinkPackage, delay);
++  await hardlinkPackage(
++    linkPath,
++    absoluteLinkPackagePath,
++    packageJson
++  );
++  if (watchMode) {
++    const globOptions = {
++      globstar: true,
++      extended: true
++    };
++    const ignoreFiles = [
++      // Files
++      "**/{npm-debug.log,*.orig,package-lock.json,yarn.lock,pnpm-lock.yaml}",
++      // Folders
++      "**/node_modules/**",
++      // Hidden files
++      "**/.{_*,*.swp,DS_Store,gitignore,npmrc,npmignore,lock-wscript,.wafpickle-*}",
++      // Hidden folders
++      "**/.{_*,git,svn,hg,CVS}/**"
++    ].map((glob) => globToRegexp$1(glob, globOptions));
++    const watcher = fs$2.watch(
++      absoluteLinkPackagePath,
++      { recursive: true }
++    );
++    for await (const { eventType, filename } of watcher) {
++      if (!filename) {
++        continue;
++      }
++      const shouldIgnore = ignoreFiles.some((ignoreFile) => ignoreFile.test(filename));
++      if (shouldIgnore) {
++        continue;
++      }
++      await debouncedSleepForDelay(delay);
++      if (litmus) {
++        await waitFor(
++          async () => fsExists(path$2.join(absoluteLinkPackagePath, litmus)),
++          interval,
++          maxBuildTime,
++          ""
++        );
++      }
++      const publishFiles = await getNpmPacklist(
++        absoluteLinkPackagePath,
++        packageJson
++      );
++      if (!publishFiles.includes(filename)) {
++        continue;
++      }
++      console.log(`
++${dim(getPrettyTime())}`, "Detected", yellow(eventType), "in", `${cyan(cwdPath(path$2.join(absoluteLinkPackagePath, filename)))}
++`);
++      await debouncedHardlinkPackage(
++        linkPath,
++        absoluteLinkPackagePath,
++        packageJson,
++        publishFiles
++      );
++    }
++  }
++};
++
++const publishCommand = G({
++  name: "publish",
++  parameters: [""],
++  flags: {
++    watch: {
++      type: Boolean,
++      alias: "w",
++      description: "Watch for changes in the package and automatically relink"
++    },
++    litmus: {
++      type: String,
++      alias: "l",
++      description: "If using the --watch flag, look for this file in the linked package to see if it's ready to re-link"
++    },
++    delay: {
++      type: Number,
++      alias: "d",
++      description: "If using the --watch flag without the litmus flag, wait this amount of time (in ms) after detecting changes before refreshing the packlist and re-linking",
++      default: 2e3
++    },
++    interval: {
++      type: Number,
++      alias: "i",
++      description: "If using the --watch flag, poll for completed builds at this frequency (in ms)",
++      default: 500
++    },
++    maxBuildTime: {
++      type: Number,
++      alias: "m",
++      description: "If using the --watch flag, the maximum amount of time to wait for all expected files to appear before re-linking",
++      default: 3e4
++    }
++  },
++  help: {
++    description: "Link a package to simulate an environment similar to `npm install`"
++  }
++});
++const publishHandler = async (cwdProjectPath, packagePaths, flags) => {
++  if (packagePaths.length > 0) {
++    await Promise.all(
++      packagePaths.map(
++        (linkPackagePath) => linkPublishMode(
++          cwdProjectPath,
++          linkPackagePath,
++          flags.watch,
++          flags.litmus,
++          flags.delay,
++          flags.interval,
++          flags.maxBuildTime
++        )
++      )
++    );
++  }
++};
++
++(async () => {
++  const argv = Z({
++    name: "link",
++    parameters: ["[package paths...]"],
++    flags: {
++      deep: {
++        type: Boolean,
++        alias: "d",
++        description: "Run `npx link` on dependencies if they have a link.config.json"
++      }
++    },
++    help: {
++      description: "A better `npm link` -- symlink local dependencies to the current project",
++      render: (nodes, renderers) => {
++        nodes[0].data = "npx link\n";
++        nodes.splice(2, 0, {
++          type: "section",
++          data: {
++            title: "Website",
++            body: "https://www.npmjs.com/package/link"
++          }
++        });
++        return renderers.render(nodes);
++      }
++    },
++    commands: [
++      publishCommand
++    ]
++  });
++  const cwdProjectPath = await fs$1.realpath(process.cwd());
++  if (!argv.command) {
++    const { packagePaths } = argv._;
++    if (packagePaths.length > 0) {
++      await Promise.all(
++        packagePaths.map(
++          (linkPackagePath) => linkPackage(
++            cwdProjectPath,
++            linkPackagePath,
++            argv.flags
++          )
++        )
++      );
++      return;
++    }
++    const config = await loadConfig(cwdProjectPath);
++    if (!config) {
++      console.warn(
++        defaultOutdent`
+ 				Warning: Config file "link.config.json" not found in current directory.
+ 							Read the documentation to learn more: https://www.npmjs.com/package/link
+-				`),u.showHelp();return}await Vu(e,D,{deep:u.flags.deep})}})().catch(u=>{console.error("Error:",u.message),process.exit(1)});
++				`
++      );
++      argv.showHelp();
++      return;
++    }
++    await linkFromConfig(
++      cwdProjectPath,
++      config,
++      {
++        deep: argv.flags.deep
++      }
++    );
++  } else if (argv.command === "publish") {
++    await publishHandler(
++      cwdProjectPath,
++      argv._,
++      argv.flags
++    );
++  }
++})().catch((error) => {
++  console.error("Error:", error.message);
++  process.exit(1);
++});
+diff --git a/node_modules/link/dist/libs/fsevents.node b/node_modules/link/dist/libs/fsevents.node
+new file mode 100755
+index 0000000..1cc3345
+Binary files /dev/null and b/node_modules/link/dist/libs/fsevents.node differ
diff --git a/patches/react-native-modal+13.0.1.patch b/patches/react-native-modal+13.0.1.patch
index cc9c8531e3a3..885faec875bc 100644
--- a/patches/react-native-modal+13.0.1.patch
+++ b/patches/react-native-modal+13.0.1.patch
@@ -11,10 +11,22 @@ index b63bcfc..bd6419e 100644
      buildPanResponder: () => void;
      getAccDistancePerDirection: (gestureState: PanResponderGestureState) => number;
 diff --git a/node_modules/react-native-modal/dist/modal.js b/node_modules/react-native-modal/dist/modal.js
-index 80f4e75..5a58eae 100644
+index 80f4e75..602cdff 100644
 --- a/node_modules/react-native-modal/dist/modal.js
 +++ b/node_modules/react-native-modal/dist/modal.js
-@@ -75,6 +75,13 @@ export class ReactNativeModal extends React.Component {
+@@ -59,6 +59,11 @@ export class ReactNativeModal extends React.Component {
+             deviceHeight: Dimensions.get('window').height,
+             isSwipeable: !!this.props.swipeDirection,
+             pan: null,
++            backdrop: {
++                prevOpacity: 0,
++                opacity: 0,
++            },
++            contentAnimation: {},
+         };
+         this.isTransitioning = false;
+         this.inSwipeClosingState = false;
+@@ -75,6 +80,13 @@ export class ReactNativeModal extends React.Component {
              }
              return false;
          };
@@ -28,7 +40,202 @@ index 80f4e75..5a58eae 100644
          this.shouldPropagateSwipe = (evt, gestureState) => {
              return typeof this.props.propagateSwipe === 'function'
                  ? this.props.propagateSwipe(evt, gestureState)
-@@ -453,10 +460,18 @@ export class ReactNativeModal extends React.Component {
+@@ -134,10 +146,12 @@ export class ReactNativeModal extends React.Component {
+                     if (this.isSwipeDirectionAllowed(gestureState)) {
+                         // Dim the background while swiping the modal
+                         const newOpacityFactor = 1 - this.calcDistancePercentage(gestureState);
+-                        this.backdropRef &&
+-                            this.backdropRef.transitionTo({
+-                                opacity: this.props.backdropOpacity * newOpacityFactor,
+-                            });
++                        this.setState((prevState) => ({
++                            backdrop: {
++                                prevOpacity: prevState.backdrop.opacity,
++                                opacity: newOpacityFactor * this.props.backdropOpacity,
++                            }
++                        }))
+                         animEvt(evt, gestureState);
+                         if (this.props.onSwipeMove) {
+                             this.props.onSwipeMove(newOpacityFactor, gestureState);
+@@ -185,11 +199,13 @@ export class ReactNativeModal extends React.Component {
+                     if (this.props.onSwipeCancel) {
+                         this.props.onSwipeCancel(gestureState);
+                     }
+-                    if (this.backdropRef) {
+-                        this.backdropRef.transitionTo({
++                    this.setState((prevState) => ({
++                        backdrop: {
++                            prevOpacity: prevState.backdrop.opacity,
+                             opacity: this.props.backdropOpacity,
+-                        });
+-                    }
++                            duration: undefined,
++                        }
++                    }))
+                     Animated.spring(this.state.pan, {
+                         toValue: { x: 0, y: 0 },
+                         bounciness: 0,
+@@ -300,40 +316,53 @@ export class ReactNativeModal extends React.Component {
+                 }
+             }
+         };
++        this.onContentAnimationEnd = () => {
++            this.isTransitioning = false;
++
++            if (this.interactionHandle) {
++                InteractionManager.clearInteractionHandle(this.interactionHandle);
++                this.interactionHandle = null;
++            }
++            if (!this.props.isVisible) {
++                this.setState({
++                    showContent: false,
++                }, () => {
++                    this.setState({
++                        isVisible: false,
++                    }, () => {
++                        this.props.onModalHide();
++                    });
++                });
++            } else {
++                this.props.onModalShow();
++            }
++        }
+         this.open = () => {
+             if (this.isTransitioning) {
+                 return;
+             }
+             this.isTransitioning = true;
+-            if (this.backdropRef) {
+-                this.backdropRef.transitionTo({ opacity: this.props.backdropOpacity }, this.props.backdropTransitionInTiming);
+-            }
++
++            this.setState((prevState) => ({
++                backdrop: {
++                    prevOpacity: prevState.backdrop.opacity,
++                    opacity: this.props.backdropOpacity,
++                    duration: this.props.backdropTransitionInTiming,
++                },
++                contentAnimation: {
++                    animation: this.animationIn,
++                    duration: this.props.animationInTiming,
++                }
++            }))
+             // This is for resetting the pan position,otherwise the modal gets stuck
+             // at the last released position when you try to open it.
+             // TODO: Could certainly be improved - no idea for the moment.
+             if (this.state.isSwipeable) {
+                 this.state.pan.setValue({ x: 0, y: 0 });
+             }
+-            if (this.contentRef) {
+-                this.props.onModalWillShow && this.props.onModalWillShow();
+-                if (this.interactionHandle == null) {
+-                    this.interactionHandle = InteractionManager.createInteractionHandle();
+-                }
+-                this.contentRef
+-                    .animate(this.animationIn, this.props.animationInTiming)
+-                    .then(() => {
+-                    this.isTransitioning = false;
+-                    if (this.interactionHandle) {
+-                        InteractionManager.clearInteractionHandle(this.interactionHandle);
+-                        this.interactionHandle = null;
+-                    }
+-                    if (!this.props.isVisible) {
+-                        this.close();
+-                    }
+-                    else {
+-                        this.props.onModalShow();
+-                    }
+-                });
++            this.props.onModalWillShow && this.props.onModalWillShow();
++            if (this.interactionHandle === null) {
++                this.interactionHandle = InteractionManager.createInteractionHandle();
+             }
+         };
+         this.close = () => {
+@@ -341,9 +370,6 @@ export class ReactNativeModal extends React.Component {
+                 return;
+             }
+             this.isTransitioning = true;
+-            if (this.backdropRef) {
+-                this.backdropRef.transitionTo({ opacity: 0 }, this.props.backdropTransitionOutTiming);
+-            }
+             let animationOut = this.animationOut;
+             if (this.inSwipeClosingState) {
+                 this.inSwipeClosingState = false;
+@@ -360,35 +386,22 @@ export class ReactNativeModal extends React.Component {
+                     animationOut = 'slideOutLeft';
+                 }
+             }
+-            if (this.contentRef) {
+-                this.props.onModalWillHide && this.props.onModalWillHide();
+-                if (this.interactionHandle == null) {
+-                    this.interactionHandle = InteractionManager.createInteractionHandle();
+-                }
+-                this.contentRef
+-                    .animate(animationOut, this.props.animationOutTiming)
+-                    .then(() => {
+-                    this.isTransitioning = false;
+-                    if (this.interactionHandle) {
+-                        InteractionManager.clearInteractionHandle(this.interactionHandle);
+-                        this.interactionHandle = null;
+-                    }
+-                    if (this.props.isVisible) {
+-                        this.open();
+-                    }
+-                    else {
+-                        this.setState({
+-                            showContent: false,
+-                        }, () => {
+-                            this.setState({
+-                                isVisible: false,
+-                            }, () => {
+-                                this.props.onModalHide();
+-                            });
+-                        });
+-                    }
+-                });
++            this.props.onModalWillHide && this.props.onModalWillHide();
++            if (this.interactionHandle == null) {
++                this.interactionHandle = InteractionManager.createInteractionHandle();
+             }
++
++            this.setState((prevState) => ({
++                backdrop: {
++                    prevOpacity: prevState.backdrop.opacity,
++                    opacity: 0,
++                    duration: this.props.backdropTransitionOutTiming,
++                },
++                contentAnimation: {
++                    animation: animationOut,
++                    duration: this.props.animationOutTiming,
++                }
++            }))
+         };
+         this.makeBackdrop = () => {
+             if (!this.props.hasBackdrop) {
+@@ -409,9 +422,20 @@ export class ReactNativeModal extends React.Component {
+                         : 'transparent',
+                 },
+             ];
++            const animation = this.state.backdrop.opacity !== this.state.backdrop.prevOpacity ? {
++                from: {
++                    opacity: this.state.backdrop.prevOpacity,
++                },
++                to: {
++                    opacity: this.state.backdrop.opacity,
++                }
++            } : undefined;
+             const backdropWrapper = (React.createElement(animatable.View, { ref: ref => (this.backdropRef = ref), useNativeDriver: useNativeDriverForBackdrop !== undefined
+                     ? useNativeDriverForBackdrop
+-                    : useNativeDriver, style: [styles.backdrop, backdropComputedStyle] }, hasCustomBackdrop && customBackdrop));
++                    : useNativeDriver, 
++                    duration: this.state.backdrop.duration,
++                    animation,
++                    style: [styles.backdrop, backdropComputedStyle] }, hasCustomBackdrop && customBackdrop));
+             if (hasCustomBackdrop) {
+                 // The user will handle backdrop presses himself
+                 return backdropWrapper;
+@@ -453,10 +477,18 @@ export class ReactNativeModal extends React.Component {
          if (this.state.isVisible) {
              this.open();
          }
@@ -48,7 +255,7 @@ index 80f4e75..5a58eae 100644
          if (this.didUpdateDimensionsEmitter) {
              this.didUpdateDimensionsEmitter.remove();
          }
-@@ -464,6 +479,9 @@ export class ReactNativeModal extends React.Component {
+@@ -464,6 +496,9 @@ export class ReactNativeModal extends React.Component {
              InteractionManager.clearInteractionHandle(this.interactionHandle);
              this.interactionHandle = null;
          }
@@ -58,7 +265,40 @@ index 80f4e75..5a58eae 100644
      }
      componentDidUpdate(prevProps) {
          // If the animations have been changed then rebuild them to make sure we're
-@@ -525,7 +543,7 @@ export class ReactNativeModal extends React.Component {
+@@ -475,9 +510,14 @@ export class ReactNativeModal extends React.Component {
+             this.animationOut = animationOut;
+         }
+         // If backdrop opacity has been changed then make sure to update it
+-        if (this.props.backdropOpacity !== prevProps.backdropOpacity &&
+-            this.backdropRef) {
+-            this.backdropRef.transitionTo({ opacity: this.props.backdropOpacity }, this.props.backdropTransitionInTiming);
++        if (this.props.backdropOpacity !== prevProps.backdropOpacity) {
++            this.setState((prevState) => ({
++                backdrop: {
++                    prevOpacity: prevState.backdrop.opacity,
++                    opacity: this.props.backdropOpacity,
++                    duration: this.props.backdropTransitionInTiming,
++                }
++            }))
+         }
+         // On modal open request, we slide the view up and fade in the backdrop
+         if (this.props.isVisible && !prevProps.isVisible) {
+@@ -515,7 +555,13 @@ export class ReactNativeModal extends React.Component {
+         const _children = this.props.hideModalContentWhileAnimating &&
+             this.props.useNativeDriver &&
+             !this.state.showContent ? (React.createElement(animatable.View, null)) : (children);
+-        const containerView = (React.createElement(animatable.View, Object.assign({}, panHandlers, { ref: ref => (this.contentRef = ref), style: [panPosition, computedStyle], pointerEvents: "box-none", useNativeDriver: useNativeDriver }, containerProps), _children));
++        const containerView = (React.createElement(animatable.View, Object.assign({}, panHandlers, {
++            ref: ref => (this.contentRef = ref), style: [panPosition, computedStyle],
++            pointerEvents: "box-none", useNativeDriver: useNativeDriver,
++            animation: this.state.contentAnimation.animation,
++            duration: this.state.contentAnimation.duration,
++            onAnimationEnd: this.onContentAnimationEnd,
++        }, containerProps), _children));
+         // If coverScreen is set to false by the user
+         // we render the modal inside the parent view directly
+         if (!coverScreen && this.state.isVisible) {
+@@ -525,7 +571,7 @@ export class ReactNativeModal extends React.Component {
          }
          return (React.createElement(Modal, Object.assign({ transparent: true, animationType: 'none', visible: this.state.isVisible, onRequestClose: onBackButtonPress }, otherProps),
              this.makeBackdrop(),
diff --git a/src/CONST.ts b/src/CONST.ts
index 9d7f245b5464..2d1396e7bfa6 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -86,6 +86,7 @@ const CONST = {
     DEFAULT_TABLE_NAME: 'keyvaluepairs',
     DEFAULT_ONYX_DUMP_FILE_NAME: 'onyx-state.txt',
     DEFAULT_POLICY_ROOM_CHAT_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL],
+    DISABLED_MAX_EXPENSE_VALUE: 10000000000,
 
     // Note: Group and Self-DM excluded as these are not tied to a Workspace
     WORKSPACE_ROOM_TYPES: [chatTypes.POLICY_ADMINS, chatTypes.POLICY_ANNOUNCE, chatTypes.DOMAIN_ALL, chatTypes.POLICY_ROOM, chatTypes.POLICY_EXPENSE_CHAT],
@@ -355,6 +356,7 @@ const CONST = {
             OPEN: 'OPEN',
         },
         MAX_LENGTH: {
+            FULL_SSN: 9,
             SSN: 4,
             ZIP_CODE: 10,
         },
@@ -381,6 +383,7 @@ const CONST = {
         REPORT_FIELDS_FEATURE: 'reportFieldsFeature',
         WORKSPACE_FEEDS: 'workspaceFeeds',
         NETSUITE_USA_TAX: 'netsuiteUsaTax',
+        WORKSPACE_RULES: 'workspaceRules',
     },
     BUTTON_STATES: {
         DEFAULT: 'default',
@@ -708,6 +711,7 @@ const CONST = {
                 FORWARDED: 'FORWARDED', // OldDot Action
                 HOLD: 'HOLD',
                 HOLD_COMMENT: 'HOLDCOMMENT',
+                INTEGRATION_SYNC_FAILED: 'INTEGRATIONSYNCFAILED',
                 IOU: 'IOU',
                 INTEGRATIONS_MESSAGE: 'INTEGRATIONSMESSAGE', // OldDot Action
                 MANAGER_ATTACH_RECEIPT: 'MANAGERATTACHRECEIPT', // OldDot Action
@@ -972,10 +976,11 @@ const CONST = {
         HOMEPAGE_INITIAL_RENDER: 'homepage_initial_render',
         REPORT_INITIAL_RENDER: 'report_initial_render',
         SWITCH_REPORT: 'switch_report',
-        SWITCH_REPORT_FROM_PREVIEW: 'switch_report_from_preview',
-        SWITCH_REPORT_THREAD: 'switch_report_thread',
+        OPEN_REPORT_FROM_PREVIEW: 'open_report_from_preview',
+        OPEN_REPORT_THREAD: 'open_report_thread',
         SIDEBAR_LOADED: 'sidebar_loaded',
         LOAD_SEARCH_OPTIONS: 'load_search_options',
+        MESSAGE_SENT: 'message_sent',
         COLD: 'cold',
         WARM: 'warm',
         REPORT_ACTION_ITEM_LAYOUT_DEBOUNCE_TIME: 1500,
@@ -1188,6 +1193,7 @@ const CONST = {
         VISIBLE_PASSWORD: 'visible-password',
         ASCII_CAPABLE: 'ascii-capable',
         NUMBER_PAD: 'number-pad',
+        DECIMAL_PAD: 'decimal-pad',
     },
 
     INPUT_MODE: {
@@ -2079,9 +2085,11 @@ const CONST = {
             ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled',
             ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled',
             ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled',
+            ARE_COMPANY_CARDS_ENABLED: 'areCompanyCardsEnabled',
             ARE_EXPENSIFY_CARDS_ENABLED: 'areExpensifyCardsEnabled',
             ARE_INVOICES_ENABLED: 'areInvoicesEnabled',
             ARE_TAXES_ENABLED: 'tax',
+            ARE_RULES_ENABLED: 'areRulesEnabled',
         },
         DEFAULT_CATEGORIES: [
             'Advertising',
@@ -2421,9 +2429,11 @@ const CONST = {
         WORKSPACE_MEMBERS: 'WorkspaceManageMembers',
         WORKSPACE_EXPENSIFY_CARD: 'WorkspaceExpensifyCard',
         WORKSPACE_WORKFLOWS: 'WorkspaceWorkflows',
+        WORKSPACE_COMPANY_CARDS: 'WorkspaceCompanyCards',
         WORKSPACE_BANK_ACCOUNT: 'WorkspaceBankAccount',
         WORKSPACE_SETTINGS: 'WorkspaceSettings',
         WORKSPACE_FEATURES: 'WorkspaceFeatures',
+        WORKSPACE_RULES: 'WorkspaceRules',
     },
     get EXPENSIFY_EMAILS() {
         return [
@@ -4175,7 +4185,7 @@ const CONST = {
 
     VIDEO_PLAYER: {
         POPOVER_Y_OFFSET: -30,
-        PLAYBACK_SPEEDS: [0.25, 0.5, 1, 1.5, 2],
+        PLAYBACK_SPEEDS: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
         HIDE_TIME_TEXT_WIDTH: 250,
         MIN_WIDTH: 170,
         MIN_HEIGHT: 120,
@@ -5471,6 +5481,22 @@ const CONST = {
                 description: 'workspace.upgrade.taxCodes.description' as const,
                 icon: 'Coins',
             },
+            companyCards: {
+                id: 'companyCards' as const,
+                alias: 'company-cards',
+                name: 'Company Cards',
+                title: 'workspace.upgrade.companyCards.title' as const,
+                description: 'workspace.upgrade.companyCards.description' as const,
+                icon: 'CompanyCard',
+            },
+            rules: {
+                id: 'rules' as const,
+                alias: 'rules',
+                name: 'Rules',
+                title: 'workspace.upgrade.rules.title' as const,
+                description: 'workspace.upgrade.rules.description' as const,
+                icon: 'Rules',
+            },
         };
     },
     REPORT_FIELD_TYPES: {
diff --git a/src/Expensify.tsx b/src/Expensify.tsx
index 620243440384..8a2ef4a2b2f4 100644
--- a/src/Expensify.tsx
+++ b/src/Expensify.tsx
@@ -256,6 +256,7 @@ function Expensify({
         
             {shouldInit && (
                 <>
diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts
index b7b6cf53a176..8d60a5b57511 100755
--- a/src/ONYXKEYS.ts
+++ b/src/ONYXKEYS.ts
@@ -458,6 +458,7 @@ const ONYXKEYS = {
         // Shared NVPs
         /** Collection of objects where each object represents the owner of the workspace that is past due billing AND the user is a member of. */
         SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END: 'sharedNVP_private_billingGracePeriodEnd_',
+        SHARED_NVP_PRIVATE_DOMAIN_MEMBER: 'sharedNVP_private_domain_member_',
 
         /**
          * Stores the card list for a given fundID and feed in the format: cards__
@@ -749,6 +750,7 @@ type OnyxCollectionValuesMapping = {
     [ONYXKEYS.COLLECTION.POLICY_CONNECTION_SYNC_PROGRESS]: OnyxTypes.PolicyConnectionSyncProgress;
     [ONYXKEYS.COLLECTION.SNAPSHOT]: OnyxTypes.SearchResults;
     [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END]: OnyxTypes.BillingGraceEndPeriod;
+    [ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER]: OnyxTypes.CompanyCards;
     [ONYXKEYS.COLLECTION.PRIVATE_EXPENSIFY_CARD_SETTINGS]: OnyxTypes.ExpensifyCardSettings;
     [ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST]: OnyxTypes.WorkspaceCardsList;
     [ONYXKEYS.COLLECTION.EXPENSIFY_CARD_CONTINUOUS_RECONCILIATION_CONNECTION]: OnyxTypes.PolicyConnectionName;
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 893fd59e38b4..47a2ad76209e 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -44,6 +44,7 @@ const ROUTES = {
     SEARCH_ADVANCED_FILTERS_MERCHANT: 'search/filters/merchant',
     SEARCH_ADVANCED_FILTERS_DESCRIPTION: 'search/filters/description',
     SEARCH_ADVANCED_FILTERS_REPORT_ID: 'search/filters/reportID',
+    SEARCH_ADVANCED_FILTERS_AMOUNT: 'search/filters/amount',
     SEARCH_ADVANCED_FILTERS_CATEGORY: 'search/filters/category',
     SEARCH_ADVANCED_FILTERS_KEYWORD: 'search/filters/keyword',
     SEARCH_ADVANCED_FILTERS_CARD: 'search/filters/card',
@@ -349,7 +350,7 @@ const ROUTES = {
     },
     ROOM_INVITE: {
         route: 'r/:reportID/invite/:role?',
-        getRoute: (reportID: string, role?: string) => `r/${reportID}/invite/${role}` as const,
+        getRoute: (reportID: string, role?: string) => `r/${reportID}/invite/${role ?? ''}` as const,
     },
     MONEY_REQUEST_HOLD_REASON: {
         route: ':type/edit/reason/:transactionID?',
@@ -927,6 +928,14 @@ const ROUTES = {
         route: 'settings/workspaces/:policyID/expensify-card/settings/frequency',
         getRoute: (policyID: string) => `settings/workspaces/${policyID}/expensify-card/settings/frequency` as const,
     },
+    WORKSPACE_COMPANY_CARDS: {
+        route: 'settings/workspaces/:policyID/company-cards',
+        getRoute: (policyID: string) => `settings/workspaces/${policyID}/company-cards` as const,
+    },
+    WORKSPACE_RULES: {
+        route: 'settings/workspaces/:policyID/rules',
+        getRoute: (policyID: string) => `settings/workspaces/${policyID}/rules` as const,
+    },
     WORKSPACE_DISTANCE_RATES: {
         route: 'settings/workspaces/:policyID/distance-rates',
         getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index 30adc5f89d08..686a752ad360 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -37,6 +37,7 @@ const SCREENS = {
         ADVANCED_FILTERS_DESCRIPTION_RHP: 'Search_Advanced_Filters_Description_RHP',
         ADVANCED_FILTERS_MERCHANT_RHP: 'Search_Advanced_Filters_Merchant_RHP',
         ADVANCED_FILTERS_REPORT_ID_RHP: 'Search_Advanced_Filters_ReportID_RHP',
+        ADVANCED_FILTERS_AMOUNT_RHP: 'Search_Advanced_Filters_Amount_RHP',
         ADVANCED_FILTERS_CATEGORY_RHP: 'Search_Advanced_Filters_Category_RHP',
         ADVANCED_FILTERS_KEYWORD_RHP: 'Search_Advanced_Filters_Keyword_RHP',
         ADVANCED_FILTERS_CARD_RHP: 'Search_Advanced_Filters_Card_RHP',
@@ -363,6 +364,7 @@ const SCREENS = {
         RATE_AND_UNIT: 'Workspace_RateAndUnit',
         RATE_AND_UNIT_RATE: 'Workspace_RateAndUnit_Rate',
         RATE_AND_UNIT_UNIT: 'Workspace_RateAndUnit_Unit',
+        COMPANY_CARDS: 'Workspace_CompanyCards',
         EXPENSIFY_CARD: 'Workspace_ExpensifyCard',
         EXPENSIFY_CARD_DETAILS: 'Workspace_ExpensifyCard_Details',
         EXPENSIFY_CARD_LIMIT: 'Workspace_ExpensifyCard_Limit',
@@ -440,6 +442,7 @@ const SCREENS = {
         DISTANCE_RATE_TAX_RECLAIMABLE_ON_EDIT: 'Distance_Rate_Tax_Reclaimable_On_Edit',
         DISTANCE_RATE_TAX_RATE_EDIT: 'Distance_Rate_Tax_Rate_Edit',
         UPGRADE: 'Workspace_Upgrade',
+        RULES: 'Policy_Rules',
     },
 
     EDIT_REQUEST: {
diff --git a/src/components/AmountWithoutCurrencyForm.tsx b/src/components/AmountWithoutCurrencyForm.tsx
new file mode 100644
index 000000000000..78b7c84ecb54
--- /dev/null
+++ b/src/components/AmountWithoutCurrencyForm.tsx
@@ -0,0 +1,66 @@
+import React, {useCallback, useMemo} from 'react';
+import type {ForwardedRef} from 'react';
+import useLocalize from '@hooks/useLocalize';
+import {addLeadingZero, replaceAllDigits, replaceCommasWithPeriod, stripSpacesFromAmount, validateAmount} from '@libs/MoneyRequestUtils';
+import CONST from '@src/CONST';
+import TextInput from './TextInput';
+import type {BaseTextInputProps, BaseTextInputRef} from './TextInput/BaseTextInput/types';
+
+type AmountFormProps = {
+    /** Amount supplied by the FormProvider */
+    value?: string;
+
+    /** Callback to update the amount in the FormProvider */
+    onInputChange?: (value: string) => void;
+} & Partial;
+
+function AmountWithoutCurrencyForm(
+    {value: amount, onInputChange, inputID, name, defaultValue, accessibilityLabel, role, label, ...rest}: AmountFormProps,
+    ref: ForwardedRef,
+) {
+    const {toLocaleDigit} = useLocalize();
+
+    const currentAmount = useMemo(() => (typeof amount === 'string' ? amount : ''), [amount]);
+
+    /**
+     * Sets the selection and the amount accordingly to the value passed to the input
+     * @param newAmount - Changed amount from user input
+     */
+    const setNewAmount = useCallback(
+        (newAmount: string) => {
+            // Remove spaces from the newAmount value because Safari on iOS adds spaces when pasting a copied value
+            // More info: https://github.com/Expensify/App/issues/16974
+            const newAmountWithoutSpaces = stripSpacesFromAmount(newAmount);
+            const replacedCommasAmount = replaceCommasWithPeriod(newAmountWithoutSpaces);
+            const withLeadingZero = addLeadingZero(replacedCommasAmount);
+            if (!validateAmount(withLeadingZero, 2)) {
+                return;
+            }
+            onInputChange?.(withLeadingZero);
+        },
+        [onInputChange],
+    );
+
+    const formattedAmount = replaceAllDigits(currentAmount, toLocaleDigit);
+
+    return (
+        
+    );
+}
+
+AmountWithoutCurrencyForm.displayName = 'AmountWithoutCurrencyForm';
+
+export default React.forwardRef(AmountWithoutCurrencyForm);
diff --git a/src/components/Composer/index.native.tsx b/src/components/Composer/index.native.tsx
index b801743732bc..a2fcae901681 100644
--- a/src/components/Composer/index.native.tsx
+++ b/src/components/Composer/index.native.tsx
@@ -1,11 +1,12 @@
 import type {MarkdownStyle} from '@expensify/react-native-live-markdown';
 import type {ForwardedRef} from 'react';
-import React, {useCallback, useMemo, useRef} from 'react';
+import React, {useCallback, useEffect, useMemo, useRef} from 'react';
 import type {NativeSyntheticEvent, TextInput, TextInputChangeEventData, TextInputPasteEventData} from 'react-native';
 import {StyleSheet} from 'react-native';
 import type {FileObject} from '@components/AttachmentModal';
 import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput';
 import RNMarkdownTextInput from '@components/RNMarkdownTextInput';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
 import useMarkdownStyle from '@hooks/useMarkdownStyle';
 import useResetComposerFocus from '@hooks/useResetComposerFocus';
 import useStyleUtils from '@hooks/useStyleUtils';
@@ -46,6 +47,15 @@ function Composer(
     const styles = useThemeStyles();
     const StyleUtils = useStyleUtils();
 
+    const {inputCallbackRef, inputRef: autoFocusInputRef} = useAutoFocusInput();
+
+    useEffect(() => {
+        if (autoFocus === !!autoFocusInputRef.current) {
+            return;
+        }
+        inputCallbackRef(autoFocus ? textInput.current : null);
+    }, [autoFocus, inputCallbackRef, autoFocusInputRef]);
+
     /**
      * Set the TextInput Ref
      * @param {Element} el
@@ -57,6 +67,10 @@ function Composer(
             return;
         }
 
+        if (autoFocus) {
+            inputCallbackRef(el);
+        }
+
         // This callback prop is used by the parent component using the constructor to
         // get a ref to the inner textInput element e.g. if we do
         //  this.textInput = el} /> this will not
diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx
index 3db92a2122b3..7a4512ad5aea 100755
--- a/src/components/Composer/index.tsx
+++ b/src/components/Composer/index.tsx
@@ -292,7 +292,7 @@ function Composer(
             return;
         }
 
-        const currentText = textInput.current.innerText;
+        const currentText = textInput.current.value;
         textInput.current.clear();
 
         // We need to reset the selection to 0,0 manually after clearing the text input on web
diff --git a/src/components/DeeplinkWrapper/index.website.tsx b/src/components/DeeplinkWrapper/index.website.tsx
index 649e66ccefa8..b395eb12c5fe 100644
--- a/src/components/DeeplinkWrapper/index.website.tsx
+++ b/src/components/DeeplinkWrapper/index.website.tsx
@@ -5,6 +5,7 @@ import Navigation from '@libs/Navigation/Navigation';
 import navigationRef from '@libs/Navigation/navigationRef';
 import shouldPreventDeeplinkPrompt from '@libs/Navigation/shouldPreventDeeplinkPrompt';
 import * as App from '@userActions/App';
+import * as Link from '@userActions/Link';
 import * as Session from '@userActions/Session';
 import CONFIG from '@src/CONFIG';
 import CONST from '@src/CONST';
@@ -15,7 +16,7 @@ function isMacOSWeb(): boolean {
     return !Browser.isMobile() && typeof navigator === 'object' && typeof navigator.userAgent === 'string' && /Mac/i.test(navigator.userAgent) && !/Electron/i.test(navigator.userAgent);
 }
 
-function promptToOpenInDesktopApp() {
+function promptToOpenInDesktopApp(initialUrl = '') {
     // If the current url path is /transition..., meaning it was opened from oldDot, during this transition period:
     // 1. The user session may not exist, because sign-in has not been completed yet.
     // 2. There may be non-idempotent operations (e.g. create a new workspace), which obviously should not be executed again in the desktop app.
@@ -26,11 +27,11 @@ function promptToOpenInDesktopApp() {
         // Match any magic link (/v//<6 digit code>)
         const isMagicLink = CONST.REGEX.ROUTES.VALIDATE_LOGIN.test(window.location.pathname);
 
-        App.beginDeepLinkRedirect(!isMagicLink);
+        App.beginDeepLinkRedirect(!isMagicLink, Link.getInternalNewExpensifyPath(initialUrl));
     }
 }
 
-function DeeplinkWrapper({children, isAuthenticated, autoAuthState}: DeeplinkWrapperProps) {
+function DeeplinkWrapper({children, isAuthenticated, autoAuthState, initialUrl}: DeeplinkWrapperProps) {
     const [currentScreen, setCurrentScreen] = useState();
     const [hasShownPrompt, setHasShownPrompt] = useState(false);
     const removeListener = useRef<() => void>();
@@ -77,7 +78,7 @@ function DeeplinkWrapper({children, isAuthenticated, autoAuthState}: DeeplinkWra
         // Otherwise, we want to wait until the navigation state is set up
         // and we know the user is on a screen that supports deeplinks.
         if (isAuthenticated) {
-            promptToOpenInDesktopApp();
+            promptToOpenInDesktopApp(initialUrl);
             setHasShownPrompt(true);
         } else {
             // Navigation state is not set up yet, we're unsure if we should show the deep link prompt or not
@@ -93,7 +94,7 @@ function DeeplinkWrapper({children, isAuthenticated, autoAuthState}: DeeplinkWra
             promptToOpenInDesktopApp();
             setHasShownPrompt(true);
         }
-    }, [currentScreen, hasShownPrompt, isAuthenticated, autoAuthState]);
+    }, [currentScreen, hasShownPrompt, isAuthenticated, autoAuthState, initialUrl]);
 
     return children;
 }
diff --git a/src/components/DeeplinkWrapper/types.ts b/src/components/DeeplinkWrapper/types.ts
index db61e5b01c24..23e096d6a093 100644
--- a/src/components/DeeplinkWrapper/types.ts
+++ b/src/components/DeeplinkWrapper/types.ts
@@ -6,6 +6,8 @@ type DeeplinkWrapperProps = ChildrenProps & {
 
     /** The auto authentication status */
     autoAuthState?: string;
+
+    initialUrl?: string;
 };
 
 export default DeeplinkWrapperProps;
diff --git a/src/components/FeedbackSurvey.tsx b/src/components/FeedbackSurvey.tsx
index a17cca5efae4..3c677a7b0f6d 100644
--- a/src/components/FeedbackSurvey.tsx
+++ b/src/components/FeedbackSurvey.tsx
@@ -43,9 +43,12 @@ type FeedbackSurveyProps = {
 
     /** Indicates whether a loading indicator should be shown */
     isLoading?: boolean;
+
+    /** Should the submit button be enabled when offline */
+    enabledWhenOffline?: boolean;
 };
 
-function FeedbackSurvey({title, description, onSubmit, optionRowStyles, footerText, isNoteRequired, isLoading, formID}: FeedbackSurveyProps) {
+function FeedbackSurvey({title, description, onSubmit, optionRowStyles, footerText, isNoteRequired, isLoading, formID, enabledWhenOffline = true}: FeedbackSurveyProps) {
     const {translate} = useLocalize();
     const styles = useThemeStyles();
     const [draft, draftResults] = useOnyx(`${formID}Draft`);
@@ -103,7 +106,7 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles, footerTe
             onSubmit={handleSubmit}
             submitButtonText={translate('common.submit')}
             isSubmitButtonVisible={false}
-            enabledWhenOffline
+            enabledWhenOffline={enabledWhenOffline}
         >
             
                 {title}
@@ -138,7 +141,7 @@ function FeedbackSurvey({title, description, onSubmit, optionRowStyles, footerTe
                     onSubmit={handleSubmit}
                     message={translate('common.error.pleaseCompleteForm')}
                     buttonText={translate('common.submit')}
-                    enabledWhenOffline
+                    enabledWhenOffline={enabledWhenOffline}
                     containerStyles={styles.mt3}
                     isLoading={isLoading}
                 />
diff --git a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
index 82e7d4f30a85..686c318a99dc 100644
--- a/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
+++ b/src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
@@ -33,6 +33,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [
     SCREENS.WORKSPACE.TAXES,
     SCREENS.WORKSPACE.REPORT_FIELDS,
     SCREENS.WORKSPACE.EXPENSIFY_CARD,
+    SCREENS.WORKSPACE.COMPANY_CARDS,
     SCREENS.WORKSPACE.DISTANCE_RATES,
     SCREENS.SEARCH.CENTRAL_PANE,
     SCREENS.SETTINGS.TROUBLESHOOT,
diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts
index ea33af302670..b1adf360bae6 100644
--- a/src/components/Icon/Expensicons.ts
+++ b/src/components/Icon/Expensicons.ts
@@ -82,6 +82,7 @@ import ExpensifyLogoNew from '@assets/images/expensify-logo-new.svg';
 import ExpensifyWordmark from '@assets/images/expensify-wordmark.svg';
 import EyeDisabled from '@assets/images/eye-disabled.svg';
 import Eye from '@assets/images/eye.svg';
+import Feed from '@assets/images/feed.svg';
 import Filter from '@assets/images/filter.svg';
 import Filters from '@assets/images/filters.svg';
 import Flag from '@assets/images/flag.svg';
@@ -386,4 +387,5 @@ export {
     Filters,
     CalendarSolid,
     Filter,
+    Feed,
 };
diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts
index 9537e7a0a7a7..afce7f519ce5 100644
--- a/src/components/Icon/Illustrations.ts
+++ b/src/components/Icon/Illustrations.ts
@@ -51,7 +51,6 @@ import CheckmarkCircle from '@assets/images/simple-illustrations/simple-illustra
 import CoffeeMug from '@assets/images/simple-illustrations/simple-illustration__coffeemug.svg';
 import Coins from '@assets/images/simple-illustrations/simple-illustration__coins.svg';
 import CommentBubbles from '@assets/images/simple-illustrations/simple-illustration__commentbubbles.svg';
-import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__company-card.svg';
 import ConciergeBubble from '@assets/images/simple-illustrations/simple-illustration__concierge-bubble.svg';
 import ConciergeNew from '@assets/images/simple-illustrations/simple-illustration__concierge.svg';
 import CreditCardsNew from '@assets/images/simple-illustrations/simple-illustration__credit-cards.svg';
@@ -87,6 +86,7 @@ import ReceiptEnvelope from '@assets/images/simple-illustrations/simple-illustra
 import ReceiptLocationMarker from '@assets/images/simple-illustrations/simple-illustration__receipt-location-marker.svg';
 import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustration__receipt-wrangler.svg';
 import ReceiptUpload from '@assets/images/simple-illustrations/simple-illustration__receiptupload.svg';
+import Rules from '@assets/images/simple-illustrations/simple-illustration__rules.svg';
 import SanFrancisco from '@assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg';
 import SendMoney from '@assets/images/simple-illustrations/simple-illustration__sendmoney.svg';
 import ShieldYellow from '@assets/images/simple-illustrations/simple-illustration__shield.svg';
@@ -101,6 +101,7 @@ import Tire from '@assets/images/simple-illustrations/simple-illustration__tire.
 import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg';
 import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg';
 import TreasureChest from '@assets/images/simple-illustrations/simple-illustration__treasurechest.svg';
+import CompanyCard from '@assets/images/simple-illustrations/simple-illustration__twocards-horizontal.svg';
 import VirtualCard from '@assets/images/simple-illustrations/simple-illustration__virtualcard.svg';
 import WalletAlt from '@assets/images/simple-illustrations/simple-illustration__wallet-alt.svg';
 import Workflows from '@assets/images/simple-illustrations/simple-illustration__workflows.svg';
@@ -216,4 +217,5 @@ export {
     Tire,
     BigVault,
     Filters,
+    Rules,
 };
diff --git a/src/components/LHNOptionsList/LHNOptionsList.tsx b/src/components/LHNOptionsList/LHNOptionsList.tsx
index b35b14016235..a734890a1f38 100644
--- a/src/components/LHNOptionsList/LHNOptionsList.tsx
+++ b/src/components/LHNOptionsList/LHNOptionsList.tsx
@@ -188,8 +188,8 @@ function LHNOptionsList({style, contentContainerStyles, data, onSelectRow, optio
     );
 
     const extraData = useMemo(
-        () => [reportActions, reports, policy, personalDetails, data.length, draftComments, optionMode, preferredLocale],
-        [reportActions, reports, policy, personalDetails, data.length, draftComments, optionMode, preferredLocale],
+        () => [reportActions, reports, transactionViolations, policy, personalDetails, data.length, draftComments, optionMode, preferredLocale],
+        [reportActions, reports, transactionViolations, policy, personalDetails, data.length, draftComments, optionMode, preferredLocale],
     );
 
     const previousOptionMode = usePrevious(optionMode);
diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index 32c1a3852c86..ee3929292cd3 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -106,6 +106,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
     const connectedIntegration = PolicyUtils.getConnectedIntegration(policy);
     const navigateBackToAfterDelete = useRef();
     const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t));
+    const hasOnlyPendingTransactions = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).every(
+        (t) => TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t),
+    );
     const transactionIDs = allTransactions.map((t) => t.transactionID);
     const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs);
     const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID);
@@ -131,7 +134,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
 
     const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
     const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
-    const shouldShowStatusBar = allHavePendingRTERViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense;
+    const shouldShowStatusBar = allHavePendingRTERViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions;
     const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !shouldShowStatusBar;
     const shouldShowAnyButton =
         shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation || shouldShowExportIntegrationButton;
@@ -217,6 +220,9 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
         if (allHavePendingRTERViolation) {
             return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')};
         }
+        if (hasOnlyPendingTransactions) {
+            return {icon: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription')};
+        }
         if (hasScanningReceipt) {
             return {icon: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription')};
         }
diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx
index ce6495e1385e..1350fd30ca54 100755
--- a/src/components/MoneyRequestConfirmationList.tsx
+++ b/src/components/MoneyRequestConfirmationList.tsx
@@ -2,7 +2,7 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native';
 import lodashIsEqual from 'lodash/isEqual';
 import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import {InteractionManager, View} from 'react-native';
-import {withOnyx} from 'react-native-onyx';
+import {useOnyx, withOnyx} from 'react-native-onyx';
 import type {OnyxEntry} from 'react-native-onyx';
 import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
 import useDebouncedState from '@hooks/useDebouncedState';
@@ -37,6 +37,7 @@ import type * as OnyxTypes from '@src/types/onyx';
 import type {Participant} from '@src/types/onyx/IOU';
 import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
 import type {SplitShares} from '@src/types/onyx/Transaction';
+import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import ButtonWithDropdownMenu from './ButtonWithDropdownMenu';
 import type {DropdownOption} from './ButtonWithDropdownMenu/types';
 import FormHelpMessage from './FormHelpMessage';
@@ -177,7 +178,7 @@ function MoneyRequestConfirmationList({
     iouAmount,
     policyCategories: policyCategoriesReal,
     policyCategoriesDraft,
-    mileageRates,
+    mileageRates: mileageRatesReal,
     isDistanceRequest = false,
     policy: policyReal,
     policyDraft,
@@ -211,6 +212,10 @@ function MoneyRequestConfirmationList({
 }: MoneyRequestConfirmationListProps) {
     const policy = policyReal ?? policyDraft;
     const policyCategories = policyCategoriesReal ?? policyCategoriesDraft;
+    const [mileageRatesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID}`, {
+        selector: (selectedPolicy: OnyxEntry) => DistanceRequestUtils.getMileageRates(selectedPolicy),
+    });
+    const mileageRates = isEmptyObject(mileageRatesReal) ? mileageRatesDraft : mileageRatesReal;
 
     const styles = useThemeStyles();
     const {translate, toLocaleDigit} = useLocalize();
@@ -228,14 +233,15 @@ function MoneyRequestConfirmationList({
     const customUnitRateID = TransactionUtils.getRateID(transaction) ?? '-1';
 
     useEffect(() => {
-        if (customUnitRateID || !canUseP2PDistanceRequests) {
+        if ((customUnitRateID && customUnitRateID !== '-1') || !isDistanceRequest) {
             return;
         }
-        if (!customUnitRateID) {
-            const rateID = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? defaultMileageRate?.customUnitRateID ?? '';
-            IOU.setCustomUnitRateID(transactionID, rateID);
-        }
-    }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID]);
+
+        const defaultRate = defaultMileageRate?.customUnitRateID ?? '';
+        const lastSelectedRate = lastSelectedDistanceRates?.[policy?.id ?? ''] ?? defaultRate;
+        const rateID = canUseP2PDistanceRequests ? lastSelectedRate : defaultRate;
+        IOU.setCustomUnitRateID(transactionID, rateID);
+    }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID, isDistanceRequest]);
 
     const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD;
 
diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx
index a38845ec3362..4a76f33de346 100644
--- a/src/components/ReportActionItem/MoneyRequestView.tsx
+++ b/src/components/ReportActionItem/MoneyRequestView.tsx
@@ -19,7 +19,6 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useViolations from '@hooks/useViolations';
 import type {ViolationField} from '@hooks/useViolations';
-import * as CardUtils from '@libs/CardUtils';
 import * as CurrencyUtils from '@libs/CurrencyUtils';
 import type {MileageRate} from '@libs/DistanceRequestUtils';
 import DistanceRequestUtils from '@libs/DistanceRequestUtils';
@@ -163,14 +162,14 @@ function MoneyRequestView({
         tag: transactionTag,
         originalAmount: transactionOriginalAmount,
         originalCurrency: transactionOriginalCurrency,
-        cardID: transactionCardID,
     } = useMemo>(() => ReportUtils.getTransactionDetails(transaction) ?? {}, [transaction]);
     const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT;
     const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction);
     const formattedTransactionAmount = transactionAmount ? CurrencyUtils.convertToDisplayString(transactionAmount, transactionCurrency) : '';
     const formattedOriginalAmount = transactionOriginalAmount && transactionOriginalCurrency && CurrencyUtils.convertToDisplayString(transactionOriginalAmount, transactionOriginalCurrency);
     const isCardTransaction = TransactionUtils.isCardTransaction(transaction);
-    const cardProgramName = isCardTransaction && transactionCardID !== undefined ? CardUtils.getCardDescription(transactionCardID) : '';
+    const cardProgramName = TransactionUtils.getCardName(transaction);
+    const shouldShowCard = isCardTransaction && cardProgramName;
     const isApproved = ReportUtils.isReportApproved(moneyRequestReport);
     const isInvoice = ReportUtils.isInvoiceReport(moneyRequestReport);
     const isPaidReport = ReportActionsUtils.isPayAction(parentReportAction);
@@ -187,7 +186,7 @@ function MoneyRequestView({
 
     // Flags for allowing or disallowing editing an expense
     // Used for non-restricted fields such as: description, category, tag, billable, etc...
-    const canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(report);
+    const canUserPerformWriteAction = !!ReportUtils.canUserPerformWriteAction(report) && !readonly;
     const canEdit = ReportActionsUtils.isMoneyRequestAction(parentReportAction) && ReportUtils.canEditMoneyRequest(parentReportAction, transaction) && canUserPerformWriteAction;
 
     const canEditTaxFields = canEdit && !isDistanceRequest;
@@ -345,8 +344,8 @@ function MoneyRequestView({
                 
                         Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
@@ -371,8 +370,8 @@ function MoneyRequestView({
              Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))}
             />
@@ -429,8 +428,8 @@ function MoneyRequestView({
                 
                         Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAG.getRoute(CONST.IOU.ACTION.EDIT, iouType, orderWeight, transaction?.transactionID ?? '', report?.reportID ?? '-1'))
@@ -502,7 +501,7 @@ function MoneyRequestView({
                 {shouldShowReceiptEmptyState && (
                     
                             Navigation.navigate(
                                 ROUTES.MONEY_REQUEST_STEP_SCAN.getRoute(
@@ -525,8 +524,8 @@ function MoneyRequestView({
                         titleIcon={Expensicons.Checkmark}
                         description={amountDescription}
                         titleStyle={styles.textHeadlineH2}
-                        interactive={canEditAmount && !readonly}
-                        shouldShowRightIcon={canEditAmount && !readonly}
+                        interactive={canEditAmount}
+                        shouldShowRightIcon={canEditAmount}
                         onPress={() =>
                             Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_AMOUNT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
                         }
@@ -539,8 +538,8 @@ function MoneyRequestView({
                         description={translate('common.description')}
                         shouldParseTitle
                         title={updatedTransactionDescription ?? transactionDescription}
-                        interactive={canEdit && !readonly}
-                        shouldShowRightIcon={canEdit && !readonly}
+                        interactive={canEdit}
+                        shouldShowRightIcon={canEdit}
                         titleStyle={styles.flex1}
                         onPress={() =>
                             Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
@@ -558,8 +557,8 @@ function MoneyRequestView({
                         
                                 Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_MERCHANT.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
@@ -575,8 +574,8 @@ function MoneyRequestView({
                     
                             Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1' ?? '-1'))
@@ -590,8 +589,8 @@ function MoneyRequestView({
                         
                                 Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CATEGORY.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
@@ -602,7 +601,7 @@ function MoneyRequestView({
                     
                 )}
                 {shouldShowTag && tagList}
-                {isCardTransaction && (
+                {shouldShowCard && (
                     
                         
                                 Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction?.transactionID ?? '-1', report?.reportID ?? '-1'))
@@ -633,8 +632,8 @@ function MoneyRequestView({
                         
                                 Navigation.navigate(
@@ -672,7 +671,7 @@ function MoneyRequestView({
                             accessibilityLabel={translate('common.billable')}
                             isOn={updatedTransaction?.billable ?? !!transactionBillable}
                             onToggle={saveBillable}
-                            disabled={!canEdit || readonly}
+                            disabled={!canEdit}
                         />
                     
                 )}
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index dae33b438a43..45a06968cefd 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -365,7 +365,7 @@ function ReportPreview({
         }
         return {
             supportText: translate('iou.expenseCount', {
-                count: numberOfRequests - numberOfScanningReceipts - numberOfPendingRequests,
+                count: numberOfRequests,
                 scanningReceipts: numberOfScanningReceipts,
                 pendingReceipts: numberOfPendingRequests,
             }),
@@ -389,7 +389,7 @@ function ReportPreview({
             
                  {
-                        Timing.start(CONST.TIMING.SWITCH_REPORT_FROM_PREVIEW);
+                        Timing.start(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
                         Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID));
                     }}
                     onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
diff --git a/src/components/Search/SearchPageHeader.tsx b/src/components/Search/SearchPageHeader.tsx
index 7e4b69c39596..bae08155c262 100644
--- a/src/components/Search/SearchPageHeader.tsx
+++ b/src/components/Search/SearchPageHeader.tsx
@@ -157,9 +157,12 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa
                 }
 
                 const reportIDList = (selectedReports?.filter((report) => !!report) as string[]) ?? [];
-                SearchActions.exportSearchItemsToCSV({query: status, reportIDList, transactionIDList: selectedTransactionsKeys, policyIDs: [activeWorkspaceID ?? '']}, () => {
-                    setDownloadErrorModalOpen?.();
-                });
+                SearchActions.exportSearchItemsToCSV(
+                    {query: status, jsonQuery: JSON.stringify(queryJSON), reportIDList, transactionIDList: selectedTransactionsKeys, policyIDs: [activeWorkspaceID ?? '']},
+                    () => {
+                        setDownloadErrorModalOpen?.();
+                    },
+                );
             },
         });
 
@@ -247,6 +250,7 @@ function SearchPageHeader({queryJSON, hash, onSelectDeleteOption, setOfflineModa
 
         return options;
     }, [
+        queryJSON,
         status,
         selectedTransactionsKeys,
         selectedTransactions,
diff --git a/src/components/SelectionList/BaseSelectionList.tsx b/src/components/SelectionList/BaseSelectionList.tsx
index bbc8615898df..7d8f4c1738c8 100644
--- a/src/components/SelectionList/BaseSelectionList.tsx
+++ b/src/components/SelectionList/BaseSelectionList.tsx
@@ -97,7 +97,7 @@ function BaseSelectionList(
         shouldDelayFocus = true,
         shouldUpdateFocusedIndex = false,
         onLongPressRow,
-        shouldShowListEmptyContent = false,
+        shouldShowListEmptyContent = true,
     }: BaseSelectionListProps,
     ref: ForwardedRef,
 ) {
diff --git a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx
index bb2b2e24aea4..966f49e45a93 100644
--- a/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx
+++ b/src/components/VideoPlayerContexts/VideoPopoverMenuContext.tsx
@@ -15,7 +15,7 @@ const Context = React.createContext(null);
 function VideoPopoverMenuContextProvider({children}: ChildrenProps) {
     const {currentlyPlayingURL} = usePlaybackContext();
     const {translate} = useLocalize();
-    const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[2]);
+    const [currentPlaybackSpeed, setCurrentPlaybackSpeed] = useState(CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS[3]);
     const {isOffline} = useNetwork();
     const isLocalFile = currentlyPlayingURL && CONST.ATTACHMENT_LOCAL_URL_PREFIX.some((prefix) => currentlyPlayingURL.startsWith(prefix));
     const videoPopoverMenuPlayerRef = useRef(null);
@@ -57,7 +57,7 @@ function VideoPopoverMenuContextProvider({children}: ChildrenProps) {
             text: translate('videoPlayer.playbackSpeed'),
             subMenuItems: CONST.VIDEO_PLAYER.PLAYBACK_SPEEDS.map((speed) => ({
                 icon: currentPlaybackSpeed === speed ? Expensicons.Checkmark : undefined,
-                text: speed.toString(),
+                text: speed === 1 ? translate('videoPlayer.normal') : speed.toString(),
                 onSelected: () => {
                     updatePlaybackSpeed(speed);
                 },
diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts
index 022d6178877d..6199a36abdca 100644
--- a/src/hooks/useHtmlPaste/index.ts
+++ b/src/hooks/useHtmlPaste/index.ts
@@ -1,6 +1,5 @@
 import {useNavigation} from '@react-navigation/native';
 import {useCallback, useEffect} from 'react';
-import type {ClipboardEvent as PasteEvent} from 'react';
 import Parser from '@libs/Parser';
 import type UseHtmlPaste from './types';
 
@@ -21,10 +20,6 @@ const insertAtCaret = (target: HTMLElement, text: string) => {
         range.setEnd(node, node.length);
         selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
 
-        // Dispatch paste event to make Markdown Input properly set cursor position
-        const pasteEvent = new ClipboardEvent('paste', {bubbles: true, cancelable: true});
-        (pasteEvent as unknown as PasteEvent).isDefaultPrevented = () => false;
-        target.dispatchEvent(pasteEvent);
         // Dispatch input event to trigger Markdown Input to parse the new text
         target.dispatchEvent(new Event('input', {bubbles: true}));
     } else {
@@ -48,11 +43,6 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi
                 insertByCommand(text);
             }
 
-            if (!textInputRef.current?.isFocused()) {
-                textInputRef.current?.focus();
-                return;
-            }
-
             // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view.
             // To avoid the keyboard toggle issue in mWeb if using blur() and focus() functions, we just need to dispatch the event to trigger the onFocus handler
             // We need to trigger the bubbled "focusin" event to make sure the onFocus handler is triggered
diff --git a/src/languages/en.ts b/src/languages/en.ts
index bffbab74d13e..37b0c2f36c63 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -750,10 +750,17 @@ export default {
         yourCompanyWebsiteNote: "If you don't have a website, you can provide your company's LinkedIn or social media profile instead.",
         invalidDomainError: 'You have entered an invalid domain. To continue, please enter a valid domain.',
         publicDomainError: 'You have entered a public domain. To continue, please enter a private domain.',
-        expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
-            `${count} ${Str.pluralize('expense', 'expenses', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} scanning` : ''}${
-                pendingReceipts > 0 ? `, ${pendingReceipts} pending` : ''
-            }`,
+        expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => {
+            const expenseText = `${count} ${Str.pluralize('expense', 'expenses', count)}`;
+            const statusText = [];
+            if (scanningReceipts > 0) {
+                statusText.push(`${scanningReceipts} scanning`);
+            }
+            if (pendingReceipts > 0) {
+                statusText.push(`${pendingReceipts} pending`);
+            }
+            return statusText.length > 0 ? `${expenseText} (${statusText.join(', ')})` : expenseText;
+        },
         deleteExpense: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Delete ${Str.pluralize('expense', 'expenses', count)}`,
         deleteConfirmation: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Are you sure that you want to delete ${Str.pluralize('this expense', 'these expenses', count)}?`,
         settledExpensify: 'Paid',
@@ -1427,7 +1434,7 @@ export default {
         addPaymentMethod: 'Add payment method',
         addNewDebitCard: 'Add new debit card',
         addNewBankAccount: 'Add new bank account',
-        accountLastFour: 'Account ending in',
+        accountLastFour: 'Ending in',
         cardLastFour: 'Card ending in',
         addFirstPaymentMethod: 'Add a payment method to send and receive payments directly in the app.',
         defaultPaymentMethod: 'Default',
@@ -2105,6 +2112,7 @@ export default {
             expensifyCard: 'Expensify Card',
             workflows: 'Workflows',
             workspace: 'Workspace',
+            companyCards: 'Company cards',
             edit: 'Edit workspace',
             enabled: 'Enabled',
             disabled: 'Disabled',
@@ -2122,6 +2130,7 @@ export default {
             travel: 'Travel',
             members: 'Members',
             accounting: 'Accounting',
+            rules: 'Rules',
             displayedAs: 'Displayed as',
             plan: 'Plan',
             profile: 'Profile',
@@ -2585,7 +2594,7 @@ export default {
                     },
                 },
                 customersOrJobs: {
-                    title: 'Customers / projects',
+                    title: 'Customers/projects',
                     subtitle: 'Choose how to handle NetSuite *customers* and *projects* in Expensify.',
                     importCustomers: 'Import customers',
                     importJobs: 'Import projects',
@@ -2600,7 +2609,7 @@ export default {
                     customSegments: {
                         title: 'Custom segments/records',
                         addText: 'Add custom segment/record',
-                        recordTitle: 'Custom segment',
+                        recordTitle: 'Custom segment/record',
                         helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS,
                         helpLinkText: 'View detailed instructions',
                         helpText: ' on configuring custom segments/records.',
@@ -2831,6 +2840,10 @@ export default {
                 title: 'Spend',
                 subtitle: 'Enable functionality that helps you scale your team.',
             },
+            manageSection: {
+                title: 'Manage',
+                subtitle: 'Add controls that help keep spend within budget.',
+            },
             earnSection: {
                 title: 'Earn',
                 subtitle: 'Enable optional functionality to streamline your revenue and get paid faster.',
@@ -2864,6 +2877,13 @@ export default {
                     ctaTitle: 'Issue new card',
                 },
             },
+            companyCards: {
+                title: 'Company Cards',
+                subtitle: 'Import spend from existing company cards',
+                disableCardTitle: 'Disable Company Cards',
+                disableCardPrompt: 'You can’t disable company cards because this feature is in use. Reach out to the Concierge for next steps.',
+                disableCardButton: 'Chat with Concierge',
+            },
             workflows: {
                 title: 'Workflows',
                 subtitle: 'Configure how spend is approved and paid.',
@@ -2898,6 +2918,10 @@ export default {
                 disconnectText: "To disable accounting, you'll need to disconnect your accounting connection from your workspace.",
                 manageSettings: 'Manage settings',
             },
+            rules: {
+                title: 'Rules',
+                subtitle: 'Configure when receipts are required, flag high spend, and more.',
+            },
         },
         reportFields: {
             addField: 'Add field',
@@ -3543,6 +3567,16 @@ export default {
                 description: `Add tax codes to your taxes for easy export of expenses to your accounting and payroll systems.`,
                 onlyAvailableOnPlan: 'Tax codes are only available on the Control plan, starting at ',
             },
+            companyCards: {
+                title: 'Company Cards',
+                description: `Company cards lets you import spend for existing company cards from all major card issuers. You can assign cards to employees, and automatically import transactions.`,
+                onlyAvailableOnPlan: 'Company cards are only available on the Control plan, starting at ',
+            },
+            rules: {
+                title: 'Rules',
+                description: `Rules run in the background and keep your spend under control so you don't have to sweat the small stuff.\n\nRequire expense details like receipts and descriptions, set limits and defaults, and automate approvals and payments – all in one place.`,
+                onlyAvailableOnPlan: 'Rules are only available on the Control plan, starting at ',
+            },
             pricing: {
                 amount: '$9 ',
                 perActiveMember: 'per active member per month.',
@@ -3574,6 +3608,16 @@ export default {
             chatInAdmins: 'Chat in #admins',
             addPaymentCard: 'Add payment card',
         },
+        rules: {
+            individualExpenseRules: {
+                title: 'Expenses',
+                subtitle: 'Set spend controls and defaults for individual expenses. You can also create rules for',
+            },
+            expenseReportRules: {
+                title: 'Expense reports',
+                subtitle: 'Automate expense report compliance, approvals, and payment.',
+            },
+        },
     },
     getAssistancePage: {
         title: 'Get assistance',
@@ -3719,6 +3763,11 @@ export default {
             keyword: 'Keyword',
             hasKeywords: 'Has keywords',
             currency: 'Currency',
+            amount: {
+                lessThan: (amount?: string) => `Less than ${amount ?? ''}`,
+                greaterThan: (amount?: string) => `Greater than ${amount ?? ''}`,
+                between: (greaterThan: string, lessThan: string) => `Between ${greaterThan} and ${lessThan}`,
+            },
         },
         expenseType: 'Expense type',
     },
@@ -3852,6 +3901,7 @@ export default {
                 stripePaid: ({amount, currency}: StripePaidParams) => `paid ${currency}${amount}`,
                 takeControl: `took control`,
                 unapproved: ({amount, currency}: UnapprovedParams) => `unapproved ${currency}${amount}`,
+                integrationSyncFailed: (label: string, errorMessage: string) => `failed to sync with ${label} ("${errorMessage}")`,
                 addEmployee: (email: string, role: string) => `added ${email} as ${role === 'user' ? 'member' : 'admin'}`,
                 updateRole: (email: string, currentRole: string, newRole: string) => `updated the role of ${email} from ${currentRole} to ${newRole}`,
                 removeMember: (email: string, role: string) => `removed ${role} ${email}`,
@@ -4173,6 +4223,7 @@ export default {
         expand: 'Expand',
         mute: 'Mute',
         unmute: 'Unmute',
+        normal: 'Normal',
     },
     exitSurvey: {
         header: 'Before you go',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 08ca657435b4..679a6297ffdd 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -743,11 +743,17 @@ export default {
         yourCompanyWebsiteNote: 'Si no tiene un sitio web, puede proporcionar el perfil de LinkedIn o de las redes sociales de su empresa.',
         invalidDomainError: 'Ha introducido un dominio no válido. Para continuar, introduzca un dominio válido.',
         publicDomainError: 'Ha introducido un dominio público. Para continuar, introduzca un dominio privado.',
-        expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) =>
-            `${count} ${Str.pluralize('gasto', 'gastos', count)}${scanningReceipts > 0 ? `, ${scanningReceipts} escaneando` : ''}${
-                pendingReceipts > 0 ? `, ${pendingReceipts} pendiente` : ''
-            }`,
-
+        expenseCount: ({count, scanningReceipts = 0, pendingReceipts = 0}: RequestCountParams) => {
+            const expenseText = `${count} ${Str.pluralize('gasto', 'gastos', count)}`;
+            const statusText = [];
+            if (scanningReceipts > 0) {
+                statusText.push(`${scanningReceipts} escaneando`);
+            }
+            if (pendingReceipts > 0) {
+                statusText.push(`${pendingReceipts} pendiente`);
+            }
+            return statusText.length > 0 ? `${expenseText} (${statusText.join(', ')})` : expenseText;
+        },
         deleteExpense: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `Eliminar ${Str.pluralize('gasto', 'gastos', count)}`,
         deleteConfirmation: ({count}: DeleteExpenseTranslationParams = {count: 1}) => `¿Estás seguro de que quieres eliminar ${Str.pluralize('esta solicitud', 'estas solicitudes', count)}?`,
         settledExpensify: 'Pagado',
@@ -1436,7 +1442,7 @@ export default {
         addPaymentMethod: 'Añadir método de pago',
         addNewDebitCard: 'Añadir nueva tarjeta de débito',
         addNewBankAccount: 'Añadir nueva cuenta de banco',
-        accountLastFour: 'Cuenta terminada en',
+        accountLastFour: 'Terminada en',
         cardLastFour: 'Tarjeta terminada en',
         addFirstPaymentMethod: 'Añade un método de pago para enviar y recibir pagos directamente desde la aplicación.',
         defaultPaymentMethod: 'Predeterminado',
@@ -2137,6 +2143,7 @@ export default {
             expensifyCard: 'Tarjeta Expensify',
             workflows: 'Flujos de trabajo',
             workspace: 'Espacio de trabajo',
+            companyCards: 'Tarjetas de empresa',
             edit: 'Editar espacio de trabajo',
             enabled: 'Activada',
             disabled: 'Desactivada',
@@ -2153,6 +2160,7 @@ export default {
             travel: 'Viajes',
             members: 'Miembros',
             accounting: 'Contabilidad',
+            rules: 'Reglas',
             plan: 'Plan',
             profile: 'Perfil',
             bankAccount: 'Cuenta bancaria',
@@ -2631,7 +2639,7 @@ export default {
                     },
                 },
                 customersOrJobs: {
-                    title: 'Clientes / proyectos',
+                    title: 'Clientes/proyectos',
                     subtitle: 'Elija cómo manejar los *clientes* y *proyectos* de NetSuite en Expensify.',
                     importCustomers: 'Importar clientes',
                     importJobs: 'Importar proyectos',
@@ -2646,7 +2654,7 @@ export default {
                     customSegments: {
                         title: 'Segmentos/registros personalizados',
                         addText: 'Añadir segmento/registro personalizado',
-                        recordTitle: 'Segmento personalizado',
+                        recordTitle: 'Segmento/registro personalizado',
                         helpLink: CONST.NETSUITE_IMPORT.HELP_LINKS.CUSTOM_SEGMENTS,
                         helpLinkText: 'Ver instrucciones detalladas',
                         helpText: ' sobre la configuración de segmentos/registros personalizado.',
@@ -2880,6 +2888,10 @@ export default {
                 title: 'Gasto',
                 subtitle: 'Habilita otras funcionalidades que ayudan a aumentar tu equipo.',
             },
+            manageSection: {
+                title: 'Gestionar',
+                subtitle: 'Añade controles que ayudan a mantener los gastos dentro del presupuesto.',
+            },
             earnSection: {
                 title: 'Gane',
                 subtitle: 'Habilita funciones opcionales para agilizar tus ingresos y recibir pagos más rápido.',
@@ -2909,6 +2921,13 @@ export default {
                     ctaTitle: 'Emitir nueva tarjeta',
                 },
             },
+            companyCards: {
+                title: 'Tarjetas de empresa',
+                subtitle: 'Importar gastos de las tarjetas de empresa existentes.',
+                disableCardTitle: 'Deshabilitar tarjetas de empresa',
+                disableCardPrompt: 'No puedes deshabilitar las tarjetas de empresa porque esta función está en uso. Por favor, contacta a Concierge para los próximos pasos.',
+                disableCardButton: 'Chatear con Concierge',
+            },
             distanceRates: {
                 title: 'Tasas de distancia',
                 subtitle: 'Añade, actualiza y haz cumplir las tasas.',
@@ -2947,6 +2966,10 @@ export default {
                 disconnectText: 'Para desactivar la contabilidad, desconecta tu conexión contable del espacio de trabajo.',
                 manageSettings: 'Gestionar la configuración',
             },
+            rules: {
+                title: 'Reglas',
+                subtitle: 'Configura cuándo se exigen los recibos, marca los gastos elevados y mucho más.',
+            },
         },
         reportFields: {
             addField: 'Añadir campo',
@@ -3593,6 +3616,16 @@ export default {
                 description: `Añada código de impuesto mayor a sus categorías para exportar fácilmente los gastos a sus sistemas de contabilidad y nómina.`,
                 onlyAvailableOnPlan: 'Los código de impuesto mayor solo están disponibles en el plan Control, a partir de ',
             },
+            companyCards: {
+                title: 'Tarjetas de empresa',
+                description: `Las tarjetas de empresa le permiten importar los gastos de las tarjetas de empresa existentes de todos los principales emisores de tarjetas. Puede asignar tarjetas a empleados e importar transacciones automáticamente.`,
+                onlyAvailableOnPlan: 'Las tarjetas de empresa solo están disponibles en el plan Control, a partir de ',
+            },
+            rules: {
+                title: 'Reglas',
+                description: `Las reglas se ejecutan en segundo plano y mantienen tus gastos bajo control para que no tengas que preocuparte por los detalles pequeños.\n\nExige detalles de los gastos, como recibos y descripciones, establece límites y valores predeterminados, y automatiza las aprobaciones y los pagos, todo en un mismo lugar.`,
+                onlyAvailableOnPlan: 'Las reglas están disponibles solo en el plan Control, que comienza en ',
+            },
             note: {
                 upgradeWorkspace: 'Mejore su espacio de trabajo para acceder a esta función, o',
                 learnMore: 'más información',
@@ -3624,6 +3657,16 @@ export default {
             chatInAdmins: 'Chatea en #admins',
             addPaymentCard: 'Agregar tarjeta de pago',
         },
+        rules: {
+            individualExpenseRules: {
+                title: 'Gastos',
+                subtitle: 'Establece controles y valores predeterminados para gastos individuales. También puedes crear reglas para',
+            },
+            expenseReportRules: {
+                title: 'Informes de gastos',
+                subtitle: 'Automatiza el cumplimiento, la aprobación y el pago de los informes de gastos.',
+            },
+        },
     },
     getAssistancePage: {
         title: 'Obtener ayuda',
@@ -3770,6 +3813,11 @@ export default {
             keyword: 'Palabra clave',
             hasKeywords: 'Tiene palabras clave',
             currency: 'Divisa',
+            amount: {
+                lessThan: (amount?: string) => `Menos de ${amount ?? ''}`,
+                greaterThan: (amount?: string) => `Más que ${amount ?? ''}`,
+                between: (greaterThan: string, lessThan: string) => `Entre ${greaterThan} y ${lessThan}`,
+            },
         },
         expenseType: 'Tipo de gasto',
     },
@@ -3904,6 +3952,7 @@ export default {
                 stripePaid: ({amount, currency}: StripePaidParams) => `pagado ${currency}${amount}`,
                 takeControl: `tomó el control`,
                 unapproved: ({amount, currency}: UnapprovedParams) => `no aprobado ${currency}${amount}`,
+                integrationSyncFailed: (label: string, errorMessage: string) => `no se pudo sincronizar con ${label} ("${errorMessage}")`,
                 addEmployee: (email: string, role: string) => `agregó a ${email} como ${role === 'user' ? 'miembro' : 'administrador'}`,
                 updateRole: (email: string, currentRole: string, newRole: string) =>
                     `actualicé el rol ${email} de ${currentRole === 'user' ? 'miembro' : 'administrador'} a ${newRole === 'user' ? 'miembro' : 'administrador'}`,
@@ -4688,6 +4737,7 @@ export default {
         expand: 'Expandir',
         mute: 'Silenciar',
         unmute: 'Activar sonido',
+        normal: 'Normal',
     },
     exitSurvey: {
         header: 'Antes de irte',
diff --git a/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts
new file mode 100644
index 000000000000..0bf3ce34b9d2
--- /dev/null
+++ b/src/libs/API/parameters/EnablePolicyCompanyCardsParams.ts
@@ -0,0 +1,7 @@
+type EnablePolicyCompanyCardsParams = {
+    authToken?: string | null;
+    policyID: string;
+    enabled: boolean;
+};
+
+export default EnablePolicyCompanyCardsParams;
diff --git a/src/libs/API/parameters/ExportSearchItemsToCSVParams.ts b/src/libs/API/parameters/ExportSearchItemsToCSVParams.ts
index 2659fac6810a..057b6188e3ea 100644
--- a/src/libs/API/parameters/ExportSearchItemsToCSVParams.ts
+++ b/src/libs/API/parameters/ExportSearchItemsToCSVParams.ts
@@ -1,7 +1,8 @@
-import type {SearchStatus} from '@components/Search/types';
+import type {SearchQueryString, SearchStatus} from '@components/Search/types';
 
 type ExportSearchItemsToCSVParams = {
     query: SearchStatus;
+    jsonQuery: SearchQueryString;
     reportIDList: string[];
     transactionIDList: string[];
     policyIDs: string[];
diff --git a/src/libs/API/parameters/SendInvoiceParams.ts b/src/libs/API/parameters/SendInvoiceParams.ts
index c95ffce14b2c..e2cac84e0d12 100644
--- a/src/libs/API/parameters/SendInvoiceParams.ts
+++ b/src/libs/API/parameters/SendInvoiceParams.ts
@@ -20,6 +20,9 @@ type SendInvoiceParams = RequireAtLeastOne<
         transactionThreadReportID: string;
         companyName?: string;
         companyWebsite?: string;
+        createdIOUReportActionID: string;
+        createdReportActionIDForThread: string;
+        reportActionID: string;
     },
     'receiverEmail' | 'receiverInvoiceRoomID'
 >;
diff --git a/src/libs/API/parameters/SetPolicyRulesEnabledParams.ts b/src/libs/API/parameters/SetPolicyRulesEnabledParams.ts
new file mode 100644
index 000000000000..c748a98e4119
--- /dev/null
+++ b/src/libs/API/parameters/SetPolicyRulesEnabledParams.ts
@@ -0,0 +1,6 @@
+type SetPolicyRulesEnabledParams = {
+    policyID: string;
+    enabled: boolean;
+};
+
+export default SetPolicyRulesEnabledParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index c95355dada31..9696f4213a48 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -272,9 +272,11 @@ export type {default as ExportSearchItemsToCSVParams} from './ExportSearchItemsT
 export type {default as UpdateExpensifyCardLimitParams} from './UpdateExpensifyCardLimitParams';
 export type {CreateWorkspaceApprovalParams, UpdateWorkspaceApprovalParams, RemoveWorkspaceApprovalParams} from './WorkspaceApprovalParams';
 export type {default as StartIssueNewCardFlowParams} from './StartIssueNewCardFlowParams';
+export type {default as SetPolicyRulesEnabledParams} from './SetPolicyRulesEnabledParams';
 export type {default as ConfigureExpensifyCardsForPolicyParams} from './ConfigureExpensifyCardsForPolicyParams';
 export type {default as CreateExpensifyCardParams} from './CreateExpensifyCardParams';
 export type {default as UpdateExpensifyCardTitleParams} from './UpdateExpensifyCardTitleParams';
 export type {default as OpenCardDetailsPageParams} from './OpenCardDetailsPageParams';
+export type {default as EnablePolicyCompanyCardsParams} from './EnablePolicyCompanyCardsParams';
 export type {default as ToggleCardContinuousReconciliationParams} from './ToggleCardContinuousReconciliationParams';
 export type {default as UpdateExpensifyCardLimitTypeParams} from './UpdateExpensifyCardLimitTypeParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index cc3504ad89ab..5ea2ae44b74d 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -199,7 +199,9 @@ const WRITE_COMMANDS = {
     ENABLE_POLICY_WORKFLOWS: 'EnablePolicyWorkflows',
     ENABLE_POLICY_REPORT_FIELDS: 'EnablePolicyReportFields',
     ENABLE_POLICY_EXPENSIFY_CARDS: 'EnablePolicyExpensifyCards',
+    ENABLE_POLICY_COMPANY_CARDS: 'EnablePolicyCompanyCards',
     ENABLE_POLICY_INVOICING: 'EnablePolicyInvoicing',
+    SET_POLICY_RULES_ENABLED: 'SetPolicyRulesEnabled',
     SET_POLICY_TAXES_CURRENCY_DEFAULT: 'SetPolicyCurrencyDefaultTax',
     SET_POLICY_TAXES_FOREIGN_CURRENCY_DEFAULT: 'SetPolicyForeignCurrencyDefaultTax',
     SET_POLICY_CUSTOM_TAX_NAME: 'SetPolicyCustomTaxName',
@@ -529,7 +531,9 @@ type WriteCommandParameters = {
     [WRITE_COMMANDS.ENABLE_POLICY_WORKFLOWS]: Parameters.EnablePolicyWorkflowsParams;
     [WRITE_COMMANDS.ENABLE_POLICY_REPORT_FIELDS]: Parameters.EnablePolicyReportFieldsParams;
     [WRITE_COMMANDS.ENABLE_POLICY_EXPENSIFY_CARDS]: Parameters.EnablePolicyExpensifyCardsParams;
+    [WRITE_COMMANDS.ENABLE_POLICY_COMPANY_CARDS]: Parameters.EnablePolicyCompanyCardsParams;
     [WRITE_COMMANDS.ENABLE_POLICY_INVOICING]: Parameters.EnablePolicyInvoicingParams;
+    [WRITE_COMMANDS.SET_POLICY_RULES_ENABLED]: Parameters.SetPolicyRulesEnabledParams;
     [WRITE_COMMANDS.JOIN_POLICY_VIA_INVITE_LINK]: Parameters.JoinPolicyInviteLinkParams;
     [WRITE_COMMANDS.ACCEPT_JOIN_REQUEST]: Parameters.AcceptJoinRequestParams;
     [WRITE_COMMANDS.DECLINE_JOIN_REQUEST]: Parameters.DeclineJoinRequestParams;
diff --git a/src/libs/Browser/index.website.ts b/src/libs/Browser/index.website.ts
index b89190dc7f78..2007f2c6cbc0 100644
--- a/src/libs/Browser/index.website.ts
+++ b/src/libs/Browser/index.website.ts
@@ -79,12 +79,12 @@ const isSafari: IsSafari = () => getBrowser() === 'safari' || isMobileSafari();
 /**
  * The session information needs to be passed to the Desktop app, and the only way to do that is by using query params. There is no other way to transfer the data.
  */
-const openRouteInDesktopApp: OpenRouteInDesktopApp = (shortLivedAuthToken = '', email = '') => {
+const openRouteInDesktopApp: OpenRouteInDesktopApp = (shortLivedAuthToken = '', email = '', initialRoute = '') => {
     const params = new URLSearchParams();
     // If the user is opening the desktop app through a third party signin flow, we need to manually add the exitTo param
     // so that the desktop app redirects to the correct home route after signin is complete.
     const openingFromDesktopRedirect = window.location.pathname === `/${ROUTES.DESKTOP_SIGN_IN_REDIRECT}`;
-    params.set('exitTo', `${openingFromDesktopRedirect ? '/r' : window.location.pathname}${window.location.search}${window.location.hash}`);
+    params.set('exitTo', `${openingFromDesktopRedirect ? '/r' : initialRoute || window.location.pathname}${window.location.search}${window.location.hash}`);
     if (email && shortLivedAuthToken) {
         params.set('email', email);
         params.set('shortLivedAuthToken', shortLivedAuthToken);
diff --git a/src/libs/Browser/types.ts b/src/libs/Browser/types.ts
index cb242d3729aa..ff0de91e7b78 100644
--- a/src/libs/Browser/types.ts
+++ b/src/libs/Browser/types.ts
@@ -12,6 +12,6 @@ type IsChromeIOS = () => boolean;
 
 type IsSafari = () => boolean;
 
-type OpenRouteInDesktopApp = (shortLivedAuthToken?: string, email?: string) => void;
+type OpenRouteInDesktopApp = (shortLivedAuthToken?: string, email?: string, initialRoute?: string) => void;
 
 export type {GetBrowser, IsMobile, IsMobileSafari, IsMobileChrome, IsMobileWebKit, IsSafari, IsChromeIOS, OpenRouteInDesktopApp};
diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts
index fa98cf32ee39..2b2aad59d58d 100644
--- a/src/libs/DistanceRequestUtils.ts
+++ b/src/libs/DistanceRequestUtils.ts
@@ -248,7 +248,7 @@ function convertToDistanceInMeters(distance: number, unit: Unit): number {
 /**
  * Returns custom unit rate ID for the distance transaction
  */
-function getCustomUnitRateID(reportID: string) {
+function getCustomUnitRateID(reportID: string, shouldUseDefault?: boolean) {
     const allReports = ReportConnection.getAllReports();
     const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
     const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`];
@@ -259,7 +259,7 @@ function getCustomUnitRateID(reportID: string) {
         const distanceUnit = Object.values(policy?.customUnits ?? {}).find((unit) => unit.name === CONST.CUSTOM_UNITS.NAME_DISTANCE);
         const lastSelectedDistanceRateID = lastSelectedDistanceRates?.[policy?.id ?? '-1'] ?? '-1';
         const lastSelectedDistanceRate = distanceUnit?.rates[lastSelectedDistanceRateID] ?? {};
-        if (lastSelectedDistanceRate.enabled && lastSelectedDistanceRateID) {
+        if (lastSelectedDistanceRate.enabled && lastSelectedDistanceRateID && !shouldUseDefault) {
             customUnitRateID = lastSelectedDistanceRateID;
         } else {
             customUnitRateID = getDefaultMileageRate(policy)?.customUnitRateID ?? '-1';
diff --git a/src/libs/E2E/tests/reportTypingTest.e2e.ts b/src/libs/E2E/tests/reportTypingTest.e2e.ts
index 9624d7ab992b..efe1c380dfd0 100644
--- a/src/libs/E2E/tests/reportTypingTest.e2e.ts
+++ b/src/libs/E2E/tests/reportTypingTest.e2e.ts
@@ -1,13 +1,16 @@
 import type {NativeConfig} from 'react-native-config';
 import Config from 'react-native-config';
+import {runOnUI} from 'react-native-reanimated';
 import E2ELogin from '@libs/E2E/actions/e2eLogin';
 import waitForAppLoaded from '@libs/E2E/actions/waitForAppLoaded';
 import waitForKeyboard from '@libs/E2E/actions/waitForKeyboard';
 import E2EClient from '@libs/E2E/client';
 import getConfigValueOrThrow from '@libs/E2E/utils/getConfigValueOrThrow';
+import getPromiseWithResolve from '@libs/E2E/utils/getPromiseWithResolve';
 import Navigation from '@libs/Navigation/Navigation';
 import Performance from '@libs/Performance';
 import {getRerenderCount, resetRerenderCount} from '@pages/home/report/ReportActionCompose/ComposerWithSuggestions/index.e2e';
+import {onSubmitAction} from '@pages/home/report/ReportActionCompose/ReportActionCompose';
 import CONST from '@src/CONST';
 import ROUTES from '@src/ROUTES';
 import * as NativeCommands from '../../../../tests/e2e/nativeCommands/NativeCommandsAction';
@@ -17,6 +20,7 @@ const test = (config: NativeConfig) => {
     console.debug('[E2E] Logging in for typing');
 
     const reportID = getConfigValueOrThrow('reportID', config);
+    const message = getConfigValueOrThrow('message', config);
 
     E2ELogin().then((neededLogin) => {
         if (neededLogin) {
@@ -28,7 +32,26 @@ const test = (config: NativeConfig) => {
 
         console.debug('[E2E] Logged in, getting typing metrics and submitting them…');
 
+        const [renderTimesPromise, renderTimesResolve] = getPromiseWithResolve();
+        const [messageSentPromise, messageSentResolve] = getPromiseWithResolve();
+
+        Promise.all([renderTimesPromise, messageSentPromise]).then(() => {
+            console.debug(`[E2E] Submitting!`);
+
+            E2EClient.submitTestDone();
+        });
+
         Performance.subscribeToMeasurements((entry) => {
+            if (entry.name === CONST.TIMING.MESSAGE_SENT) {
+                E2EClient.submitTestResults({
+                    branch: Config.E2E_BRANCH,
+                    name: 'Message sent',
+                    metric: entry.duration,
+                    unit: 'ms',
+                }).then(messageSentResolve);
+                return;
+            }
+
             if (entry.name !== CONST.TIMING.SIDEBAR_LOADED) {
                 return;
             }
@@ -46,18 +69,26 @@ const test = (config: NativeConfig) => {
                         return Promise.resolve();
                     })
                     .then(() => E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand('A')))
-                    .then(() => {
-                        setTimeout(() => {
-                            const rerenderCount = getRerenderCount();
+                    .then(
+                        () =>
+                            new Promise((resolve) => {
+                                setTimeout(() => {
+                                    const rerenderCount = getRerenderCount();
 
-                            E2EClient.submitTestResults({
-                                branch: Config.E2E_BRANCH,
-                                name: 'Composer typing rerender count',
-                                metric: rerenderCount,
-                                unit: 'renders',
-                            }).then(E2EClient.submitTestDone);
-                        }, 3000);
-                    })
+                                    E2EClient.submitTestResults({
+                                        branch: Config.E2E_BRANCH,
+                                        name: 'Composer typing rerender count',
+                                        metric: rerenderCount,
+                                        unit: 'renders',
+                                    })
+                                        .then(renderTimesResolve)
+                                        .then(resolve);
+                                }, 3000);
+                            }),
+                    )
+                    .then(() => E2EClient.sendNativeCommand(NativeCommands.makeBackspaceCommand()))
+                    .then(() => E2EClient.sendNativeCommand(NativeCommands.makeTypeTextCommand(message)))
+                    .then(() => runOnUI(onSubmitAction)())
                     .catch((error) => {
                         console.error('[E2E] Error while test', error);
                         E2EClient.submitTestDone();
diff --git a/src/libs/IOUUtils.ts b/src/libs/IOUUtils.ts
index 986fd165d2d7..d1bf8fcd8c8c 100644
--- a/src/libs/IOUUtils.ts
+++ b/src/libs/IOUUtils.ts
@@ -141,10 +141,11 @@ function insertTagIntoTransactionTagsString(transactionTags: string, tag: string
     const tagArray = TransactionUtils.getTagArrayFromName(transactionTags);
     tagArray[tagIndex] = tag;
 
-    return tagArray
-        .map((tagItem) => tagItem.trim())
-        .filter((tagItem) => !!tagItem)
-        .join(CONST.COLON);
+    while (tagArray.length > 0 && !tagArray[tagArray.length - 1]) {
+        tagArray.pop();
+    }
+
+    return tagArray.map((tagItem) => tagItem.trim()).join(CONST.COLON);
 }
 
 function isMovingTransactionFromTrackExpense(action?: IOUAction) {
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index f105de52a5ac..7722696245fd 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -514,17 +514,18 @@ const SearchReportModalStackNavigator = createModalStackNavigator({
     [SCREENS.SEARCH.ADVANCED_FILTERS_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: () => require('../../../../pages/Search/SearchFiltersDatePage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP]: () => require('../../../../pages/Search/SearchFiltersCurrencyPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP]: () => require('../../../../pages/Search/SearchFiltersDescriptionPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP]: () => require('../../../../pages/Search/SearchFiltersMerchantPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP]: () => require('../../../../pages/Search/SearchFiltersReportIDPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP]: () => require('../../../../pages/Search/SearchFiltersCategoryPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_KEYWORD_RHP]: () => require('../../../../pages/Search/SearchFiltersKeywordPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_CARD_RHP]: () => require('../../../../pages/Search/SearchFiltersCardPage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_TAX_RATE_RHP]: () => require('../../../../pages/Search/SearchFiltersTaxRatePage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_EXPENSE_TYPE_RHP]: () => require('../../../../pages/Search/SearchFiltersExpenseTypePage').default,
-    [SCREENS.SEARCH.ADVANCED_FILTERS_TAG_RHP]: () => require('../../../../pages/Search/SearchFiltersTagPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_DATE_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersDatePage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_CURRENCY_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersCurrencyPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersDescriptionPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersMerchantPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersReportIDPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_AMOUNT_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersCategoryPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_KEYWORD_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersKeywordPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_CARD_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersCardPage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_TAX_RATE_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersTaxRatePage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_EXPENSE_TYPE_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersExpenseTypePage').default,
+    [SCREENS.SEARCH.ADVANCED_FILTERS_TAG_RHP]: () => require('../../../../pages/Search/SearchAdvancedFiltersPage/SearchFiltersTagPage').default,
     [SCREENS.SEARCH.ADVANCED_FILTERS_FROM_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersFromPage').default,
     [SCREENS.SEARCH.ADVANCED_FILTERS_TO_RHP]: () => require('@pages/Search/SearchAdvancedFiltersPage/SearchFiltersToPage').default,
 });
diff --git a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx
index 748d92b49a1c..22a190913ed2 100644
--- a/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx
+++ b/src/libs/Navigation/AppNavigator/Navigators/FullScreenNavigator.tsx
@@ -32,7 +32,9 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = {
     [SCREENS.WORKSPACE.TAXES]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesPage').default,
     [SCREENS.WORKSPACE.REPORT_FIELDS]: () => require('../../../../pages/workspace/reportFields/WorkspaceReportFieldsPage').default,
     [SCREENS.WORKSPACE.EXPENSIFY_CARD]: () => require('../../../../pages/workspace/expensifyCard/WorkspaceExpensifyCardPage').default,
+    [SCREENS.WORKSPACE.COMPANY_CARDS]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardsPage').default,
     [SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default,
+    [SCREENS.WORKSPACE.RULES]: () => require('../../../../pages/workspace/rules/PolicyRulesPage').default,
 } satisfies Screens;
 
 function FullScreenNavigator() {
diff --git a/src/libs/Navigation/isSearchTopmostCentralPane.ts b/src/libs/Navigation/isSearchTopmostCentralPane.ts
new file mode 100644
index 000000000000..3c315116bb00
--- /dev/null
+++ b/src/libs/Navigation/isSearchTopmostCentralPane.ts
@@ -0,0 +1,22 @@
+import SCREENS from '@src/SCREENS';
+import getTopmostCentralPaneRoute from './getTopmostCentralPaneRoute';
+import {navigationRef} from './Navigation';
+import type {RootStackParamList, State} from './types';
+
+const isSearchTopmostCentralPane = (): boolean => {
+    const rootState = navigationRef.getRootState() as State;
+
+    if (!rootState) {
+        return false;
+    }
+
+    const topmostCentralPaneRoute = getTopmostCentralPaneRoute(rootState);
+
+    if (topmostCentralPaneRoute?.name === SCREENS.SEARCH.CENTRAL_PANE) {
+        return true;
+    }
+
+    return false;
+};
+
+export default isSearchTopmostCentralPane;
diff --git a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
index add67e5b273b..8a4a53e0f705 100755
--- a/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/CENTRAL_PANE_TO_RHP_MAPPING.ts
@@ -50,6 +50,7 @@ const CENTRAL_PANE_TO_RHP_MAPPING: Partial> =
         SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP,
         SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP,
         SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP,
+        SCREENS.SEARCH.ADVANCED_FILTERS_AMOUNT_RHP,
         SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP,
         SCREENS.SEARCH.ADVANCED_FILTERS_KEYWORD_RHP,
         SCREENS.SEARCH.ADVANCED_FILTERS_TAX_RATE_RHP,
diff --git a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
index 942a23068979..e4072ea1e696 100755
--- a/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
+++ b/src/libs/Navigation/linkingConfig/FULL_SCREEN_TO_RHP_MAPPING.ts
@@ -160,6 +160,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial> = {
         SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE,
     ],
     [SCREENS.WORKSPACE.INVOICES]: [SCREENS.WORKSPACE.INVOICES_COMPANY_NAME, SCREENS.WORKSPACE.INVOICES_COMPANY_WEBSITE],
+    [SCREENS.WORKSPACE.COMPANY_CARDS]: [],
     [SCREENS.WORKSPACE.EXPENSIFY_CARD]: [
         SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW,
         SCREENS.WORKSPACE.EXPENSIFY_CARD_BANK_ACCOUNT,
diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts
index 236b56882dde..4d3f19984b8f 100644
--- a/src/libs/Navigation/linkingConfig/config.ts
+++ b/src/libs/Navigation/linkingConfig/config.ts
@@ -1029,6 +1029,7 @@ const config: LinkingOptions['config'] = {
                         [SCREENS.SEARCH.ADVANCED_FILTERS_MERCHANT_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_MERCHANT,
                         [SCREENS.SEARCH.ADVANCED_FILTERS_DESCRIPTION_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_DESCRIPTION,
                         [SCREENS.SEARCH.ADVANCED_FILTERS_REPORT_ID_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID,
+                        [SCREENS.SEARCH.ADVANCED_FILTERS_AMOUNT_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT,
                         [SCREENS.SEARCH.ADVANCED_FILTERS_CATEGORY_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_CATEGORY,
                         [SCREENS.SEARCH.ADVANCED_FILTERS_KEYWORD_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_KEYWORD,
                         [SCREENS.SEARCH.ADVANCED_FILTERS_CARD_RHP]: ROUTES.SEARCH_ADVANCED_FILTERS_CARD,
@@ -1059,6 +1060,9 @@ const config: LinkingOptions['config'] = {
                 [SCREENS.WORKSPACE.EXPENSIFY_CARD]: {
                     path: ROUTES.WORKSPACE_EXPENSIFY_CARD.route,
                 },
+                [SCREENS.WORKSPACE.COMPANY_CARDS]: {
+                    path: ROUTES.WORKSPACE_COMPANY_CARDS.route,
+                },
                 [SCREENS.WORKSPACE.WORKFLOWS]: {
                     path: ROUTES.WORKSPACE_WORKFLOWS.route,
                 },
@@ -1098,6 +1102,9 @@ const config: LinkingOptions['config'] = {
                 [SCREENS.WORKSPACE.DISTANCE_RATES]: {
                     path: ROUTES.WORKSPACE_DISTANCE_RATES.route,
                 },
+                [SCREENS.WORKSPACE.RULES]: {
+                    path: ROUTES.WORKSPACE_RULES.route,
+                },
             },
         },
     },
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index b689f36d8a35..ceb62f1dac1c 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -1119,6 +1119,9 @@ type FullScreenNavigatorParamList = {
     [SCREENS.WORKSPACE.EXPENSIFY_CARD]: {
         policyID: string;
     };
+    [SCREENS.WORKSPACE.COMPANY_CARDS]: {
+        policyID: string;
+    };
     [SCREENS.WORKSPACE.WORKFLOWS]: {
         policyID: string;
     };
@@ -1194,6 +1197,9 @@ type FullScreenNavigatorParamList = {
     [SCREENS.WORKSPACE.EXPENSIFY_CARD]: {
         policyID: string;
     };
+    [SCREENS.WORKSPACE.RULES]: {
+        policyID: string;
+    };
 };
 
 type OnboardingModalNavigatorParamList = {
diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts
index 98274e829001..3f519f4de7c4 100644
--- a/src/libs/OptionsListUtils.ts
+++ b/src/libs/OptionsListUtils.ts
@@ -53,7 +53,6 @@ import * as PersonalDetailsUtils from './PersonalDetailsUtils';
 import * as PhoneNumber from './PhoneNumber';
 import * as PolicyUtils from './PolicyUtils';
 import * as ReportActionUtils from './ReportActionsUtils';
-import * as ReportConnection from './ReportConnection';
 import * as ReportUtils from './ReportUtils';
 import * as TaskUtils from './TaskUtils';
 import * as TransactionUtils from './TransactionUtils';
@@ -220,7 +219,6 @@ type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: bo
 
 type FilterOptionsConfig = Pick & {
     preferChatroomsOverThreads?: boolean;
-    includeChatRoomsByParticipants?: boolean;
     preferPolicyExpenseChat?: boolean;
 };
 
@@ -351,35 +349,12 @@ Onyx.connect({
             }, {});
     },
 });
-
-let allReportsDraft: OnyxCollection;
-Onyx.connect({
-    key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
-    waitForCollectionCallback: true,
-    callback: (value) => (allReportsDraft = value),
-});
-
 let activePolicyID: OnyxEntry;
 Onyx.connect({
     key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
     callback: (value) => (activePolicyID = value),
 });
 
-/**
- * Get the report or draft report given a reportID
- */
-function getReportOrDraftReport(reportID: string | undefined): OnyxEntry {
-    const allReports = ReportConnection.getAllReports();
-    if (!allReports && !allReportsDraft) {
-        return undefined;
-    }
-
-    const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
-    const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`];
-
-    return report ?? draftReport;
-}
-
 /**
  * @param defaultValues {login: accountID} In workspace invite page, when new user is added we pass available data to opt in
  * @returns Returns avatar data for a list of user accountIDs
@@ -473,31 +448,6 @@ function getParticipantsOption(participant: ReportUtils.OptionData | Participant
     };
 }
 
-/**
- * Constructs a Set with all possible names (displayName, firstName, lastName, email) for all participants in a report,
- * to be used in isSearchStringMatch.
- */
-function getParticipantNames(personalDetailList?: Array> | null): Set {
-    // We use a Set because `Set.has(value)` on a Set of with n entries is up to n (or log(n)) times faster than
-    // `_.contains(Array, value)` for an Array with n members.
-    const participantNames = new Set();
-    personalDetailList?.forEach((participant) => {
-        if (participant.login) {
-            participantNames.add(participant.login.toLowerCase());
-        }
-        if (participant.firstName) {
-            participantNames.add(participant.firstName.toLowerCase());
-        }
-        if (participant.lastName) {
-            participantNames.add(participant.lastName.toLowerCase());
-        }
-        if (participant.displayName) {
-            participantNames.add(PersonalDetailsUtils.getDisplayNameOrDefault(participant).toLowerCase());
-        }
-    });
-    return participantNames;
-}
-
 /**
  * A very optimized method to remove duplicates from an array.
  * Taken from https://stackoverflow.com/a/9229821/9114791
@@ -578,7 +528,7 @@ function getLastActorDisplayName(lastActorDetails: Partial | nu
  * Update alternate text for the option when applicable
  */
 function getAlternateText(option: ReportUtils.OptionData, {showChatPreviewLine = false, forcePolicyNamePreview = false}: PreviewConfig) {
-    const report = getReportOrDraftReport(option.reportID);
+    const report = ReportUtils.getReportOrDraftReport(option.reportID);
     const isAdminRoom = ReportUtils.isAdminRoom(report);
     const isAnnounceRoom = ReportUtils.isAnnounceRoom(report);
 
@@ -634,7 +584,7 @@ function getIOUReportIDOfLastAction(report: OnyxEntry): string | undefin
     if (!ReportActionUtils.isReportPreviewAction(lastAction)) {
         return;
     }
-    return getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastAction))?.reportID;
+    return ReportUtils.getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastAction))?.reportID;
 }
 
 /**
@@ -675,7 +625,7 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails
         const properSchemaForMoneyRequestMessage = ReportUtils.getReportPreviewMessage(report, lastReportAction, true, false, null, true);
         lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForMoneyRequestMessage);
     } else if (ReportActionUtils.isReportPreviewAction(lastReportAction)) {
-        const iouReport = getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction));
+        const iouReport = ReportUtils.getReportOrDraftReport(ReportActionUtils.getIOUReportIDFromReportActionPreview(lastReportAction));
         const lastIOUMoneyReportAction = allSortedReportActions[iouReport?.reportID ?? '-1']?.find(
             (reportAction, key): reportAction is ReportAction =>
                 ReportActionUtils.shouldReportActionBeVisible(reportAction, key) &&
@@ -711,11 +661,11 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails
     } else if (ReportActionUtils.isCreatedTaskReportAction(lastReportAction)) {
         lastMessageTextFromReport = TaskUtils.getTaskCreatedMessage(lastReportAction);
     } else if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.SUBMITTED) {
-        lastMessageTextFromReport = ReportUtils.getIOUSubmittedMessage(reportID);
+        lastMessageTextFromReport = ReportUtils.getIOUSubmittedMessage(lastReportAction);
     } else if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.APPROVED) {
-        lastMessageTextFromReport = ReportUtils.getIOUApprovedMessage(reportID);
+        lastMessageTextFromReport = ReportUtils.getIOUApprovedMessage(lastReportAction);
     } else if (lastReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.FORWARDED) {
-        lastMessageTextFromReport = ReportUtils.getIOUForwardedMessage(reportID);
+        lastMessageTextFromReport = ReportUtils.getIOUForwardedMessage(lastReportAction);
     } else if (ReportActionUtils.isActionableAddPaymentCard(lastReportAction)) {
         lastMessageTextFromReport = ReportActionUtils.getReportActionMessageText(lastReportAction);
     } else if (lastReportAction?.actionName === 'EXPORTINTEGRATION') {
@@ -862,7 +812,7 @@ function createOption(
  * Get the option for a given report.
  */
 function getReportOption(participant: Participant): ReportUtils.OptionData {
-    const report = getReportOrDraftReport(participant.reportID);
+    const report = ReportUtils.getReportOrDraftReport(participant.reportID);
     const visibleParticipantAccountIDs = ReportUtils.getParticipantsAccountIDsForDisplay(report, true);
 
     const option = createOption(
@@ -896,7 +846,7 @@ function getReportOption(participant: Participant): ReportUtils.OptionData {
  * Get the option for a policy expense report.
  */
 function getPolicyExpenseReportOption(participant: Participant | ReportUtils.OptionData): ReportUtils.OptionData {
-    const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? getReportOrDraftReport(participant.reportID) : null;
+    const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReportOrDraftReport(participant.reportID) : null;
 
     const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {})
         .filter(([, reportParticipant]) => reportParticipant && !reportParticipant.hidden)
@@ -2438,7 +2388,6 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
         maxRecentReportsToShow = 0,
         excludeLogins = [],
         preferChatroomsOverThreads = false,
-        includeChatRoomsByParticipants = false,
         preferPolicyExpenseChat = false,
     } = config ?? {};
     if (searchInputValue.trim() === '' && maxRecentReportsToShow > 0) {
@@ -2455,29 +2404,9 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
         optionsToExclude.push({login});
     });
 
-    const getParticipantsLoginsArray = (item: ReportUtils.OptionData) => {
-        const keys: string[] = [];
-        const visibleChatMemberAccountIDs = item.participantsList ?? [];
-        if (allPersonalDetails) {
-            visibleChatMemberAccountIDs.forEach((participant) => {
-                const login = participant?.login;
-
-                if (participant?.displayName) {
-                    keys.push(participant.displayName);
-                }
-
-                if (login) {
-                    keys.push(login);
-                    keys.push(login.replace(CONST.EMAIL_SEARCH_REGEX, ''));
-                }
-            });
-        }
-
-        return keys;
-    };
     const matchResults = searchTerms.reduceRight((items, term) => {
         const recentReports = filterArrayByMatch(items.recentReports, term, (item) => {
-            let values: string[] = [];
+            const values: string[] = [];
             if (item.text) {
                 values.push(item.text);
             }
@@ -2491,21 +2420,10 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
                 if (item.alternateText) {
                     values.push(item.alternateText);
                 }
-                values = values.concat(getParticipantsLoginsArray(item));
             } else if (!!item.isChatRoom || !!item.isPolicyExpenseChat) {
                 if (item.subtitle) {
                     values.push(item.subtitle);
                 }
-
-                if (includeChatRoomsByParticipants) {
-                    values = values.concat(getParticipantsLoginsArray(item));
-                }
-            }
-
-            if (!item.isChatRoom) {
-                const participantNames = getParticipantNames(item.participantsList ?? []);
-                values = values.concat(Array.from(participantNames));
-                values = values.concat(getParticipantsLoginsArray(item));
             }
 
             return uniqFast(values);
diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts
index 15c15e113c8c..0a6756034f7d 100644
--- a/src/libs/Permissions.ts
+++ b/src/libs/Permissions.ts
@@ -36,6 +36,10 @@ function canUseNetSuiteUSATax(betas: OnyxEntry): boolean {
     return !!betas?.includes(CONST.BETAS.NETSUITE_USA_TAX) || canUseAllBetas(betas);
 }
 
+function canUseWorkspaceRules(betas: OnyxEntry): boolean {
+    return !!betas?.includes(CONST.BETAS.WORKSPACE_RULES) || canUseAllBetas(betas);
+}
+
 /**
  * Link previews are temporarily disabled.
  */
@@ -52,4 +56,5 @@ export default {
     canUseSpotnanaTravel,
     canUseWorkspaceFeeds,
     canUseNetSuiteUSATax,
+    canUseWorkspaceRules,
 };
diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts
index 6f87e09a61d7..fe3ea7de3bac 100644
--- a/src/libs/PersonalDetailsUtils.ts
+++ b/src/libs/PersonalDetailsUtils.ts
@@ -33,8 +33,19 @@ Onyx.connect({
     },
 });
 
-const hiddenTranslation = Localize.translateLocal('common.hidden');
-const youTranslation = Localize.translateLocal('common.you').toLowerCase();
+let hiddenTranslation = '';
+let youTranslation = '';
+
+Onyx.connect({
+    key: ONYXKEYS.NVP_PREFERRED_LOCALE,
+    callback: (value) => {
+        if (!value) {
+            return;
+        }
+        hiddenTranslation = Localize.translateLocal('common.hidden');
+        youTranslation = Localize.translateLocal('common.you').toLowerCase();
+    },
+});
 
 const regexMergedAccount = new RegExp(CONST.REGEX.MERGED_ACCOUNT_PREFIX);
 
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index 54fd137c7c86..4d126cf9cbf4 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -152,6 +152,13 @@ function isReportPreviewAction(reportAction: OnyxInputOrEntry): re
 function isSubmittedAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction {
     return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.SUBMITTED);
 }
+function isApprovedAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction {
+    return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.APPROVED);
+}
+
+function isForwardedAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction {
+    return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.FORWARDED);
+}
 
 function isModifiedExpenseAction(reportAction: OnyxInputOrEntry): reportAction is ReportAction {
     return isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE);
@@ -1730,6 +1737,9 @@ export {
     isTransactionThread,
     isTripPreview,
     isWhisperAction,
+    isSubmittedAction,
+    isApprovedAction,
+    isForwardedAction,
     isWhisperActionTargetedToOthers,
     shouldHideNewMarker,
     shouldReportActionBeVisible,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 2c4d6d9bc1ed..38b73ffc2057 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -658,14 +658,7 @@ function getChatType(report: OnyxInputOrEntry | Participant): ValueOf {
     const allReports = ReportConnection.getAllReports();
-    if (!allReports && !allReportsDraft) {
-        return undefined;
-    }
-
-    const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
-    const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`];
-
-    return report ?? draftReport;
+    return allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`];
 }
 
 /**
@@ -3671,6 +3664,16 @@ function getReportName(
     const parentReportAction = parentReportActionParam ?? ReportActionsUtils.getParentReportAction(report);
     const parentReportActionMessage = ReportActionsUtils.getReportActionMessage(parentReportAction);
 
+    if (parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.SUBMITTED) {
+        return getIOUSubmittedMessage(parentReportAction);
+    }
+    if (parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.FORWARDED) {
+        return getIOUForwardedMessage(parentReportAction);
+    }
+    if (parentReportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.APPROVED) {
+        return getIOUApprovedMessage(parentReportAction);
+    }
+
     if (isChatThread(report)) {
         if (!isEmptyObject(parentReportAction) && ReportActionsUtils.isTransactionThread(parentReportAction)) {
             formattedName = getTransactionReportName(parentReportAction);
@@ -4373,23 +4376,25 @@ function buildOptimisticExpenseReport(
     return expenseReport;
 }
 
-function getFormattedAmount(reportID: string) {
-    const report = getReportOrDraftReport(reportID);
-    const linkedReport = isChatThread(report) ? getParentReport(report) : report;
-    const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(linkedReport?.total ?? 0), linkedReport?.currency);
+function getFormattedAmount(reportAction: ReportAction) {
+    if (!ReportActionsUtils.isSubmittedAction(reportAction) && !ReportActionsUtils.isForwardedAction(reportAction) && !ReportActionsUtils.isApprovedAction(reportAction)) {
+        return '';
+    }
+    const originalMessage = ReportActionsUtils.getOriginalMessage(reportAction);
+    const formattedAmount = CurrencyUtils.convertToDisplayString(Math.abs(originalMessage?.amount ?? 0), originalMessage?.currency);
     return formattedAmount;
 }
 
-function getIOUSubmittedMessage(reportID: string) {
-    return Localize.translateLocal('iou.submittedAmount', {formattedAmount: getFormattedAmount(reportID)});
+function getIOUSubmittedMessage(reportAction: ReportAction) {
+    return Localize.translateLocal('iou.submittedAmount', {formattedAmount: getFormattedAmount(reportAction)});
 }
 
-function getIOUApprovedMessage(reportID: string) {
-    return Localize.translateLocal('iou.approvedAmount', {amount: getFormattedAmount(reportID)});
+function getIOUApprovedMessage(reportAction: ReportAction) {
+    return Localize.translateLocal('iou.approvedAmount', {amount: getFormattedAmount(reportAction)});
 }
 
-function getIOUForwardedMessage(reportID: string) {
-    return Localize.translateLocal('iou.forwardedAmount', {amount: getFormattedAmount(reportID)});
+function getIOUForwardedMessage(reportAction: ReportAction) {
+    return Localize.translateLocal('iou.forwardedAmount', {amount: getFormattedAmount(reportAction)});
 }
 
 function getWorkspaceNameUpdatedMessage(action: ReportAction) {
@@ -4431,7 +4436,7 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num
             iouMessage = `approved ${amount}`;
             break;
         case CONST.REPORT.ACTIONS.TYPE.FORWARDED:
-            iouMessage = getIOUForwardedMessage(iouReportID);
+            iouMessage = Localize.translateLocal('iou.forwardedAmount', {amount});
             break;
         case CONST.REPORT.ACTIONS.TYPE.UNAPPROVED:
             iouMessage = `unapproved ${amount}`;
@@ -5808,7 +5813,11 @@ function doesTransactionThreadHaveViolations(
     if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.SUBMITTED) {
         return false;
     }
-    return TransactionUtils.hasViolation(IOUTransactionID, transactionViolations) || TransactionUtils.hasWarningTypeViolation(IOUTransactionID, transactionViolations);
+    return (
+        TransactionUtils.hasViolation(IOUTransactionID, transactionViolations) ||
+        TransactionUtils.hasWarningTypeViolation(IOUTransactionID, transactionViolations) ||
+        TransactionUtils.hasModifiedAmountOrDateViolation(IOUTransactionID, transactionViolations)
+    );
 }
 
 /**
@@ -7831,6 +7840,7 @@ export {
     getReportParticipantsTitle,
     getReportPreviewMessage,
     getReportRecipientAccountIDs,
+    getReportOrDraftReport,
     getRoom,
     getRootParentReport,
     getRouteFromLink,
diff --git a/src/libs/SearchUtils.ts b/src/libs/SearchUtils.ts
index bb49697a8345..ce2429c5653b 100644
--- a/src/libs/SearchUtils.ts
+++ b/src/libs/SearchUtils.ts
@@ -441,6 +441,28 @@ function buildDateFilterQuery(filterValues: Partial)
     return dateFilter;
 }
 
+/**
+ * @private
+ * returns Date filter query string part, which needs special logic
+ */
+function buildAmountFilterQuery(filterValues: Partial) {
+    const lessThan = filterValues[FILTER_KEYS.LESS_THAN];
+    const greaterThan = filterValues[FILTER_KEYS.GREATER_THAN];
+
+    let amountFilter = '';
+    if (greaterThan) {
+        amountFilter += `${CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT}>${greaterThan}`;
+    }
+    if (lessThan && greaterThan) {
+        amountFilter += ' ';
+    }
+    if (lessThan) {
+        amountFilter += `${CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT}<${lessThan}`;
+    }
+
+    return amountFilter;
+}
+
 function sanitizeString(str: string) {
     if (str.includes(' ') || str.includes(',')) {
         return `"${str}"`;
@@ -464,42 +486,43 @@ function getExpenseTypeTranslationKey(expenseType: ValueOf) {
-    const filtersString = Object.entries(filterValues)
-        .map(([filterKey, filterValue]) => {
-            if ((filterKey === FILTER_KEYS.MERCHANT || filterKey === FILTER_KEYS.DESCRIPTION || filterKey === FILTER_KEYS.REPORT_ID || filterKey === FILTER_KEYS.KEYWORD) && filterValue) {
-                const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as KeysOfFilterKeysObject[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey);
-                if (keyInCorrectForm) {
-                    return `${CONST.SEARCH.SYNTAX_FILTER_KEYS[keyInCorrectForm]}:${filterValue as string}`;
-                }
+    const filtersString = Object.entries(filterValues).map(([filterKey, filterValue]) => {
+        if ((filterKey === FILTER_KEYS.MERCHANT || filterKey === FILTER_KEYS.DESCRIPTION || filterKey === FILTER_KEYS.REPORT_ID || filterKey === FILTER_KEYS.KEYWORD) && filterValue) {
+            const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as KeysOfFilterKeysObject[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey);
+            if (keyInCorrectForm) {
+                return `${CONST.SEARCH.SYNTAX_FILTER_KEYS[keyInCorrectForm]}:${filterValue as string}`;
             }
+        }
 
-            if (
-                (filterKey === FILTER_KEYS.CATEGORY ||
-                    filterKey === FILTER_KEYS.CARD_ID ||
-                    filterKey === FILTER_KEYS.TAX_RATE ||
-                    filterKey === FILTER_KEYS.EXPENSE_TYPE ||
-                    filterKey === FILTER_KEYS.TAG ||
-                    filterKey === FILTER_KEYS.CURRENCY ||
-                    filterKey === FILTER_KEYS.FROM ||
-                    filterKey === FILTER_KEYS.TO) &&
-                Array.isArray(filterValue) &&
-                filterValue.length > 0
-            ) {
-                const filterValueArray = filterValues[filterKey] ?? [];
-                const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as KeysOfFilterKeysObject[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey);
-                if (keyInCorrectForm) {
-                    return `${CONST.SEARCH.SYNTAX_FILTER_KEYS[keyInCorrectForm]}:${filterValueArray.map(sanitizeString).join(',')}`;
-                }
+        if (
+            (filterKey === FILTER_KEYS.CATEGORY ||
+                filterKey === FILTER_KEYS.CARD_ID ||
+                filterKey === FILTER_KEYS.TAX_RATE ||
+                filterKey === FILTER_KEYS.EXPENSE_TYPE ||
+                filterKey === FILTER_KEYS.TAG ||
+                filterKey === FILTER_KEYS.CURRENCY ||
+                filterKey === FILTER_KEYS.FROM ||
+                filterKey === FILTER_KEYS.TO) &&
+            Array.isArray(filterValue) &&
+            filterValue.length > 0
+        ) {
+            const filterValueArray = filterValues[filterKey] ?? [];
+            const keyInCorrectForm = (Object.keys(CONST.SEARCH.SYNTAX_FILTER_KEYS) as KeysOfFilterKeysObject[]).find((key) => CONST.SEARCH.SYNTAX_FILTER_KEYS[key] === filterKey);
+            if (keyInCorrectForm) {
+                return `${CONST.SEARCH.SYNTAX_FILTER_KEYS[keyInCorrectForm]}:${filterValueArray.map(sanitizeString).join(',')}`;
             }
+        }
 
-            return undefined;
-        })
-        .filter(Boolean)
-        .join(' ');
+        return undefined;
+    });
 
     const dateFilter = buildDateFilterQuery(filterValues);
+    filtersString.push(dateFilter);
+
+    const amountFilter = buildAmountFilterQuery(filterValues);
+    filtersString.push(amountFilter);
 
-    return dateFilter ? `${filtersString} ${dateFilter}` : filtersString;
+    return filtersString.filter(Boolean).join(' ');
 }
 
 function getFilters(queryJSON: SearchQueryJSON) {
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 3aaf4476eb0b..e5bd5d9b0753 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -4,7 +4,6 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
 import type {ValueOf} from 'type-fest';
 import type {TransactionMergeParams} from '@libs/API/parameters';
-import {isCorporateCard, isExpensifyCard} from '@libs/CardUtils';
 import {getCurrencyDecimals} from '@libs/CurrencyUtils';
 import DateUtils from '@libs/DateUtils';
 import DistanceRequestUtils from '@libs/DistanceRequestUtils';
@@ -507,18 +506,18 @@ function getFormattedCreated(transaction: OnyxInputOrEntry, dateFor
  * Determine whether a transaction is made with an Expensify card.
  */
 function isExpensifyCardTransaction(transaction: OnyxEntry): boolean {
-    if (!transaction?.cardID) {
-        return false;
-    }
-    return isExpensifyCard(transaction.cardID);
+    return transaction?.bank === CONST.EXPENSIFY_CARD.BANK;
 }
 
 /**
  * Determine whether a transaction is made with a card (Expensify or Company Card).
  */
 function isCardTransaction(transaction: OnyxEntry): boolean {
-    const cardID = transaction?.cardID ?? -1;
-    return isCorporateCard(cardID);
+    return !!transaction?.managedCard;
+}
+
+function getCardName(transaction: OnyxEntry): string {
+    return transaction?.cardName ?? '';
 }
 
 /**
@@ -734,6 +733,15 @@ function hasWarningTypeViolation(transactionID: string, transactionViolations: O
     return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING);
 }
 
+/**
+ * Checks if any violations for the provided transaction are of modifiedAmount or modifiedDate
+ */
+function hasModifiedAmountOrDateViolation(transactionID: string, transactionViolations: OnyxCollection): boolean {
+    return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some(
+        (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.MODIFIED_AMOUNT || violation.name === CONST.VIOLATIONS.MODIFIED_DATE,
+    );
+}
+
 /**
  * Calculates tax amount from the given expense amount and tax percentage
  */
@@ -1107,6 +1115,7 @@ export {
     hasViolation,
     hasNoticeTypeViolation,
     hasWarningTypeViolation,
+    hasModifiedAmountOrDateViolation,
     isCustomUnitRateIDForP2P,
     getRateID,
     getTransaction,
@@ -1116,6 +1125,7 @@ export {
     buildTransactionsMergeParams,
     getReimbursable,
     isPayAtEndExpense,
+    getCardName,
 };
 
 export type {TransactionChanges};
diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts
index 7be767d73f48..0c1a157f76f3 100644
--- a/src/libs/actions/App.ts
+++ b/src/libs/actions/App.ts
@@ -449,7 +449,7 @@ function redirectThirdPartyDesktopSignIn() {
 /**
  * @param shouldAuthenticateWithCurrentAccount Optional, indicates whether default authentication method (shortLivedAuthToken) should be used
  */
-function beginDeepLinkRedirect(shouldAuthenticateWithCurrentAccount = true) {
+function beginDeepLinkRedirect(shouldAuthenticateWithCurrentAccount = true, initialRoute?: string) {
     // There's no support for anonymous users on desktop
     if (Session.isAnonymousUser()) {
         return;
@@ -475,7 +475,7 @@ function beginDeepLinkRedirect(shouldAuthenticateWithCurrentAccount = true) {
             return;
         }
 
-        Browser.openRouteInDesktopApp(response.shortLivedAuthToken, currentUserEmail);
+        Browser.openRouteInDesktopApp(response.shortLivedAuthToken, currentUserEmail, initialRoute);
     });
 }
 
diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts
index e84a14836efd..90d8d7311b1c 100644
--- a/src/libs/actions/IOU.ts
+++ b/src/libs/actions/IOU.ts
@@ -37,6 +37,7 @@ import * as FileUtils from '@libs/fileDownload/FileUtils';
 import * as IOUUtils from '@libs/IOUUtils';
 import * as LocalePhoneNumber from '@libs/LocalePhoneNumber';
 import * as Localize from '@libs/Localize';
+import isSearchTopmostCentralPane from '@libs/Navigation/isSearchTopmostCentralPane';
 import Navigation from '@libs/Navigation/Navigation';
 import * as NextStepUtils from '@libs/NextStepUtils';
 import {rand64} from '@libs/NumberUtils';
@@ -111,6 +112,9 @@ type SendInvoiceInformation = {
     reportPreviewReportActionID: string;
     transactionID: string;
     transactionThreadReportID: string;
+    createdIOUReportActionID: string;
+    createdReportActionIDForThread: string;
+    reportActionID: string;
     onyxData: OnyxData;
 };
 
@@ -161,13 +165,6 @@ Onyx.connect({
     },
 });
 
-let allReportsDraft: OnyxCollection;
-Onyx.connect({
-    key: ONYXKEYS.COLLECTION.REPORT_DRAFT,
-    waitForCollectionCallback: true,
-    callback: (value) => (allReportsDraft = value),
-});
-
 let allTransactions: NonNullable> = {};
 Onyx.connect({
     key: ONYXKEYS.COLLECTION.TRANSACTION,
@@ -275,21 +272,6 @@ Onyx.connect({
     callback: (value) => (activePolicyID = value),
 });
 
-/**
- * Get the report or draft report given a reportID
- */
-function getReportOrDraftReport(reportID: string | undefined): OnyxEntry {
-    const allReports = ReportConnection.getAllReports();
-    if (!allReports && !allReportsDraft) {
-        return undefined;
-    }
-
-    const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
-    const draftReport = allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`];
-
-    return report ?? draftReport;
-}
-
 /**
  * Find the report preview action from given chat report and iou report
  */
@@ -1966,6 +1948,9 @@ function getSendInvoiceInformation(
     );
 
     return {
+        createdIOUReportActionID: optimisticCreatedActionForIOUReport.reportActionID,
+        createdReportActionIDForThread: optimisticCreatedActionForTransactionThread?.reportActionID ?? '-1',
+        reportActionID: iouAction.reportActionID,
         senderWorkspaceID,
         receiver,
         invoiceRoom: chatReport,
@@ -3478,7 +3463,7 @@ function requestMoney(
 ) {
     // If the report is iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
     const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
-    const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report?.chatReportID) : report;
+    const currentChatReport = isMoneyRequestReport ? ReportUtils.getReportOrDraftReport(report?.chatReportID) : report;
     const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : '';
     const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action);
 
@@ -3587,7 +3572,7 @@ function requestMoney(
         }
     }
 
-    Navigation.dismissModal(activeReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : activeReportID);
     if (activeReportID) {
         Report.notifyNewAction(activeReportID, payeeAccountID);
     }
@@ -3604,10 +3589,25 @@ function sendInvoice(
     companyName?: string,
     companyWebsite?: string,
 ) {
-    const {senderWorkspaceID, receiver, invoiceRoom, createdChatReportActionID, invoiceReportID, reportPreviewReportActionID, transactionID, transactionThreadReportID, onyxData} =
-        getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories, companyName, companyWebsite);
+    const {
+        senderWorkspaceID,
+        receiver,
+        invoiceRoom,
+        createdChatReportActionID,
+        invoiceReportID,
+        reportPreviewReportActionID,
+        transactionID,
+        transactionThreadReportID,
+        createdIOUReportActionID,
+        createdReportActionIDForThread,
+        reportActionID,
+        onyxData,
+    } = getSendInvoiceInformation(transaction, currentUserAccountID, invoiceChatReport, receiptFile, policy, policyTagList, policyCategories, companyName, companyWebsite);
 
     const parameters: SendInvoiceParams = {
+        createdIOUReportActionID,
+        createdReportActionIDForThread,
+        reportActionID,
         senderWorkspaceID,
         accountID: currentUserAccountID,
         amount: transaction?.amount ?? 0,
@@ -3629,7 +3629,12 @@ function sendInvoice(
 
     API.write(WRITE_COMMANDS.SEND_INVOICE, parameters, onyxData);
 
-    Navigation.dismissModalWithReport(invoiceRoom);
+    if (isSearchTopmostCentralPane()) {
+        Navigation.dismissModal();
+    } else {
+        Navigation.dismissModalWithReport(invoiceRoom);
+    }
+
     Report.notifyNewAction(invoiceRoom.reportID, receiver.accountID);
 }
 
@@ -3663,7 +3668,7 @@ function trackExpense(
     linkedTrackedExpenseReportID?: string,
 ) {
     const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
-    const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report.chatReportID) : report;
+    const currentChatReport = isMoneyRequestReport ? ReportUtils.getReportOrDraftReport(report.chatReportID) : report;
     const moneyRequestReportID = isMoneyRequestReport ? report.reportID : '';
     const isMovingTransactionFromTrackExpense = IOUUtils.isMovingTransactionFromTrackExpense(action);
 
@@ -3807,7 +3812,7 @@ function trackExpense(
             API.write(WRITE_COMMANDS.TRACK_EXPENSE, parameters, onyxData);
         }
     }
-    Navigation.dismissModal(activeReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : activeReportID);
 
     if (action === CONST.IOU.ACTION.SHARE) {
         Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(activeReportID ?? '-1', CONST.IOU.SHARE.ROLE.ACCOUNTANT));
@@ -4373,7 +4378,7 @@ function splitBill({
 
     API.write(WRITE_COMMANDS.SPLIT_BILL, parameters, onyxData);
 
-    Navigation.dismissModal(existingSplitChatReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : existingSplitChatReportID);
     Report.notifyNewAction(splitData.chatReportID, currentUserAccountID);
 }
 
@@ -4440,7 +4445,7 @@ function splitBillAndOpenReport({
 
     API.write(WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT, parameters, onyxData);
 
-    Navigation.dismissModal(splitData.chatReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : splitData.chatReportID);
     Report.notifyNewAction(splitData.chatReportID, currentUserAccountID);
 }
 
@@ -4994,7 +4999,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA
     };
 
     API.write(WRITE_COMMANDS.COMPLETE_SPLIT_BILL, parameters, {optimisticData, successData, failureData});
-    Navigation.dismissModal(chatReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : chatReportID);
     Report.notifyNewAction(chatReportID, sessionAccountID);
 }
 
@@ -5036,7 +5041,7 @@ function createDistanceRequest(
 ) {
     // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function
     const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report);
-    const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report?.chatReportID) : report;
+    const currentChatReport = isMoneyRequestReport ? ReportUtils.getReportOrDraftReport(report?.chatReportID) : report;
     const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : '';
 
     const optimisticReceipt: Receipt = {
@@ -5153,7 +5158,7 @@ function createDistanceRequest(
 
     API.write(WRITE_COMMANDS.CREATE_DISTANCE_REQUEST, parameters, onyxData);
     const activeReportID = isMoneyRequestReport ? report?.reportID ?? '-1' : parameters.chatReportID;
-    Navigation.dismissModal(activeReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : activeReportID);
     Report.notifyNewAction(activeReportID, userAccountID);
 }
 
@@ -6421,7 +6426,7 @@ function getReportFromHoldRequestsOnyxData(
             pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
         };
 
-        const heldReport = getReportOrDraftReport(holdReportAction.childReportID);
+        const heldReport = ReportUtils.getReportOrDraftReport(holdReportAction.childReportID);
         if (heldReport) {
             optimisticHoldReportExpenseActionIDs.push({optimisticReportActionID: reportActionID, oldReportActionID: holdReportAction.reportActionID});
 
@@ -6754,7 +6759,7 @@ function sendMoneyElsewhere(report: OnyxEntry, amount: number,
 
     API.write(WRITE_COMMANDS.SEND_MONEY_ELSEWHERE, params, {optimisticData, successData, failureData});
 
-    Navigation.dismissModal(params.chatReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : params.chatReportID);
     Report.notifyNewAction(params.chatReportID, managerID);
 }
 
@@ -6767,7 +6772,7 @@ function sendMoneyWithWallet(report: OnyxEntry, amount: number
 
     API.write(WRITE_COMMANDS.SEND_MONEY_WITH_WALLET, params, {optimisticData, successData, failureData});
 
-    Navigation.dismissModal(params.chatReportID);
+    Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : params.chatReportID);
     Report.notifyNewAction(params.chatReportID, managerID);
 }
 
@@ -6864,7 +6869,7 @@ function hasIOUToApproveOrPay(chatReport: OnyxEntry, excludedI
     const chatReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`] ?? {};
 
     return Object.values(chatReportActions).some((action) => {
-        const iouReport = getReportOrDraftReport(action.childReportID ?? '-1');
+        const iouReport = ReportUtils.getReportOrDraftReport(action.childReportID ?? '-1');
         const policy = PolicyUtils.getPolicy(iouReport?.policyID);
         const shouldShowSettlementButton = canIOUBePaid(iouReport, chatReport, policy) || canApproveIOU(iouReport, chatReport, policy);
         return action.childReportID?.toString() !== excludedIOUReportID && action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW && shouldShowSettlementButton;
@@ -6898,7 +6903,7 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?:
     const predictedNextState = isLastApprover(approvalChain) ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED;
 
     const optimisticNextStep = NextStepUtils.buildNextStep(expenseReport, predictedNextStatus);
-    const chatReport = getReportOrDraftReport(expenseReport?.chatReportID);
+    const chatReport = ReportUtils.getReportOrDraftReport(expenseReport?.chatReportID);
 
     const optimisticReportActionsData: OnyxUpdate = {
         onyxMethod: Onyx.METHOD.MERGE,
@@ -7135,7 +7140,7 @@ function submitReport(expenseReport: OnyxTypes.Report) {
     }
 
     const currentNextStep = allNextSteps[`${ONYXKEYS.COLLECTION.NEXT_STEP}${expenseReport.reportID}`] ?? null;
-    const parentReport = getReportOrDraftReport(expenseReport.parentReportID);
+    const parentReport = ReportUtils.getReportOrDraftReport(expenseReport.parentReportID);
     const policy = PolicyUtils.getPolicy(expenseReport.policyID);
     const isCurrentUserManager = currentUserPersonalDetails?.accountID === expenseReport.managerID;
     const isSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
@@ -7483,7 +7488,7 @@ function replaceReceipt(transactionID: string, file: File, source: string) {
  */
 function setMoneyRequestParticipantsFromReport(transactionID: string, report: OnyxEntry): Participant[] {
     // If the report is iou or expense report, we should get the chat report to set participant for request money
-    const chatReport = ReportUtils.isMoneyRequestReport(report) ? getReportOrDraftReport(report?.chatReportID) : report;
+    const chatReport = ReportUtils.isMoneyRequestReport(report) ? ReportUtils.getReportOrDraftReport(report?.chatReportID) : report;
     const currentUserAccountID = currentUserPersonalDetails?.accountID;
     const shouldAddAsReport = !isEmptyObject(chatReport) && ReportUtils.isSelfDM(chatReport);
     let participants: Participant[] = [];
diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts
index d0913a2b0ac1..753428e4a6df 100644
--- a/src/libs/actions/Policy/Policy.ts
+++ b/src/libs/actions/Policy/Policy.ts
@@ -12,6 +12,7 @@ import type {
     CreateWorkspaceParams,
     DeleteWorkspaceAvatarParams,
     DeleteWorkspaceParams,
+    EnablePolicyCompanyCardsParams,
     EnablePolicyConnectionsParams,
     EnablePolicyExpensifyCardsParams,
     EnablePolicyInvoicingParams,
@@ -42,6 +43,7 @@ import type {
     UpdateWorkspaceGeneralSettingsParams,
     UpgradeToCorporateParams,
 } from '@libs/API/parameters';
+import type SetPolicyRulesEnabledParams from '@libs/API/parameters/SetPolicyRulesEnabledParams';
 import type UpdatePolicyAddressParams from '@libs/API/parameters/UpdatePolicyAddressParams';
 import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types';
 import DateUtils from '@libs/DateUtils';
@@ -233,27 +235,24 @@ function getInvoicePrimaryWorkspace(activePolicyID?: OnyxEntry): Policy
 /**
  * Check if the user has any active free policies (aka workspaces)
  */
-function hasActiveChatEnabledPolicies(policies: Array> | OnyxCollection, includeOnlyFreePolicies = false): boolean {
-    const adminChatEnabledPolicies = Object.values(policies ?? {}).filter(
-        (policy) =>
-            policy &&
-            ((policy.type === CONST.POLICY.TYPE.FREE && policy.role === CONST.POLICY.ROLE.ADMIN) ||
-                (!includeOnlyFreePolicies && policy.type !== CONST.POLICY.TYPE.PERSONAL && policy.role === CONST.POLICY.ROLE.ADMIN && policy.isPolicyExpenseChatEnabled)),
+function hasActiveChatEnabledPolicies(policies: Array> | OnyxCollection, includeOnlyAdminPolicies = false): boolean {
+    const chatEnabledPolicies = Object.values(policies ?? {}).filter(
+        (policy) => policy?.isPolicyExpenseChatEnabled && (!includeOnlyAdminPolicies || policy.role === CONST.POLICY.ROLE.ADMIN),
     );
 
-    if (adminChatEnabledPolicies.length === 0) {
+    if (chatEnabledPolicies.length === 0) {
         return false;
     }
 
-    if (adminChatEnabledPolicies.some((policy) => !policy?.pendingAction)) {
+    if (chatEnabledPolicies.some((policy) => !policy?.pendingAction)) {
         return true;
     }
 
-    if (adminChatEnabledPolicies.some((policy) => policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD)) {
+    if (chatEnabledPolicies.some((policy) => policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD)) {
         return true;
     }
 
-    if (adminChatEnabledPolicies.some((policy) => policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)) {
+    if (chatEnabledPolicies.some((policy) => policy?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE)) {
         return false;
     }
 
@@ -1038,6 +1037,9 @@ function updateGeneralSettings(policyID: string, name: string, currencyValue?: s
     const customUnitID = distanceUnit?.customUnitID;
     const currency = currencyValue ?? policy?.outputCurrency ?? CONST.CURRENCY.USD;
 
+    const currencyPendingAction = currency !== policy?.outputCurrency ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE : undefined;
+    const namePendingAction = name !== policy?.name ? CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE : undefined;
+
     const currentRates = distanceUnit?.rates ?? {};
     const optimisticRates: Record = {};
     const finallyRates: Record = {};
@@ -1073,12 +1075,14 @@ function updateGeneralSettings(policyID: string, name: string, currencyValue?: s
 
                 pendingFields: {
                     ...policy.pendingFields,
-                    generalSettings: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+                    ...(namePendingAction !== undefined && {name: namePendingAction}),
+                    ...(currencyPendingAction !== undefined && {outputCurrency: currencyPendingAction}),
                 },
 
                 // Clear errorFields in case the user didn't dismiss the general settings error
                 errorFields: {
-                    generalSettings: null,
+                    name: null,
+                    outputCurrency: null,
                 },
                 name,
                 outputCurrency: currency,
@@ -1099,7 +1103,8 @@ function updateGeneralSettings(policyID: string, name: string, currencyValue?: s
             key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
             value: {
                 pendingFields: {
-                    generalSettings: null,
+                    name: null,
+                    outputCurrency: null,
                 },
                 ...(customUnitID && {
                     customUnits: {
@@ -1113,14 +1118,20 @@ function updateGeneralSettings(policyID: string, name: string, currencyValue?: s
         },
     ];
 
+    const errorFields: Policy['errorFields'] = {
+        name: namePendingAction && ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.editor.genericFailureMessage'),
+    };
+
+    if (!errorFields.name && currencyPendingAction) {
+        errorFields.outputCurrency = ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.editor.genericFailureMessage');
+    }
+
     const failureData: OnyxUpdate[] = [
         {
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
             value: {
-                errorFields: {
-                    generalSettings: ErrorUtils.getMicroSecondOnyxErrorWithTranslationKey('workspace.editor.genericFailureMessage'),
-                },
+                errorFields,
                 ...(customUnitID && {
                     customUnits: {
                         [customUnitID]: {
@@ -1280,12 +1291,29 @@ function updateAddress(policyID: string, newAddress: CompanyAddress) {
             key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
             value: {
                 address: newAddress,
+                pendingFields: {
+                    address: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+                },
+            },
+        },
+    ];
+
+    const finallyData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                address: newAddress,
+                pendingFields: {
+                    address: null,
+                },
             },
         },
     ];
 
     API.write(WRITE_COMMANDS.UPDATE_POLICY_ADDRESS, parameters, {
         optimisticData,
+        finallyData,
     });
 }
 
@@ -1597,7 +1625,9 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName
                     autoReporting: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
                     approvalMode: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
                     reimbursementChoice: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
-                    generalSettings: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+                    name: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+                    outputCurrency: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
+                    address: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
                     description: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
                 },
             },
@@ -1674,6 +1704,9 @@ function buildPolicyData(policyOwnerEmail = '', makeMeAdmin = false, policyName
                     autoReporting: null,
                     approvalMode: null,
                     reimbursementChoice: null,
+                    name: null,
+                    outputCurrency: null,
+                    address: null,
                 },
             },
         },
@@ -2687,6 +2720,56 @@ function enableExpensifyCard(policyID: string, enabled: boolean) {
     }
 }
 
+function enableCompanyCards(policyID: string, enabled: boolean) {
+    const authToken = NetworkStore.getAuthToken();
+
+    const onyxData: OnyxData = {
+        optimisticData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+                value: {
+                    areCompanyCardsEnabled: enabled,
+                    pendingFields: {
+                        areCompanyCardsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+                    },
+                },
+            },
+        ],
+        successData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+                value: {
+                    pendingFields: {
+                        areCompanyCardsEnabled: null,
+                    },
+                },
+            },
+        ],
+        failureData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+                value: {
+                    areCompanyCardsEnabled: !enabled,
+                    pendingFields: {
+                        areCompanyCardsEnabled: null,
+                    },
+                },
+            },
+        ],
+    };
+
+    const parameters: EnablePolicyCompanyCardsParams = {authToken, policyID, enabled};
+
+    API.write(WRITE_COMMANDS.ENABLE_POLICY_COMPANY_CARDS, parameters, onyxData);
+
+    if (enabled && getIsNarrowLayout()) {
+        navigateWhenEnableFeature(policyID);
+    }
+}
+
 function enablePolicyReportFields(policyID: string, enabled: boolean, disableRedirect = false) {
     const onyxData: OnyxData = {
         optimisticData: [
@@ -2940,6 +3023,68 @@ function enablePolicyWorkflows(policyID: string, enabled: boolean) {
     }
 }
 
+const DISABLED_MAX_EXPENSE_VALUES: Pick = {
+    maxExpenseAmountNoReceipt: CONST.DISABLED_MAX_EXPENSE_VALUE,
+    maxExpenseAmount: CONST.DISABLED_MAX_EXPENSE_VALUE,
+    maxExpenseAge: CONST.DISABLED_MAX_EXPENSE_VALUE,
+};
+
+function enablePolicyRules(policyID: string, enabled: boolean, disableRedirect = false) {
+    const policy = getPolicy(policyID);
+    const onyxData: OnyxData = {
+        optimisticData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+                value: {
+                    areRulesEnabled: enabled,
+                    ...(!enabled ? DISABLED_MAX_EXPENSE_VALUES : {}),
+                    pendingFields: {
+                        areRulesEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
+                    },
+                },
+            },
+        ],
+        successData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+                value: {
+                    pendingFields: {
+                        areRulesEnabled: null,
+                    },
+                },
+            },
+        ],
+        failureData: [
+            {
+                onyxMethod: Onyx.METHOD.MERGE,
+                key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+                value: {
+                    areRulesEnabled: !enabled,
+                    ...(!enabled
+                        ? {
+                              maxExpenseAmountNoReceipt: policy?.maxExpenseAmountNoReceipt,
+                              maxExpenseAmount: policy?.maxExpenseAmount,
+                              maxExpenseAge: policy?.maxExpenseAge,
+                          }
+                        : {}),
+                    pendingFields: {
+                        areRulesEnabled: null,
+                    },
+                },
+            },
+        ],
+    };
+
+    const parameters: SetPolicyRulesEnabledParams = {policyID, enabled};
+    API.write(WRITE_COMMANDS.SET_POLICY_RULES_ENABLED, parameters, onyxData);
+
+    if (enabled && getIsNarrowLayout() && !disableRedirect) {
+        navigateWhenEnableFeature(policyID);
+    }
+}
+
 function enableDistanceRequestTax(policyID: string, customUnitName: string, customUnitID: string, attributes: Attributes) {
     const policy = getPolicy(policyID);
     const onyxData: OnyxData = {
@@ -3332,6 +3477,7 @@ export {
     setWorkspacePayer,
     setWorkspaceReimbursement,
     openPolicyWorkflowsPage,
+    enableCompanyCards,
     enablePolicyConnections,
     enablePolicyReportFields,
     enablePolicyTaxes,
@@ -3367,6 +3513,7 @@ export {
     getAdminPoliciesConnectedToNetSuite,
     getAdminPoliciesConnectedToSageIntacct,
     hasInvoicingDetails,
+    enablePolicyRules,
 };
 
 export type {NewCustomUnit};
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 2611dae20d7e..7eb7e01d6edd 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -1238,6 +1238,11 @@ function readNewestAction(reportID: string, shouldResetUnreadMarker = false) {
  * Sets the last read time on a report
  */
 function markCommentAsUnread(reportID: string, reportActionCreated: string) {
+    if (reportID === '-1') {
+        Log.warn('7339cd6c-3263-4f89-98e5-730f0be15784 Invalid report passed to MarkCommentAsUnread. Not calling the API because it wil fail.');
+        return;
+    }
+
     const reportActions = allReportActions?.[reportID];
 
     // Find the latest report actions from other users
diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts
index 040fd6e47491..b51955c9cc59 100644
--- a/src/libs/actions/Search.ts
+++ b/src/libs/actions/Search.ts
@@ -97,9 +97,10 @@ function deleteMoneyRequestOnSearch(hash: number, transactionIDList: string[]) {
 
 type Params = Record;
 
-function exportSearchItemsToCSV({query, reportIDList, transactionIDList, policyIDs}: ExportSearchItemsToCSVParams, onDownloadFailed: () => void) {
+function exportSearchItemsToCSV({query, jsonQuery, reportIDList, transactionIDList, policyIDs}: ExportSearchItemsToCSVParams, onDownloadFailed: () => void) {
     const finalParameters = enhanceParameters(WRITE_COMMANDS.EXPORT_SEARCH_ITEMS_TO_CSV, {
         query,
+        jsonQuery,
         reportIDList,
         transactionIDList,
         policyIDs,
diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts
index c06e6087d336..b687abd61bb9 100644
--- a/src/libs/actions/Session/index.ts
+++ b/src/libs/actions/Session/index.ts
@@ -1001,6 +1001,7 @@ function handleExitToNavigation(exitTo: Route | HybridAppRoute) {
         waitForUserSignIn().then(() => {
             Navigation.waitForProtectedRoutes().then(() => {
                 const url = NativeModules.HybridAppModule ? Navigation.parseHybridAppUrl(exitTo) : (exitTo as Route);
+                Navigation.goBack();
                 Navigation.navigate(url);
             });
         });
diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts
index 0b6c29c9890a..ec1b9c08025e 100644
--- a/src/libs/actions/Workflow.ts
+++ b/src/libs/actions/Workflow.ts
@@ -1,6 +1,6 @@
 import lodashDropRightWhile from 'lodash/dropRightWhile';
 import lodashMapKeys from 'lodash/mapKeys';
-import type {OnyxCollection, OnyxUpdate} from 'react-native-onyx';
+import type {OnyxCollection, OnyxMergeInput, OnyxUpdate} from 'react-native-onyx';
 import Onyx from 'react-native-onyx';
 import * as API from '@libs/API';
 import type {CreateWorkspaceApprovalParams, RemoveWorkspaceApprovalParams, UpdateWorkspaceApprovalParams} from '@libs/API/parameters';
@@ -327,7 +327,7 @@ function clearApprovalWorkflowApprovers() {
     Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, {approvers: []});
 }
 
-function setApprovalWorkflow(approvalWorkflow: ApprovalWorkflowOnyx) {
+function setApprovalWorkflow(approvalWorkflow: OnyxMergeInput) {
     Onyx.merge(ONYXKEYS.APPROVAL_WORKFLOW, approvalWorkflow);
 }
 
diff --git a/src/pages/EditReportFieldPage.tsx b/src/pages/EditReportFieldPage.tsx
index a58f95eebc9d..9922eeb2a430 100644
--- a/src/pages/EditReportFieldPage.tsx
+++ b/src/pages/EditReportFieldPage.tsx
@@ -12,6 +12,7 @@ import ScreenWrapper from '@components/ScreenWrapper';
 import useLocalize from '@hooks/useLocalize';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWindowDimensions from '@hooks/useWindowDimensions';
+import isSearchTopmostCentralPane from '@libs/Navigation/isSearchTopmostCentralPane';
 import Navigation from '@libs/Navigation/Navigation';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as ReportActions from '@src/libs/actions/Report';
@@ -80,14 +81,14 @@ function EditReportFieldPage({route, policy, report}: EditReportFieldPageProps)
             Navigation.goBack();
         } else {
             ReportActions.updateReportField(report.reportID, {...reportField, value: value === '' ? null : value}, reportField);
-            Navigation.dismissModal(report?.reportID);
+            Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : report?.reportID);
         }
     };
 
     const handleReportFieldDelete = () => {
         ReportActions.deleteReportField(report.reportID, reportField);
         setIsDeleteModalVisible(false);
-        Navigation.dismissModal(report?.reportID);
+        Navigation.dismissModal(isSearchTopmostCentralPane() ? undefined : report?.reportID);
     };
 
     const fieldValue = isReportFieldTitle ? report.reportName ?? '' : reportField.value ?? reportField.defaultValue;
diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx
index ea1b70cd6074..71450b7ebf24 100644
--- a/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx
+++ b/src/pages/EnablePayments/PersonalInfo/substeps/ConfirmationStep.tsx
@@ -32,6 +32,7 @@ function ConfirmationStep({onNext, onMove}: SubStepProps) {
     const isLoading = walletAdditionalDetails?.isLoading ?? false;
     const error = ErrorUtils.getLatestErrorMessage(walletAdditionalDetails ?? {});
     const values = useMemo(() => getSubstepValues(PERSONAL_INFO_STEP_KEYS, walletAdditionalDetailsDraft, walletAdditionalDetails), [walletAdditionalDetails, walletAdditionalDetailsDraft]);
+    const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN;
 
     return (
         
@@ -76,7 +77,7 @@ function ConfirmationStep({onNext, onMove}: SubStepProps) {
                         }}
                     />
                      {
diff --git a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx
index bdaa3fe98f67..c12f90db072e 100644
--- a/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx
+++ b/src/pages/EnablePayments/PersonalInfo/substeps/SocialSecurityNumberStep.tsx
@@ -23,21 +23,26 @@ function SocialSecurityNumberStep({onNext, isEditing}: SubStepProps) {
     const {translate} = useLocalize();
     const styles = useThemeStyles();
 
+    const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS);
+    const shouldAskForFullSSN = walletAdditionalDetails?.errorCode === CONST.WALLET.ERROR.SSN;
+
     const validate = useCallback(
         (values: FormOnyxValues): FormInputErrors => {
             const errors = ValidationUtils.getFieldRequiredErrors(values, STEP_FIELDS);
 
-            if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) {
+            if (shouldAskForFullSSN) {
+                if (values.ssn && !ValidationUtils.isValidSSNFullNine(values.ssn)) {
+                    errors.ssn = translate('additionalDetailsStep.ssnFull9Error');
+                }
+            } else if (values.ssn && !ValidationUtils.isValidSSNLastFour(values.ssn)) {
                 errors.ssn = translate('bankAccount.error.ssnLast4');
             }
 
             return errors;
         },
-        [translate],
+        [translate, shouldAskForFullSSN],
     );
 
-    const [walletAdditionalDetails] = useOnyx(ONYXKEYS.WALLET_ADDITIONAL_DETAILS);
-
     const defaultSsnLast4 = walletAdditionalDetails?.[PERSONAL_INFO_STEP_KEY.SSN_LAST_4] ?? '';
 
     const handleSubmit = useWalletAdditionalDetailsStepFormSubmit({
@@ -61,13 +66,13 @@ function SocialSecurityNumberStep({onNext, isEditing}: SubStepProps) {
                     
                 
diff --git a/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx b/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx
index 8687ff89ba62..599a7a1cf6f1 100644
--- a/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx
+++ b/src/pages/ReimbursementAccount/EnableBankAccount/EnableBankAccount.tsx
@@ -42,7 +42,7 @@ function EnableBankAccount({reimbursementAccount, user, onBackButtonPress}: Enab
     const achData = reimbursementAccount?.achData ?? {};
     const {icon, iconSize} = getBankIcon({bankName: achData.bankName, styles});
     const isUsingExpensifyCard = user?.isUsingExpensifyCard;
-    const formattedBankAccountNumber = achData.accountNumber ? `${translate('paymentMethodList.accountLastFour')} ${achData.accountNumber.slice(-4)}` : '';
+    const formattedBankAccountNumber = achData.accountNumber ? `${translate('bankAccount.accountEnding')} ${achData.accountNumber.slice(-4)}` : '';
     const bankAccountOwnerName = achData.addressName;
     const errors = reimbursementAccount?.errors ?? {};
     const pendingAction = reimbursementAccount?.pendingAction;
diff --git a/src/pages/Search/AdvancedSearchFilters.tsx b/src/pages/Search/AdvancedSearchFilters.tsx
index 644ae64466f7..4b859e37785e 100644
--- a/src/pages/Search/AdvancedSearchFilters.tsx
+++ b/src/pages/Search/AdvancedSearchFilters.tsx
@@ -12,6 +12,7 @@ import useLocalize from '@hooks/useLocalize';
 import useSingleExecution from '@hooks/useSingleExecution';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useWaitForNavigation from '@hooks/useWaitForNavigation';
+import {convertToDisplayStringWithoutCurrency} from '@libs/CurrencyUtils';
 import Navigation from '@libs/Navigation/Navigation';
 import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
 import {getAllTaxRates} from '@libs/PolicyUtils';
@@ -65,6 +66,19 @@ function getFilterDisplayTitle(filters: Partial, fiel
         return dateValue;
     }
 
+    if (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT) {
+        const {lessThan, greaterThan} = filters;
+        if (lessThan && greaterThan) {
+            return translate('search.filters.amount.between', convertToDisplayStringWithoutCurrency(Number(greaterThan)), convertToDisplayStringWithoutCurrency(Number(lessThan)));
+        }
+        if (lessThan) {
+            return translate('search.filters.amount.lessThan', convertToDisplayStringWithoutCurrency(Number(lessThan)));
+        }
+        if (greaterThan) {
+            return translate('search.filters.amount.greaterThan', convertToDisplayStringWithoutCurrency(Number(greaterThan)));
+        }
+    }
+
     if (
         (fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.CURRENCY || fieldName === CONST.SEARCH.SYNTAX_FILTER_KEYS.TAG) &&
         filters[fieldName]
@@ -149,6 +163,11 @@ function AdvancedSearchFilters() {
                 description: 'common.reportID' as const,
                 route: ROUTES.SEARCH_ADVANCED_FILTERS_REPORT_ID,
             },
+            {
+                title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.AMOUNT, translate),
+                description: 'common.total' as const,
+                route: ROUTES.SEARCH_ADVANCED_FILTERS_AMOUNT,
+            },
             {
                 title: getFilterDisplayTitle(searchAdvancedFilters, CONST.SEARCH.SYNTAX_FILTER_KEYS.CATEGORY, translate),
                 description: 'common.category' as const,
@@ -206,31 +225,33 @@ function AdvancedSearchFilters() {
     };
 
     return (
-        
-            
-                {advancedFilters.map((item) => {
-                    const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route)));
-                    if (item.shouldHide) {
-                        return undefined;
-                    }
-                    return (
-                        
-                    );
-                })}
-            
+        <>
+            
+                
+                    {advancedFilters.map((item) => {
+                        const onPress = singleExecution(waitForNavigate(() => Navigation.navigate(item.route)));
+                        if (item.shouldHide) {
+                            return undefined;
+                        }
+                        return (
+                            
+                        );
+                    })}
+                
+            
             
-        
+        
     );
 }
 
diff --git a/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx
new file mode 100644
index 000000000000..56eb3dce4350
--- /dev/null
+++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersAmountPage.tsx
@@ -0,0 +1,92 @@
+import React from 'react';
+import {View} from 'react-native';
+import {useOnyx} from 'react-native-onyx';
+import AmountWithoutCurrencyForm from '@components/AmountWithoutCurrencyForm';
+import FormProvider from '@components/Form/FormProvider';
+import InputWrapper from '@components/Form/InputWrapper';
+import type {FormOnyxValues} from '@components/Form/types';
+import HeaderWithBackButton from '@components/HeaderWithBackButton';
+import ScreenWrapper from '@components/ScreenWrapper';
+import useAutoFocusInput from '@hooks/useAutoFocusInput';
+import useLocalize from '@hooks/useLocalize';
+import useThemeStyles from '@hooks/useThemeStyles';
+import {updateAdvancedFilters} from '@libs/actions/Search';
+import {convertToBackendAmount, convertToFrontendAmountAsString} from '@libs/CurrencyUtils';
+import Navigation from '@libs/Navigation/Navigation';
+import CONST from '@src/CONST';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import INPUT_IDS from '@src/types/form/SearchAdvancedFiltersForm';
+
+function SearchFiltersAmountPage() {
+    const styles = useThemeStyles();
+    const {translate} = useLocalize();
+
+    const [searchAdvancedFiltersForm] = useOnyx(ONYXKEYS.FORMS.SEARCH_ADVANCED_FILTERS_FORM);
+    const greaterThan = searchAdvancedFiltersForm?.[INPUT_IDS.GREATER_THAN];
+    const greaterThanFormattedAmount = greaterThan ? convertToFrontendAmountAsString(Number(greaterThan)) : undefined;
+    const lessThan = searchAdvancedFiltersForm?.[INPUT_IDS.LESS_THAN];
+    const lessThanFormattedAmount = lessThan ? convertToFrontendAmountAsString(Number(lessThan)) : undefined;
+    const {inputCallbackRef} = useAutoFocusInput();
+
+    const updateAmountFilter = (values: FormOnyxValues) => {
+        const greater = values[INPUT_IDS.GREATER_THAN];
+        const greaterThanBackendAmount = greater ? convertToBackendAmount(Number(greater)) : '';
+        const less = values[INPUT_IDS.LESS_THAN];
+        const lessThanBackendAmount = less ? convertToBackendAmount(Number(less)) : '';
+        updateAdvancedFilters({greaterThan: greaterThanBackendAmount?.toString(), lessThan: lessThanBackendAmount?.toString()});
+        Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS);
+    };
+
+    return (
+        
+             {
+                    Navigation.goBack(ROUTES.SEARCH_ADVANCED_FILTERS);
+                }}
+            />
+            
+                
+                    
+                
+                
+                    
+                
+            
+        
+    );
+}
+
+SearchFiltersAmountPage.displayName = 'SearchFiltersAmountPage';
+
+export default SearchFiltersAmountPage;
diff --git a/src/pages/Search/SearchFiltersCardPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCardPage.tsx
similarity index 99%
rename from src/pages/Search/SearchFiltersCardPage.tsx
rename to src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCardPage.tsx
index 4760dcdf0ff3..3ee7542da280 100644
--- a/src/pages/Search/SearchFiltersCardPage.tsx
+++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersCardPage.tsx
@@ -88,6 +88,7 @@ function SearchFiltersCardPage() {
             testID={SearchFiltersCardPage.displayName}
             shouldShowOfflineIndicatorInWideScreen
             offlineIndicatorStyle={styles.mtAuto}
+            shouldEnableMaxHeight
         >
             
             
             
             
             
             
             
             
diff --git a/src/pages/Search/SearchFiltersKeywordPage.tsx b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersKeywordPage.tsx
similarity index 98%
rename from src/pages/Search/SearchFiltersKeywordPage.tsx
rename to src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersKeywordPage.tsx
index 3a0fb0a67367..41b1bcbc943b 100644
--- a/src/pages/Search/SearchFiltersKeywordPage.tsx
+++ b/src/pages/Search/SearchAdvancedFiltersPage/SearchFiltersKeywordPage.tsx
@@ -36,6 +36,7 @@ function SearchFiltersKeywordPage() {
             testID={SearchFiltersKeywordPage.displayName}
             shouldShowOfflineIndicatorInWideScreen
             offlineIndicatorStyle={styles.mtAuto}
+            shouldEnableMaxHeight
         >
             
                 
             
             
             
             
             
             
diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
index cfba04fee23c..db7e482a0457 100644
--- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
+++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx
@@ -440,13 +440,13 @@ const ContextMenuActions: ContextMenuAction[] = [
                 } else if (ReportActionsUtils.isRenamedAction(reportAction)) {
                     setClipboardMessage(ReportActionsUtils.getRenamedAction(reportAction));
                 } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.SUBMITTED) {
-                    const displayMessage = ReportUtils.getIOUSubmittedMessage(reportID);
+                    const displayMessage = ReportUtils.getIOUSubmittedMessage(reportAction);
                     Clipboard.setString(displayMessage);
                 } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.APPROVED) {
-                    const displayMessage = ReportUtils.getIOUApprovedMessage(reportID);
+                    const displayMessage = ReportUtils.getIOUApprovedMessage(reportAction);
                     Clipboard.setString(displayMessage);
                 } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.FORWARDED) {
-                    const displayMessage = ReportUtils.getIOUForwardedMessage(reportID);
+                    const displayMessage = ReportUtils.getIOUForwardedMessage(reportAction);
                     Clipboard.setString(displayMessage);
                 } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) {
                     Clipboard.setString(Localize.translateLocal('iou.heldExpense'));
@@ -470,6 +470,9 @@ const ContextMenuActions: ContextMenuAction[] = [
                     setClipboardMessage(ReportActionsUtils.getPolicyChangeLogChangeRoleMessage(reportAction));
                 } else if (reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.DELETE_EMPLOYEE) {
                     setClipboardMessage(ReportActionsUtils.getPolicyChangeLogDeleteMemberMessage(reportAction));
+                } else if (ReportActionsUtils.isActionOfType(reportAction, CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED)) {
+                    const {label, errorMessage} = ReportActionsUtils.getOriginalMessage(reportAction) ?? {label: '', errorMessage: ''};
+                    setClipboardMessage(Localize.translateLocal('report.actions.type.integrationSyncFailed', label, errorMessage));
                 } else if (content) {
                     setClipboardMessage(
                         content.replace(/()(.*?)(<\/mention-user>)/gi, (match, openTag: string, innerContent: string, closeTag: string): string => {
@@ -637,7 +640,6 @@ const ContextMenuActions: ContextMenuAction[] = [
 ];
 
 const restrictedReadOnlyActions: TranslationPaths[] = [
-    'common.download',
     'reportActionContextMenu.replyInThread',
     'reportActionContextMenu.editAction',
     'reportActionContextMenu.joinThread',
diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
index 005824fa949f..da7b4f72bfb6 100644
--- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
+++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx
@@ -1,4 +1,5 @@
 import {useNavigation} from '@react-navigation/native';
+import noop from 'lodash/noop';
 import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react';
 import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputFocusEventData, TextInputSelectionChangeEventData} from 'react-native';
 import {View} from 'react-native';
@@ -31,6 +32,7 @@ import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus';
 import * as DeviceCapabilities from '@libs/DeviceCapabilities';
 import {getDraftComment} from '@libs/DraftCommentUtils';
 import getModalState from '@libs/getModalState';
+import Performance from '@libs/Performance';
 import * as ReportUtils from '@libs/ReportUtils';
 import playSound, {SOUNDS} from '@libs/Sound';
 import willBlurTextInputOnTapOutsideFunc from '@libs/willBlurTextInputOnTapOutside';
@@ -116,6 +118,9 @@ const shouldFocusInputOnScreenFocus = canFocusInputOnScreenFocus();
 
 const willBlurTextInputOnTapOutside = willBlurTextInputOnTapOutsideFunc();
 
+// eslint-disable-next-line import/no-mutable-exports
+let onSubmitAction = noop;
+
 function ReportActionCompose({
     blockedFromConcierge,
     currentUserPersonalDetails,
@@ -309,6 +314,7 @@ function ReportActionCompose({
                 Report.addAttachment(reportID, attachmentFileRef.current, newCommentTrimmed);
                 attachmentFileRef.current = null;
             } else {
+                Performance.markStart(CONST.TIMING.MESSAGE_SENT, {message: newCommentTrimmed});
                 onSubmit(newCommentTrimmed);
             }
         },
@@ -390,6 +396,9 @@ function ReportActionCompose({
         clearComposer();
     }, [isSendDisabled, isReportReadyForDisplay, composerRefShared]);
 
+    // eslint-disable-next-line react-compiler/react-compiler
+    onSubmitAction = handleSendMessage;
+
     const emojiShiftVertical = useMemo(() => {
         const chatItemComposeSecondaryRowHeight = styles.chatItemComposeSecondaryRow.height + styles.chatItemComposeSecondaryRow.marginTop + styles.chatItemComposeSecondaryRow.marginBottom;
         const reportActionComposeHeight = styles.chatItemComposeBox.minHeight + chatItemComposeSecondaryRowHeight;
@@ -594,5 +603,5 @@ export default withCurrentUserPersonalDetails(
         },
     })(memo(ReportActionCompose)),
 );
-
+export {onSubmitAction};
 export type {SuggestionsRef, ComposerRef};
diff --git a/src/pages/home/report/ReportActionItem.tsx b/src/pages/home/report/ReportActionItem.tsx
index 4f348213f198..e4dab8518eb2 100644
--- a/src/pages/home/report/ReportActionItem.tsx
+++ b/src/pages/home/report/ReportActionItem.tsx
@@ -639,11 +639,11 @@ function ReportActionItem({
         } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.MODIFIED_EXPENSE) {
             children = ;
         } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.SUBMITTED) {
-            children = ;
+            children = ;
         } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.APPROVED) {
-            children = ;
+            children = ;
         } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.FORWARDED) {
-            children = ;
+            children = ;
         } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD) {
             children = ;
         } else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.HOLD_COMMENT) {
@@ -673,6 +673,9 @@ function ReportActionItem({
         } else if (ReportActionsUtils.isRenamedAction(action)) {
             const message = ReportActionsUtils.getRenamedAction(action);
             children = ;
+        } else if (ReportActionsUtils.isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED)) {
+            const {label, errorMessage} = ReportActionsUtils.getOriginalMessage(action) ?? {label: '', errorMessage: ''};
+            children = ;
         } else {
             const hasBeenFlagged =
                 ![CONST.MODERATION.MODERATOR_DECISION_APPROVED, CONST.MODERATION.MODERATOR_DECISION_PENDING].some((item) => item === moderationDecision) &&
diff --git a/src/pages/home/report/ReportActionItemThread.tsx b/src/pages/home/report/ReportActionItemThread.tsx
index d18adf6ba0d2..94a8592d9607 100644
--- a/src/pages/home/report/ReportActionItemThread.tsx
+++ b/src/pages/home/report/ReportActionItemThread.tsx
@@ -46,7 +46,7 @@ function ReportActionItemThread({numberOfReplies, icons, mostRecentReply, childR
              {
                     Report.navigateToAndOpenChildReport(childReportID);
-                    Timing.start(CONST.TIMING.SWITCH_REPORT_THREAD);
+                    Timing.start(CONST.TIMING.OPEN_REPORT_THREAD);
                 }}
                 role={CONST.ROLE.BUTTON}
                 accessibilityLabel={`${numberOfReplies} ${replyText}`}
diff --git a/src/pages/home/report/ReportActionsView.tsx b/src/pages/home/report/ReportActionsView.tsx
index 463bbe119514..0386b37f1aa9 100755
--- a/src/pages/home/report/ReportActionsView.tsx
+++ b/src/pages/home/report/ReportActionsView.tsx
@@ -436,8 +436,8 @@ function ReportActionsView({
             Performance.markEnd(CONST.TIMING.SWITCH_REPORT);
         }
         Timing.end(CONST.TIMING.SWITCH_REPORT, hasCachedActionOnFirstRender ? CONST.TIMING.WARM : CONST.TIMING.COLD);
-        Timing.end(CONST.TIMING.SWITCH_REPORT_THREAD);
-        Timing.end(CONST.TIMING.SWITCH_REPORT_FROM_PREVIEW);
+        Timing.end(CONST.TIMING.OPEN_REPORT_THREAD);
+        Timing.end(CONST.TIMING.OPEN_REPORT_FROM_PREVIEW);
     }, [hasCachedActionOnFirstRender]);
 
     // Check if the first report action in the list is the one we're currently linked to
diff --git a/src/pages/home/report/comment/TextCommentFragment.tsx b/src/pages/home/report/comment/TextCommentFragment.tsx
index 68827de96172..9d1a4d9ad4b7 100644
--- a/src/pages/home/report/comment/TextCommentFragment.tsx
+++ b/src/pages/home/report/comment/TextCommentFragment.tsx
@@ -1,6 +1,6 @@
 import {Str} from 'expensify-common';
 import {isEmpty} from 'lodash';
-import React, {memo} from 'react';
+import React, {memo, useEffect} from 'react';
 import type {StyleProp, TextStyle} from 'react-native';
 import Text from '@components/Text';
 import ZeroWidthView from '@components/ZeroWidthView';
@@ -11,6 +11,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
 import convertToLTR from '@libs/convertToLTR';
 import * as DeviceCapabilities from '@libs/DeviceCapabilities';
 import * as EmojiUtils from '@libs/EmojiUtils';
+import Performance from '@libs/Performance';
 import variables from '@styles/variables';
 import CONST from '@src/CONST';
 import type {OriginalMessageSource} from '@src/types/onyx/OriginalMessage';
@@ -48,6 +49,10 @@ function TextCommentFragment({fragment, styleAsDeleted, styleAsMuted = false, so
     const {translate} = useLocalize();
     const {shouldUseNarrowLayout} = useResponsiveLayout();
 
+    useEffect(() => {
+        Performance.markEnd(CONST.TIMING.MESSAGE_SENT, {message: text});
+    }, [text]);
+
     // If the only difference between fragment.text and fragment.html is 
tags and emoji tag // on native, we render it as text, not as html // on other device, only render it as text if the only difference is
tag diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 81d9577d4d4d..3b42b1d9a1be 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import SelectionList from '@components/SelectionList'; import RadioListItem from '@components/SelectionList/RadioListItem'; import Text from '@components/Text'; @@ -8,7 +8,6 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as IOU from '@libs/actions/IOU'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import type {MileageRate} from '@libs/DistanceRequestUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getCustomUnitRate, isTaxTrackingEnabled} from '@libs/PolicyUtils'; @@ -33,9 +32,6 @@ type IOURequestStepDistanceRateOnyxProps = { /** Collection of tags attached to the policy */ policyTags: OnyxEntry; - - /** Mileage rates */ - rates: Record; }; type IOURequestStepDistanceRateProps = IOURequestStepDistanceRateOnyxProps & @@ -45,16 +41,21 @@ type IOURequestStepDistanceRateProps = IOURequestStepDistanceRateOnyxProps & }; function IOURequestStepDistanceRate({ - policy, + policy: policyReal, report, + reportDraft, route: { params: {action, reportID, backTo, transactionID}, }, transaction, - rates, policyTags, policyCategories, }: IOURequestStepDistanceRateProps) { + const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${IOU.getIOURequestPolicyID(transaction, reportDraft) ?? '-1'}`); + + const policy = policyReal ?? policyDraft; + const rates = DistanceRequestUtils.getMileageRates(policy); + const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); const isDistanceRequest = TransactionUtils.isDistanceRequest(transaction); @@ -138,10 +139,6 @@ const IOURequestStepDistanceRateWithOnyx = withOnyx `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report ? report.policyID : '0'}`, }, - rates: { - key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? '-1'}`, - selector: (policy: OnyxEntry) => DistanceRequestUtils.getMileageRates(policy), - }, })(IOURequestStepDistanceRate); // eslint-disable-next-line rulesdir/no-negated-variables diff --git a/src/pages/iou/request/step/IOURequestStepParticipants.tsx b/src/pages/iou/request/step/IOURequestStepParticipants.tsx index 7fbc8d260f8a..552ad4d54e39 100644 --- a/src/pages/iou/request/step/IOURequestStepParticipants.tsx +++ b/src/pages/iou/request/step/IOURequestStepParticipants.tsx @@ -4,6 +4,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; import FormHelpMessage from '@components/FormHelpMessage'; import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import {READ_COMMANDS} from '@libs/API/types'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; @@ -45,6 +46,7 @@ function IOURequestStepParticipants({ const {translate} = useLocalize(); const styles = useThemeStyles(); const isFocused = useIsFocused(); + const {canUseP2PDistanceRequests} = usePermissions(iouType); // We need to set selectedReportID if user has navigated back from confirmation page and navigates to confirmation page with already selected participant const selectedReportID = useRef(participants?.length === 1 ? participants[0]?.reportID ?? reportID : reportID); @@ -93,7 +95,7 @@ function IOURequestStepParticipants({ HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); const firstParticipantReportID = val[0]?.reportID ?? ''; - const rateID = DistanceRequestUtils.getCustomUnitRateID(firstParticipantReportID); + const rateID = DistanceRequestUtils.getCustomUnitRateID(firstParticipantReportID, !canUseP2PDistanceRequests); const isInvoice = iouType === CONST.IOU.TYPE.INVOICE && ReportUtils.isInvoiceRoomWithID(firstParticipantReportID); numberOfParticipants.current = val.length; @@ -110,7 +112,7 @@ function IOURequestStepParticipants({ // When a participant is selected, the reportID needs to be saved because that's the reportID that will be used in the confirmation step. selectedReportID.current = firstParticipantReportID || reportID; }, - [iouType, reportID, transactionID], + [iouType, reportID, transactionID, canUseP2PDistanceRequests], ); const goToNextStep = useCallback(() => { diff --git a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx index 7f753e02ee6d..3f5db6cf5613 100644 --- a/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx +++ b/src/pages/settings/AboutPage/ShareLogList/BaseShareLogList.tsx @@ -60,7 +60,6 @@ function BaseShareLogList({onAttachLogToReport}: BaseShareLogListProps) { } const filteredOptions = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchValue, { - includeChatRoomsByParticipants: true, preferChatroomsOverThreads: true, sortByReportTypeInSearch: true, }); diff --git a/src/pages/settings/Subscription/RequestEarlyCancellationPage/index.tsx b/src/pages/settings/Subscription/RequestEarlyCancellationPage/index.tsx index ab148f8d8edc..d74e126ff94e 100644 --- a/src/pages/settings/Subscription/RequestEarlyCancellationPage/index.tsx +++ b/src/pages/settings/Subscription/RequestEarlyCancellationPage/index.tsx @@ -107,6 +107,7 @@ function RequestEarlyCancellationPage() { footerText={{acknowledgementText}} isNoteRequired isLoading={isLoading} + enabledWhenOffline={false} /> ), [acknowledgementText, isLoading, styles.flex1, styles.mb2, styles.mt4, translate], diff --git a/src/pages/signin/UnlinkLoginForm.tsx b/src/pages/signin/UnlinkLoginForm.tsx index 2fa43fbe61b2..9ccbbaaa56cd 100644 --- a/src/pages/signin/UnlinkLoginForm.tsx +++ b/src/pages/signin/UnlinkLoginForm.tsx @@ -32,6 +32,7 @@ function UnlinkLoginForm({account, credentials}: UnlinkLoginFormProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); const {isOffline} = useNetwork(); + const unlinkMessage = account?.message === 'unlinkLoginForm.linkSent' || account?.message === 'unlinkLoginForm.succesfullyUnlinkedLogin' ? translate(account?.message) : account?.message; const primaryLogin = useMemo(() => { if (!account?.primaryLogin) { return ''; @@ -53,13 +54,13 @@ function UnlinkLoginForm({account, credentials}: UnlinkLoginFormProps) { {translate('unlinkLoginForm.noLongerHaveAccess', {primaryLogin})} - {!!account?.message && ( + {!!unlinkMessage && ( // DotIndicatorMessage mostly expects onyxData errors, so we need to mock an object so that the messages looks similar to prop.account.errors )} {!!account?.errors && !isEmptyObject(account.errors) && ( diff --git a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx index df894ac3e3a7..1754c512bd6b 100644 --- a/src/pages/tasks/TaskShareDestinationSelectorModal.tsx +++ b/src/pages/tasks/TaskShareDestinationSelectorModal.tsx @@ -91,7 +91,6 @@ function TaskShareDestinationSelectorModal() { const filteredReports = OptionsListUtils.filterOptions(defaultOptions, debouncedSearchValue.trim(), { excludeLogins: CONST.EXPENSIFY_EMAILS, canInviteUser: false, - includeChatRoomsByParticipants: true, }); const header = OptionsListUtils.getHeaderMessage(filteredReports.recentReports && filteredReports.recentReports.length !== 0, false, debouncedSearchValue); return {...filteredReports, header}; diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 115a24691838..c3a384fa5d8b 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -16,6 +16,7 @@ import ScrollView from '@components/ScrollView'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import usePermissions from '@hooks/usePermissions'; import usePrevious from '@hooks/usePrevious'; import useSingleExecution from '@hooks/useSingleExecution'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -64,7 +65,9 @@ type WorkspaceMenuItem = { | typeof SCREENS.WORKSPACE.PROFILE | typeof SCREENS.WORKSPACE.MEMBERS | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD - | typeof SCREENS.WORKSPACE.REPORT_FIELDS; + | typeof SCREENS.WORKSPACE.COMPANY_CARDS + | typeof SCREENS.WORKSPACE.REPORT_FIELDS + | typeof SCREENS.WORKSPACE.RULES; }; type WorkspaceInitialPageOnyxProps = { @@ -99,6 +102,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc const activeRoute = useNavigationState(getTopmostRouteName); const {translate} = useLocalize(); const {isOffline} = useNetwork(); + const {canUseWorkspaceRules} = usePermissions(); const wasRendered = useRef(false); const prevPendingFields = usePrevious(policy?.pendingFields); @@ -109,9 +113,11 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc [CONST.POLICY.MORE_FEATURES.ARE_CATEGORIES_ENABLED]: policy?.areCategoriesEnabled, [CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED]: policy?.areTagsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED]: policy?.tax?.trackingEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]: policy?.areCompanyCardsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED]: !!policy?.areConnectionsEnabled || !isEmptyObject(policy?.connections), [CONST.POLICY.MORE_FEATURES.ARE_EXPENSIFY_CARDS_ENABLED]: policy?.areExpensifyCardsEnabled, [CONST.POLICY.MORE_FEATURES.ARE_REPORT_FIELDS_ENABLED]: policy?.areReportFieldsEnabled, + [CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED]: policy?.areRulesEnabled, }), [policy], ) as PolicyFeatureStates; @@ -162,7 +168,11 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc const hasMembersError = PolicyUtils.hasEmployeeListError(policy); const hasPolicyCategoryError = PolicyUtils.hasPolicyCategoriesError(policyCategories); - const hasGeneralSettingsError = !isEmptyObject(policy?.errorFields?.generalSettings ?? {}) || !isEmptyObject(policy?.errorFields?.avatarURL ?? {}); + const hasGeneralSettingsError = + !isEmptyObject(policy?.errorFields?.name ?? {}) || + !isEmptyObject(policy?.errorFields?.avatarURL ?? {}) || + !isEmptyObject(policy?.errorFields?.ouputCurrency ?? {}) || + !isEmptyObject(policy?.errorFields?.address ?? {}); const {login} = useCurrentUserPersonalDetails(); const shouldShowProtectedItems = PolicyUtils.isPolicyAdmin(policy, login); const isPaidGroupPolicy = PolicyUtils.isPaidGroupPolicy(policy); @@ -248,6 +258,15 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc }); } + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED]) { + protectedCollectPolicyMenuItems.push({ + translationKey: 'workspace.common.companyCards', + icon: Expensicons.CreditCard, + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)))), + routeName: SCREENS.WORKSPACE.COMPANY_CARDS, + }); + } + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_WORKFLOWS_ENABLED]) { protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.workflows', @@ -306,6 +325,15 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc }); } + if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED] && canUseWorkspaceRules) { + protectedCollectPolicyMenuItems.push({ + translationKey: 'workspace.common.rules', + icon: Expensicons.Feed, + action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_RULES.getRoute(policyID)))), + routeName: SCREENS.WORKSPACE.RULES, + }); + } + protectedCollectPolicyMenuItems.push({ translationKey: 'workspace.common.moreFeatures', icon: Expensicons.Gear, diff --git a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx index 47af86b53315..5b356f768dd3 100644 --- a/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx +++ b/src/pages/workspace/WorkspaceMoreFeaturesPage.tsx @@ -61,7 +61,7 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const styles = useThemeStyles(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const {translate} = useLocalize(); - const {canUseWorkspaceFeeds} = usePermissions(); + const {canUseWorkspaceFeeds, canUseWorkspaceRules} = usePermissions(); const hasAccountingConnection = !isEmptyObject(policy?.connections); const isAccountingEnabled = !!policy?.areConnectionsEnabled || !isEmptyObject(policy?.connections); const isSyncTaxEnabled = @@ -71,10 +71,12 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro const policyID = policy?.id; const workspaceAccountID = policy?.workspaceAccountID ?? -1; const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${workspaceAccountID.toString()}${CONST.EXPENSIFY_CARD.BANK}`); + const [companyCardsList] = useOnyx(`${ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_DOMAIN_MEMBER}${workspaceAccountID.toString()}`); const [isOrganizeWarningModalOpen, setIsOrganizeWarningModalOpen] = useState(false); const [isIntegrateWarningModalOpen, setIsIntegrateWarningModalOpen] = useState(false); const [isReportFieldsWarningModalOpen, setIsReportFieldsWarningModalOpen] = useState(false); const [isDisableExpensifyCardWarningModalOpen, setIsDisableExpensifyCardWarningModalOpen] = useState(false); + const [isDisableCompanyCardsWarningModalOpen, setIsDisableCompanyCardsWarningModalOpen] = useState(false); const spendItems: Item[] = [ { @@ -90,6 +92,53 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro DistanceRate.enablePolicyDistanceRates(policyID, isEnabled); }, }, + ]; + + // TODO remove this when feature will be fully done, and move spend item inside spendItems array + if (canUseWorkspaceFeeds) { + spendItems.push({ + icon: Illustrations.HandCard, + titleTranslationKey: 'workspace.moreFeatures.expensifyCard.title', + subtitleTranslationKey: 'workspace.moreFeatures.expensifyCard.subtitle', + isActive: policy?.areExpensifyCardsEnabled ?? false, + pendingAction: policy?.pendingFields?.areExpensifyCardsEnabled, + disabled: !isEmptyObject(cardsList), + action: (isEnabled: boolean) => { + if (!policyID) { + return; + } + Policy.enableExpensifyCard(policyID, isEnabled); + }, + disabledAction: () => { + setIsDisableExpensifyCardWarningModalOpen(true); + }, + }); + spendItems.push({ + icon: Illustrations.CompanyCard, + titleTranslationKey: 'workspace.moreFeatures.companyCards.title', + subtitleTranslationKey: 'workspace.moreFeatures.companyCards.subtitle', + isActive: policy?.areCompanyCardsEnabled ?? false, + pendingAction: policy?.pendingFields?.areCompanyCardsEnabled, + disabled: !isEmptyObject(companyCardsList), + action: (isEnabled: boolean) => { + if (!policyID) { + return; + } + if (isEnabled && !isControlPolicy(policy)) { + Navigation.navigate( + ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.companyCards.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)), + ); + return; + } + Policy.enableCompanyCards(policyID, isEnabled); + }, + disabledAction: () => { + setIsDisableCompanyCardsWarningModalOpen(true); + }, + }); + } + + const manageItems: Item[] = [ { icon: Illustrations.Workflows, titleTranslationKey: 'workspace.moreFeatures.workflows.title', @@ -105,23 +154,24 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro }, ]; - // TODO remove this when feature will be fully done, and move spend item inside spendItems array - if (canUseWorkspaceFeeds) { - spendItems.splice(1, 0, { - icon: Illustrations.HandCard, - titleTranslationKey: 'workspace.moreFeatures.expensifyCard.title', - subtitleTranslationKey: 'workspace.moreFeatures.expensifyCard.subtitle', - isActive: policy?.areExpensifyCardsEnabled ?? false, - pendingAction: policy?.pendingFields?.areExpensifyCardsEnabled, - disabled: !isEmptyObject(cardsList), + // TODO remove this when feature will be fully done, and move manage item inside manageItems array + if (canUseWorkspaceRules) { + manageItems.splice(1, 0, { + icon: Illustrations.Rules, + titleTranslationKey: 'workspace.moreFeatures.rules.title', + subtitleTranslationKey: 'workspace.moreFeatures.rules.subtitle', + isActive: policy?.areRulesEnabled ?? false, + pendingAction: policy?.pendingFields?.areRulesEnabled, action: (isEnabled: boolean) => { if (!policyID) { return; } - Policy.enableExpensifyCard(policyID, isEnabled); - }, - disabledAction: () => { - setIsDisableExpensifyCardWarningModalOpen(true); + + if (isEnabled && !isControlPolicy(policy)) { + Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.rules.alias, ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID))); + return; + } + Policy.enablePolicyRules(policyID, isEnabled); }, }); } @@ -262,6 +312,11 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro subtitleTranslationKey: 'workspace.moreFeatures.spendSection.subtitle', items: spendItems, }, + { + titleTranslationKey: 'workspace.moreFeatures.manageSection.title', + subtitleTranslationKey: 'workspace.moreFeatures.manageSection.subtitle', + items: manageItems, + }, { titleTranslationKey: 'workspace.moreFeatures.earnSection.title', subtitleTranslationKey: 'workspace.moreFeatures.earnSection.subtitle', @@ -414,6 +469,18 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro confirmText={translate('workspace.moreFeatures.expensifyCard.disableCardButton')} cancelText={translate('common.cancel')} /> + { + setIsDisableCompanyCardsWarningModalOpen(false); + Report.navigateToConciergeChat(true); + }} + onCancel={() => setIsDisableCompanyCardsWarningModalOpen(false)} + prompt={translate('workspace.moreFeatures.companyCards.disableCardPrompt')} + confirmText={translate('workspace.moreFeatures.companyCards.disableCardButton')} + cancelText={translate('common.cancel')} + /> ); diff --git a/src/pages/workspace/WorkspaceProfilePage.tsx b/src/pages/workspace/WorkspaceProfilePage.tsx index 025cc9587bf6..8cd5c7c1127b 100644 --- a/src/pages/workspace/WorkspaceProfilePage.tsx +++ b/src/pages/workspace/WorkspaceProfilePage.tsx @@ -36,8 +36,8 @@ import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import withPolicy from './withPolicy'; import type {WithPolicyProps} from './withPolicy'; +import withPolicy from './withPolicy'; import WorkspacePageWithSections from './WorkspacePageWithSections'; type WorkspaceProfilePageOnyxProps = { @@ -196,7 +196,7 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, currencyList = { disabledStyle={styles.cursorDefault} errorRowStyles={styles.mt3} /> - + )} Policy.clearPolicyErrorField(policy?.id ?? '-1', CONST.POLICY.COLLECTION_KEYS.GENERAL_SETTINGS)} errorRowStyles={[styles.mt2]} @@ -249,7 +249,7 @@ function WorkspaceProfilePage({policyDraft, policy: policyProp, currencyList = {
{canUseSpotnanaTravel && shouldShowAddress && ( - + ; + +function WorkspaceCompanyCardPage({route}: WorkspaceCompanyCardPageProps) { + const {translate} = useLocalize(); + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + + return ( + + + + + + ); +} + +WorkspaceCompanyCardPage.displayName = 'WorkspaceCompanyCardPage'; + +export default WorkspaceCompanyCardPage; diff --git a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx index 61967a729cd3..19f00948fdb7 100644 --- a/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx +++ b/src/pages/workspace/reportFields/ReportFieldsListValuesPage.tsx @@ -321,7 +321,7 @@ function ReportFieldsListValuesPage({ {!shouldShowEmptyState && ( item && toggleValue(item)} sections={listValuesSections} onCheckboxPress={toggleValue} diff --git a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx index d4cc5a65e272..872946905974 100644 --- a/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx +++ b/src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx @@ -57,7 +57,7 @@ function WorkspaceReportFieldsPage({ params: {policyID}, }, }: WorkspaceReportFieldsPageProps) { - const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const styles = useThemeStyles(); const theme = useTheme(); const {translate} = useLocalize(); @@ -78,7 +78,7 @@ function WorkspaceReportFieldsPage({ const isConnectedToAccounting = Object.keys(policy?.connections ?? {}).length > 0; const currentConnectionName = PolicyUtils.getCurrentConnectionName(policy); - const canSelectMultiple = !hasAccountingConnections && shouldUseNarrowLayout ? selectionMode?.isEnabled : true; + const canSelectMultiple = !hasAccountingConnections && (isSmallScreenWidth ? selectionMode?.isEnabled : true); const fetchReportFields = useCallback(() => { ReportField.openPolicyReportFieldsPage(policyID); @@ -297,7 +297,7 @@ function WorkspaceReportFieldsPage({ {!shouldShowEmptyState && !isLoading && ( item && updateSelectedReportFields(item)} sections={reportFieldsSections} onCheckboxPress={updateSelectedReportFields} diff --git a/src/pages/workspace/rules/PolicyRulesPage.tsx b/src/pages/workspace/rules/PolicyRulesPage.tsx new file mode 100644 index 000000000000..ec7cdffb8df5 --- /dev/null +++ b/src/pages/workspace/rules/PolicyRulesPage.tsx @@ -0,0 +1,106 @@ +import type {StackScreenProps} from '@react-navigation/stack'; +import React from 'react'; +import {View} from 'react-native'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import usePermissions from '@hooks/usePermissions'; +import usePolicy from '@hooks/usePolicy'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useThemeStyles from '@hooks/useThemeStyles'; +import Navigation from '@libs/Navigation/Navigation'; +import type {FullScreenNavigatorParamList} from '@libs/Navigation/types'; +import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections'; +import * as Illustrations from '@src/components/Icon/Illustrations'; +import CONST from '@src/CONST'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; + +type PolicyRulesPageProps = StackScreenProps; + +function PolicyRulesPage({route}: PolicyRulesPageProps) { + const {translate} = useLocalize(); + const {policyID} = route.params; + const policy = usePolicy(policyID); + const styles = useThemeStyles(); + const {shouldUseNarrowLayout} = useResponsiveLayout(); + const {canUseWorkspaceRules} = usePermissions(); + + const handleOnPressCategoriesLink = () => { + if (policy?.areCategoriesEnabled) { + Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID)); + return; + } + + Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)); + }; + + const handleOnPressTagsLink = () => { + if (policy?.areTagsEnabled) { + Navigation.navigate(ROUTES.WORKSPACE_TAGS.getRoute(policyID)); + return; + } + + Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)); + }; + + return ( + + + +
( + + {translate('workspace.rules.individualExpenseRules.subtitle')}{' '} + + {translate('workspace.common.categories').toLowerCase()} + {' '} + {translate('common.and')}{' '} + + {translate('workspace.common.tags').toLowerCase()} + + . + + )} + subtitle={translate('workspace.rules.individualExpenseRules.subtitle')} + titleStyles={styles.accountSettingsSectionTitle} + /> +
+ + + + ); +} + +PolicyRulesPage.displayName = 'PolicyRulesPage'; + +export default PolicyRulesPage; diff --git a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx index 0837ac164600..bb4bdff27097 100644 --- a/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx +++ b/src/pages/workspace/upgrade/WorkspaceUpgradePage.tsx @@ -47,6 +47,9 @@ function WorkspaceUpgradePage({route}: WorkspaceUpgradePageProps) { case CONST.UPGRADE_FEATURE_INTRO_MAPPING.reportFields.id: Policy.enablePolicyReportFields(policyID, true, true); return Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)); + case CONST.UPGRADE_FEATURE_INTRO_MAPPING.rules.id: + Policy.enablePolicyRules(policyID, true, true); + return Navigation.navigate(ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID)); default: return route.params.backTo ? Navigation.navigate(route.params.backTo) : Navigation.goBack(); } diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index dbd7a7cb3a58..b6da4dd689e6 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -21,6 +21,7 @@ type PolicyRoute = RouteProp< | typeof SCREENS.WORKSPACE.MORE_FEATURES | typeof SCREENS.WORKSPACE.MEMBERS | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD + | typeof SCREENS.WORKSPACE.COMPANY_CARDS | typeof SCREENS.WORKSPACE.INVITE | typeof SCREENS.WORKSPACE.INVITE_MESSAGE | typeof SCREENS.WORKSPACE.WORKFLOWS_PAYER @@ -45,6 +46,7 @@ type PolicyRoute = RouteProp< | typeof SCREENS.WORKSPACE.REPORT_FIELDS_EDIT_INITIAL_VALUE | typeof SCREENS.WORKSPACE.REPORT_FIELDS_VALUE_SETTINGS | typeof SCREENS.WORKSPACE.ACCOUNTING.CARD_RECONCILIATION + | typeof SCREENS.WORKSPACE.RULES >; function getPolicyIDFromRoute(route: PolicyRoute): string { diff --git a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx index 18b1314c9b1e..23f341d308e2 100644 --- a/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx +++ b/src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx @@ -127,7 +127,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr } const hasReimburserError = !!policy?.errorFields?.reimburser; const hasApprovalError = !!policy?.errorFields?.approvalMode; - const hasDelayedSubmissionError = !!policy?.errorFields?.autoReporting; + const hasDelayedSubmissionError = !!policy?.errorFields?.autoReporting ?? !!policy?.errorFields?.autoReportingFrequency; return [ { @@ -158,7 +158,7 @@ function WorkspaceWorkflowsPage({policy, betas, route}: WorkspaceWorkflowsPagePr /> ), isActive: (policy?.autoReportingFrequency !== CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT && !hasDelayedSubmissionError) ?? false, - pendingAction: policy?.pendingFields?.autoReporting, + pendingAction: policy?.pendingFields?.autoReporting ?? policy?.pendingFields?.autoReportingFrequency, errors: ErrorUtils.getLatestErrorField(policy ?? {}, CONST.POLICY.COLLECTION_KEYS.AUTOREPORTING), onCloseError: () => Policy.clearPolicyErrorField(route.params.policyID, CONST.POLICY.COLLECTION_KEYS.AUTOREPORTING), }, diff --git a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx index a0b9beb5bec4..9864241a8e84 100644 --- a/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx +++ b/src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx @@ -98,7 +98,7 @@ function ApprovalWorkflowEditor({approvalWorkflow, removeApprovalWorkflow, polic // User should be allowed to add additional approver only if they upgraded to Control Plan, otherwise redirected to the Upgrade Page const addAdditionalApprover = useCallback(() => { if (!PolicyUtils.isControlPolicy(policy)) { - Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.approvals.alias, ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID))); + Navigation.navigate(ROUTES.WORKSPACE_UPGRADE.getRoute(policyID, CONST.UPGRADE_FEATURE_INTRO_MAPPING.approvals.alias, Navigation.getActiveRoute())); return; } Navigation.navigate(ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_APPROVER.getRoute(policyID, approvalWorkflow.approvers.length, ROUTES.WORKSPACE_WORKFLOWS_APPROVALS_NEW.getRoute(policyID))); diff --git a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx index 25f288690c5d..41a8d5c6a4ee 100644 --- a/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx +++ b/src/pages/workspace/workflows/approvals/WorkspaceWorkflowsApprovalsEditPage.tsx @@ -87,6 +87,7 @@ function WorkspaceWorkflowsApprovalsEditPage({policy, isLoadingReportData = true availableMembers: [...currentApprovalWorkflow.members, ...(workflows.at(0)?.members ?? [])], action: CONST.APPROVAL_WORKFLOW.ACTION.EDIT, isLoading: false, + errors: null, }); setInitialApprovalWorkflow(currentApprovalWorkflow); }, [initialApprovalWorkflow, personalDetails, policy, route.params.firstApproverEmail, route.params.policyID]); diff --git a/src/styles/index.ts b/src/styles/index.ts index fe65e48bc4a1..9f93c799abb5 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -3777,7 +3777,7 @@ const styles = (theme: ThemeColors) => paymentMethod: { paddingHorizontal: 20, - height: variables.optionRowHeight, + minHeight: variables.optionRowHeight, }, chatFooterBanner: { diff --git a/src/types/form/SearchAdvancedFiltersForm.ts b/src/types/form/SearchAdvancedFiltersForm.ts index f2643c0c987d..6541072cae81 100644 --- a/src/types/form/SearchAdvancedFiltersForm.ts +++ b/src/types/form/SearchAdvancedFiltersForm.ts @@ -11,6 +11,8 @@ const FILTER_KEYS = { MERCHANT: 'merchant', DESCRIPTION: 'description', REPORT_ID: 'reportID', + LESS_THAN: 'lessThan', + GREATER_THAN: 'greaterThan', TAX_RATE: 'taxRate', EXPENSE_TYPE: 'expenseType', TAG: 'tag', @@ -33,6 +35,8 @@ type SearchAdvancedFiltersForm = Form< [FILTER_KEYS.MERCHANT]: string; [FILTER_KEYS.DESCRIPTION]: string; [FILTER_KEYS.REPORT_ID]: string; + [FILTER_KEYS.LESS_THAN]: string; + [FILTER_KEYS.GREATER_THAN]: string; [FILTER_KEYS.KEYWORD]: string; [FILTER_KEYS.TAX_RATE]: string[]; [FILTER_KEYS.EXPENSE_TYPE]: string[]; diff --git a/src/types/onyx/CompanyCards.ts b/src/types/onyx/CompanyCards.ts new file mode 100644 index 000000000000..17ebbaf98bb1 --- /dev/null +++ b/src/types/onyx/CompanyCards.ts @@ -0,0 +1,41 @@ +/** Model of CompanyCard's Shared NVP record */ +// TODO update information here during implementation Add Company Card flow +type CompanyCards = { + /** Company cards object */ + companyCards: { + /** Company card info key */ + cdfbmo: CompanyCardInfo; + }; + /** Company cards nicknames */ + companyCardNicknames: { + /** Company cards info key */ + cdfbmo: string; + }; +}; +/** + * Model of company card information + */ +type CompanyCardInfo = { + /** Company card pending state */ + pending: boolean; + + /** Company card asr state */ + asrEnabled: boolean; + + /** Company card force reimbursable value */ + forceReimbursable: string; + + /** Company card liability type */ + liabilityType: string; + + /** Company card preferred policy */ + preferredPolicy: string; + + /** Company card report title format */ + reportTitleFormat: string; + + /** Company card statement period */ + statementPeriodEndDay: string; +}; + +export default CompanyCards; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 8bcd5932bfc6..c18992923c21 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -496,6 +496,20 @@ type OriginalMessageUnapproved = { */ type OriginalMessageAddPaymentCard = Record; +/** + * Original message for INTEGRATIONSYNCFAILED actions + */ +type OriginalMessageIntegrationSyncFailed = { + /** The user friendly connection name */ + label: string; + + /** The source of the connection sync */ + source: string; + + /** The error message from Integration Server */ + errorMessage: string; +}; + /** The map type of original message */ type OriginalMessageMap = { /** */ @@ -624,6 +638,8 @@ type OriginalMessageMap = { [CONST.REPORT.ACTIONS.TYPE.CARD_MISSING_ADDRESS]: never; /** */ [CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL]: never; + /** */ + [CONST.REPORT.ACTIONS.TYPE.INTEGRATION_SYNC_FAILED]: OriginalMessageIntegrationSyncFailed; } & OldDotOriginalMessageMap & { [T in ValueOf]: OriginalMessageChangeLog; } & { diff --git a/src/types/onyx/Policy.ts b/src/types/onyx/Policy.ts index 8f48205d8749..532be964ad23 100644 --- a/src/types/onyx/Policy.ts +++ b/src/types/onyx/Policy.ts @@ -1510,6 +1510,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether the workflows feature is enabled */ areWorkflowsEnabled?: boolean; + /** Whether the reules feature is enabled */ + areRulesEnabled?: boolean; + /** Whether the Report Fields feature is enabled */ areReportFieldsEnabled?: boolean; @@ -1519,6 +1522,9 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Whether the Invoices feature is enabled */ areInvoicesEnabled?: boolean; + /** Whether the Company Cards feature is enabled */ + areCompanyCardsEnabled?: boolean; + /** The verified bank account linked to the policy */ achAccount?: ACHAccount; @@ -1555,7 +1561,7 @@ type Policy = OnyxCommon.OnyxValueWithOfflineFeedback< /** Workspace account ID configured for Expensify Card */ workspaceAccountID?: number; } & Partial, - 'generalSettings' | 'addWorkspaceRoom' | 'employeeList' | keyof ACHAccount | keyof Attributes + 'addWorkspaceRoom' | 'employeeList' | keyof ACHAccount | keyof Attributes >; /** Stages of policy connection sync */ diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 2bb129708981..ca7dc271f84a 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -10,6 +10,7 @@ import type BlockedFromConcierge from './BlockedFromConcierge'; import type CancellationDetails from './CancellationDetails'; import type Card from './Card'; import type {CardList, IssueNewCard, WorkspaceCardsList} from './Card'; +import type CompanyCards from './CompanyCards'; import type {CapturedLogs, Log} from './Console'; import type Credentials from './Credentials'; import type Currency from './Currency'; @@ -114,6 +115,7 @@ export type { Credentials, Currency, CurrencyList, + CompanyCards, CustomStatusDraft, DismissedReferralBanners, Download, diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index bd2a13304078..9590f878116e 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -28,11 +28,6 @@ describe('ReportUtils', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal expect(ReportUtils.getAllReportActions).toBeUndefined(); }); - - it('does not export getReport', () => { - // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal - expect(ReportUtils.getReportOrDraftReport).toBeUndefined(); - }); }); describe('Policy', () => { diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 405760435d14..42840bea35f6 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -34,6 +34,8 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ goBack: jest.fn(), })); +jest.mock('@src/libs/Navigation/isSearchTopmostCentralPane', () => jest.fn()); + const CARLOS_EMAIL = 'cmartins@expensifail.com'; const CARLOS_ACCOUNT_ID = 1; const CARLOS_PARTICIPANT: Participant = {hidden: false, role: 'member'}; diff --git a/tests/e2e/config.ts b/tests/e2e/config.ts index 8963e07c31c8..bdb24bee0bc5 100644 --- a/tests/e2e/config.ts +++ b/tests/e2e/config.ts @@ -83,6 +83,7 @@ export default { }, // Crowded Policy (Do Not Delete) Report, has a input bar available: reportID: '8268282951170052', + message: `Measure_performance#${Math.floor(Math.random() * 1000000)}`, }, [TEST_NAMES.ChatOpening]: { name: TEST_NAMES.ChatOpening, diff --git a/tests/e2e/nativeCommands/adbBackspace.ts b/tests/e2e/nativeCommands/adbBackspace.ts index 2891d1daf0e9..112a80bdb37e 100644 --- a/tests/e2e/nativeCommands/adbBackspace.ts +++ b/tests/e2e/nativeCommands/adbBackspace.ts @@ -1,10 +1,9 @@ import execAsync from '../utils/execAsync'; import * as Logger from '../utils/logger'; -const adbBackspace = () => { +const adbBackspace = (): Promise => { Logger.log(`🔙 Pressing backspace`); - execAsync(`adb shell input keyevent KEYCODE_DEL`); - return true; + return execAsync(`adb shell input keyevent KEYCODE_DEL`).then(() => true); }; export default adbBackspace; diff --git a/tests/e2e/nativeCommands/adbTypeText.ts b/tests/e2e/nativeCommands/adbTypeText.ts index 72fefbd25d26..deea16b198c8 100644 --- a/tests/e2e/nativeCommands/adbTypeText.ts +++ b/tests/e2e/nativeCommands/adbTypeText.ts @@ -3,8 +3,7 @@ import * as Logger from '../utils/logger'; const adbTypeText = (text: string) => { Logger.log(`📝 Typing text: ${text}`); - execAsync(`adb shell input text "${text}"`); - return true; + return execAsync(`adb shell input text "${text}"`).then(() => true); }; export default adbTypeText; diff --git a/tests/e2e/nativeCommands/index.ts b/tests/e2e/nativeCommands/index.ts index 31af618c8ec1..310aa2ab3c22 100644 --- a/tests/e2e/nativeCommands/index.ts +++ b/tests/e2e/nativeCommands/index.ts @@ -4,7 +4,7 @@ import adbTypeText from './adbTypeText'; // eslint-disable-next-line rulesdir/prefer-import-module-contents import {NativeCommandsAction} from './NativeCommandsAction'; -const executeFromPayload = (actionName?: string, payload?: NativeCommandPayload): boolean => { +const executeFromPayload = (actionName?: string, payload?: NativeCommandPayload): Promise => { switch (actionName) { case NativeCommandsAction.scroll: throw new Error('Not implemented yet'); diff --git a/tests/e2e/server/index.ts b/tests/e2e/server/index.ts index 494b8b4644e1..512492908049 100644 --- a/tests/e2e/server/index.ts +++ b/tests/e2e/server/index.ts @@ -169,8 +169,8 @@ const createServerInstance = (): ServerInstance => { case Routes.testNativeCommand: { getPostJSONRequestData(req, res) - ?.then((data) => { - const status = nativeCommands.executeFromPayload(data?.actionName, data?.payload); + ?.then((data) => nativeCommands.executeFromPayload(data?.actionName, data?.payload)) + .then((status) => { if (status) { res.end('ok'); return; diff --git a/tests/perf-test/ReportActionCompose.perf-test.tsx b/tests/perf-test/ReportActionCompose.perf-test.tsx index e3aaccd1f050..f8f3ce499218 100644 --- a/tests/perf-test/ReportActionCompose.perf-test.tsx +++ b/tests/perf-test/ReportActionCompose.perf-test.tsx @@ -1,5 +1,5 @@ import {fireEvent, screen} from '@testing-library/react-native'; -import type {ComponentType} from 'react'; +import type {ComponentType, EffectCallback} from 'react'; import React from 'react'; import Onyx from 'react-native-onyx'; import type Animated from 'react-native-reanimated'; @@ -36,6 +36,7 @@ jest.mock('@react-navigation/native', () => { }), useIsFocused: () => true, useNavigationState: () => {}, + useFocusEffect: (cb: EffectCallback) => cb(), }; }); diff --git a/tests/unit/IOUUtilsTest.ts b/tests/unit/IOUUtilsTest.ts index b8640e4ecdf1..04a5b3babd5e 100644 --- a/tests/unit/IOUUtilsTest.ts +++ b/tests/unit/IOUUtilsTest.ts @@ -114,6 +114,24 @@ describe('IOUUtils', () => { expect(IOUUtils.calculateAmount(participantsAccountIDs.length, 100, 'BHD')).toBe(33); }); }); + + describe('insertTagIntoTransactionTagsString', () => { + test('Inserting a tag into tag string should update the tag', () => { + expect(IOUUtils.insertTagIntoTransactionTagsString(':NY:Texas', 'California', 2)).toBe(':NY:California'); + }); + + test('Inserting a tag into an index with no tags should update the tag', () => { + expect(IOUUtils.insertTagIntoTransactionTagsString('::California', 'NY', 1)).toBe(':NY:California'); + }); + + test('Inserting a tag with colon in name into tag string should keep the colon in tag', () => { + expect(IOUUtils.insertTagIntoTransactionTagsString('East:NY:California', 'City \\: \\:', 1)).toBe('East:City \\: \\::California'); + }); + + test('Remove a tag from tagString', () => { + expect(IOUUtils.insertTagIntoTransactionTagsString('East:City \\: \\::California', '', 1)).toBe('East::California'); + }); + }); }); describe('isValidMoneyRequestType', () => { diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 34a0a9af7383..c2beebfa563d 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -297,24 +297,6 @@ describe('OptionsListUtils', () => { }, }; - const REPORTS_WITH_WORKSPACE: OnyxCollection = { - ...REPORTS, - '15': { - lastReadTime: '2021-01-14 11:25:39.295', - lastVisibleActionCreated: '2022-11-22 03:26:02.015', - isPinned: false, - isChatRoom: false, - reportID: '15', - participants: { - 1: {}, - 2: {}, - }, - reportName: 'Test Workspace', - type: CONST.REPORT.TYPE.CHAT, - chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, - }, - }; - const REPORTS_WITH_CHAT_ROOM: OnyxCollection = { ...REPORTS, 15: { @@ -411,7 +393,6 @@ describe('OptionsListUtils', () => { let OPTIONS_WITH_CHRONOS: OptionsListUtils.OptionList; let OPTIONS_WITH_RECEIPTS: OptionsListUtils.OptionList; let OPTIONS_WITH_WORKSPACE_ROOM: OptionsListUtils.OptionList; - let OPTIONS_WITH_WORKSPACE: OptionsListUtils.OptionList; beforeEach(() => { OPTIONS = OptionsListUtils.createOptionList(PERSONAL_DETAILS, REPORTS); @@ -419,7 +400,6 @@ describe('OptionsListUtils', () => { OPTIONS_WITH_CHRONOS = OptionsListUtils.createOptionList(PERSONAL_DETAILS_WITH_CHRONOS, REPORTS_WITH_CHRONOS); OPTIONS_WITH_RECEIPTS = OptionsListUtils.createOptionList(PERSONAL_DETAILS_WITH_RECEIPTS, REPORTS_WITH_RECEIPTS); OPTIONS_WITH_WORKSPACE_ROOM = OptionsListUtils.createOptionList(PERSONAL_DETAILS, REPORTS_WITH_WORKSPACE_ROOMS); - OPTIONS_WITH_WORKSPACE = OptionsListUtils.createOptionList(PERSONAL_DETAILS, REPORTS_WITH_WORKSPACE); }); it('getSearchOptions()', () => { @@ -2638,12 +2618,11 @@ describe('OptionsListUtils', () => { const options = OptionsListUtils.getSearchOptions(OPTIONS, '', [CONST.BETAS.ALL]); const filteredOptions = OptionsListUtils.filterOptions(options, searchText, {sortByReportTypeInSearch: true}); - expect(filteredOptions.recentReports.length).toBe(5); + expect(filteredOptions.recentReports.length).toBe(4); expect(filteredOptions.recentReports[0].text).toBe('Invisible Woman'); expect(filteredOptions.recentReports[1].text).toBe('Spider-Man'); expect(filteredOptions.recentReports[2].text).toBe('Black Widow'); expect(filteredOptions.recentReports[3].text).toBe('Mister Fantastic, Invisible Woman'); - expect(filteredOptions.recentReports[4].text).toBe("SHIELD's workspace (archived)"); }); it('should filter users by email', () => { @@ -2692,7 +2671,7 @@ describe('OptionsListUtils', () => { const filteredOptions = OptionsListUtils.filterOptions(options, searchText); - expect(filteredOptions.recentReports.length).toBe(2); + expect(filteredOptions.recentReports.length).toBe(1); expect(filteredOptions.recentReports[0].login).toBe(searchText); }); @@ -2744,17 +2723,6 @@ describe('OptionsListUtils', () => { expect(filteredOptions.userToInvite?.login).toBe(searchText); }); - it('should return the workspaces that match the participant login', () => { - const searchText = 'reedrichards@expensify.com'; - - const options = OptionsListUtils.getSearchOptions(OPTIONS_WITH_WORKSPACE, ''); - const filteredOptions = OptionsListUtils.filterOptions(options, searchText); - - const recentReportsNames = filteredOptions.recentReports.map((option) => option.text); - - expect(recentReportsNames).toContain('Test Workspace'); - }); - it('should return limited amount of recent reports if the limit is set', () => { const searchText = '';