Compare commits

...

10 commits

Author SHA1 Message Date
sriram veeraghanta
cf696d200d
release: v1.3.0 #8835 2026-04-06 20:00:08 +05:30
sriram veeraghanta
bb128e3e16
chore: upgrade turbo from v2.8.12 to v2.9.4 (#8859) 2026-04-06 16:04:57 +05:30
sriram veeraghanta
63fac3b8c4
fix: validate redirects in favicon fetching to prevent SSRF (#8858)
* fix: validate redirects in favicon fetching to prevent SSRF

The previous SSRF fix (GHSA-jcc6-f9v6-f7jw) only validated redirects for
the main page URL but not for the favicon fetch path. An attacker could
craft an HTML page with a favicon link that redirects to a private IP,
bypassing the IP validation and leaking internal network data as base64.

Extract a reusable `safe_get()` function that validates every redirect hop
against private/internal IPs and use it for both page and favicon fetches.

Resolves: GHSA-9fr2-pprw-pp9j

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: address PR review feedback for SSRF favicon fix

- Fix off-by-one in redirect limit: only raise RuntimeError when the
  response is still a redirect after MAX_REDIRECTS hops, not when the
  final response is a successful 200
- Return final URL from safe_get() so favicon href resolution uses the
  correct origin after redirects instead of the original URL
- Add unit tests for validate_url_ip and safe_get covering private IP
  blocking, redirect-following, and redirect limit enforcement

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 16:04:43 +05:30
sriram veeraghanta
587fe76032
fix: prevent privilege escalation in project member role updates (GHSA-494h-3rcq-5g3c) (#8833)
Restrict role modification in ProjectMemberViewSet.partial_update to
Admins only and enforce that requesters cannot modify or assign roles
equal to or higher than their own. Previously, Guests could demote
Admins by exploiting a missing lower-bound check on role changes.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 15:54:01 +05:30
Anmol Singh Bhatia
a18d90da86
[WEB-6813] fix: module not associated when accepting intake work items (#8839)
* fix: intake module association on accept

* chore: code refactoring
2026-03-31 23:39:34 +05:30
Akshat Jain
febf98ea54
[INFRA-351] fix: correct directory and command for space program in supervisor.conf #8838 2026-03-31 18:53:51 +05:30
sriramveeraghanta
5747dc6fd8 chore: Intake snooze modal width 2026-03-31 18:26:41 +05:30
Akshat Jain
d83944cc8d
[INFRA-346] chore: remove artifacts.plane.so references from community deployments (#8836) 2026-03-31 17:56:32 +05:30
sriramveeraghanta
799b9cbfc5 chore: adding traget commit sha for the github release 2026-03-31 17:54:47 +05:30
sriram veeraghanta
a01b51fca5
fix: scope IssueBulkUpdateDateEndpoint query to workspace and project (#8834)
The bulk update date endpoint fetched issues by ID without filtering
by workspace or project, allowing any authenticated project member to
modify start_date and target_date of issues in any workspace/project
across the entire instance (IDOR - CWE-639).

Scoped the query to include workspace__slug and project_id filters,
consistent with other issue endpoints in the codebase.

Ref: GHSA-4q54-h4x9-m329
2026-03-31 17:43:35 +05:30
20 changed files with 399 additions and 190 deletions

View file

@ -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

View file

@ -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 . .

View file

@ -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 = []

View file

@ -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)

View file

@ -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")

View file

@ -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

View file

@ -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

View file

@ -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 . .

View file

@ -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 . .

View file

@ -26,7 +26,13 @@ export function InboxIssueSnoozeModal(props: InboxIssueSnoozeModalProps) {
const { t } = useTranslation();
return (
<ModalCore isOpen={isOpen} handleClose={handleClose} position={EModalPosition.CENTER} width={EModalWidth.XXL}>
<ModalCore
isOpen={isOpen}
handleClose={handleClose}
position={EModalPosition.CENTER}
width={EModalWidth.SM}
className="w-auto"
>
<div className="flex h-full w-full flex-col gap-y-1 px-5 py-8 sm:p-6">
<Calendar
className="rounded-md border border-subtle p-3"

View file

@ -5,7 +5,7 @@
*/
import { useEffect, useRef, useState } from "react";
import { xor } from "lodash-es";
import { isEqual, xor } from "lodash-es";
import { observer } from "mobx-react";
import { useParams } from "next/navigation";
// Plane imports
@ -260,6 +260,67 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
}
};
const handleCycleChange = async (data: Partial<TIssue> | undefined, payload: Partial<TIssue>) => {
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<TIssue>, payload: Partial<TIssue>) => {
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<TIssue>): Promise<TIssue | undefined> => {
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,

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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",

170
pnpm-lock.yaml generated
View file

@ -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

View file

@ -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