diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index 00fc16531..8ad71e7d6 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -397,6 +397,7 @@ jobs: with: tag_name: ${{ env.REL_VERSION }} name: ${{ env.REL_VERSION }} + target_commitish: ${{ github.sha }} draft: false prerelease: ${{ env.IS_PRERELEASE }} generate_release_notes: true diff --git a/apps/admin/Dockerfile.admin b/apps/admin/Dockerfile.admin index 63b30a1c3..19ad2c392 100644 --- a/apps/admin/Dockerfile.admin +++ b/apps/admin/Dockerfile.admin @@ -13,7 +13,7 @@ RUN corepack enable pnpm FROM base AS builder -RUN pnpm add -g turbo@2.8.12 +RUN pnpm add -g turbo@2.9.4 COPY . . diff --git a/apps/api/plane/app/views/issue/base.py b/apps/api/plane/app/views/issue/base.py index 98a59b648..bb331802c 100644 --- a/apps/api/plane/app/views/issue/base.py +++ b/apps/api/plane/app/views/issue/base.py @@ -1118,7 +1118,7 @@ class IssueBulkUpdateDateEndpoint(BaseAPIView): epoch = int(timezone.now().timestamp()) # Fetch all relevant issues in a single query - issues = list(Issue.objects.filter(id__in=issue_ids)) + issues = list(Issue.objects.filter(id__in=issue_ids, workspace__slug=slug, project_id=project_id)) issues_dict = {str(issue.id): issue for issue in issues} issues_to_update = [] diff --git a/apps/api/plane/app/views/project/member.py b/apps/api/plane/app/views/project/member.py index 1ad7639fb..7dfe70900 100644 --- a/apps/api/plane/app/views/project/member.py +++ b/apps/api/plane/app/views/project/member.py @@ -226,21 +226,36 @@ class ProjectMemberViewSet(BaseViewSet): is_active=True, ) - if workspace_role in [5] and int(request.data.get("role", project_member.role)) in [15, 20]: - return Response( - {"error": "You cannot add a user with role higher than the workspace role"}, - status=status.HTTP_400_BAD_REQUEST, - ) + if "role" in request.data: + # Only Admins can modify roles + if requested_project_member.role < ROLE.ADMIN.value and not is_workspace_admin: + return Response( + {"error": "You do not have permission to update roles"}, + status=status.HTTP_403_FORBIDDEN, + ) - if ( - "role" in request.data - and int(request.data.get("role", project_member.role)) > requested_project_member.role - and not is_workspace_admin - ): - return Response( - {"error": "You cannot update a role that is higher than your own role"}, - status=status.HTTP_400_BAD_REQUEST, - ) + # Cannot modify a member whose role is equal to or higher than your own + if project_member.role >= requested_project_member.role and not is_workspace_admin: + return Response( + {"error": "You cannot update the role of a member with a role equal to or higher than your own"}, + status=status.HTTP_403_FORBIDDEN, + ) + + new_role = int(request.data.get("role")) + + # Cannot assign a role equal to or higher than your own + if new_role >= requested_project_member.role and not is_workspace_admin: + return Response( + {"error": "You cannot assign a role equal to or higher than your own"}, + status=status.HTTP_403_FORBIDDEN, + ) + + # Cannot assign a role higher than the target's workspace role + if workspace_role in [5] and new_role in [15, 20]: + return Response( + {"error": "You cannot add a user with role higher than the workspace role"}, + status=status.HTTP_400_BAD_REQUEST, + ) serializer = ProjectMemberSerializer(project_member, data=request.data, partial=True) diff --git a/apps/api/plane/bgtasks/work_item_link_task.py b/apps/api/plane/bgtasks/work_item_link_task.py index 442396c7f..5cf0fbb19 100644 --- a/apps/api/plane/bgtasks/work_item_link_task.py +++ b/apps/api/plane/bgtasks/work_item_link_task.py @@ -13,7 +13,7 @@ from bs4 import BeautifulSoup from urllib.parse import urlparse, urljoin import base64 import ipaddress -from typing import Dict, Any +from typing import Dict, Any, Tuple from typing import Optional from plane.db.models import IssueLink from plane.utils.exception_logger import log_exception @@ -66,6 +66,52 @@ def validate_url_ip(url: str) -> None: MAX_REDIRECTS = 5 +def safe_get( + url: str, + headers: Optional[Dict[str, str]] = None, + timeout: int = 1, +) -> Tuple[requests.Response, str]: + """ + Perform a GET request that validates every redirect hop against private IPs. + Prevents SSRF by ensuring no redirect lands on a private/internal address. + + Args: + url: The URL to fetch + headers: Optional request headers + timeout: Request timeout in seconds + + Returns: + A tuple of (final Response object, final URL after redirects) + + Raises: + ValueError: If any URL in the redirect chain points to a private IP + requests.RequestException: On network errors + RuntimeError: If max redirects exceeded + """ + validate_url_ip(url) + + current_url = url + response = requests.get( + current_url, headers=headers, timeout=timeout, allow_redirects=False + ) + + redirect_count = 0 + while response.is_redirect: + if redirect_count >= MAX_REDIRECTS: + raise RuntimeError(f"Too many redirects for URL: {url}") + redirect_url = response.headers.get("Location") + if not redirect_url: + break + current_url = urljoin(current_url, redirect_url) + validate_url_ip(current_url) + redirect_count += 1 + response = requests.get( + current_url, headers=headers, timeout=timeout, allow_redirects=False + ) + + return response, current_url + + def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]: """ Crawls a URL to extract the title and favicon. @@ -86,26 +132,8 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]: title = None final_url = url - validate_url_ip(final_url) - try: - # Manually follow redirects to validate each URL before requesting - redirect_count = 0 - response = requests.get(final_url, headers=headers, timeout=1, allow_redirects=False) - - while response.is_redirect and redirect_count < MAX_REDIRECTS: - redirect_url = response.headers.get("Location") - if not redirect_url: - break - # Resolve relative redirects against current URL - final_url = urljoin(final_url, redirect_url) - # Validate the redirect target BEFORE making the request - validate_url_ip(final_url) - redirect_count += 1 - response = requests.get(final_url, headers=headers, timeout=1, allow_redirects=False) - - if redirect_count >= MAX_REDIRECTS: - logger.warning(f"Too many redirects for URL: {url}") + response, final_url = safe_get(url, headers=headers) soup = BeautifulSoup(response.content, "html.parser") title_tag = soup.find("title") @@ -113,8 +141,10 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]: except requests.RequestException as e: logger.warning(f"Failed to fetch HTML for title: {str(e)}") + except (ValueError, RuntimeError) as e: + logger.warning(f"URL validation failed: {str(e)}") - # Fetch and encode favicon using final URL (after redirects) + # Fetch and encode favicon using final URL (after redirects) for correct relative href resolution favicon_base64 = fetch_and_encode_favicon(headers, soup, final_url) # Prepare result @@ -204,9 +234,7 @@ def fetch_and_encode_favicon( "favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}", } - validate_url_ip(favicon_url) - - response = requests.get(favicon_url, headers=headers, timeout=1) + response, _ = safe_get(favicon_url, headers=headers) # Get content type content_type = response.headers.get("content-type", "image/x-icon") diff --git a/apps/api/plane/tests/unit/bg_tasks/test_work_item_link_task.py b/apps/api/plane/tests/unit/bg_tasks/test_work_item_link_task.py new file mode 100644 index 000000000..2838260e8 --- /dev/null +++ b/apps/api/plane/tests/unit/bg_tasks/test_work_item_link_task.py @@ -0,0 +1,126 @@ +# Copyright (c) 2023-present Plane Software, Inc. and contributors +# SPDX-License-Identifier: AGPL-3.0-only +# See the LICENSE file for details. + +import pytest +from unittest.mock import patch, MagicMock +from plane.bgtasks.work_item_link_task import safe_get, validate_url_ip + + +def _make_response(status_code=200, headers=None, is_redirect=False, content=b""): + """Create a mock requests.Response.""" + resp = MagicMock() + resp.status_code = status_code + resp.is_redirect = is_redirect + resp.headers = headers or {} + resp.content = content + return resp + + +@pytest.mark.unit +class TestValidateUrlIp: + """Test validate_url_ip blocks private/internal IPs.""" + + def test_rejects_private_ip(self): + with patch("plane.bgtasks.work_item_link_task.socket.getaddrinfo") as mock_dns: + mock_dns.return_value = [(None, None, None, None, ("192.168.1.1", 0))] + with pytest.raises(ValueError, match="private/internal"): + validate_url_ip("http://example.com") + + def test_rejects_loopback(self): + with patch("plane.bgtasks.work_item_link_task.socket.getaddrinfo") as mock_dns: + mock_dns.return_value = [(None, None, None, None, ("127.0.0.1", 0))] + with pytest.raises(ValueError, match="private/internal"): + validate_url_ip("http://example.com") + + def test_rejects_non_http_scheme(self): + with pytest.raises(ValueError, match="Only HTTP and HTTPS"): + validate_url_ip("file:///etc/passwd") + + def test_allows_public_ip(self): + with patch("plane.bgtasks.work_item_link_task.socket.getaddrinfo") as mock_dns: + mock_dns.return_value = [(None, None, None, None, ("93.184.216.34", 0))] + validate_url_ip("https://example.com") # Should not raise + + +@pytest.mark.unit +class TestSafeGet: + """Test safe_get follows redirects safely and blocks SSRF.""" + + @patch("plane.bgtasks.work_item_link_task.requests.get") + @patch("plane.bgtasks.work_item_link_task.validate_url_ip") + def test_returns_response_for_non_redirect(self, mock_validate, mock_get): + final_resp = _make_response(status_code=200, content=b"OK") + mock_get.return_value = final_resp + + response, final_url = safe_get("https://example.com") + + assert response is final_resp + assert final_url == "https://example.com" + mock_validate.assert_called_once_with("https://example.com") + + @patch("plane.bgtasks.work_item_link_task.requests.get") + @patch("plane.bgtasks.work_item_link_task.validate_url_ip") + def test_follows_redirect_and_validates_each_hop(self, mock_validate, mock_get): + redirect_resp = _make_response( + status_code=301, + is_redirect=True, + headers={"Location": "https://other.com/page"}, + ) + final_resp = _make_response(status_code=200, content=b"OK") + mock_get.side_effect = [redirect_resp, final_resp] + + response, final_url = safe_get("https://example.com") + + assert response is final_resp + assert final_url == "https://other.com/page" + # Should validate both the initial URL and the redirect target + assert mock_validate.call_count == 2 + mock_validate.assert_any_call("https://example.com") + mock_validate.assert_any_call("https://other.com/page") + + @patch("plane.bgtasks.work_item_link_task.requests.get") + @patch("plane.bgtasks.work_item_link_task.validate_url_ip") + def test_blocks_redirect_to_private_ip(self, mock_validate, mock_get): + redirect_resp = _make_response( + status_code=302, + is_redirect=True, + headers={"Location": "http://192.168.1.1:8080"}, + ) + mock_get.return_value = redirect_resp + # First call (initial URL) succeeds, second call (redirect target) fails + mock_validate.side_effect = [None, ValueError("Access to private/internal networks is not allowed")] + + with pytest.raises(ValueError, match="private/internal"): + safe_get("https://evil.com/redirect") + + @patch("plane.bgtasks.work_item_link_task.requests.get") + @patch("plane.bgtasks.work_item_link_task.validate_url_ip") + def test_raises_on_too_many_redirects(self, mock_validate, mock_get): + redirect_resp = _make_response( + status_code=302, + is_redirect=True, + headers={"Location": "https://example.com/loop"}, + ) + mock_get.return_value = redirect_resp + + with pytest.raises(RuntimeError, match="Too many redirects"): + safe_get("https://example.com/start") + + @patch("plane.bgtasks.work_item_link_task.requests.get") + @patch("plane.bgtasks.work_item_link_task.validate_url_ip") + def test_succeeds_at_exact_max_redirects(self, mock_validate, mock_get): + """After exactly MAX_REDIRECTS hops, if the final response is 200, it should succeed.""" + redirect_resp = _make_response( + status_code=302, + is_redirect=True, + headers={"Location": "https://example.com/next"}, + ) + final_resp = _make_response(status_code=200, content=b"OK") + # 5 redirects then a 200 + mock_get.side_effect = [redirect_resp] * 5 + [final_resp] + + response, final_url = safe_get("https://example.com/start") + + assert response is final_resp + assert not response.is_redirect diff --git a/apps/live/Dockerfile.live b/apps/live/Dockerfile.live index 58d3f58c7..801afca67 100644 --- a/apps/live/Dockerfile.live +++ b/apps/live/Dockerfile.live @@ -15,7 +15,7 @@ RUN apk update RUN apk add --no-cache libc6-compat # Set working directory WORKDIR /app -ARG TURBO_VERSION=2.8.12 +ARG TURBO_VERSION=2.9.4 RUN corepack enable pnpm && pnpm add -g turbo@${TURBO_VERSION} COPY . . RUN turbo prune --scope=live --docker diff --git a/apps/space/Dockerfile.space b/apps/space/Dockerfile.space index 10bc612f6..60d4a155a 100644 --- a/apps/space/Dockerfile.space +++ b/apps/space/Dockerfile.space @@ -13,7 +13,7 @@ RUN corepack enable pnpm FROM base AS builder -RUN pnpm add -g turbo@2.8.12 +RUN pnpm add -g turbo@2.9.4 COPY . . diff --git a/apps/web/Dockerfile.web b/apps/web/Dockerfile.web index ab1a40462..38af19e74 100644 --- a/apps/web/Dockerfile.web +++ b/apps/web/Dockerfile.web @@ -14,7 +14,7 @@ RUN apk add --no-cache libc6-compat # Set working directory WORKDIR /app -ARG TURBO_VERSION=2.8.12 +ARG TURBO_VERSION=2.9.4 RUN corepack enable pnpm && pnpm add -g turbo@${TURBO_VERSION} COPY . . diff --git a/apps/web/core/components/inbox/modals/snooze-issue-modal.tsx b/apps/web/core/components/inbox/modals/snooze-issue-modal.tsx index 708458efb..1ad83a6b0 100644 --- a/apps/web/core/components/inbox/modals/snooze-issue-modal.tsx +++ b/apps/web/core/components/inbox/modals/snooze-issue-modal.tsx @@ -26,7 +26,13 @@ export function InboxIssueSnoozeModal(props: InboxIssueSnoozeModalProps) { const { t } = useTranslation(); return ( - +
| undefined, payload: Partial) => { + if (!workspaceSlug || !data?.project_id || !data?.id) return; + // return if user is not trying to change the cycle, i.e + // - cycle_id is not present in payload + // - cycle_id is the same as the current cycle id + if (!("cycle_id" in payload) || isEqual(data?.cycle_id, payload.cycle_id)) return; + + const slug = workspaceSlug.toString(); + + // Removing the cycle + const currentCycleId = data?.cycle_id; + if (currentCycleId && payload.cycle_id === null) { + await issues.removeIssueFromCycle(slug, data.project_id, currentCycleId, data.id); + fetchCycleDetails(slug, data.project_id, currentCycleId).catch((error) => { + console.error(error); + }); + } + + // Adding the cycle + const newCycleId = payload.cycle_id; + if (newCycleId && newCycleId !== "" && (payload.cycle_id !== cycleId || storeType !== EIssuesStoreType.CYCLE)) { + await addIssueToCycle(data as TBaseIssue, newCycleId); + } + }; + + const handleModuleChange = async (data: Partial, payload: Partial) => { + if (!workspaceSlug || !data?.project_id || !data?.id) return; + // return if user is not trying to change the module, i.e + // - module_ids is not present in payload + // - module_ids is not an array + // - module_ids is the same as the current module ids + if ( + !("module_ids" in payload) || + !Array.isArray(payload.module_ids) || + isEqual(data?.module_ids, payload.module_ids) + ) + return; + + const updatedModuleIds = xor(data.module_ids, payload.module_ids); + const modulesToAdd: string[] = []; + const modulesToRemove: string[] = []; + + for (const moduleId of updatedModuleIds) { + if (data.module_ids?.includes(moduleId)) { + modulesToRemove.push(moduleId); + } else { + modulesToAdd.push(moduleId); + } + } + // update modules if there are modules to add or remove + if (modulesToAdd.length > 0 || modulesToRemove.length > 0) { + await issues.changeModulesInIssue( + workspaceSlug.toString(), + data.project_id, + data.id, + modulesToAdd, + modulesToRemove + ); + } + }; + const handleUpdateIssue = async (payload: Partial): Promise => { if (!workspaceSlug || !payload.project_id || !data?.id) return; @@ -267,41 +328,10 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod if (isDraft) await draftIssues.updateIssue(workspaceSlug.toString(), data.id, payload); else if (updateIssue) await updateIssue(payload.project_id, data.id, payload); - // check if we should add/remove issue to/from cycle - if ( - payload.cycle_id && - payload.cycle_id !== "" && - (payload.cycle_id !== cycleId || storeType !== EIssuesStoreType.CYCLE) - ) { - await addIssueToCycle(data as TBaseIssue, payload.cycle_id); - } - if (data.cycle_id && !payload.cycle_id && data.project_id) { - await issues.removeIssueFromCycle(workspaceSlug.toString(), data.project_id, data.cycle_id, data.id); - fetchCycleDetails(workspaceSlug.toString(), data.project_id, data.cycle_id); - } - - if (data.module_ids && payload.module_ids && data.project_id) { - const updatedModuleIds = xor(data.module_ids, payload.module_ids); - const modulesToAdd: string[] = []; - const modulesToRemove: string[] = []; - - for (const moduleId of updatedModuleIds) { - if (data.module_ids.includes(moduleId)) { - modulesToRemove.push(moduleId); - } else { - modulesToAdd.push(moduleId); - } - } - await issues.changeModulesInIssue( - workspaceSlug.toString(), - data.project_id, - data.id, - modulesToAdd, - modulesToRemove - ); - } - - // add other property values + // Run cycle, module, and property changes sequentially to avoid + // optimistic store writes from racing against each other. + await handleCycleChange(data, payload); + await handleModuleChange(data, payload); await handleCreateUpdatePropertyValues({ issueId: data.id, issueTypeId: payload.type_id, diff --git a/deployments/aio/community/Dockerfile b/deployments/aio/community/Dockerfile index 47e1227d9..642c9bac6 100644 --- a/deployments/aio/community/Dockerfile +++ b/deployments/aio/community/Dockerfile @@ -6,12 +6,12 @@ FROM --platform=$BUILDPLATFORM tonistiigi/binfmt AS binfmt # ************************************************** FROM node:22-alpine AS node -FROM artifacts.plane.so/makeplane/plane-frontend:${PLANE_VERSION} AS web-img -FROM artifacts.plane.so/makeplane/plane-backend:${PLANE_VERSION} AS backend-img -FROM artifacts.plane.so/makeplane/plane-space:${PLANE_VERSION} AS space-img -FROM artifacts.plane.so/makeplane/plane-admin:${PLANE_VERSION} AS admin-img -FROM artifacts.plane.so/makeplane/plane-live:${PLANE_VERSION} AS live-img -FROM artifacts.plane.so/makeplane/plane-proxy:${PLANE_VERSION} AS proxy-img +FROM makeplane/plane-frontend:${PLANE_VERSION} AS web-img +FROM makeplane/plane-backend:${PLANE_VERSION} AS backend-img +FROM makeplane/plane-space:${PLANE_VERSION} AS space-img +FROM makeplane/plane-admin:${PLANE_VERSION} AS admin-img +FROM makeplane/plane-live:${PLANE_VERSION} AS live-img +FROM makeplane/plane-proxy:${PLANE_VERSION} AS proxy-img # ************************************************** # STAGE 1: Runner diff --git a/deployments/aio/community/README.md b/deployments/aio/community/README.md index 96aab6737..8e945a826 100644 --- a/deployments/aio/community/README.md +++ b/deployments/aio/community/README.md @@ -59,7 +59,7 @@ docker run --name plane-aio --rm -it \ -e AWS_ACCESS_KEY_ID=your-access-key \ -e AWS_SECRET_ACCESS_KEY=your-secret-key \ -e AWS_S3_BUCKET_NAME=your-bucket \ - artifacts.plane.so/makeplane/plane-aio-community:latest + makeplane/plane-aio-community:latest ``` ### Example with IP Address @@ -78,7 +78,7 @@ docker run --name myaio --rm -it \ -e AWS_S3_BUCKET_NAME=plane-app \ -e AWS_S3_ENDPOINT_URL=http://${MYIP}:19000 \ -e FILE_SIZE_LIMIT=10485760 \ - artifacts.plane.so/makeplane/plane-aio-community:latest + makeplane/plane-aio-community:latest ``` ## Configuration Options diff --git a/deployments/aio/community/supervisor.conf b/deployments/aio/community/supervisor.conf index 33b12985e..4474ee3b5 100644 --- a/deployments/aio/community/supervisor.conf +++ b/deployments/aio/community/supervisor.conf @@ -18,8 +18,8 @@ priority=10 [program:space] -directory=/app/space/apps/space/build/server -command=sh -c "npx react-router-serve index.js" +directory=/app/space/apps/space +command=sh -c "npx react-router-serve ./build/server/index.js" autostart=true autorestart=true stdout_logfile=/app/logs/access/space.log diff --git a/deployments/cli/community/docker-compose.yml b/deployments/cli/community/docker-compose.yml index d4de33a74..2ed44f037 100644 --- a/deployments/cli/community/docker-compose.yml +++ b/deployments/cli/community/docker-compose.yml @@ -61,7 +61,7 @@ x-app-env: &app-env services: web: - image: artifacts.plane.so/makeplane/plane-frontend:${APP_RELEASE:-stable} + image: makeplane/plane-frontend:${APP_RELEASE:-stable} deploy: replicas: ${WEB_REPLICAS:-1} restart_policy: @@ -71,7 +71,7 @@ services: - worker space: - image: artifacts.plane.so/makeplane/plane-space:${APP_RELEASE:-stable} + image: makeplane/plane-space:${APP_RELEASE:-stable} deploy: replicas: ${SPACE_REPLICAS:-1} restart_policy: @@ -82,7 +82,7 @@ services: - web admin: - image: artifacts.plane.so/makeplane/plane-admin:${APP_RELEASE:-stable} + image: makeplane/plane-admin:${APP_RELEASE:-stable} deploy: replicas: ${ADMIN_REPLICAS:-1} restart_policy: @@ -92,7 +92,7 @@ services: - web live: - image: artifacts.plane.so/makeplane/plane-live:${APP_RELEASE:-stable} + image: makeplane/plane-live:${APP_RELEASE:-stable} environment: <<: [*live-env, *redis-env] deploy: @@ -104,7 +104,7 @@ services: - web api: - image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-stable} + image: makeplane/plane-backend:${APP_RELEASE:-stable} command: ./bin/docker-entrypoint-api.sh deploy: replicas: ${API_REPLICAS:-1} @@ -120,7 +120,7 @@ services: - plane-mq worker: - image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-stable} + image: makeplane/plane-backend:${APP_RELEASE:-stable} command: ./bin/docker-entrypoint-worker.sh deploy: replicas: ${WORKER_REPLICAS:-1} @@ -137,7 +137,7 @@ services: - plane-mq beat-worker: - image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-stable} + image: makeplane/plane-backend:${APP_RELEASE:-stable} command: ./bin/docker-entrypoint-beat.sh deploy: replicas: ${BEAT_WORKER_REPLICAS:-1} @@ -154,7 +154,7 @@ services: - plane-mq migrator: - image: artifacts.plane.so/makeplane/plane-backend:${APP_RELEASE:-stable} + image: makeplane/plane-backend:${APP_RELEASE:-stable} command: ./bin/docker-entrypoint-migrator.sh deploy: replicas: 1 @@ -216,7 +216,7 @@ services: # Comment this if you already have a reverse proxy running proxy: - image: artifacts.plane.so/makeplane/plane-proxy:${APP_RELEASE:-stable} + image: makeplane/plane-proxy:${APP_RELEASE:-stable} deploy: replicas: 1 restart_policy: diff --git a/deployments/cli/community/install.sh b/deployments/cli/community/install.sh index 582aa8394..accd89c81 100755 --- a/deployments/cli/community/install.sh +++ b/deployments/cli/community/install.sh @@ -5,7 +5,7 @@ SCRIPT_DIR=$PWD SERVICE_FOLDER=plane-app PLANE_INSTALL_DIR=$PWD/$SERVICE_FOLDER export APP_RELEASE=stable -export DOCKERHUB_USER=artifacts.plane.so/makeplane +export DOCKERHUB_USER=makeplane export PULL_POLICY=${PULL_POLICY:-if_not_present} export GH_REPO=makeplane/plane export RELEASE_DOWNLOAD_URL="https://github.com/$GH_REPO/releases/download" @@ -690,7 +690,7 @@ if [ -f "$DOCKER_ENV_PATH" ]; then CUSTOM_BUILD=$(getEnvValue "CUSTOM_BUILD" "$DOCKER_ENV_PATH") if [ -z "$DOCKERHUB_USER" ]; then - DOCKERHUB_USER=artifacts.plane.so/makeplane + DOCKERHUB_USER=makeplane updateEnvFile "DOCKERHUB_USER" "$DOCKERHUB_USER" "$DOCKER_ENV_PATH" fi diff --git a/deployments/swarm/community/swarm.sh b/deployments/swarm/community/swarm.sh index d496c25e6..02f170f4d 100755 --- a/deployments/swarm/community/swarm.sh +++ b/deployments/swarm/community/swarm.sh @@ -5,7 +5,7 @@ SERVICE_FOLDER=plane-app SCRIPT_DIR=$PWD PLANE_INSTALL_DIR=$PWD/$SERVICE_FOLDER export APP_RELEASE="stable" -export DOCKERHUB_USER=artifacts.plane.so/makeplane +export DOCKERHUB_USER=makeplane export GH_REPO=makeplane/plane export RELEASE_DOWNLOAD_URL="https://github.com/$GH_REPO/releases/download" @@ -595,7 +595,7 @@ if [ -f "$DOCKER_ENV_PATH" ]; then APP_RELEASE=$(getEnvValue "APP_RELEASE" "$DOCKER_ENV_PATH") if [ -z "$DOCKERHUB_USER" ]; then - DOCKERHUB_USER=artifacts.plane.so/makeplane + DOCKERHUB_USER=makeplane updateEnvFile "DOCKERHUB_USER" "$DOCKERHUB_USER" "$DOCKER_ENV_PATH" fi diff --git a/package.json b/package.json index ffe45b22f..ec1fdbc00 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "lint-staged": "16.2.7", "oxfmt": "0.35.0", "oxlint": "1.51.0", - "turbo": "2.8.12" + "turbo": "2.9.4" }, "lint-staged": { "*.{js,jsx,ts,tsx,cjs,mjs,cts,mts,json,css,md}": [ @@ -75,7 +75,8 @@ "picomatch": "2.3.2", "yaml@1": "1.10.3", "yaml@2": "2.8.3", - "path-to-regexp": "0.1.13" + "path-to-regexp": "0.1.13", + "defu": "6.1.5" }, "onlyBuiltDependencies": [ "@parcel/watcher", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7929b2669..e048c47f8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,7 +103,7 @@ overrides: qs: 6.14.2 diff: 5.2.2 webpack: 5.104.1 - lodash-es: 4.17.23 + lodash-es: 4.18.0 '@isaacs/brace-expansion': 5.0.1 lodash: 4.17.23 markdown-it: 14.1.1 @@ -119,6 +119,7 @@ overrides: yaml@1: 1.10.3 yaml@2: 2.8.3 path-to-regexp: 0.1.13 + defu: 6.1.5 importers: @@ -137,8 +138,8 @@ importers: specifier: 1.51.0 version: 1.51.0 turbo: - specifier: 2.8.12 - version: 2.8.12 + specifier: 2.9.4 + version: 2.9.4 apps/admin: dependencies: @@ -194,8 +195,8 @@ importers: specifier: ^5.1.31 version: 5.1.31 lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 lucide-react: specifier: 'catalog:' version: 0.469.0(react@18.3.1) @@ -472,8 +473,8 @@ importers: specifier: ^5.1.31 version: 5.1.31 lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 lucide-react: specifier: 'catalog:' version: 0.469.0(react@18.3.1) @@ -644,8 +645,8 @@ importers: specifier: ^5.1.31 version: 5.1.31 lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 lucide-react: specifier: 'catalog:' version: 0.469.0(react@18.3.1) @@ -937,8 +938,8 @@ importers: specifier: ^4.3.2 version: 4.3.2 lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 lowlight: specifier: ^3.0.0 version: 3.3.0 @@ -1035,8 +1036,8 @@ importers: specifier: ^10.7.11 version: 10.7.16 lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 mobx: specifier: 'catalog:' version: 6.12.0 @@ -1216,8 +1217,8 @@ importers: specifier: workspace:* version: link:../utils lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 mobx: specifier: 'catalog:' version: 6.12.0 @@ -1332,8 +1333,8 @@ importers: specifier: ^2.0.0 version: 2.1.1 lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 lucide-react: specifier: 'catalog:' version: 0.469.0(react@18.3.1) @@ -1456,8 +1457,8 @@ importers: specifier: ^10.1.2 version: 10.1.2 lodash-es: - specifier: 4.17.23 - version: 4.17.23 + specifier: 4.18.0 + version: 4.18.0 lucide-react: specifier: 'catalog:' version: 0.469.0(react@18.3.1) @@ -4188,6 +4189,36 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@turbo/darwin-64@2.9.4': + resolution: {integrity: sha512-ZSlPqJ5Vqg/wgVw8P3AOVCIosnbBilOxLq7TMz3MN/9U46DUYfdG2jtfevNDufyxyrg98pcPs/GBgDRaaids6g==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.9.4': + resolution: {integrity: sha512-9cjTWe4OiNlFMSRggPNh+TJlRs7MS5FWrHc96MOzft5vESWjjpvaadYPv5ykDW7b45mVHOF2U/W+48LoX9USWw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.9.4': + resolution: {integrity: sha512-Cl1GjxqBXQ+r9KKowmXG+lhD1gclLp48/SE7NxL//66iaMytRw0uiphWGOkccD92iPiRjHLRUaA9lOTtgr5OCA==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.9.4': + resolution: {integrity: sha512-j2hPAKVmGNN2EsKigEWD+43y9m7zaPhNAs6ptsyfq0u7evHHBAXAwOfv86OEMg/gvC+pwGip0i1CIm1bR1vYug==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.9.4': + resolution: {integrity: sha512-1jWPjCe9ZRmsDTXE7uzqfySNQspnUx0g6caqvwps+k/sc+fm9hC/4zRQKlXZLbVmP3Xxp601Ju71boegHdnYGw==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.9.4': + resolution: {integrity: sha512-dlko15TQVu/BFYmIY018Y3covWMRQlUgAkD+OOk+Rokcfj6VY02Vv4mCfT/Zns6B4q8jGbOd6IZhnCFYsE8Viw==} + cpu: [arm64] + os: [win32] + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -5259,8 +5290,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + defu@6.1.5: + resolution: {integrity: sha512-pwdBJxJuJXmqrLO6s0VBmfbRz+G7FUzkjldAsdi9Yrv86mPyzq0ll1o8+8gB4Gsr6GJHbK1Lh3ngllgTInDCjA==} delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -6410,8 +6441,9 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lodash-es@4.17.23: - resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} + lodash-es@4.18.0: + resolution: {integrity: sha512-koAgswPPA+UTaPN64Etp+PGP+WT6oqOS2NMi5yDkMaiGw9qY4VxQbQF0mtKMyr4BlTznWyzePV5UpECTJQmSUA==} + deprecated: Bad release. Please use lodash-es@4.17.23 instead. lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} @@ -8222,38 +8254,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - turbo-darwin-64@2.8.12: - resolution: {integrity: sha512-EiHJmW2MeQQx+21x8hjMHw/uPhXt9PIxvDrxzOtyVwrXzL0tQmsxtO4qHf2l7uA+K6PUJ4+TjY1MHZDuCvWXrw==} - cpu: [x64] - os: [darwin] - - turbo-darwin-arm64@2.8.12: - resolution: {integrity: sha512-cbqqGN0vd7ly2TeuaM8k9AK9u1CABO4kBA5KPSqovTiLL3sORccn/mZzJSbvQf0EsYRfU34MgW5FotfwW3kx8Q==} - cpu: [arm64] - os: [darwin] - - turbo-linux-64@2.8.12: - resolution: {integrity: sha512-jXKw9j4r4q6s0goSXuKI3aKbQK2qiNeP25lGGEnq018TM6SWRW1CCpPMxyG91aCKrub7wDm/K45sGNT4ZFBcFQ==} - cpu: [x64] - os: [linux] - - turbo-linux-arm64@2.8.12: - resolution: {integrity: sha512-BRJCMdyXjyBoL0GYpvj9d2WNfMHwc3tKmJG5ATn2Efvil9LsiOsd/93/NxDqW0jACtHFNVOPnd/CBwXRPiRbwA==} - cpu: [arm64] - os: [linux] - - turbo-windows-64@2.8.12: - resolution: {integrity: sha512-vyFOlpFFzQFkikvSVhVkESEfzIopgs2J7J1rYvtSwSHQ4zmHxkC95Q8Kjkus8gg+8X2mZyP1GS5jirmaypGiPw==} - cpu: [x64] - os: [win32] - - turbo-windows-arm64@2.8.12: - resolution: {integrity: sha512-9nRnlw5DF0LkJClkIws1evaIF36dmmMEO84J5Uj4oQ8C0QTHwlH7DNe5Kq2Jdmu8GXESCNDNuUYG8Cx6W/vm3g==} - cpu: [arm64] - os: [win32] - - turbo@2.8.12: - resolution: {integrity: sha512-auUAMLmi0eJhxDhQrxzvuhfEbICnVt0CTiYQYY8WyRJ5nwCDZxD0JG8bCSxT4nusI2CwJzmZAay5BfF6LmK7Hw==} + turbo@2.9.4: + resolution: {integrity: sha512-wZ/kMcZCuK5oEp7sXSSo/5fzKjP9I2EhoiarZjyCm2Ixk0WxFrC/h0gF3686eHHINoFQOOSWgB/pGfvkR8rkgQ==} hasBin: true tween-functions@1.2.0: @@ -11367,6 +11369,24 @@ snapshots: '@tokenizer/token@0.3.0': {} + '@turbo/darwin-64@2.9.4': + optional: true + + '@turbo/darwin-arm64@2.9.4': + optional: true + + '@turbo/linux-64@2.9.4': + optional: true + + '@turbo/linux-arm64@2.9.4': + optional: true + + '@turbo/windows-64@2.9.4': + optional: true + + '@turbo/windows-arm64@2.9.4': + optional: true + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -12535,7 +12555,7 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - defu@6.1.4: {} + defu@6.1.5: {} delayed-stream@1.0.0: {} @@ -13753,7 +13773,7 @@ snapshots: dependencies: p-locate: 6.0.0 - lodash-es@4.17.23: {} + lodash-es@4.18.0: {} lodash.debounce@4.0.8: {} @@ -15021,7 +15041,7 @@ snapshots: dependencies: '@icons/material': 0.2.4(react@18.3.1) lodash: 4.17.23 - lodash-es: 4.17.23 + lodash-es: 4.18.0 material-colors: 1.2.6 prop-types: 15.8.1 react: 18.3.1 @@ -16027,32 +16047,14 @@ snapshots: tslib@2.8.1: {} - turbo-darwin-64@2.8.12: - optional: true - - turbo-darwin-arm64@2.8.12: - optional: true - - turbo-linux-64@2.8.12: - optional: true - - turbo-linux-arm64@2.8.12: - optional: true - - turbo-windows-64@2.8.12: - optional: true - - turbo-windows-arm64@2.8.12: - optional: true - - turbo@2.8.12: + turbo@2.9.4: optionalDependencies: - turbo-darwin-64: 2.8.12 - turbo-darwin-arm64: 2.8.12 - turbo-linux-64: 2.8.12 - turbo-linux-arm64: 2.8.12 - turbo-windows-64: 2.8.12 - turbo-windows-arm64: 2.8.12 + '@turbo/darwin-64': 2.9.4 + '@turbo/darwin-arm64': 2.9.4 + '@turbo/linux-64': 2.9.4 + '@turbo/linux-arm64': 2.9.4 + '@turbo/windows-64': 2.9.4 + '@turbo/windows-arm64': 2.9.4 tween-functions@1.2.0: {} @@ -16079,7 +16081,7 @@ snapshots: unconfig@7.5.0: dependencies: '@quansync/fs': 1.0.0 - defu: 6.1.4 + defu: 6.1.5 jiti: 2.6.1 quansync: 1.0.0 unconfig-core: 7.5.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 011d59c67..b20c3be19 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -21,7 +21,7 @@ catalog: "@types/react": 18.3.11 axios: 1.13.5 express: 4.22.0 - lodash-es: 4.17.23 + lodash-es: 4.18.0 lucide-react: 0.469.0 mobx-react: 9.1.1 mobx-utils: 6.0.8