Skip to content

Commit 4d00971

Browse files
authored
Merge branch 'main' into dependabot/github_actions/codecov/codecov-action-6
2 parents 9c405ee + 4f45e0a commit 4d00971

82 files changed

Lines changed: 4106 additions & 1101 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
5353
- name: Upload coverage report
5454
if: always()
55-
uses: actions/upload-artifact@v6
55+
uses: actions/upload-artifact@v7
5656
with:
5757
name: snapcraftio-coverage
5858
path: coverage

package.json

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
"@babel/preset-react": "7.28.5",
2727
"@babel/preset-typescript": "7.28.5",
2828
"@canonical/analytics-events": "1.0.5",
29-
"@canonical/cookie-policy": "3.8.4",
29+
"@canonical/cookie-policy": "3.10.0",
3030
"@canonical/global-nav": "3.8.0",
31-
"@canonical/react-components": "3.12.1",
31+
"@canonical/react-components": "4.5.1",
3232
"@canonical/store-components": "0.55.0",
3333
"@dnd-kit/core": "6.3.1",
3434
"@dnd-kit/sortable": "10.0.0",
@@ -63,7 +63,7 @@
6363
"jotai": "2.17.1",
6464
"jotai-family": "1.0.1",
6565
"nanoid": "5.1.6",
66-
"postcss": "8.5.6",
66+
"postcss": "8.5.10",
6767
"postcss-cli": "11.0.1",
6868
"prettier": "3.8.1",
6969
"prop-types": "15.8.1",
@@ -86,21 +86,21 @@
8686
"tinyglobby": "0.2.15",
8787
"topojson-client": "3.1.0",
8888
"typescript": "5.9.3",
89-
"uuid": "13.0.0",
90-
"vanilla-framework": "4.44.0",
91-
"vite": "7.3.1"
89+
"uuid": "14.0.0",
90+
"vanilla-framework": "4.50.0",
91+
"vite": "7.3.2"
9292
},
9393
"devDependencies": {
9494
"@babel/eslint-parser": "7.28.6",
95-
"@percy/cli": "1.31.8",
95+
"@percy/cli": "1.31.10",
9696
"@types/cypress": "1.1.6",
9797
"@types/d3": "7.4.3",
9898
"@types/redux-mock-store": "^1.5.0",
9999
"@types/uuid": "11.0.0",
100-
"@typescript-eslint/eslint-plugin": "^8.19.1",
101-
"@typescript-eslint/parser": "^8.19.1",
102-
"@vitest/coverage-v8": "4.0.18",
103-
"@vitest/ui": "4.0.18",
100+
"@typescript-eslint/eslint-plugin": "^8.58.0",
101+
"@typescript-eslint/parser": "^8.58.0",
102+
"@vitest/coverage-v8": "4.1.3",
103+
"@vitest/ui": "4.1.3",
104104
"concurrently": "9.2.1",
105105
"cypress": "15.10.0",
106106
"eslint": "9.39.2",
@@ -109,13 +109,17 @@
109109
"eslint-plugin-prettier": "5.5.5",
110110
"eslint-plugin-react": "7.37.5",
111111
"msw": "2.12.8",
112-
"stylelint": "17.1.1",
112+
"stylelint": "17.6.0",
113113
"stylelint-config-standard-scss": "17.0.0",
114114
"stylelint-order": "7.0.1",
115-
"vitest": "^4.0.0"
115+
"vitest": "4.1.3"
116116
},
117117
"resolutions": {
118118
"vitest/**/glob": "13.0.1",
119-
"lodash": "4.17.23"
119+
"vitest/vite": "7.3.2",
120+
"lodash": "4.18.1",
121+
"picomatch": ">=2.3.2",
122+
"brace-expansion": ">=2.0.2",
123+
"@tootallnate/once": "3.0.1"
120124
}
121125
}

redirects.yaml

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -146,74 +146,74 @@ docs/gnome-3-34-extension/?: https://forum.snapcraft.io/t/the-gnome-3-34-extensi
146146
docs/snapcraft-interfaces/?: https://forum.snapcraft.io/t/supported-interfaces/7744
147147

