chore: merge hoppscotch/patch into hoppscotch/next

This commit is contained in:
Andrew Bastin
2024-06-20 22:26:42 +05:30
52 changed files with 629 additions and 364 deletions

View File

@@ -7,20 +7,15 @@ Please make sure that the pull request is limited to one type (docs, feature, et
<!-- If this pull request closes an issue, please mention the issue number below -->
Closes # <!-- Issue # here -->
### Description
<!-- Add a brief description of the pull request -->
<!-- Add an introduction into what this PR tries to solve in a couple of sentences -->
### What's changed
<!-- Describe point by point the different things you have changed in this PR -->
<!-- You can also choose to add a list of changes and if they have been completed or not by using the markdown to-do list syntax
- [ ] Not Completed
- [x] Completed
-->
### Checks
<!-- Make sure your pull request passes the CI checks and do check the following fields as needed - -->
- [ ] My pull request adheres to the code style of this project
- [ ] My code requires changes to the documentation
- [ ] I have updated the documentation as required
- [ ] All the tests have passed
### Additional Information
<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behaviour, etc. -->
### Notes to reviewers
<!-- Any information you feel the reviewer should know about when reviewing your PR -->

View File

@@ -1,30 +1,21 @@
# CODEOWNERS is prioritized from bottom to top
# If none of the below matched
* @AndrewBastin @liyasthomas
# Packages
/packages/codemirror-lang-graphql/ @AndrewBastin
/packages/hoppscotch-cli/ @AndrewBastin
/packages/hoppscotch-common/ @amk-dev @AndrewBastin
/packages/hoppscotch-cli/ @jamesgeorge007
/packages/hoppscotch-data/ @AndrewBastin
/packages/hoppscotch-js-sandbox/ @AndrewBastin
/packages/hoppscotch-ui/ @anwarulislam
/packages/hoppscotch-web/ @amk-dev
/packages/hoppscotch-selfhost-web/ @amk-dev
/packages/hoppscotch-js-sandbox/ @jamesgeorge007
/packages/hoppscotch-selfhost-web/ @jamesgeorge007
/packages/hoppscotch-selfhost-desktop/ @AndrewBastin
/packages/hoppscotch-sh-admin/ @JoelJacobStephen
/packages/hoppscotch-backend/ @ankitsridhar16 @balub
/packages/hoppscotch-backend/ @balub
# Sections within Hoppscotch Common
/packages/hoppscotch-common/src/components @anwarulislam
/packages/hoppscotch-common/src/components/collections @nivedin @amk-dev
/packages/hoppscotch-common/src/components/environments @nivedin @amk-dev
/packages/hoppscotch-common/src/composables @amk-dev
/packages/hoppscotch-common/src/modules @AndrewBastin @amk-dev
/packages/hoppscotch-common/src/pages @AndrewBastin @amk-dev
/packages/hoppscotch-common/src/newstore @AndrewBastin @amk-dev
# READMEs and other documentation files
*.md @liyasthomas
README.md @liyasthomas
# The lockfile has no owner
pnpm-lock.yaml
# Self Host deployment related files
*.Dockerfile @balub
docker-compose.yml @balub
docker-compose.deploy.yml @balub
*.Caddyfile @balub
.dockerignore @balub

View File

@@ -11,7 +11,4 @@ Please note we have a code of conduct, please follow it in all your interactions
build.
2. Update the README.md with details of changes to the interface, this includes new environment
variables, exposed ports, useful file locations and container parameters.
3. Increase the version numbers in any examples files and the README.md to the new version that this
Pull Request would represent. The versioning scheme we use is [SemVer](https://semver.org).
4. You may merge the Pull Request once you have the sign-off of two other developers, or if you
do not have permission to do that, you may request the second reviewer merge it for you.
3. Make sure you do not expose environment variables or other sensitive information in your PR.

View File

@@ -4,19 +4,36 @@ This document outlines security procedures and general policies for the Hoppscot
- [Security Policy](#security-policy)
- [Reporting a security vulnerability](#reporting-a-security-vulnerability)
- [What is not a valid vulnerability](#what-is-not-a-valid-vulnerability)
- [Incident response process](#incident-response-process)
## Reporting a security vulnerability
Report security vulnerabilities by emailing the Hoppscotch Support team at support@hoppscotch.io.
We use [Github Security Advisories](https://github.com/hoppscotch/hoppscotch/security/advisories) to manage vulnerability reports and collaboration.
Someone from the Hoppscotch team shall report to you within 48 hours of the disclosure of the vulnerability in GHSA. If no response was received, please reach out to
Hoppscotch Support at support@hoppscotch.io along with the GHSA advisory link.
The primary security point of contact from Hoppscotch Support team will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
> NOTE: Since we have multiple open source components, Advisories may move into the relevant repo (for example, an XSS in a UI component might be part of [`@hoppscotch/ui`](https://github.com/hoppscotch/ui)).
> If in doubt, open your report in `hoppscotch/hoppscotch` GHSA.
**Do not create a GitHub issue ticket to report a security vulnerability.**
**Do not create a GitHub issue ticket to report a security vulnerability!**
The Hoppscotch team and community take all security vulnerability reports in Hoppscotch seriously. Thank you for improving the security of Hoppscotch. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
The Hoppscotch team takes all security vulnerability reports in Hoppscotch seriously. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions.
Report security bugs in third-party modules to the person or team maintaining the module.
## What is not a valid vulnerability
We receive many reports about different sections of the Hoppscotch platform. Hence, we have a fine line we have drawn defining what is considered valid vulnerability.
Please refrain from opening an advisory if it describes the following:
- A vulnerability in a dependency of Hoppscotch (unless you have practical attack with it on the Hoppscotch codebase)
- Reports of vulnerabilities related to old runtimes (like NodeJS) or container images used by the codebase
- Vulnerabilities present when using Hoppscotch in anything other than the defined minimum requirements that Hoppscotch supports.
Hoppscotch Team ensures security support for:
- Modern Browsers (Chrome/Firefox/Safari/Edge) with versions up to 1 year old.
- Windows versions on or above Windows 10 on Intel and ARM.
- macOS versions dating back up to 2 years on Intel and Apple Silicon.
- Popular Linux distributions with up-to-date packages with preference to x86/64 CPUs.
- Docker/OCI Runtimes (preference to Docker and Podman) dating back up to 1 year.
## Incident response process

View File

@@ -1,6 +1,6 @@
{
"name": "hoppscotch-backend",
"version": "2024.3.3",
"version": "2024.3.4",
"description": "",
"author": "",
"private": true,

View File

@@ -112,6 +112,7 @@ describe('AdminService', () => {
NOT: {
inviteeEmail: {
in: [dbAdminUsers[0].email],
mode: 'insensitive',
},
},
},
@@ -220,7 +221,10 @@ describe('AdminService', () => {
expect(mockPrisma.invitedUsers.deleteMany).toHaveBeenCalledWith({
where: {
inviteeEmail: { in: [invitedUsers[0].inviteeEmail] },
inviteeEmail: {
in: [invitedUsers[0].inviteeEmail],
mode: 'insensitive',
},
},
});
expect(result).toEqualRight(true);

View File

@@ -89,12 +89,17 @@ export class AdminService {
adminEmail: string,
inviteeEmail: string,
) {
if (inviteeEmail == adminEmail) return E.left(DUPLICATE_EMAIL);
if (inviteeEmail.toLowerCase() == adminEmail.toLowerCase()) {
return E.left(DUPLICATE_EMAIL);
}
if (!validateEmail(inviteeEmail)) return E.left(INVALID_EMAIL);
const alreadyInvitedUser = await this.prisma.invitedUsers.findFirst({
where: {
inviteeEmail: inviteeEmail,
inviteeEmail: {
equals: inviteeEmail,
mode: 'insensitive',
},
},
});
if (alreadyInvitedUser != null) return E.left(USER_ALREADY_INVITED);
@@ -159,7 +164,7 @@ export class AdminService {
try {
await this.prisma.invitedUsers.deleteMany({
where: {
inviteeEmail: { in: inviteeEmails },
inviteeEmail: { in: inviteeEmails, mode: 'insensitive' },
},
});
return E.right(true);
@@ -189,6 +194,7 @@ export class AdminService {
NOT: {
inviteeEmail: {
in: userEmailObjs.map((user) => user.email),
mode: 'insensitive',
},
},
},

View File

@@ -299,7 +299,10 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
where: userEmail
? {
User: {
email: userEmail,
email: {
equals: userEmail,
mode: 'insensitive',
},
},
}
: undefined,

View File

@@ -75,12 +75,13 @@ export class TeamInvitationService {
if (!isEmailValid) return E.left(INVALID_EMAIL);
try {
const teamInvite = await this.prisma.teamInvitation.findUniqueOrThrow({
const teamInvite = await this.prisma.teamInvitation.findFirstOrThrow({
where: {
teamID_inviteeEmail: {
inviteeEmail: inviteeEmail,
teamID: teamID,
inviteeEmail: {
equals: inviteeEmail,
mode: 'insensitive',
},
teamID,
},
});

View File

@@ -157,7 +157,7 @@ beforeEach(() => {
describe('UserService', () => {
describe('findUserByEmail', () => {
test('should successfully return a valid user given a valid email', async () => {
mockPrisma.user.findUniqueOrThrow.mockResolvedValueOnce(user);
mockPrisma.user.findFirst.mockResolvedValueOnce(user);
const result = await userService.findUserByEmail(
'dwight@dundermifflin.com',
@@ -166,7 +166,7 @@ describe('UserService', () => {
});
test('should return a null user given a invalid email', async () => {
mockPrisma.user.findUniqueOrThrow.mockRejectedValueOnce('NotFoundError');
mockPrisma.user.findFirst.mockResolvedValueOnce(null);
const result = await userService.findUserByEmail('jim@dundermifflin.com');
expect(result).resolves.toBeNone;

View File

@@ -62,16 +62,16 @@ export class UserService {
* @returns Option of found User
*/
async findUserByEmail(email: string): Promise<O.None | O.Some<AuthUser>> {
try {
const user = await this.prisma.user.findUniqueOrThrow({
where: {
email: email,
const user = await this.prisma.user.findFirst({
where: {
email: {
equals: email,
mode: 'insensitive',
},
});
return O.some(user);
} catch (error) {
return O.none;
}
},
});
if (!user) return O.none;
return O.some(user);
}
/**

View File

@@ -17,15 +17,17 @@
"dismiss": "Boşver",
"dont_save": "Don't save",
"download_file": "Dosyayı Indir",
"drag_to_reorder": "Drag to reorder",
"drag_to_reorder": "Yeniden sıralamak için sürükleyin",
"duplicate": "Klonla",
"edit": "Düzenle",
"filter": "Filter",
"filter": "Filtre",
"go_back": "Geri git",
"go_forward": "Go forward",
"go_forward": "İleri git",
"group_by": "Group by",
"hide_secret": "Hide secret",
"label": "Etiket",
"learn_more": "Daha fazla bilgi edin",
"download_here": "Download here",
"less": "Daha az",
"more": "Daha fazla",
"new": "Yeni",
@@ -42,8 +44,9 @@
"scroll_to_top": "Scroll to top",
"search": "Arama",
"send": "Gönder",
"share": "Share",
"share": "Paylaş",
"start": "Başla",
"show_secret": "Show secret",
"starting": "Starting",
"stop": "Dur",
"to_close": "kapatmak için",
@@ -101,16 +104,18 @@
"auth": {
"account_exists": "Farklı kimlik bilgilerine sahip hesap var - Her iki hesabı birbirine bağlamak için giriş yapın",
"all_sign_in_options": "Tüm oturum açma seçenekleri",
"continue_with_auth_provider": "{provider} ile devam et",
"continue_with_email": "E-posta ile devam et",
"continue_with_github": "GitHub hesabı ile devam et",
"continue_with_google": "Google hesabı ile devam et",
"continue_with_microsoft": "Microsoft hesabı ile devam et",
"continue_with_github": "GitHub ile devam et",
"continue_with_github_enterprise": "GitHub Enterprise ile devam et",
"continue_with_google": "Google ile devam et",
"continue_with_microsoft": "Microsoft ile devam et",
"email": "E-posta",
"logged_out": ıkış yapıldı",
"login": "Giriş yap",
"login": "Giriş",
"login_success": "Başarıyla giriş yapıldı",
"login_to_hoppscotch": "Hoppscotch'a giriş yapın",
"logout": ıkış yap",
"logout": ıkış",
"re_enter_email": "E-mail adresinizi yeniden girin",
"send_magic_link": "Sihirli bir bağlantı gönder",
"sync": "Senkronizasyon",
@@ -135,20 +140,42 @@
"redirect_no_token_endpoint": "No Token Endpoint Defined",
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect",
"something_went_wrong_on_token_generation": "Something went wrong on token generation",
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed"
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed",
"grant_type": "Grant Type",
"grant_type_auth_code": "Authorization Code",
"token_fetched_successfully": "Token fetched successfully",
"token_fetch_failed": "Failed to fetch token",
"validation_failed": "Validation Failed, please check the form fields",
"label_authorization_endpoint": "Authorization Endpoint",
"label_client_id": "Client ID",
"label_client_secret": "Client Secret",
"label_code_challenge": "Code Challenge",
"label_code_challenge_method": "Code Challenge Method",
"label_code_verifier": "Code Verifier",
"label_scopes": "Scopes",
"label_token_endpoint": "Token Endpoint",
"label_use_pkce": "Use PKCE",
"label_implicit": "Implicit",
"label_password": "Password",
"label_username": "Username",
"label_auth_code": "Authorization Code",
"label_client_credentials": "Client Credentials"
},
"pass_key_by": "Şunla anahtar ekleyin",
"pass_by_query_params_label": "Query Parameters",
"pass_by_headers_label": "Headers",
"password": "Parola",
"save_to_inherit": "Please save this request in any collection to inherit the authorization",
"token": "Jeton",
"type": "Yetki türü",
"type": "Yetki Türü",
"username": "Kullanıcı adı"
},
"collection": {
"created": "Koleksiyon oluşturuldu",
"different_parent": "Cannot reorder collection with different parent",
"edit": "Koleksiyonu düzenle",
"import_or_create": "Import or create a collection",
"import_or_create": "Koleksiyon oluşturun veya içe aktarın",
"import_collection":"Koleksiyonu İçe Aktar",
"invalid_name": "Lütfen koleksiyon için geçerli bir ad girin",
"invalid_root_move": "Collection already in the root",
"moved": "Başarıyla taşındı",
@@ -158,15 +185,13 @@
"new": "Yeni koleksiyon",
"order_changed": "Collection Order Updated",
"properties": "Collection Properties",
"properties_updated": "Collection Properties Updated",
"properties_updated": "Koleksiyon Özellikleri Güncellendi",
"renamed": "Koleksiyon yeniden adlandırıldı",
"request_in_use": "Kullanımda istek",
"save_as": "Farklı kaydet",
"save_to_collection": "Save to Collection",
"select": "Bir koleksiyon Seçin",
"select_location": "Konum seçin",
"select_team": "Bir takım seçin",
"team_collections": "Takım koleksiyonları"
"select_location": "Konum seçin"
},
"confirm": {
"close_unsaved_tab": "Are you sure you want to close this tab?",
@@ -237,14 +262,15 @@
"pending_invites": "Bu takım için bekleyen bir istek yok",
"profile": "Bu profili görüntülemek için giriş yapın",
"protocols": "Protokoller boş",
"request_variables": "This request does not have any request variables",
"schema": "Bir GraphQL uç noktasına bağlanma",
"secret_environments": "Secrets are not synced to Hoppscotch",
"shared_requests": "Shared requests are empty",
"shared_requests_logout": "Login to view your shared requests or create a new one",
"subscription": "Subscriptions are empty",
"team_name": "Takım adı boş",
"teams": "Takımlar boş",
"tests": "Bu istek için test yok",
"shortcodes": "Shortcodes are empty"
"tests": "Bu istek için test yok"
},
"environment": {
"add_to_global": "Globale ekle",
@@ -260,24 +286,27 @@
"import_or_create": "Import or create a environment",
"invalid_name": "Lütfen ortam için geçerli bir ad girin",
"list": "Environment variables",
"my_environments": "My Environments",
"my_environments": "Ortamlarım",
"name": "Name",
"nested_overflow": "İç içe ortam değişkenleri 10 düzeyle sınırlıdır",
"new": "Yeni ortam",
"no_active_environment": "No active environment",
"no_active_environment": "Aktif ortam yok",
"no_environment": "Ortam yok",
"no_environment_description": "Hiçbir ortam seçilmedi. Aşağıdaki değişkenlerle ne yapacağınızı seçin.",
"quick_peek": "Environment Quick Peek",
"replace_with_variable": "Replace with variable",
"scope": "Scope",
"secrets": "Secrets",
"secret_value": "Secret value",
"select": "Ortam seçin",
"set": "Set environment",
"set_as_environment": "Set as environment",
"team_environments": "Team Environments",
"title": "Ortamlar",
"updated": "Ortam güncellendi",
"value": "Value",
"variable": "Variable",
"value": "Değer",
"variable": "Değişken",
"variables": "Değişkenler",
"variable_list": "Değişken listesi"
},
"error": {
@@ -289,6 +318,7 @@
"danger_zone": "Danger zone",
"delete_account": "Your account is currently an owner in these teams:",
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.",
"empty_profile_name": "Profile name cannot be empty",
"empty_req_name": "Boş İstek Adı",
"f12_details": "(Ayrıntılar için F12)",
"gql_prettify_invalid_query": "Geçersiz bir sorgu güzelleştirilemedi, sorgu sözdizimi hatalarını çözüp tekrar deneyin",
@@ -296,7 +326,8 @@
"incorrect_email": "Geçersiz e-posta",
"invalid_link": "Geçersiz bağlantı",
"invalid_link_description": "Tıkladığınız linkin süresi geçmiş veya geçersiz",
"json_parsing_failed": "Invalid JSON",
"invalid_embed_link": "The embed does not exist or is invalid.",
"json_parsing_failed": "Geçersiz JSON",
"json_prettify_invalid_body": "Geçersiz bir gövde güzelleştirilemedi, JSON sözdizimi hatalarını çözüp tekrar deneyin",
"network_error": "Görünene göre bir ağ hatası var. Lütfen tekrar deneyin.",
"network_fail": "İstek gönderilemedi",
@@ -307,22 +338,26 @@
"page_not_found": "This page could not be found",
"please_install_extension": "Please install the extension and add origin to the extension.",
"proxy_error": "Proxy error",
"same_profile_name": "Updated profile name is same as the current profile name",
"script_fail": "Ön istek komut dosyası çalıştırılamadı",
"something_went_wrong": "Bir şeyler yanlış gitti",
"test_script_fail": "Could not execute post-request script"
"test_script_fail": "Could not execute post-request script",
"reading_files": "Error while reading one or more files."
},
"export": {
"as_json": "JSON olarak dışa aktar",
"create_secret_gist": "Gizli Gist oluştur",
"create_secret_gist_tooltip_text": "Export as secret Gist",
"failed": "Something went wrong while exporting",
"gist_created": "Gist oluşturuldu",
"require_github": "Gizli Gist oluşturmak için GitHub ile giriş yapın",
"secret_gist_success": "Successfully exported as secret Gist",
"success": "Successfully exported",
"title": "Dışarı Aktar"
},
"filter": {
"all": "All",
"none": "None",
"starred": "Starred"
"all": "Tümü",
"none": "Hiçbiri",
"starred": "Yıldızlı"
},
"folder": {
"created": "Klasör oluşturuldu",
@@ -339,7 +374,8 @@
"mutations": "Mutasyonlar",
"schema": "Şema",
"subscriptions": "Abonelikler",
"switch_connection": "Switch connection"
"switch_connection": "Switch connection",
"url_placeholder": "Enter a GraphQL endpoint URL"
},
"graphql_collections": {
"title": "GraphQL Collections"
@@ -379,6 +415,8 @@
"environments_from_gist": "Import From Gist",
"environments_from_gist_description": "Import Hoppscotch Environments From Gist",
"failed": "İçe aktarılamadı",
"file_size_limit_exceeded_warning_multiple_files": "Chosen files exceed the recommended limit of 10MB. Only the first {files} selected will be imported",
"file_size_limit_exceeded_warning_single_file": "The currently chosen file exceeds the recommended limit of 10MB. Please select another file.",
"from_file": "Import from File",
"from_gist": "Gist'ten içe aktar",
"from_gist_description": "Gist ile içe aktar",
@@ -405,12 +443,15 @@
"json_description": "Import collections from a Hoppscotch Collections JSON file",
"postman_environment": "Postman Environment",
"postman_environment_description": "Import Postman Environment from a JSON file",
"success": "Successfully imported",
"title": "İçe aktar"
},
"inspections": {
"description": "Inspect possible errors",
"environment": {
"add_environment": "Add to Environment",
"add_environment_value": "Add value",
"empty_value": "Environment value is empty for the variable '{variable}' ",
"not_found": "Environment variable “{environment}” not found."
},
"header": {
@@ -546,6 +587,7 @@
"raw_body": "Ham istek gövdesi",
"rename": "Rename Request",
"renamed": "Yeniden adlandırılmış istek",
"request_variables": "Request variables",
"run": "Çalıştır",
"save": "Kaydet",
"save_as": "Farklı kaydet",
@@ -557,9 +599,9 @@
"title": "İstek",
"type": "İstek türü",
"url": "URL",
"url_placeholder": "Enter a URL or paste a cURL command",
"variables": "Değişkenler",
"view_my_links": "View my links",
"copy_link": "Bağlantıyı kopyala"
"view_my_links": "View my links"
},
"response": {
"audio": "Audio",
@@ -611,7 +653,7 @@
"profile_description": "Profil detaylarını güncelle",
"profile_email": "E-posta adresi",
"profile_name": "Profil ismi",
"proxy": "vekil",
"proxy": "Proxy",
"proxy_url": "Proxy URL'si",
"proxy_use_toggle": "İstek göndermek için proxy ara yazılımını kullanın",
"read_the": "Oku",
@@ -704,8 +746,7 @@
"send_request": "İstek gönder",
"share_request": "Share Request",
"show_code": "Generate code snippet",
"title": "İstek",
"copy_request_link": "İstek bağlantısını kopyala"
"title": "İstek"
},
"response": {
"copy": "Copy response to clipboard",
@@ -726,6 +767,11 @@
"more": "Daha fazla göster",
"sidebar": "Kenar çubuğunu göster"
},
"site_protection": {
"error_fetching_site_protection_status": "Something Went Wrong While Fetching Site Protection Status",
"login_to_continue": "Login to continue",
"login_to_continue_description": "You need to be logged in to access this Hoppscotch Enterprise Instance."
},
"socketio": {
"communication": "İletişim",
"connection_not_authorized": "This SocketIO connection does not use any authentication.",
@@ -763,6 +809,13 @@
"invite": "Invite your friends to Hoppscotch",
"title": "Miscellaneous"
},
"phrases": {
"create_environment": "Create environment",
"create_workspace": "Create workspace",
"import_collections": "Import collections",
"share_request": "Share request",
"try": "Try"
},
"request": {
"save_as_new": "Save as new request",
"select_method": "Select method",
@@ -867,7 +920,6 @@
"forum": "Sorular sorun ve cevaplar alın",
"github": "Bizi Github'da takip edin",
"shortcuts": "Uygulamaya daha hızlı göz atın",
"team": "Takımla iletişim kurun",
"title": "Destek",
"twitter": "Bizi Twitter'da takip edin"
},
@@ -888,6 +940,7 @@
"queries": "Sorgular",
"query": "Sorgu",
"schema": "Schema",
"share_tab_request": "Share tab request",
"shared_requests": "Shared Requests",
"socketio": "Socket.IO",
"sse": "SSE",
@@ -917,7 +970,6 @@
"invite_tooltip": "İnsanları bu çalışma alanına davet edin",
"invited_to_team": "{owner} seni {team} takımına davet etti.",
"join": "Davet kabul edildi",
"join_beta": "Takımlara erişmek için beta programına katılın.",
"join_team": "{team}'e katıl",
"joined_team": "{team} takımına katıldın",
"joined_team_description": "Artık bu takımın bir üyesisin",
@@ -946,6 +998,7 @@
"permissions": "İzinler",
"same_target_destination": "Same target and destination",
"saved": "Takım kaydedildi",
"search_title": "Team Requests",
"select_a_team": "Takım seç",
"success_invites": "Success invites",
"title": "Başlık",
@@ -976,6 +1029,7 @@
},
"workspace": {
"change": "Change workspace",
"other_workspaces": "My Workspaces",
"personal": "My Workspace",
"team": "Team Workspace",
"title": "Workspaces"

View File

@@ -1,7 +1,7 @@
{
"name": "@hoppscotch/common",
"private": true,
"version": "2024.3.3",
"version": "2024.3.4",
"scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*",
"test": "vitest --run",

View File

@@ -1,11 +1,11 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// Generated by unplugin-vue-components
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
export {}
declare module 'vue' {
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
AppBanner: typeof import('./components/app/Banner.vue')['default']
@@ -148,7 +148,6 @@ declare module 'vue' {
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
IconLucideBrush: (typeof import("~icons/lucide/brush"))["default"]
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
@@ -158,7 +157,6 @@ declare module 'vue' {
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideRss: (typeof import("~icons/lucide/rss"))["default"]
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default']
IconLucideX: typeof import('~icons/lucide/x')['default']
@@ -214,4 +212,5 @@ declare module 'vue' {
WorkspaceCurrent: typeof import('./components/workspace/Current.vue')['default']
WorkspaceSelector: typeof import('./components/workspace/Selector.vue')['default']
}
}

View File

@@ -1,6 +1,6 @@
<template>
<div class="flex flex-col space-y-2">
<div class="flex flex-col px-4 pt-2">
<div v-if="isTooltipComponent" class="flex flex-col px-4 pt-2">
<h2 class="inline-flex pb-1 font-semibold text-secondaryDark">
{{ t("settings.interceptor") }}
</h2>
@@ -19,6 +19,9 @@
:value="interceptor.interceptorID"
:label="unref(interceptor.name(t))"
:selected="interceptorSelection === interceptor.interceptorID"
:class="{
'!px-0 hover:bg-transparent': !isTooltipComponent,
}"
@change="interceptorSelection = interceptor.interceptorID"
/>
@@ -39,6 +42,15 @@ import { InterceptorService } from "~/services/interceptor.service"
const t = useI18n()
withDefaults(
defineProps<{
isTooltipComponent?: boolean
}>(),
{
isTooltipComponent: true,
}
)
const interceptorService = useService(InterceptorService)
const interceptorSelection =

View File

@@ -6,7 +6,11 @@
@close="hideModal"
>
<template #body>
<template v-if="isLoadingAllowedAuthProviders">
<template v-if="platform.auth.customLoginSelectorUI">
<component :is="platform.auth.customLoginSelectorUI" />
</template>
<template v-else-if="isLoadingAllowedAuthProviders">
<div class="flex justify-center">
<HoppSmartSpinner />
</div>

View File

@@ -302,7 +302,7 @@ const supportedGrantTypes = [
auth.value.grantTypeInfo = {
...auth.value.grantTypeInfo,
clientSecret: value,
clientSecret: value ?? "",
}
}
)

View File

@@ -89,6 +89,7 @@ import { readFileAsText } from "~/helpers/functional/files"
import xmlFormat from "xml-formatter"
import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings"
import * as LJSON from "lossless-json"
type PossibleContentTypes = Exclude<
ValidContentTypes,
@@ -187,8 +188,8 @@ const prettifyRequestBody = () => {
let prettifyBody = ""
try {
if (body.value.contentType.endsWith("json")) {
const jsonObj = JSON.parse(rawParamsBody.value as string)
prettifyBody = JSON.stringify(jsonObj, null, 2)
const jsonObj = LJSON.parse(rawParamsBody.value as string)
prettifyBody = LJSON.stringify(jsonObj, undefined, 2) as string
} else if (body.value.contentType === "application/xml") {
prettifyBody = prettifyXML(rawParamsBody.value as string)
}

View File

@@ -36,16 +36,6 @@
/>
</span>
</div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<HoppSmartToggle
:on="extensionEnabled"
@change="extensionEnabled = !extensionEnabled"
>
{{ t("settings.extensions_use_toggle") }}
</HoppSmartToggle>
</div>
</div>
</template>
<script setup lang="ts">
@@ -55,34 +45,12 @@ import IconCheckCircle from "~icons/lucide/check-circle"
import { useI18n } from "@composables/i18n"
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
import { useService } from "dioc/vue"
import { computed } from "vue"
import { InterceptorService } from "~/services/interceptor.service"
import { platform } from "~/platform"
const t = useI18n()
const interceptorService = useService(InterceptorService)
const extensionService = useService(ExtensionInterceptorService)
const extensionVersion = extensionService.extensionVersion
const hasChromeExtInstalled = extensionService.chromeExtensionInstalled
const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled
const extensionEnabled = computed({
get() {
return (
interceptorService.currentInterceptorID.value ===
extensionService.interceptorID
)
},
set(active) {
if (active) {
interceptorService.currentInterceptorID.value =
extensionService.interceptorID
} else {
interceptorService.currentInterceptorID.value =
platform.interceptors.default
}
},
})
</script>

View File

@@ -8,16 +8,6 @@
:label="t('app.proxy_privacy_policy')"
/>.
</div>
<div class="space-y-4 py-4">
<div class="flex items-center">
<HoppSmartToggle
:on="proxyEnabled"
@change="proxyEnabled = !proxyEnabled"
>
{{ t("settings.proxy_use_toggle") }}
</HoppSmartToggle>
</div>
</div>
<div class="flex items-center space-x-2 py-4">
<HoppSmartInput
v-model="PROXY_URL"
@@ -50,7 +40,6 @@ import { computed } from "vue"
import { useService } from "dioc/vue"
import { InterceptorService } from "~/services/interceptor.service"
import { proxyInterceptor } from "~/platform/std/interceptors/proxy"
import { platform } from "~/platform"
const t = useI18n()
const toast = useToast()
@@ -59,23 +48,11 @@ const interceptorService = useService(InterceptorService)
const PROXY_URL = useSetting("PROXY_URL")
const proxyEnabled = computed({
get() {
return (
interceptorService.currentInterceptorID.value ===
proxyInterceptor.interceptorID
)
},
set(active) {
if (active) {
interceptorService.currentInterceptorID.value =
proxyInterceptor.interceptorID
} else {
interceptorService.currentInterceptorID.value =
platform.interceptors.default
}
},
})
const proxyEnabled = computed(
() =>
interceptorService.currentInterceptorID.value ===
proxyInterceptor.interceptorID
)
const clearIcon = refAutoReset<typeof IconRotateCCW | typeof IconCheck>(
IconRotateCCW,

View File

@@ -15,10 +15,6 @@ export type NetworkStrategy = (
req: AxiosRequestConfig
) => TE.TaskEither<any, NetworkResponse>
export const cancelRunningRequest = () => {
// TODO: Implement
}
function processResponse(
res: NetworkResponse,
req: EffectiveHoppRESTRequest,
@@ -34,10 +30,13 @@ function processResponse(
statusCode: res.status,
statusText: res.statusText,
body: res.data,
headers: Object.keys(res.headers).map((x) => ({
key: x,
value: res.headers[x],
})),
// If multi headers are present, then we can just use that, else fallback to Axios type
headers:
res.additional?.multiHeaders ??
Object.keys(res.headers).map((x) => ({
key: x,
value: res.headers[x],
})),
meta: {
responseSize: contentLength,
responseDuration: backupTimeEnd - backupTimeStart,

View File

@@ -1,8 +1,6 @@
import { cloneDeep, defaultsDeep, has } from "lodash-es"
import { Observable } from "rxjs"
import { distinctUntilChanged, pluck } from "rxjs/operators"
import { nextTick } from "vue"
import { platform } from "~/platform"
import type { KeysMatching } from "~/types/ts-utils"
import DispatchingStore, { defineDispatchers } from "./DispatchingStore"
@@ -70,63 +68,52 @@ export type SettingsDef = {
HAS_OPENED_SPOTLIGHT: boolean
}
export const getDefaultSettings = (): SettingsDef => {
const defaultSettings: SettingsDef = {
syncCollections: true,
syncHistory: true,
syncEnvironments: true,
export const getDefaultSettings = (): SettingsDef => ({
syncCollections: true,
syncHistory: true,
syncEnvironments: true,
WRAP_LINES: {
httpRequestBody: true,
httpResponseBody: true,
httpHeaders: true,
httpParams: true,
httpUrlEncoded: true,
httpPreRequest: true,
httpTest: true,
httpRequestVariables: true,
graphqlQuery: true,
graphqlResponseBody: true,
graphqlHeaders: false,
graphqlVariables: false,
graphqlSchema: true,
importCurl: true,
codeGen: true,
cookie: true,
},
WRAP_LINES: {
httpRequestBody: true,
httpResponseBody: true,
httpHeaders: true,
httpParams: true,
httpUrlEncoded: true,
httpPreRequest: true,
httpTest: true,
httpRequestVariables: true,
graphqlQuery: true,
graphqlResponseBody: true,
graphqlHeaders: false,
graphqlVariables: false,
graphqlSchema: true,
importCurl: true,
codeGen: true,
cookie: true,
},
CURRENT_INTERCEPTOR_ID: "",
// Set empty because interceptor module will set the default value
CURRENT_INTERCEPTOR_ID: "",
// TODO: Interceptor related settings should move under the interceptor systems
PROXY_URL: "https://proxy.hoppscotch.io/",
URL_EXCLUDES: {
auth: true,
httpUser: true,
httpPassword: true,
bearerToken: true,
oauth2Token: true,
},
THEME_COLOR: "indigo",
BG_COLOR: "system",
TELEMETRY_ENABLED: true,
EXPAND_NAVIGATION: false,
SIDEBAR: true,
SIDEBAR_ON_LEFT: false,
COLUMN_LAYOUT: true,
// TODO: Interceptor related settings should move under the interceptor systems
PROXY_URL: "https://proxy.hoppscotch.io/",
URL_EXCLUDES: {
auth: true,
httpUser: true,
httpPassword: true,
bearerToken: true,
oauth2Token: true,
},
THEME_COLOR: "indigo",
BG_COLOR: "system",
TELEMETRY_ENABLED: true,
EXPAND_NAVIGATION: false,
SIDEBAR: true,
SIDEBAR_ON_LEFT: false,
COLUMN_LAYOUT: true,
HAS_OPENED_SPOTLIGHT: false,
}
// Wait for platform to initialize before setting CURRENT_INTERCEPTOR_ID
nextTick(() => {
applySetting(
"CURRENT_INTERCEPTOR_ID",
platform?.interceptors.default || "browser"
)
})
return defaultSettings
}
HAS_OPENED_SPOTLIGHT: false,
})
type ApplySettingPayload = {
[K in keyof SettingsDef]: {

View File

@@ -98,6 +98,12 @@
</p>
</div>
<div class="space-y-8 p-8 md:col-span-2">
<section class="flex flex-col space-y-2">
<h4 class="font-semibold text-secondaryDark">
{{ t("settings.interceptor") }}
</h4>
<AppInterceptor :is-tooltip-component="false" />
</section>
<section v-for="[id, settings] in interceptorsWithSettings" :key="id">
<h4 class="font-semibold text-secondaryDark">
{{ settings.entryTitle(t) }}

View File

@@ -52,6 +52,13 @@ export type LoginItemDef = {
}
export type AuthPlatformDef = {
/**
* Whether this platform shows a custom login selector UI. Used for situations
* where we don't want to render the traditional UI and want to replace it
* with something else
*/
customLoginSelectorUI?: Component
/**
* Returns an observable that emits the current user as per the auth implementation.
*

View File

@@ -16,6 +16,22 @@ export type NetworkResponse = AxiosResponse<unknown> & {
endTime: number
}
}
/**
* Optional additional fields with special optional metadata that can be used
*/
additional?: {
/**
* By the HTTP spec, we can have multiple headers with the same name, but
* this is not accessible in the AxiosResponse type as the headers there are Record<string, string>
* (and hence cannot have secondary values).
*
* If this value is present, headers can be read from here which will have the data.
*/
multiHeaders?: Array<{
key: string
value: string
}>
}
}
/**

View File

@@ -30,7 +30,6 @@ const AuthCodeOauthFlowParamsSchema = AuthCodeGrantTypeParams.pick({
params.authEndpoint.length >= 1 &&
params.tokenEndpoint.length >= 1 &&
params.clientID.length >= 1 &&
params.clientSecret.length >= 1 &&
(!params.scopes || params.scopes.trim().length >= 1)
)
},
@@ -85,7 +84,7 @@ const initAuthCodeOauthFlow = async ({
grant_type: "AUTHORIZATION_CODE"
authEndpoint: string
tokenEndpoint: string
clientSecret: string
clientSecret?: string
clientID: string
isPKCE: boolean
codeVerifier?: string

View File

@@ -25,7 +25,7 @@ export const REST_COLLECTIONS_MOCK: HoppCollection[] = [
folders: [],
requests: [
{
v: "4",
v: "5",
endpoint: "https://echo.hoppscotch.io",
name: "Echo test",
params: [],
@@ -50,7 +50,7 @@ export const GQL_COLLECTIONS_MOCK: HoppCollection[] = [
folders: [],
requests: [
{
v: 4,
v: 5,
name: "Echo test",
url: "https://echo.hoppscotch.io/graphql",
headers: [],
@@ -138,7 +138,7 @@ export const REST_HISTORY_MOCK: RESTHistoryEntry[] = [
preRequestScript: "",
testScript: "",
requestVariables: [],
v: "4",
v: "5",
},
responseMeta: { duration: 807, statusCode: 200 },
star: false,
@@ -150,7 +150,7 @@ export const GQL_HISTORY_MOCK: GQLHistoryEntry[] = [
{
v: 1,
request: {
v: 4,
v: 5,
name: "Untitled",
url: "https://echo.hoppscotch.io/graphql",
query: "query Request { url }",
@@ -171,7 +171,7 @@ export const GQL_TAB_STATE_MOCK: PersistableTabState<HoppGQLDocument> = {
tabID: "5edbe8d4-65c9-4381-9354-5f1bf05d8ccc",
doc: {
request: {
v: 4,
v: 5,
name: "Untitled",
url: "https://echo.hoppscotch.io/graphql",
headers: [],
@@ -194,7 +194,7 @@ export const REST_TAB_STATE_MOCK: PersistableTabState<HoppRESTDocument> = {
tabID: "e6e8d800-caa8-44a2-a6a6-b4765a3167aa",
doc: {
request: {
v: "4",
v: "5",
endpoint: "https://echo.hoppscotch.io",
name: "Echo test",
params: [],

View File

@@ -400,7 +400,7 @@ const HoppTestResultSchema = z
(x) => "secret" in x && !x.secret
).and(
z.object({
previousValue: z.string(),
previousValue: z.optional(z.string()),
})
)
),
@@ -415,7 +415,7 @@ const HoppTestResultSchema = z
(x) => "secret" in x && !x.secret
).and(
z.object({
previousValue: z.string(),
previousValue: z.optional(z.string()),
})
)
),

View File

@@ -4,6 +4,7 @@ import V1_VERSION from "./v/1"
import V2_VERSION from "./v/2"
import V3_VERSION from "./v/3"
import V4_VERSION from "./v/4"
import V5_VERSION from "./v/5"
export { GQLHeader } from "./v/1"
export {
@@ -13,23 +14,24 @@ export {
HoppGQLAuthInherit,
} from "./v/2"
export { HoppGQLAuth } from "./v/4"
export { HoppGQLAuthOAuth2 } from "./v/3"
export { HoppGQLAuthOAuth2, HoppGQLAuth } from "./v/5"
export { HoppGQLAuthAPIKey } from "./v/4"
export const GQL_REQ_SCHEMA_VERSION = 4
export const GQL_REQ_SCHEMA_VERSION = 5
const versionedObject = z.object({
v: z.number(),
})
export const HoppGQLRequest = createVersionedEntity({
latestVersion: 4,
latestVersion: 5,
versionMap: {
1: V1_VERSION,
2: V2_VERSION,
3: V3_VERSION,
4: V4_VERSION,
5: V5_VERSION,
},
getVersion(x) {
const result = versionedObject.safeParse(x)

View File

@@ -0,0 +1,47 @@
import { z } from "zod"
import { defineVersion } from "verzod"
import { HoppRESTAuthOAuth2 } from "../../rest/v/5"
import {
HoppGQLAuthBasic,
HoppGQLAuthBearer,
HoppGQLAuthInherit,
HoppGQLAuthNone,
} from "./2"
import { HoppGQLAuthAPIKey, V4_SCHEMA } from "./4"
export { HoppRESTAuthOAuth2 as HoppGQLAuthOAuth2 } from "../../rest/v/5"
export const HoppGQLAuth = z
.discriminatedUnion("authType", [
HoppGQLAuthNone,
HoppGQLAuthInherit,
HoppGQLAuthBasic,
HoppGQLAuthBearer,
HoppGQLAuthAPIKey,
HoppRESTAuthOAuth2, // both rest and gql have the same auth type for oauth2
])
.and(
z.object({
authActive: z.boolean(),
})
)
export type HoppGQLAuth = z.infer<typeof HoppGQLAuth>
export const V5_SCHEMA = V4_SCHEMA.extend({
v: z.literal(5),
auth: HoppGQLAuth,
})
export default defineVersion({
initial: false,
schema: V5_SCHEMA,
up(old: z.infer<typeof V4_SCHEMA>) {
return {
...old,
v: 5 as const,
}
},
})

View File

@@ -6,12 +6,13 @@ import V1_VERSION from "./v/1"
import V2_VERSION from "./v/2"
import V3_VERSION from "./v/3"
import V4_VERSION from "./v/4"
import V5_VERSION from "./v/5"
import { createVersionedEntity, InferredEntity } from "verzod"
import { lodashIsEqualEq, mapThenEq, undefinedEq } from "../utils/eq"
import { HoppRESTReqBody, HoppRESTHeaders, HoppRESTParams } from "./v/1"
import { HoppRESTAuth } from "./v/4"
import { HoppRESTAuth } from "./v/5"
import { HoppRESTRequestVariables } from "./v/2"
import { z } from "zod"
@@ -29,14 +30,18 @@ export {
} from "./v/1"
export {
HoppRESTAuthOAuth2,
AuthCodeGrantTypeParams,
ClientCredentialsGrantTypeParams,
ImplicitOauthFlowParams,
PasswordGrantTypeParams,
} from "./v/3"
export { HoppRESTAuth, HoppRESTAuthAPIKey } from "./v/4"
export {
AuthCodeGrantTypeParams,
HoppRESTAuthOAuth2,
HoppRESTAuth,
} from "./v/5"
export { HoppRESTAuthAPIKey } from "./v/4"
export { HoppRESTRequestVariables } from "./v/2"
@@ -46,13 +51,14 @@ const versionedObject = z.object({
})
export const HoppRESTRequest = createVersionedEntity({
latestVersion: 4,
latestVersion: 5,
versionMap: {
0: V0_VERSION,
1: V1_VERSION,
2: V2_VERSION,
3: V3_VERSION,
4: V4_VERSION,
5: V5_VERSION,
},
getVersion(data) {
// For V1 onwards we have the v string storing the number
@@ -94,7 +100,7 @@ const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
),
})
export const RESTReqSchemaVersion = "4"
export const RESTReqSchemaVersion = "5"
export type HoppRESTParam = HoppRESTRequest["params"][number]
export type HoppRESTHeader = HoppRESTRequest["headers"][number]
@@ -189,7 +195,7 @@ export function makeRESTRequest(
export function getDefaultRESTRequest(): HoppRESTRequest {
return {
v: "4",
v: "5",
endpoint: "https://echo.hoppscotch.io",
name: "Untitled",
params: [],

View File

@@ -0,0 +1,66 @@
import { z } from "zod"
import { defineVersion } from "verzod"
import {
HoppRESTAuthBasic,
HoppRESTAuthBearer,
HoppRESTAuthInherit,
HoppRESTAuthNone,
} from "./1"
import { HoppRESTAuthAPIKey, V4_SCHEMA } from "./4"
import {
AuthCodeGrantTypeParams as AuthCodeGrantTypeParamsOld,
ClientCredentialsGrantTypeParams,
HoppRESTAuthOAuth2 as HoppRESTAuthOAuth2Old,
ImplicitOauthFlowParams,
PasswordGrantTypeParams,
} from "./3"
export const AuthCodeGrantTypeParams = AuthCodeGrantTypeParamsOld.extend({
clientSecret: z.string().optional(),
})
export const HoppRESTAuthOAuth2 = HoppRESTAuthOAuth2Old.extend({
grantTypeInfo: z.discriminatedUnion("grantType", [
AuthCodeGrantTypeParams,
ClientCredentialsGrantTypeParams,
PasswordGrantTypeParams,
ImplicitOauthFlowParams,
]),
})
export type HoppRESTAuthOAuth2 = z.infer<typeof HoppRESTAuthOAuth2>
export const HoppRESTAuth = z
.discriminatedUnion("authType", [
HoppRESTAuthNone,
HoppRESTAuthInherit,
HoppRESTAuthBasic,
HoppRESTAuthBearer,
HoppRESTAuthOAuth2,
HoppRESTAuthAPIKey,
])
.and(
z.object({
authActive: z.boolean(),
})
)
export type HoppRESTAuth = z.infer<typeof HoppRESTAuth>
export const V5_SCHEMA = V4_SCHEMA.extend({
v: z.literal("5"),
auth: HoppRESTAuth,
})
export default defineVersion({
schema: V5_SCHEMA,
initial: false,
up(old: z.infer<typeof V4_SCHEMA>) {
// v5 is not a breaking change in terms of migrations
// we're just making clientSecret in authcode + pkce flow optional
return {
...old,
v: "5" as const,
}
},
})

View File

@@ -1,7 +1,7 @@
{
"name": "@hoppscotch/selfhost-desktop",
"private": true,
"version": "2024.3.3",
"version": "2024.3.4",
"type": "module",
"scripts": {
"dev:vite": "vite",
@@ -17,6 +17,7 @@
"@fontsource-variable/material-symbols-rounded": "5.0.16",
"@fontsource-variable/roboto-mono": "5.0.16",
"@hoppscotch/common": "workspace:^",
"@hoppscotch/data": "workspace:^",
"@platform/auth": "0.1.106",
"@tauri-apps/api": "1.5.1",
"@tauri-apps/cli": "1.5.6",

View File

@@ -1260,7 +1260,7 @@ dependencies = [
[[package]]
name = "hoppscotch-desktop"
version = "24.3.3"
version = "24.3.4"
dependencies = [
"cocoa 0.25.0",
"hex_color",

View File

@@ -1,6 +1,6 @@
[package]
name = "hoppscotch-desktop"
version = "24.3.3"
version = "24.3.4"
description = "A Tauri App"
authors = ["you"]
license = ""

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "Hoppscotch",
"version": "24.3.3"
"version": "24.3.4"
},
"tauri": {
"allowlist": {

View File

@@ -5,7 +5,6 @@ import { def as collectionsDef } from "./platform/collections/collections.platfo
import { def as settingsDef } from "./platform/settings/settings.platform"
import { def as historyDef } from "./platform/history/history.platform"
import { proxyInterceptor } from "@hoppscotch/common/platform/std/interceptors/proxy"
import { ExtensionInspectorService } from "@hoppscotch/common/platform/std/inspections/extension.inspector"
import { NativeInterceptorService } from "./platform/interceptors/native"
import { nextTick, ref, watch } from "vue"
import { emit, listen } from "@tauri-apps/api/event"
@@ -53,9 +52,6 @@ const headerPaddingTop = ref("0px")
{ type: "standalone", interceptor: proxyInterceptor },
],
},
additionalInspectors: [
{ type: "service", service: ExtensionInspectorService },
],
platformFeatureFlags: {
exportAsGIST: false,
hasTelemetry: false,

View File

@@ -97,6 +97,11 @@ async function runRequest(
endTime: timeEnd,
},
},
additional: {
multiHeaders: Object.entries(res.rawHeaders).flatMap(
([header, values]) => values.map((value) => ({ key: header, value }))
),
},
})
} catch (e) {
const timeEnd = Date.now()

View File

@@ -1,7 +1,7 @@
{
"name": "@hoppscotch/selfhost-web",
"private": true,
"version": "2024.3.3",
"version": "2024.3.4",
"type": "module",
"scripts": {
"dev:vite": "vite",

View File

@@ -14,6 +14,7 @@
"client_id": "CLIENT ID",
"client_secret": "CLIENT SECRET",
"description": "Configure authentication providers for your server",
"provider_not_specified": "Please enable at least one authentication provider",
"scope": "SCOPE",
"tenant": "TENANT",
"title": "Authentication Providers",
@@ -146,6 +147,7 @@
"email_failure": "Failed to send invitation",
"email_signin_failure": "Failed to login with Email",
"email_success": "Email invitation sent successfully",
"emails_cannot_be_same": "You cannot invite yourself, please choose a different email address!!",
"enter_team_email": "Please enter email of workspace owner!!",
"error": "Something went wrong",
"error_auth_providers": "Unable to load auth providers",
@@ -171,6 +173,7 @@
"remove_admin_from_users_success": "Admin status removed from selected users!!",
"remove_admin_to_delete_user": "Remove admin privilege to delete the user!!",
"remove_owner_to_delete_user": "Remove team ownership status to delete the user!!",
"remove_owner_failure_only_one_owner": "Failed to remove member. There should be atleast one owner in a team!!",
"remove_admin_for_deletion": "Remove admin status before attempting deletion!!",
"remove_owner_for_deletion": "One or more users are team owners. Update ownership before deletion!!",
"remove_invitee_failure": "Removal of invitee failed!!",
@@ -193,7 +196,6 @@
"sign_in_options": "All sign in option",
"sign_out": "Sign out",
"team_name_too_short": "Workspace name should be atleast 6 characters long!!",
"team_name_long": "Workspace name should be atleast 6 characters long!!",
"user_already_invited": "Failed to send invite. User is already invited!!",
"user_not_found": "User not found in the infra!!",
"users_to_admin_success": "Selected users are elevated to admin status!!",

View File

@@ -1,7 +1,7 @@
{
"name": "hoppscotch-sh-admin",
"private": true,
"version": "2024.3.3",
"version": "2024.3.4",
"type": "module",
"scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*",

View File

@@ -1,56 +1,51 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
import '@vue/runtime-core';
export {}
export {};
declare module '@vue/runtime-core' {
export interface GlobalComponents {
AppHeader: typeof import('./components/app/Header.vue')['default']
AppLogin: typeof import('./components/app/Login.vue')['default']
AppLogout: typeof import('./components/app/Logout.vue')['default']
AppModal: typeof import('./components/app/Modal.vue')['default']
AppSidebar: typeof import('./components/app/Sidebar.vue')['default']
AppToast: typeof import('./components/app/Toast.vue')['default']
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default']
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
HoppSmartTable: typeof import('@hoppscotch/ui')['HoppSmartTable']
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideChevronDown: typeof import('~icons/lucide/chevron-down')['default']
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUser: typeof import('~icons/lucide/user')['default']
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default']
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default']
SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default']
SettingsReset: typeof import('./components/settings/Reset.vue')['default']
SettingsServerRestart: typeof import('./components/settings/ServerRestart.vue')['default']
SettingsSmtpConfiguration: typeof import('./components/settings/SmtpConfiguration.vue')['default']
SetupDataSharingAndNewsletter: typeof import('./components/setup/DataSharingAndNewsletter.vue')['default']
TeamsAdd: typeof import('./components/teams/Add.vue')['default']
TeamsDetails: typeof import('./components/teams/Details.vue')['default']
TeamsInvite: typeof import('./components/teams/Invite.vue')['default']
TeamsMembers: typeof import('./components/teams/Members.vue')['default']
TeamsPendingInvites: typeof import('./components/teams/PendingInvites.vue')['default']
Tippy: typeof import('vue-tippy')['Tippy']
UiAutoResetIcon: typeof import('./components/ui/AutoResetIcon.vue')['default']
UsersDetails: typeof import('./components/users/Details.vue')['default']
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default']
UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default']
AppHeader: typeof import('./components/app/Header.vue')['default'];
AppLogin: typeof import('./components/app/Login.vue')['default'];
AppLogout: typeof import('./components/app/Logout.vue')['default'];
AppModal: typeof import('./components/app/Modal.vue')['default'];
AppSidebar: typeof import('./components/app/Sidebar.vue')['default'];
AppToast: typeof import('./components/app/Toast.vue')['default'];
DashboardMetricsCard: typeof import('./components/dashboard/MetricsCard.vue')['default'];
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'];
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'];
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'];
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'];
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'];
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'];
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'];
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'];
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture'];
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder'];
HoppSmartSelectWrapper: typeof import('@hoppscotch/ui')['HoppSmartSelectWrapper'];
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'];
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'];
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'];
HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'];
IconLucideInbox: typeof import('~icons/lucide/inbox')['default'];
SettingsAuthProvider: typeof import('./components/settings/AuthProvider.vue')['default'];
SettingsConfigurations: typeof import('./components/settings/Configurations.vue')['default'];
SettingsDataSharing: typeof import('./components/settings/DataSharing.vue')['default'];
SettingsReset: typeof import('./components/settings/Reset.vue')['default'];
SettingsServerRestart: typeof import('./components/settings/ServerRestart.vue')['default'];
SettingsSmtpConfiguration: typeof import('./components/settings/SmtpConfiguration.vue')['default'];
SetupDataSharingAndNewsletter: typeof import('./components/setup/DataSharingAndNewsletter.vue')['default'];
TeamsAdd: typeof import('./components/teams/Add.vue')['default'];
TeamsDetails: typeof import('./components/teams/Details.vue')['default'];
TeamsInvite: typeof import('./components/teams/Invite.vue')['default'];
TeamsMembers: typeof import('./components/teams/Members.vue')['default'];
TeamsPendingInvites: typeof import('./components/teams/PendingInvites.vue')['default'];
Tippy: typeof import('vue-tippy')['Tippy'];
UiAutoResetIcon: typeof import('./components/ui/AutoResetIcon.vue')['default'];
UsersDetails: typeof import('./components/users/Details.vue')['default'];
UsersInviteModal: typeof import('./components/users/InviteModal.vue')['default'];
UsersSharedRequests: typeof import('./components/users/SharedRequests.vue')['default'];
}
}

View File

@@ -24,7 +24,11 @@
</div>
</div>
<SettingsServerRestart v-if="resetInfraConfigs" :reset="resetInfraConfigs" />
<SettingsServerRestart
v-if="resetInfraConfigs"
:reset="resetInfraConfigs"
@mutation-failure="resetInfraConfigs = false"
/>
<HoppSmartConfirmModal
:show="resetModal"

View File

@@ -39,6 +39,10 @@ const props = withDefaults(
}
);
const emit = defineEmits<{
(e: 'mutationFailure'): void;
}>();
// Mutations to update or reset server configurations and audit logs
const resetInfraConfigsMutation = useMutation(ResetInfraConfigsDocument);
const updateInfraConfigsMutation = useMutation(UpdateInfraConfigsDocument);
@@ -73,28 +77,40 @@ const startCountdown = () => {
}, 1000);
};
const triggerComponentUnMount = () => emit('mutationFailure');
// Call relevant mutations on component mount and initiate server restart
onMounted(async () => {
let success = true;
if (props.reset) {
success = await resetInfraConfigs(resetInfraConfigsMutation);
if (!success) return;
const resetInfraConfigsResult = await resetInfraConfigs(
resetInfraConfigsMutation
);
if (!resetInfraConfigsResult) {
return triggerComponentUnMount();
}
} else {
const infraResult = await updateInfraConfigs(updateInfraConfigsMutation);
if (!infraResult) return;
if (!infraResult) {
return triggerComponentUnMount();
}
const authResult = await updateAuthProvider(
updateAllowedAuthProviderMutation
);
if (!authResult) return;
if (!authResult) {
return triggerComponentUnMount();
}
const dataSharingResult = await updateDataSharingConfigs(
toggleDataSharingMutation
);
if (!dataSharingResult) return;
if (!dataSharingResult) {
return triggerComponentUnMount();
}
}
restart.value = true;

View File

@@ -168,6 +168,7 @@ import { useRoute } from 'vue-router';
import { useI18n } from '~/composables/i18n';
import { useToast } from '~/composables/toast';
import { useClientHandler } from '~/composables/useClientHandler';
import { getCompiledErrorMessage } from '~/helpers/errors';
import IconChevronDown from '~icons/lucide/chevron-down';
import IconCircle from '~icons/lucide/circle';
import IconCircleDot from '~icons/lucide/circle-dot';
@@ -353,7 +354,13 @@ const removeExistingTeamMember = async (userID: string, index: number) => {
team.value.id
)();
if (removeTeamMemberResult.error) {
toast.error(t('state.remove_member_failure'));
const compiledErrorMessage = getCompiledErrorMessage(
removeTeamMemberResult.error.message
);
compiledErrorMessage
? toast.error(compiledErrorMessage)
: toast.error(t('state.remove_member_failure'));
} else {
team.value.teamMembers = team.value.teamMembers?.filter(
(member: any) => member.user.uid !== userID

View File

@@ -1,6 +1,5 @@
<template>
<HoppSmartModal
v-if="show"
dialog
:title="t('users.invite_user')"
@close="emit('hide-modal')"
@@ -38,15 +37,6 @@ import { useToast } from '~/composables/toast';
const t = useI18n();
const toast = useToast();
withDefaults(
defineProps<{
show: boolean;
}>(),
{
show: false,
}
);
const emit = defineEmits<{
(event: 'hide-modal'): void;
(event: 'send-invite', email: string): void;

View File

@@ -27,6 +27,7 @@ import {
ServerConfigs,
UpdatedConfigs,
} from '~/helpers/configs';
import { getCompiledErrorMessage } from '~/helpers/errors';
import { useToast } from './toast';
import { useClientHandler } from './useClientHandler';
@@ -201,7 +202,12 @@ export function useConfigHandler(updatedConfigs?: ServerConfigs) {
const result = await mutation.executeMutation(variables);
if (result.error) {
toast.error(t(errorMessage));
const { message } = result.error;
const compiledErrorMessage = getCompiledErrorMessage(message);
compiledErrorMessage
? toast.error(t(compiledErrorMessage))
: toast.error(t(errorMessage));
return false;
}

View File

@@ -8,9 +8,14 @@ export const UNAUTHORIZED = 'Unauthorized' as const;
// Sometimes the backend returns Unauthorized error message as follows:
export const GRAPHQL_UNAUTHORIZED = '[GraphQL] Unauthorized' as const;
// When the email is invalid
export const INVALID_EMAIL = '[GraphQL] invalid/email' as const;
// When trying to remove the only admin account
export const ONLY_ONE_ADMIN_ACCOUNT_FOUND =
'[GraphQL] admin/only_one_admin_account_found' as const;
// When trying to delete an admin account
export const ADMIN_CANNOT_BE_DELETED =
'admin/admin_can_not_be_deleted' as const;
@@ -19,4 +24,53 @@ export const USER_ALREADY_INVITED =
'[GraphQL] admin/user_already_invited' as const;
// When attempting to delete a user who is an owner of a team
export const USER_IS_OWNER = 'user/is_owner' as const;
export const USER_IS_OWNER = 'user/is_owner';
// When attempting to delete a user who is the only owner of a team
export const TEAM_ONLY_ONE_OWNER = '[GraphQL] team/only_one_owner';
// Even one auth provider is not specified
export const AUTH_PROVIDER_NOT_SPECIFIED =
'[GraphQL] auth/provider_not_specified' as const;
export const BOTH_EMAILS_CANNOT_BE_SAME =
'[GraphQL] email/both_emails_cannot_be_same' as const;
type ErrorMessages = {
message: string;
alternateMessage?: string;
};
const ERROR_MESSAGES: Record<string, ErrorMessages> = {
[INVALID_EMAIL]: {
message: 'state.invalid_email',
},
[ONLY_ONE_ADMIN_ACCOUNT_FOUND]: {
message: 'state.remove_admin_failure_only_one_admin',
},
[ADMIN_CANNOT_BE_DELETED]: {
message: 'state.remove_admin_to_delete_user',
alternateMessage: 'state.remove_admin_for_deletion',
},
[USER_ALREADY_INVITED]: {
message: 'state.user_already_invited',
},
[USER_IS_OWNER]: {
message: 'state.remove_owner_to_delete_user',
alternateMessage: 'state.remove_owner_for_deletion',
},
[TEAM_ONLY_ONE_OWNER]: {
message: 'state.remove_owner_failure_only_one_owner',
},
[AUTH_PROVIDER_NOT_SPECIFIED]: {
message: 'configs.auth_providers.provider_not_specified',
},
[BOTH_EMAILS_CANNOT_BE_SAME]: {
message: 'state.emails_cannot_be_same',
},
};
export const getCompiledErrorMessage = (name: string, altMessage = false) => {
const error = ERROR_MESSAGES[name];
return altMessage ? error?.alternateMessage ?? '' : error?.message ?? '';
};

View File

@@ -1,7 +1,11 @@
import { useToast } from '~/composables/toast';
import { getI18n } from '~/modules/i18n';
import { UserDeletionResult } from './backend/graphql';
import { ADMIN_CANNOT_BE_DELETED, USER_IS_OWNER } from './errors';
import {
ADMIN_CANNOT_BE_DELETED,
USER_IS_OWNER,
getCompiledErrorMessage,
} from './errors';
type ToastMessage = {
message: string;
@@ -49,14 +53,12 @@ export const handleUserDeletion = (deletedUsersList: UserDeletionResult[]) => {
}
const errMsgMap = {
[ADMIN_CANNOT_BE_DELETED]: isBulkAction
? t('state.remove_admin_for_deletion')
: t('state.remove_admin_to_delete_user'),
[USER_IS_OWNER]: isBulkAction
? t('state.remove_owner_for_deletion')
: t('state.remove_owner_to_delete_user'),
[ADMIN_CANNOT_BE_DELETED]: t(
getCompiledErrorMessage(ADMIN_CANNOT_BE_DELETED, isBulkAction)
),
[USER_IS_OWNER]: t(getCompiledErrorMessage(USER_IS_OWNER, isBulkAction)),
};
const errMsgMapKeys = Object.keys(errMsgMap);
const toastMessages: ToastMessage[] = [];

View File

@@ -37,6 +37,7 @@
<SettingsServerRestart
v-if="initiateServerRestart"
:workingConfigs="workingConfigs"
@mutation-failure="initiateServerRestart = false"
/>
<HoppSmartConfirmModal

View File

@@ -174,18 +174,21 @@
class="py-4 border-divider rounded-r-none bg-emerald-800 text-secondaryDark"
/>
<HoppButtonSecondary
v-if="areNonAdminsSelected"
:icon="IconUserCheck"
:label="t('users.make_admin')"
class="py-4 border-divider border-r-1 rounded-none hover:bg-emerald-600"
@click="confirmUsersToAdmin = true"
/>
<HoppButtonSecondary
v-if="areAdminsSelected"
:icon="IconUserMinus"
:label="t('users.remove_admin_status')"
class="py-4 border-divider border-r-1 rounded-none hover:bg-orange-500"
@click="confirmAdminsToUsers = true"
/>
<HoppButtonSecondary
v-if="areNonAdminsSelected"
:icon="IconTrash"
:label="t('users.delete_users')"
class="py-4 border-divider rounded-none hover:bg-red-500"
@@ -203,7 +206,7 @@
</div>
<UsersInviteModal
:show="showInviteUserModal"
v-if="showInviteUserModal"
@hide-modal="showInviteUserModal = false"
@send-invite="sendInvite"
/>
@@ -258,10 +261,7 @@ import {
UsersListQuery,
UsersListV2Document,
} from '~/helpers/backend/graphql';
import {
ONLY_ONE_ADMIN_ACCOUNT_FOUND,
USER_ALREADY_INVITED,
} from '~/helpers/errors';
import { getCompiledErrorMessage } from '~/helpers/errors';
import { handleUserDeletion } from '~/helpers/userManagement';
import IconCheck from '~icons/lucide/check';
import IconLeft from '~icons/lucide/chevron-left';
@@ -291,6 +291,7 @@ const headings = [
// Get Paginated Results of all the users in the infra
const usersPerPage = 20;
const {
fetching,
error,
@@ -306,6 +307,19 @@ const {
// Selected Rows
const selectedRows = ref<UsersListQuery['infra']['allUsers']>([]);
const areAdminsSelected = computed(() =>
selectedRows.value.some((user) => user.isAdmin)
);
const areNonAdminsSelected = computed(() => {
// No Admins selected implicitly conveys that all the selected users are non-Admins assuming `selectedRows.length` > 0 (markup render condition)
if (!areAdminsSelected.value) {
return true;
}
return selectedRows.value.some((user) => !user.isAdmin);
});
// Ensure this variable is declared outside the debounce function
let debounceTimeout: ReturnType<typeof setTimeout> | null = null;
@@ -442,9 +456,12 @@ const sendInvite = async (email: string) => {
const variables = { inviteeEmail: email.trim() };
const result = await sendInvitation.executeMutation(variables);
if (result.error) {
if (result.error.message === USER_ALREADY_INVITED)
toast.error(t('state.user_already_invited'));
else toast.error(t('state.email_failure'));
const { message } = result.error;
const compiledErrorMessage = getCompiledErrorMessage(message);
compiledErrorMessage
? toast.error(t(compiledErrorMessage))
: toast.error(t('state.email_failure'));
} else {
toast.success(t('state.email_success'));
showInviteUserModal.value = false;
@@ -522,8 +539,10 @@ const makeAdminsToUsers = async (id: string | null) => {
const variables = { userUIDs };
const result = await adminsToUser.executeMutation(variables);
if (result.error) {
if (result.error.message === ONLY_ONE_ADMIN_ACCOUNT_FOUND) {
return toast.error(t('state.remove_admin_failure_only_one_admin'));
const compiledErrorMessage = getCompiledErrorMessage(result.error.message);
if (compiledErrorMessage) {
return toast.error(t(getCompiledErrorMessage(result.error.message)));
}
toast.error(

25
pnpm-lock.yaml generated
View File

@@ -907,6 +907,9 @@ importers:
'@hoppscotch/common':
specifier: workspace:^
version: link:../hoppscotch-common
'@hoppscotch/data':
specifier: workspace:^
version: link:../hoppscotch-data
'@platform/auth':
specifier: 0.1.106
version: 0.1.106
@@ -3514,8 +3517,8 @@ packages:
resolution: {integrity: sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==}
engines: {node: '>= 14'}
'@intlify/message-compiler@10.0.0-alpha.3':
resolution: {integrity: sha512-WjM1KAl5enpOfprfVAJ3FzwACmizZFPgyV0sn+QXoWH8BG2ahVkf7uVEqQH0mvUr2rKKaScwpzhH3wZ5F7ZdPw==}
'@intlify/message-compiler@10.0.0-beta.1':
resolution: {integrity: sha512-rBmXBZzDgq3yPkL/3/r9uK0nrsJOYHSpaW0mtGBxxjt9pY9vaPL0UAKbVAjFPRnfEY41ixgpkpTjai6IKZ+hvg==}
engines: {node: '>= 16'}
'@intlify/message-compiler@9.2.2':
@@ -3530,8 +3533,8 @@ packages:
resolution: {integrity: sha512-McnYWhcoYmDJvssVu6QGR0shqlkJuL1HHdi5lK7fNqvQqRYaQ4lSLjYmZxwc8tRNMdIe9/KUKfyPxU9M6yCtNQ==}
engines: {node: '>= 16'}
'@intlify/shared@10.0.0-alpha.3':
resolution: {integrity: sha512-fi2q48i+C6sSCAt3vOj/9LD3tkr1wcvLt+ifZEHrpPiwHCyKLDYGp5qBNUHUBBA/iqFTeWdtHUbHE9z9OeTXkw==}
'@intlify/shared@10.0.0-beta.1':
resolution: {integrity: sha512-61MnYhgqS/TyAto9CXOltHlhK2WflLBcKpIkRhZCUL2IkiVvh7qKevsqZ3RYZylyC3q19ajLW6mB+iJtnbAOpg==}
engines: {node: '>= 16'}
'@intlify/shared@9.2.2':
@@ -15519,8 +15522,8 @@ snapshots:
'@intlify/bundle-utils@3.4.0(vue-i18n@9.8.0(vue@3.3.9(typescript@5.3.2)))':
dependencies:
'@intlify/message-compiler': 10.0.0-alpha.3
'@intlify/shared': 10.0.0-alpha.3
'@intlify/message-compiler': 10.0.0-beta.1
'@intlify/shared': 10.0.0-beta.1
jsonc-eslint-parser: 1.4.1
source-map: 0.6.1
yaml-eslint-parser: 0.3.2
@@ -15573,9 +15576,9 @@ snapshots:
dependencies:
'@intlify/shared': 9.2.2
'@intlify/message-compiler@10.0.0-alpha.3':
'@intlify/message-compiler@10.0.0-beta.1':
dependencies:
'@intlify/shared': 10.0.0-alpha.3
'@intlify/shared': 10.0.0-beta.1
source-map-js: 1.0.2
'@intlify/message-compiler@9.2.2':
@@ -15593,7 +15596,7 @@ snapshots:
'@intlify/shared': 9.8.0
source-map-js: 1.0.2
'@intlify/shared@10.0.0-alpha.3': {}
'@intlify/shared@10.0.0-beta.1': {}
'@intlify/shared@9.2.2': {}
@@ -15624,7 +15627,7 @@ snapshots:
'@intlify/vite-plugin-vue-i18n@6.0.1(vite@4.5.0(@types/node@18.18.8)(sass@1.69.5)(terser@5.27.0))(vue-i18n@9.8.0(vue@3.3.9(typescript@4.9.5)))':
dependencies:
'@intlify/bundle-utils': 7.0.0(vue-i18n@9.8.0(vue@3.3.9(typescript@4.9.5)))
'@intlify/shared': 10.0.0-alpha.3
'@intlify/shared': 10.0.0-beta.1
'@rollup/pluginutils': 4.2.1
debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.2
@@ -15638,7 +15641,7 @@ snapshots:
'@intlify/vite-plugin-vue-i18n@7.0.0(vite@4.5.0(@types/node@18.18.8)(sass@1.69.5)(terser@5.27.0))(vue-i18n@9.8.0(vue@3.3.9(typescript@5.3.2)))':
dependencies:
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.8.0(vue@3.3.9(typescript@5.3.2)))
'@intlify/shared': 10.0.0-alpha.3
'@intlify/shared': 10.0.0-beta.1
'@rollup/pluginutils': 4.2.1
debug: 4.3.4(supports-color@9.2.2)
fast-glob: 3.3.2