mirror of
https://github.com/penpot/penpot.git
synced 2026-08-27 15:19:49 +00:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
17befc1db9
@ -212,6 +212,37 @@ superseded it:
|
||||
|
||||
Replace the reference in the changelog entry with the correct merged PR number.
|
||||
|
||||
### 5b. Security advisory (GHSA) entries
|
||||
|
||||
Security advisories fixed in a release are documented in the changelog even
|
||||
though they are **neither milestone issues nor PRs**. The GHSA ID and its
|
||||
description are supplied by the user or the release notes — they never come
|
||||
from the milestone fetch in step 2.
|
||||
|
||||
**Format** (matches the existing precedent in `CHANGES.md`, e.g. the
|
||||
`create-font-variant` arbitrary file read advisory):
|
||||
|
||||
```markdown
|
||||
- Fix <user-facing description> (https://github.com/penpot/penpot/security/advisories/GHSA-XXXX-XXXX-XXXX)
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Place the entry under `### :bug: Bugs fixed`, with **no issue or PR link** —
|
||||
only the advisory URL.
|
||||
- The advisory may be **draft/unpublished** at changelog time (the URL 404s
|
||||
publicly). Do **not** web-fetch or verify the URL, and do **not** drop the
|
||||
entry because of that. Rely on the GHSA ID provided by the user.
|
||||
- Derive the description from the supplied advisory title, imperative mood and
|
||||
user-facing (e.g. `Fix command injection in SVG exporter via legacy fill-color`).
|
||||
- These entries are **invisible to the automation**: they are not returned by
|
||||
`gh.py issues`, not matched by `--compare` (step 3), not part of the PR
|
||||
cross-reference (step 10), and not scanned by the anomaly-report regexes
|
||||
(step 11, which only match `issues/` and `pull/` links). Add them manually.
|
||||
- During pre-flight checks (step 6a) apply only the **backport/duplicate**
|
||||
check: if the same GHSA already appears in an earlier version section, remove
|
||||
it from the current section. Their absence from milestone cross-references
|
||||
is expected, not an anomaly.
|
||||
|
||||
### 6. Read the current CHANGES.md
|
||||
|
||||
Read the top of `CHANGES.md` to understand the existing format and find the
|
||||
@ -400,6 +431,8 @@ if closed:
|
||||
- ✅ Every merged milestone PR is either in the changelog or excluded by label
|
||||
- ✅ PR and issue counts are internally consistent
|
||||
- ✅ No false-positive PR-to-issue associations
|
||||
- ✅ Advisory (GHSA) entries are not milestone PRs — their absence from the
|
||||
cross-reference is intentional (see step 5b)
|
||||
|
||||
## Version section template
|
||||
|
||||
@ -410,8 +443,12 @@ if closed:
|
||||
|
||||
- <fix description> [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>))
|
||||
- <fix description> (by @contributor) [#<ISSUE>](https://github.com/penpot/penpot/issues/<ISSUE>) (PR: [#<PR>](https://github.com/penpot/penpot/pull/<PR>))
|
||||
- <fix description> (https://github.com/penpot/penpot/security/advisories/GHSA-XXXX-XXXX-XXXX)
|
||||
```
|
||||
|
||||
Advisory (GHSA) entries have no issue or PR link — just the advisory URL. See
|
||||
step 5b.
|
||||
|
||||
### 11. Generate anomaly report and save to CHANGES-ISSUES.md
|
||||
|
||||
After all edits and cross-referencing are complete, generate a structured
|
||||
@ -732,6 +769,14 @@ self-contained and clickable in any Markdown viewer.
|
||||
Taiga description text or by searching GitHub PRs that reference the Taiga
|
||||
URL. Replace the Taiga reference with the GitHub issue link and add the PR
|
||||
reference if applicable.
|
||||
- **Security advisory (GHSA) entries.** Advisories fixed in the release are
|
||||
listed under `### :bug: Bugs fixed` with the advisory URL and **no issue or
|
||||
PR link**, even though they are not in the milestone. The GHSA ID and
|
||||
description come from the user — do **not** fetch or verify the URL, and do
|
||||
not drop a draft (unpublished) advisory. Precedent:
|
||||
`- Fix arbitrary file read security issue on create-font-variant rpc method
|
||||
(https://github.com/penpot/penpot/security/advisories/GHSA-xp3f-g8rq-9px2)`.
|
||||
See step 5b.
|
||||
- **Re-fetch before editing.** Milestones can change — always re-fetch issues
|
||||
before making edits, don't rely on cached data.
|
||||
- **Use `scripts/gh.py`.** Prefer the helper script over raw `gh api` calls for
|
||||
|
||||
@ -23,6 +23,13 @@
|
||||
- Refactor wasm rulers and UI state [#10116](https://github.com/penpot/penpot/issues/10116) (PR: [#10461](https://github.com/penpot/penpot/pull/10461))
|
||||
- Improve team invitations modal in the dashboard [#10484](https://github.com/penpot/penpot/issues/10484) (PR: [#10459](https://github.com/penpot/penpot/pull/10459))
|
||||
|
||||
## 2.17.2
|
||||
|
||||
### :bug: Bugs fixed
|
||||
|
||||
- Fix linear gradients in SVG text exports being emitted as radial gradients [#5972](https://github.com/penpot/penpot/issues/5972) (PR: [#11272](https://github.com/penpot/penpot/pull/11272))
|
||||
- Fix typography token becoming detached when editing text content [#11362](https://github.com/penpot/penpot/issues/11362) (PR: [#11366](https://github.com/penpot/penpot/pull/11366))
|
||||
- Fix command injection in SVG exporter via legacy fill-color (https://github.com/penpot/penpot/security/advisories/GHSA-4f36-m4hj-cv86)
|
||||
|
||||
## 2.17.1
|
||||
|
||||
|
||||
@ -1978,6 +1978,26 @@
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(defmethod migrate-data "0026-fix-svg-raw-shapes-uuids"
|
||||
;; Before the svg-raw schema declared :shapes as a vector of uuid,
|
||||
;; the JSON decoder had no type information for those child ids and
|
||||
;; left them as plain strings on any round trip, so they got
|
||||
;; persisted as strings. Once the schema was tightened, such files
|
||||
;; fail schema validation; this migration parses the strings back
|
||||
;; into uuid instances.
|
||||
[data _]
|
||||
(letfn [(update-object [object]
|
||||
(cond-> object
|
||||
(cfh/svg-raw-shape? object)
|
||||
(d/update-when :shapes #(mapv uuid/coerce %))))
|
||||
|
||||
(update-container [container]
|
||||
(d/update-when container :objects d/update-vals update-object))]
|
||||
|
||||
(-> data
|
||||
(update :pages-index d/update-vals update-container)
|
||||
(d/update-when :components d/update-vals update-container))))
|
||||
|
||||
(def available-migrations
|
||||
(into (d/ordered-set)
|
||||
["legacy-2"
|
||||
@ -2060,4 +2080,5 @@
|
||||
"0022-normalize-component-root-and-resync"
|
||||
"0023-repair-token-themes-with-inexistent-sets"
|
||||
"0024b-fix-stroke-cap-placement"
|
||||
"0025-repair-empty-text-content"]))
|
||||
"0025-repair-empty-text-content"
|
||||
"0026-fix-svg-raw-shapes-uuids"]))
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.flags :as flags]
|
||||
[app.common.math :as mth]
|
||||
[app.common.types.color :as clr]
|
||||
[app.common.types.fills :as types.fills]
|
||||
[clojure.set :as set]
|
||||
@ -217,7 +218,10 @@
|
||||
attributes or other things that may be attached).
|
||||
- Consider nil values, empty strings or empty lists all equal.
|
||||
- Normalize numeric values (legacy) into strings.
|
||||
- No value is equal than the default value."
|
||||
- No value is equal than the default value.
|
||||
- Numeric attrs (e.g. line-height) compare with float tolerance so
|
||||
editor/WASM round-trips like \"1.3333333333333333\" vs \"1.33333\"
|
||||
do not count as a real style change (avoids detaching tokens)."
|
||||
[key value1 value2]
|
||||
(when (text-node-attr? key)
|
||||
(let [default-value (get default-text-attrs key)
|
||||
@ -229,7 +233,16 @@
|
||||
$)))
|
||||
value1' (normalize-value value1)
|
||||
value2' (normalize-value value2)]
|
||||
(not= value1' value2'))))
|
||||
(cond
|
||||
(= value1' value2')
|
||||
false
|
||||
|
||||
:else
|
||||
(let [n1 (when (string? value1') (d/parse-double value1'))
|
||||
n2 (when (string? value2') (d/parse-double value2'))]
|
||||
(if (and (some? n1) (some? n2))
|
||||
(not (mth/close? n1 n2))
|
||||
true))))))
|
||||
|
||||
(defn- compare-text-content
|
||||
"Given two content text structures, conformed by maps and vectors,
|
||||
|
||||
110
common/test/common_tests/files_migrations_0026_test.cljc
Normal file
110
common/test/common_tests/files_migrations_0026_test.cljc
Normal file
@ -0,0 +1,110 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS INC Sucursal en España SL
|
||||
|
||||
(ns common-tests.files-migrations-0026-test
|
||||
(:require
|
||||
[app.common.files.migrations :as cfm]
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]))
|
||||
|
||||
;; 0026-fix-svg-raw-shapes-uuids
|
||||
;; Before the svg-raw schema declared :shapes as a vector of uuid, the
|
||||
;; JSON decoder had no type information for those child ids and left
|
||||
;; them as plain strings on any round trip, so they got persisted as
|
||||
;; strings. Once the schema was tightened, such files fail schema
|
||||
;; validation; this migration parses the strings back into uuids.
|
||||
|
||||
(defn- make-svg-raw-shape
|
||||
"Build a minimal svg-raw shape with the supplied :shapes vector.
|
||||
When `shapes` is nil the :shapes key is omitted, like a leaf svg-raw
|
||||
shape."
|
||||
[shape-id shapes]
|
||||
(cond-> {:id shape-id
|
||||
:type :svg-raw}
|
||||
(some? shapes)
|
||||
(assoc :shapes shapes)))
|
||||
|
||||
(defn- make-other-shape
|
||||
"Build a minimal non-svg-raw shape that must stay untouched."
|
||||
[shape-id shapes]
|
||||
{:id shape-id
|
||||
:type :group
|
||||
:shapes shapes})
|
||||
|
||||
(t/deftest migration-0026-converts-svg-raw-shapes-strings-to-uuids-in-pages
|
||||
(let [shape-id (uuid/next)
|
||||
child-id (uuid/next)
|
||||
page-id (uuid/next)
|
||||
data {:pages-index
|
||||
{page-id
|
||||
{:objects
|
||||
{shape-id (make-svg-raw-shape
|
||||
shape-id
|
||||
[(str child-id)
|
||||
"1c2986ce-4a0f-8001-8007-1fb8f3b5ab31"])}}}}
|
||||
data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids")
|
||||
shape (get-in data' [:pages-index page-id :objects shape-id])]
|
||||
|
||||
(t/is (= 2 (count (:shapes shape))) "child ids preserved")
|
||||
(t/is (= child-id (first (:shapes shape))) "existing uuid string parsed to uuid")
|
||||
(t/is (= #uuid "1c2986ce-4a0f-8001-8007-1fb8f3b5ab31" (second (:shapes shape)))
|
||||
"foreign uuid string parsed to uuid")
|
||||
(t/is (every? uuid? (:shapes shape)) "all child ids are uuids")))
|
||||
|
||||
(t/deftest migration-0026-converts-svg-raw-shapes-strings-to-uuids-in-components
|
||||
(let [shape-id (uuid/next)
|
||||
child-id (uuid/next)
|
||||
component-id (uuid/next)
|
||||
data {:components
|
||||
{component-id
|
||||
{:objects
|
||||
{shape-id (make-svg-raw-shape
|
||||
shape-id
|
||||
[(str child-id)
|
||||
"1c2986ce-4a0f-8001-8007-1fb92196e65f"])}}}}
|
||||
data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids")
|
||||
shape (get-in data' [:components component-id :objects shape-id])]
|
||||
|
||||
(t/is (= 2 (count (:shapes shape))) "child ids preserved")
|
||||
(t/is (= child-id (first (:shapes shape))) "existing uuid string parsed to uuid")
|
||||
(t/is (= #uuid "1c2986ce-4a0f-8001-8007-1fb92196e65f" (second (:shapes shape)))
|
||||
"foreign uuid string parsed to uuid")
|
||||
(t/is (every? uuid? (:shapes shape)) "all child ids are uuids")))
|
||||
|
||||
(t/deftest migration-0026-leaves-uuids-and-other-shapes-untouched
|
||||
(let [svg-raw-id (uuid/next)
|
||||
child-id (uuid/next)
|
||||
group-id (uuid/next)
|
||||
leaf-id (uuid/next)
|
||||
page-id (uuid/next)
|
||||
data {:pages-index
|
||||
{page-id
|
||||
{:objects
|
||||
{svg-raw-id (make-svg-raw-shape svg-raw-id [child-id])
|
||||
group-id (make-other-shape group-id [(str child-id)])
|
||||
leaf-id (make-svg-raw-shape leaf-id nil)}}}}
|
||||
data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids")
|
||||
objects (get-in data' [:pages-index page-id :objects])]
|
||||
|
||||
(t/is (= [child-id] (:shapes (get objects svg-raw-id)))
|
||||
"already-uuid svg-raw children untouched")
|
||||
(t/is (= [(str child-id)] (:shapes (get objects group-id)))
|
||||
"non-svg-raw shapes untouched")
|
||||
(t/is (nil? (:shapes (get objects leaf-id)))
|
||||
"svg-raw leaf without :shapes untouched")))
|
||||
|
||||
(t/deftest migration-0026-is-idempotent
|
||||
(let [shape-id (uuid/next)
|
||||
child-id (uuid/next)
|
||||
page-id (uuid/next)
|
||||
data {:pages-index
|
||||
{page-id
|
||||
{:objects
|
||||
{shape-id (make-svg-raw-shape shape-id [(str child-id)])}}}}
|
||||
data' (cfm/migrate-data data "0026-fix-svg-raw-shapes-uuids")
|
||||
data'' (cfm/migrate-data data' "0026-fix-svg-raw-shapes-uuids")]
|
||||
|
||||
(t/is (= data' data'') "second run is a no-op")))
|
||||
@ -19,6 +19,7 @@
|
||||
[common-tests.files-builder-test]
|
||||
[common-tests.files-changes-test]
|
||||
[common-tests.files-migrations-0025-test]
|
||||
[common-tests.files-migrations-0026-test]
|
||||
[common-tests.files-migrations-test]
|
||||
[common-tests.files.shapes-builder-test]
|
||||
[common-tests.files.validate-test]
|
||||
@ -97,6 +98,7 @@
|
||||
'common-tests.files-changes-test
|
||||
'common-tests.files-builder-test
|
||||
'common-tests.files-migrations-0025-test
|
||||
'common-tests.files-migrations-0026-test
|
||||
'common-tests.files-migrations-test
|
||||
'common-tests.files.validate-test
|
||||
'common-tests.geom-align-test
|
||||
|
||||
@ -78,6 +78,14 @@
|
||||
(def content-changed-line-height
|
||||
(assoc-in content-base [:children 0 :children 0 :line-height] "1.5"))
|
||||
|
||||
;; Token/WASM may store full float precision; editor round-trips often
|
||||
;; truncate (e.g. CSS / f32). These must compare as equal.
|
||||
(def content-line-height-full-precision
|
||||
(assoc-in content-base [:children 0 :children 0 :line-height] "1.3333333333333333"))
|
||||
|
||||
(def content-line-height-truncated
|
||||
(assoc-in content-base [:children 0 :children 0 :line-height] "1.33333"))
|
||||
|
||||
(def content-redundant-span-line-height
|
||||
(assoc-in content-base [:children 0 :children 0 :children 0 :line-height] "1.5"))
|
||||
|
||||
@ -208,6 +216,8 @@
|
||||
;; Other text-node-attr categories
|
||||
attrs-font-family (cttx/get-diff-attrs content-base content-changed-font-family)
|
||||
attrs-line-height (cttx/get-diff-attrs content-base content-changed-line-height)
|
||||
attrs-line-height-precision (cttx/get-diff-attrs content-line-height-full-precision
|
||||
content-line-height-truncated)
|
||||
attrs-span-line-height (cttx/get-diff-attrs content-base content-redundant-span-line-height)
|
||||
attrs-roundtrip-line-height (cttx/get-diff-attrs content-token-like-line-height
|
||||
content-after-editor-roundtrip)
|
||||
@ -242,6 +252,7 @@
|
||||
;; Each text-node-attr category reports correct attr key
|
||||
(t/is (= #{:font-family} attrs-font-family))
|
||||
(t/is (= #{:line-height} attrs-line-height))
|
||||
(t/is (= #{} attrs-line-height-precision))
|
||||
(t/is (= #{} attrs-span-line-height))
|
||||
(t/is (= #{} attrs-roundtrip-line-height))
|
||||
(t/is (= #{} attrs-nil-typography-refs))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user