148148
# Old URL redirects
149-
docs/build-snaps: /docs/snap-format
150-
docs/build-snaps/build-for-another-arch: /docs/building-snaps
151-
docs/build-snaps/build-on-lxd-docker: /docs/build-snaps-on-docker
152-
docs/build-snaps/builders: /docs/t/building-the-snap/6800
153-
docs/build-snaps/debugging: /docs/debugging-building-snaps
154-
docs/build-snaps/electron: /docs/electron-apps
155-
docs/build-snaps/go: /docs/go-applications
156-
docs/build-snaps/hooks: /docs/supported-snap-hooks
157-
docs/build-snaps/java: /docs/java-applications
158-
docs/build-snaps/languages: /docs/creating-a-snap
159-
docs/build-snaps/metadata: /docs/snapcraft-app-and-service-metadata
160-
docs/build-snaps/moos: /docs/moos-applications
161-
docs/build-snaps/node: /docs/node-apps
162-
docs/build-snaps/parts: /docs/remote-reusable-parts
163-
docs/build-snaps/plugins: /docs/snapcraft-plugin-api
164-
docs/build-snaps/pre-built: /docs/t/pre-built-apps/6739
165-
docs/build-snaps/publish: /docs/releasing-to-the-snap-store
166-
docs/build-snaps/register-snap: /docs/registering-your-app-name
167-
docs/build-snaps/release: /docs/releasing-your-app
168-
docs/build-snaps/remote-parts: /docs/remote-reusable-parts
169-
docs/build-snaps/ros: /docs/ros-applications
170-
docs/build-snaps/ros2: /docs/ros2-applications
171-
docs/build-snaps/ruby: /docs/ruby-applications
172-
docs/build-snaps/rust: /docs/rust-applications
173-
docs/build-snaps/syntax: /docs/snapcraft-yaml-reference
174-
docs/build-snaps/upload: /docs/releasing-your-app
175-
docs/build-snaps/your-first-snap: /docs/build-on-lxd
176-
docs/core: /docs/quickstart-tour
177-
docs/core/install-arch-linux: /docs/installing-snap-on-arch-linux
178-
docs/core/install-debian: /docs/installing-snap-on-debian
179-
docs/core/install-elementary-os: /docs/installing-snap-on-elementary-os
180-
docs/core/install-fedora: /docs/installing-snap-on-fedora
181-
docs/core/install-gentoo: /docs/installing-snapd
182-
docs/core/install-linux-mint: /docs/installing-snap-on-linux-mint
183-
docs/core/install-manjaro: /docs/installing-snap-on-manjaro-linux
184-
docs/core/install-oe-yocto: /docs/installing-snapd
185-
docs/core/install-opensuse: /docs/installing-snapd
186-
docs/core/install-openwrt: /docs/installing-snapd
187-
docs/core/install-raspbian: /docs/installing-snap-on-raspbian
188-
docs/core/install-solus: /docs/installing-snap-on-solus
189-
docs/core/install-ubuntu: /docs/installing-snap-on-ubuntu
190-
docs/core/install: /docs/installing-snapd
191-
docs/core/interfaces: /docs/interface-management
192-
docs/core/updates: /docs/keeping-snaps-up-to-date
193-
docs/core/usage: /docs/quickstart-tour
194-
docs/deprecation-notices/dn1: /docs/t/deprecation-notice-1/8397
195-
docs/deprecation-notices/dn2: /docs/t/deprecation-notice-2/8398
196-
docs/deprecation-notices/dn3: /docs/t/deprecation-notice-3/8403
197-
docs/deprecation-notices/dn4: /docs/t/deprecation-notice-4/8404
198-
docs/deprecation-notices/dn5: /docs/t/deprecation-notice-5/8405
199-
docs/deprecation-notices/dn6: /docs/t/deprecation-notice-6/8406
200-
docs/deprecation-notices/dn7: /docs/t/deprecation-notice-7/8407
201-
docs/deprecation-notices/dn8: /docs/t/deprecation-notice-8/8408
202-
docs/deprecation-notices/dn9: /docs/t/deprecation-notice-9/8409
203-
docs/reference/channels: /docs/channels
204-
docs/reference/confinement: /docs/snap-confinement
205-
docs/reference/env: /docs/environment-variables
206-
docs/reference/interfaces: /docs/interface-management
149+
docs/build-snaps/?: /docs/snap-format
150+
docs/build-snaps/build-for-another-arch/?: /docs/building-snaps
151+
docs/build-snaps/build-on-lxd-docker/?: /docs/build-snaps-on-docker
152+
docs/build-snaps/builders/?: /docs/t/building-the-snap/6800
153+
docs/build-snaps/debugging/?: /docs/debugging-building-snaps
154+
docs/build-snaps/electron/?: /docs/electron-apps
155+
docs/build-snaps/go/?: /docs/go-applications
156+
docs/build-snaps/hooks/?: /docs/supported-snap-hooks
157+
docs/build-snaps/java/?: /docs/java-applications
158+
docs/build-snaps/languages/?: /docs/creating-a-snap
159+
docs/build-snaps/metadata/?: /docs/snapcraft-app-and-service-metadata
160+
docs/build-snaps/moos/?: /docs/moos-applications
161+
docs/build-snaps/node/?: /docs/node-apps
162+
docs/build-snaps/parts/?: /docs/remote-reusable-parts
163+
docs/build-snaps/plugins/?: /docs/snapcraft-plugin-api
164+
docs/build-snaps/pre-built/?: /docs/t/pre-built-apps/6739
165+
docs/build-snaps/publish/?: /docs/releasing-to-the-snap-store
166+
docs/build-snaps/register-snap/?: /docs/registering-your-app-name
167+
docs/build-snaps/release/?: /docs/releasing-your-app
168+
docs/build-snaps/remote-parts/?: /docs/remote-reusable-parts
169+
docs/build-snaps/ros/?: /docs/ros-applications
170+
docs/build-snaps/ros2/?: /docs/ros2-applications
171+
docs/build-snaps/ruby/?: /docs/ruby-applications
172+
docs/build-snaps/rust/?: /docs/rust-applications
173+
docs/build-snaps/syntax/?: /docs/snapcraft-yaml-reference
174+
docs/build-snaps/upload/?: /docs/releasing-your-app
175+
docs/build-snaps/your-first-snap/?: /docs/build-on-lxd
176+
docs/core/?: /docs/quickstart-tour
177+
docs/core/install-arch-linux/?: /docs/installing-snap-on-arch-linux
178+
docs/core/install-debian/?: /docs/installing-snap-on-debian
179+
docs/core/install-elementary-os/?: /docs/installing-snap-on-elementary-os
180+
docs/core/install-fedora/?: /docs/installing-snap-on-fedora
181+
docs/core/install-gentoo/?: /docs/installing-snapd
182+
docs/core/install-linux-mint/?: /docs/installing-snap-on-linux-mint
183+
docs/core/install-manjaro/?: /docs/installing-snap-on-manjaro-linux
184+
docs/core/install-oe-yocto/?: /docs/installing-snapd
185+
docs/core/install-opensuse/?: /docs/installing-snapd
186+
docs/core/install-openwrt/?: /docs/installing-snapd
187+
docs/core/install-raspbian/?: /docs/installing-snap-on-raspbian
188+
docs/core/install-solus/?: /docs/installing-snap-on-solus
189+
docs/core/install-ubuntu/?: /docs/installing-snap-on-ubuntu
190+
docs/core/install/?: /docs/installing-snapd
191+
docs/core/interfaces/?: /docs/interface-management
192+
docs/core/updates/?: /docs/keeping-snaps-up-to-date
193+
docs/core/usage/?: /docs/quickstart-tour
194+
docs/deprecation-notices/dn1/?: /docs/t/deprecation-notice-1/8397
195+
docs/deprecation-notices/dn2/?: /docs/t/deprecation-notice-2/8398
196+
docs/deprecation-notices/dn3/?: /docs/t/deprecation-notice-3/8403
197+
docs/deprecation-notices/dn4/?: /docs/t/deprecation-notice-4/8404
198+
docs/deprecation-notices/dn5/?: /docs/t/deprecation-notice-5/8405
199+
docs/deprecation-notices/dn6/?: /docs/t/deprecation-notice-6/8406
200+
docs/deprecation-notices/dn7/?: /docs/t/deprecation-notice-7/8407
201+
docs/deprecation-notices/dn8/?: /docs/t/deprecation-notice-8/8408
202+
docs/deprecation-notices/dn9/?: /docs/t/deprecation-notice-9/8409
203+
docs/reference/channels/?: /docs/channels
204+
docs/reference/confinement/?: /docs/snap-confinement
205+
docs/reference/env/?: /docs/environment-variables
206+
docs/reference/interfaces/?: /docs/interface-management
207207
docs/reference/plugins.*: /docs/writing-local-plugins
208-
docs/snaps: /docs
209-
docs/snaps/intro: /docs/quickstart-tour
210-
docs/snaps/metadata: /docs/snap-format
211-
docs/snaps/structure: /docs/system-snap-directory
208+
docs/snaps/?: /docs
209+
docs/snaps/intro/?: /docs/quickstart-tour
210+
docs/snaps/metadata/?: /docs/snap-format
211+
docs/snaps/structure/?: /docs/system-snap-directory
212212
account/details: /admin/account
213213

