Skip to content

Commit c1cb8ca

Browse files
fix(vscode-extension): unblock vsce release + theme bug
Release blockers caught by `vsce package`: - `@types/vscode 1.110.0` greater than `engines.vscode ^1.86.0` → vsce errors at package time. Pin types to `~1.86.0` to match the broadest installable base. - `LICENSE.txt` was 0 bytes; populate from repo LICENSE. - `CHANGELOG.md` was 0 bytes; delete (Marketplace will render an empty Changelog tab otherwise). User-visible bug: - `editor/src/index.tsx` called `setTheme("light")` unconditionally, forcing white background regardless of VS Code's actual theme. Replace JS-based theme application with CSS variables scoped under the `body.vscode-light` / `body.vscode-dark` / `.vscode-high-contrast` classes VS Code applies automatically to webviews. The editor now follows live theme switches without any JS. - Delete the dead `theme-switcher/` directory entirely (the `toggleTheme` helper had inverted logic and was never called). Cleanup the round-3 audit caught: - Drop dead deps: `@vscode/codicons` (both webviews, 0 references), `uuid` (replaced with `crypto.randomUUID()` / `node:crypto` randomUUID — VS Code's bundled Chromium 92+ and Node 24 both support it natively), `@types/uuid` (both workspaces). - `vscode-extension/menu/package.json`: add `@tumaet/apollon` to dependencies — `App.tsx` imports `UMLDiagramType` and was relying on pnpm hoisting. - `vscode-extension/src/types.ts`: replace hand-rolled `UMLDiagramType` / `UMLModel` with imports from `@tumaet/apollon`. The duplicate was already drifting (v4-pinned `version` template literal at library v5). - `editor/tsconfig.json` / `menu/tsconfig.json`: drop dead cross- package includes (`../src/util.ts`, `../editor/src/types.ts`). - Root `tsconfig.json`: `noEmit: true`, drop stale `outDir: "out"`, exclude `dist` + `.vscode-test.mjs` + `vite.config.ts`. - `.vscodeignore`: drop dead `.vscode-test-web/**`, `dist/test/**`, `.yarnrc`, `vsc-extension-quickstart.md`, `**/.eslintrc.json`, `../**`. Add `eslint.config.mjs`, `*.tsbuildinfo`. - `.gitignore`: drop stale `out` (Vite writes to `dist/`). - `release-vscode-extension.yml`: backfill race — when `publish` is skipped (Marketplace already has this version) but the GitHub Release tag is missing, the `release` job was gated on `publish.result == 'success'` only. Accept `'skipped'` too. Bundle effect: `dist/extension.js` smaller (uuid package gone), editor + menu drop `@vscode/codicons` weight too.
1 parent 7c731a8 commit c1cb8ca

33 files changed

Lines changed: 334 additions & 272 deletions

.editorconfig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
indent_style = space
7+
indent_size = 2
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true
10+
11+
[*.md]
12+
trim_trailing_whitespace = false
13+
14+
[Makefile]
15+
indent_style = tab

.github/workflows/release-vscode-extension.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,14 @@ jobs:
191191
release:
192192
name: Tag + GitHub Release
193193
needs: [check, build, publish]
194+
# Backfill case: marketplace already has this version (publish skipped)
195+
# but the GitHub Release tag is missing. We still need to cut the
196+
# Release. Accept publish.result in {success, skipped}.
194197
if: |
195198
!cancelled() && !inputs.dry_run &&
196199
needs.check.outputs.release == 'true' &&
197200
needs.build.result == 'success' &&
198-
needs.publish.result == 'success'
201+
(needs.publish.result == 'success' || needs.publish.result == 'skipped')
199202
runs-on: ubuntu-latest
200203
timeout-minutes: 5
201204
permissions:

SECURITY.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Security Policy
2+
3+
## Reporting a vulnerability
4+
5+
Please report security issues privately via [GitHub Security Advisories](https://github.com/ls1intum/Apollon/security/advisories/new). Do not open public GitHub issues for vulnerabilities.
6+
7+
We aim to acknowledge reports within 5 working days and to coordinate a fix and disclosure timeline with you.
8+
9+
## Supported versions
10+
11+
Only the latest minor of each major is supported with security fixes.
12+
13+
| Package | Supported |
14+
| --------------------------------------------------- | ---------------- |
15+
| `@tumaet/apollon` (npm) | `5.x` |
16+
| Standalone Docker images (server + webapp) | latest `vX.Y.Z` |
17+
| `tumaet.apollon-vscode` (VS Marketplace / Open VSX) | latest published |
18+
19+
Older majors are end-of-life and will not receive backports.
20+
21+
## Scope
22+
23+
In scope:
24+
25+
- `@tumaet/apollon` library code and its public API.
26+
- The standalone server (`standalone/server`) and webapp (`standalone/webapp`).
27+
- The VS Code extension (`vscode-extension`).
28+
29+
Out of scope:
30+
31+
- Third-party services the deployment connects to (e.g., Artemis instances).
32+
- Self-XSS that requires a privileged user to paste an attacker-controlled payload into a diagram body inside the same browser session.

library/CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Changelog
2+
3+
All notable changes to `@tumaet/apollon` will be documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4+
5+
GitHub Releases at <https://github.com/ls1intum/Apollon/releases> carry the full per-release notes; this file tracks breaking-change summaries.
6+
7+
## [5.0.0]
8+
9+
### Breaking
10+
11+
- **Dropped the `@tumaet/apollon/react` subpath** and the standalone 2.4 MB bundle that inlined React/MUI/emotion/xyflow. The default `@tumaet/apollon` import now always externalizes those packages as **mandatory peer dependencies**.
12+
- **`peerDependenciesMeta` removed** — peers are no longer optional. Install React, ReactDOM, `@mui/material`, `@emotion/react`, `@emotion/styled`, and `@xyflow/react` explicitly.
13+
- **Internal sync exports moved** to a new `@tumaet/apollon/internals` subpath (`YjsSyncClass`, `MessageType`, `createHeadlessSync`). The new subpath is explicitly **not** covered by SemVer.
14+
15+
### Migration from 4.x
16+
17+
```diff
18+
- import { ApollonEditor } from "@tumaet/apollon/react"
19+
+ import { ApollonEditor } from "@tumaet/apollon"
20+
```
21+
22+
```sh
23+
npm install @tumaet/apollon@5 \
24+
react react-dom \
25+
@emotion/react @emotion/styled @mui/material @xyflow/react
26+
```
27+
28+
Standalone (non-React) consumers who previously imported from `@tumaet/apollon` must now install the React peers as well. See `README.md`.
29+
30+
### Other
31+
32+
- Single Vite pass (`tsc -b && vite build`); the prior dual-build + `rm -rf dist/react/assets` hack is gone.
33+
- `dist/index.d.ts` is rolled up into a single self-contained file (no internal relative imports without `.js` suffix). NodeNext consumers resolve cleanly.
34+
- esbuild `drop` narrowed to `["debugger"]``console.warn` calls from React/MUI/emotion are no longer stripped at minification.
35+
- README rewritten: ~73 lines, React-only, one factual install command.
36+
37+
[5.0.0]: https://github.com/ls1intum/Apollon/releases

library/lib/utils/quadrantUtils.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { Quadrant } from "@/enums"
22
import { XYPosition } from "@xyflow/react"
33

4+
// Tiebreak on axis equality: `>=` on both axes means a target sitting
5+
// exactly on the reference line falls into the bottom-right quadrant.
46
export const getQuadrant = (
57
target: XYPosition,
68
reference: XYPosition
79
): Quadrant => {
8-
if (target.x > reference.x && target.y > reference.y) {
9-
return Quadrant.BottomRight
10-
} else if (target.x < reference.x && target.y > reference.y) {
11-
return Quadrant.BottomLeft
12-
} else if (target.x > reference.x && target.y < reference.y) {
13-
return Quadrant.TopRight
14-
} else {
15-
return Quadrant.TopLeft
16-
}
10+
const right = target.x >= reference.x
11+
const bottom = target.y >= reference.y
12+
if (right && bottom) return Quadrant.BottomRight
13+
if (!right && bottom) return Quadrant.BottomLeft
14+
if (right && !bottom) return Quadrant.TopRight
15+
return Quadrant.TopLeft
1716
}

library/lib/utils/storeUtils.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,32 @@
1-
export const deepEqual = <T>(a: T, b: T): boolean => {
2-
return JSON.stringify(a) === JSON.stringify(b)
1+
// Structural deep-equal. Used by `diagramStore` to short-circuit nodes/edges
2+
// replays when no semantic change occurred. Tolerant of key-order differences
3+
// and treats `{a: undefined}` and `{}` as equal (matching the JSON wire format
4+
// the editor round-trips through).
5+
export const deepEqual = (a: unknown, b: unknown): boolean => {
6+
if (Object.is(a, b)) return true
7+
if (
8+
typeof a !== "object" ||
9+
typeof b !== "object" ||
10+
a === null ||
11+
b === null
12+
) {
13+
return false
14+
}
15+
if (Array.isArray(a)) {
16+
if (!Array.isArray(b) || a.length !== b.length) return false
17+
for (let i = 0; i < a.length; i++) {
18+
if (!deepEqual(a[i], b[i])) return false
19+
}
20+
return true
21+
}
22+
if (Array.isArray(b)) return false
23+
const aObj = a as Record<string, unknown>
24+
const bObj = b as Record<string, unknown>
25+
const keys = new Set<string>()
26+
for (const k of Object.keys(aObj)) if (aObj[k] !== undefined) keys.add(k)
27+
for (const k of Object.keys(bObj)) if (bObj[k] !== undefined) keys.add(k)
28+
for (const k of keys) {
29+
if (!deepEqual(aObj[k], bObj[k])) return false
30+
}
31+
return true
332
}

library/tests/unit/quadrantUtils.test.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,25 @@ describe("getQuadrant", () => {
2121
expect(getQuadrant({ x: 50, y: 50 }, ref)).toBe(Quadrant.TopLeft)
2222
})
2323

24-
// Boundary: same x or same y → falls into else (TopLeft)
25-
it("returns TopLeft when x equals reference x and y < ref.y", () => {
26-
expect(getQuadrant({ x: 100, y: 50 }, ref)).toBe(Quadrant.TopLeft)
24+
// Axis-equality tiebreak: `>=` on both axes lands at BottomRight.
25+
it("returns TopRight when x equals reference x and y < ref.y", () => {
26+
expect(getQuadrant({ x: 100, y: 50 }, ref)).toBe(Quadrant.TopRight)
2727
})
2828

29-
it("returns TopLeft when y equals reference y and x < ref.x", () => {
30-
expect(getQuadrant({ x: 50, y: 100 }, ref)).toBe(Quadrant.TopLeft)
29+
it("returns BottomLeft when y equals reference y and x < ref.x", () => {
30+
expect(getQuadrant({ x: 50, y: 100 }, ref)).toBe(Quadrant.BottomLeft)
3131
})
3232

33-
it("returns TopLeft when both x and y equal reference", () => {
34-
expect(getQuadrant({ x: 100, y: 100 }, ref)).toBe(Quadrant.TopLeft)
33+
it("returns BottomRight when target sits exactly on the reference", () => {
34+
expect(getQuadrant({ x: 100, y: 100 }, ref)).toBe(Quadrant.BottomRight)
3535
})
3636

37-
it("returns TopLeft when x equals reference x and y > ref.y", () => {
38-
// x == ref.x → first two conditions fail, third fails → else → TopLeft
39-
expect(getQuadrant({ x: 100, y: 200 }, ref)).toBe(Quadrant.TopLeft)
37+
it("returns BottomRight when x equals reference x and y > ref.y", () => {
38+
expect(getQuadrant({ x: 100, y: 200 }, ref)).toBe(Quadrant.BottomRight)
4039
})
4140

42-
it("returns TopLeft when y equals reference y and x > ref.x", () => {
43-
// y == ref.y → all conditions that check y fail → else → TopLeft
44-
expect(getQuadrant({ x: 200, y: 100 }, ref)).toBe(Quadrant.TopLeft)
41+
it("returns BottomRight when y equals reference y and x > ref.x", () => {
42+
expect(getQuadrant({ x: 200, y: 100 }, ref)).toBe(Quadrant.BottomRight)
4543
})
4644

4745
it("works with negative coordinates", () => {
@@ -60,12 +58,6 @@ describe("getQuadrant", () => {
6058
)
6159
})
6260

63-
it("works with large coordinate values", () => {
64-
expect(getQuadrant({ x: 999999, y: 999999 }, { x: 0, y: 0 })).toBe(
65-
Quadrant.BottomRight
66-
)
67-
})
68-
6961
it("works with floating point coordinates", () => {
7062
expect(getQuadrant({ x: 100.001, y: 100.001 }, ref)).toBe(
7163
Quadrant.BottomRight

library/tests/unit/storeUtils.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,19 @@ describe("deepEqual", () => {
9696
const b = { nodes: [{ id: "1", pos: { x: 0, y: 0 } }], edges: [] }
9797
expect(deepEqual(a, b)).toBe(true)
9898
})
99+
100+
it("treats key insertion order as semantically irrelevant", () => {
101+
expect(deepEqual({ x: 1, y: 2 }, { y: 2, x: 1 })).toBe(true)
102+
})
103+
104+
it("treats explicit undefined as equal to missing property", () => {
105+
expect(deepEqual({ a: 1, b: undefined }, { a: 1 })).toBe(true)
106+
expect(deepEqual({ a: 1 }, { a: 1, b: undefined })).toBe(true)
107+
})
108+
109+
it("returns false for primitive vs object", () => {
110+
expect(deepEqual({}, null)).toBe(false)
111+
expect(deepEqual(null, {})).toBe(false)
112+
expect(deepEqual([1], 1)).toBe(false)
113+
})
99114
})

library/vite.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ export default defineConfig({
4242
},
4343
},
4444
minify: true,
45-
commonjsOptions: { include: [/node_modules/] },
4645
},
4746
resolve: {
4847
alias: { "@": resolve(__dirname, "lib") },

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"version": "1.0.0",
44
"description": "Apollon monorepo — UML modeling editor (library, webapp, server, VS Code extension).",
55
"private": true,
6-
"packageManager": "pnpm@11.1.3",
6+
"packageManager": "pnpm@11.1.3+sha512.c85357fe17ca12dd23dd7071822666dfd7e3cb76fe214e3370b5ea2fb34f2a231185509b63e717f3cd0acb38dd3f8d82bcd5e8172400ae678b70ea4fbed0896d",
77
"scripts": {
88
"build": "pnpm run build:lib && concurrently \"pnpm run build:server\" \"pnpm run build:webapp\"",
99
"build:lib": "pnpm --filter @tumaet/apollon run build",

0 commit comments

Comments
 (0)