214214
# Redirects webhook requests using old URL format (i.e. not starting with api/)
215215
(?P<snap_name>[^/]*)/webhook/notify: /api/{snap_name}/webhook/notify
216-
(?!api/.*)(?P<github_owner>[^/]*)/(?P<github_repo>[^/]*)/webhook/notify: /api/{github_owner}/{github_repo}/webhook/notify
216+
(?!api/.*)(?P<github_owner>[^/]*)/(?P<github_repo>[^/]*)/webhook/notify: /api/{github_owner}/{github_repo}/webhook/notify
217217

218218
# First snap flow pages
219219
first-snap/python: https://documentation.ubuntu.com/snapcraft/stable/how-to/integrations/craft-a-python-app/

requirements.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# App dependencies
2-
canonicalwebteam.flask-base==3.1.1
3-
canonicalwebteam.flask-vite==0.4.0
2+
canonicalwebteam.flask-base==3.1.2
3+
canonicalwebteam.flask-vite==0.5.1
44
canonicalwebteam.candid==0.9.0
5-
canonicalwebteam.discourse==7.1.1
5+
canonicalwebteam.discourse==7.2.0
66
canonicalwebteam.blog==6.8.4
77
canonicalwebteam.search==2.1.2
88
canonicalwebteam.image-template==1.9.0
9-
canonicalwebteam.store-api==7.4.10
9+
canonicalwebteam.store-api==7.8.3
1010
canonicalwebteam.launchpad==0.9.0
1111
django-openid-auth==0.17
1212
Flask-OpenID==1.3.1
@@ -26,7 +26,7 @@ ruamel.yaml==0.18.5
2626
vcrpy-unittest==0.1.7
2727
user-agents==2.2.0
2828
dnspython==2.8.0
29-
werkzeug==2.3.8
29+
werkzeug==3.1.6
3030
sentry-sdk==2.52.0
3131

3232
# Development dependencies
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import declareGlobal from "../declare";
2+
3+
describe("declareGlobal", () => {
4+
afterEach(() => {
5+
// Clean up any globals we set
6+
delete (globalThis as Record<string, unknown>).testApp;
7+
});
8+
9+
test("sets a nested global value", () => {
10+
declareGlobal("testApp.foo", { bar: 1 });
11+
const g = globalThis as unknown as Record<
12+
string,
13+
Record<string, Record<string, number>>
14+
>;
15+
expect(g.testApp.foo.bar).toBe(1);
16+
});
17+
18+
test("merges overlapping paths", () => {
19+
declareGlobal("testApp.a", { x: 1 });
20+
declareGlobal("testApp.b", { y: 2 });
21+
const app = (globalThis as Record<string, unknown>).testApp as Record<
22+
string,
23+
unknown
24+
>;
25+
expect(app.a).toEqual({ x: 1 });
26+
expect(app.b).toEqual({ y: 2 });
27+
});
28+
29+
test("does not pollute Object.prototype via __proto__", () => {
30+
const payload = JSON.parse('{"__proto__": {"polluted": true}}');
31+
declareGlobal("testApp.merge", payload);
32+
expect(({} as Record<string, unknown>).polluted).toBeUndefined();
33+
});
34+
35+
test("does not pollute Object.prototype via constructor.prototype", () => {
36+
const payload = JSON.parse(
37+
'{"constructor": {"prototype": {"polluted": true}}}',
38+
);
39+
declareGlobal("testApp.merge2", payload);
40+
expect(({} as Record<string, unknown>).polluted).toBeUndefined();
41+
});
42+
});

static/js/libs/declare.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ function buildObjectFromPath(path: string, value: unknown): Mergeable {
1818

1919
function deepMerge(target: Mergeable, source: Mergeable, override: boolean) {
2020
for (const key of Object.keys(source)) {
21+
// Guard against prototype pollution (CodeQL js/prototype-pollution-utility)
22+
const isBlockedKey =
23+
key === "__proto__" || key === "constructor" || key === "prototype";
24+
const isInheritedProperty =
25+
!Object.prototype.hasOwnProperty.call(target, key) &&
26+
typeof target[key] !== "undefined";
27+
if (isBlockedKey || isInheritedProperty) {
28+
continue;
29+
}
2130
if (typeof target[key] === "object" && typeof source[key] === "object") {
2231
target[key] = deepMerge(
2332
target[key] as Mergeable,

static/js/public/featured-snaps.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import declareGlobal from "../libs/declare";
2+
import { trackFeaturedSnapClicked } from "../store/utils/featuredTracker";
23

34
type PackageData = {
45
apps: Array<string>;
@@ -31,21 +32,26 @@ async function buildCards(category: string): Promise<void> {
3132
);
3233

3334
featuredSnapCards.forEach((featuredSnapCard, index) => {
34-
buildCard(featuredSnapCard, localData[index]);
35+
buildCard(featuredSnapCard, localData[index], category, index);
3536
});
3637
} else {
3738
const response = await fetch(`/store/featured-snaps/${category}`);
3839
const data: Array<PackageData> = await response.json();
3940

4041
featuredSnapCards.forEach((featuredSnapCard, index) => {
41-
buildCard(featuredSnapCard, data[index]);
42+
buildCard(featuredSnapCard, data[index], category, index);
4243
});
4344

4445
window.sessionStorage.setItem(category, JSON.stringify(data));
4546
}
4647
}
4748

48-
function buildCard(featuredSnapCard: Element, data: PackageData) {
49+
function buildCard(
50+
featuredSnapCard: Element,
51+
data: PackageData,
52+
category: string,
53+
index: number,
54+
) {
4955
const placeholder = featuredSnapCard.querySelector(
5056
"[data-js='featured-snap-card-placeholder']",
5157
) as HTMLElement;
@@ -117,6 +123,12 @@ function buildCard(featuredSnapCard: Element, data: PackageData) {
117123
}
118124

119125
content.appendChild(clone);
126+
127+
content.addEventListener("click", () => {
128+
if (category === "featured") {
129+
trackFeaturedSnapClicked(data.package_name, index + 1, "home");
130+
}
131+
});
120132
}
121133

122134
placeholder.classList.add("u-hide");

static/js/public/first-snap-flow.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@ function install(language: unknown, fsfFlow: string): void {
2828
) as HTMLLinkElement;
2929
if (paginationBtn) {
3030
paginationBtn.classList.remove("is-disabled");
31-
paginationBtn.href =
32-
`/${fsfFlow}/${language}/${selectedOs}/package` as string;
31+
paginationBtn.href = `/${encodeURIComponent(fsfFlow)}/${encodeURIComponent(String(language))}/${encodeURIComponent(selectedOs)}/package`;
3332
}
3433
}
3534
}
@@ -98,7 +97,7 @@ function install(language: unknown, fsfFlow: string): void {
9897
`#js-pagination-next`,
9998
) as HTMLLinkElement;
10099
if (paginationBtn) {
101-
const paginationBtnLink: string = `/${fsfFlow}/${language}/${type}/package`;
100+
const paginationBtnLink: string = `/${encodeURIComponent(fsfFlow)}/${encodeURIComponent(String(language))}/${encodeURIComponent(type)}/package`;
102101
paginationBtn.classList.remove("is-disabled");
103102
paginationBtn.href = paginationBtnLink;
104103
}
@@ -193,7 +192,7 @@ function push(): void {
193192
".js-continue",
194193
) as HTMLLinkElement;
195194
if (continueBtn) {
196-
continueBtn.href = `/${snapName}/releases`;
195+
continueBtn.href = `/${encodeURIComponent(snapName)}/releases`;
197196
continueBtn.classList.add("p-button--positive");
198197
continueBtn.classList.remove("p-button");
199198
continueBtn.classList.remove("is-disabled");
@@ -204,7 +203,7 @@ function push(): void {
204203
"#js-pagination-next",
205204
) as HTMLLinkElement;
206205
if (paginationBtn) {
207-
paginationBtn.href = `/${snapName}/listing?from=first-snap`;
206+
paginationBtn.href = `/${encodeURIComponent(snapName)}/listing?from=first-snap`;
208207
paginationBtn.classList.remove("is-disabled");
209208
}
210209
});
@@ -372,7 +371,7 @@ function initRegisterName(
372371
"#js-pagination-next",
373372
) as HTMLLinkElement;
374373
if (paginationBtn) {
375-
paginationBtn.href = `/${snapName}/listing?from=first-snap-unpublished`;
374+
paginationBtn.href = `/${encodeURIComponent(snapName)}/listing?from=first-snap-unpublished`;
376375
paginationBtn.classList.remove("is-disabled");
377376
}
378377
};

0 commit comments

Comments
 (0)