Skip to content

Commit 9625263

Browse files
committed
fix: address PR review feedback for changelog widget
- Scope wildcard CORS to only widget-consumed routes (/changelogs, /products) - Restrict remaining public API routes to app origin via getCorsOrigins() - Fix Swagger doc: rename pageSize → limit to match actual query param - Fix Swagger doc: use valid product slug example (insights, not security) - Encode entry slug/id in widget URLs to prevent path injection - Remove unsafe `as object` cast in Prisma productSlug filter - Handle stale content when product attribute is removed from widget - Pin npm@10.9.2 in publish workflow for reproducibility - Use SemVer 2.0 compliant regex for version validation - Add keywords to widget package.json for npm discoverability - Add .gitignore for widget dist/ directory - Add local development section to widget README Signed-off-by: Asitha de Silva <asithade@gmail.com>
1 parent 869864a commit 9625263

File tree

9 files changed

+54
-10
lines changed

9 files changed

+54
-10
lines changed

.github/workflows/npm-publish-widget.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
INPUT_VERSION: ${{ inputs.version }}
3131
run: |
3232
set -euo pipefail
33-
if ! echo "$INPUT_VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9._-]+)?(\+[a-zA-Z0-9._-]+)?$'; then
33+
if ! echo "$INPUT_VERSION" | grep -qE '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9A-Za-z-][0-9A-Za-z-]*))*))?(\+([0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$'; then
3434
echo "::error::Invalid semver: $INPUT_VERSION (expected format: X.Y.Z, X.Y.Z-prerelease, or X.Y.Z+build)"
3535
exit 1
3636
fi
@@ -88,7 +88,7 @@ jobs:
8888
run: npm pack --dry-run
8989

9090
- name: Upgrade npm for OIDC publishing
91-
run: npm install -g npm@latest && npm --version
91+
run: npm install -g npm@10.9.2 && npm --version
9292

9393
- name: Publish to NPM
9494
if: ${{ !inputs.dry_run }}

apps/lfx-changelog/src/server/services/changelog.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export class ChangelogService {
2424

2525
const where: Prisma.ChangelogEntryWhereInput = { status: 'published', product: { isActive: true } };
2626
if (params.productId) where.productId = params.productId;
27-
if (params.productSlug) where.product = { ...(where.product as object), slug: params.productSlug };
27+
if (params.productSlug) where.product = { isActive: true, slug: params.productSlug };
2828
if (params.query) {
2929
where.OR = [{ title: { contains: params.query, mode: 'insensitive' } }, { description: { contains: params.query, mode: 'insensitive' } }];
3030
}

apps/lfx-changelog/src/server/setup/cors.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ import type { Express, NextFunction, Request, Response } from 'express';
1414
* - `/api` — conditional CORS for API-key requests (excluding chat)
1515
*/
1616
export function setupCors(app: Express): void {
17-
// Public API — open CORS for embeddable widget consumers; chat routes are same-origin only
17+
// Widget-consumed routes — open CORS for embeddable widget on any origin
18+
const widgetCors = cors({ origin: '*', methods: ['GET', 'HEAD', 'OPTIONS'], maxAge: 86400 });
19+
app.use('/public/api/changelogs', widgetCors);
20+
app.use('/public/api/products', widgetCors);
21+
22+
// Remaining public API routes (search, blogs, chat, etc.) — restricted to app origin
1823
app.use('/public/api', (req: Request, res: Response, next: NextFunction) => {
19-
if (req.path.startsWith('/chat')) {
24+
// Skip paths already handled by widgetCors above to avoid double-applying
25+
if (req.path.startsWith('/changelogs') || req.path.startsWith('/products')) {
2026
next();
2127
return;
2228
}
2329
cors({
24-
origin: '*',
30+
origin: getCorsOrigins(),
2531
methods: ['GET', 'HEAD', 'OPTIONS'],
2632
maxAge: 86400,
2733
})(req, res, next);

apps/lfx-changelog/src/server/swagger/paths/public-changelogs.path.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ publicChangelogRegistry.registerPath({
1717
request: {
1818
query: z.object({
1919
page: z.coerce.number().optional().openapi({ description: 'Page number (default: 1)' }),
20-
pageSize: z.coerce.number().optional().openapi({ description: 'Items per page (default: 10)' }),
20+
limit: z.coerce.number().optional().openapi({ description: 'Items per page (default: 10)' }),
2121
productId: z.string().optional().openapi({ description: 'Filter by product ID' }),
22-
productSlug: z.string().optional().openapi({ description: 'Filter by product slug (e.g., "security", "easycla")' }),
22+
productSlug: z.string().optional().openapi({ description: 'Filter by product slug (e.g., "insights", "easycla")' }),
2323
}),
2424
},
2525
responses: {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Copyright The Linux Foundation and each contributor to LFX.
2+
# SPDX-License-Identifier: MIT
3+
4+
dist/

packages/changelog-widget/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,29 @@ lfx-changelog::part(date) {
220220
}
221221
```
222222

223+
## Local Development
224+
225+
To test the widget locally against your dev server:
226+
227+
1. Start the backend:
228+
229+
```bash
230+
yarn start
231+
```
232+
233+
2. Build the widget (from the widget package directory):
234+
235+
```bash
236+
cd packages/changelog-widget
237+
yarn build
238+
```
239+
240+
3. Open `packages/changelog-widget/test.html` in your browser.
241+
242+
The test page includes light/dark theme demos, CSS custom property overrides, and an error state scenario — all pointed at `http://localhost:4204`.
243+
244+
For live-reload during development, run `yarn dev` in the widget package to watch for source changes and rebuild automatically.
245+
223246
## SSR Compatibility
224247

225248
The widget is SSR-safe. On the server, `customElements.define()` is skipped (guarded by `typeof window !== 'undefined'`). The element renders nothing during SSR and hydrates on the client.

packages/changelog-widget/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
"files": [
1919
"dist"
2020
],
21+
"keywords": [
22+
"changelog",
23+
"widget",
24+
"web-component",
25+
"lfx",
26+
"linux-foundation"
27+
],
2128
"sideEffects": true,
2229
"publishConfig": {
2330
"registry": "https://registry.npmjs.org/",

packages/changelog-widget/src/lfx-changelog.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,12 @@ export class LfxChangelogElement extends SafeHTMLElement {
7474
if (oldValue === newValue) return;
7575

7676
if (name === 'product' || name === 'limit' || name === 'base-url') {
77-
if (this.isConnected && this.product) {
77+
if (!this.isConnected) return;
78+
if (this.product) {
7879
this.loadChangelogs();
80+
} else {
81+
this.abortController?.abort();
82+
this.showError('Missing required "product" attribute');
7983
}
8084
}
8185
// Theme changes are handled via CSS :host([theme="dark"]) — no re-render needed

packages/changelog-widget/src/renderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ function el<K extends keyof HTMLElementTagNameMap>(tag: K, attrs?: Record<string
5858
}
5959

6060
export function renderCard(entry: ChangelogEntry, baseUrl: string): HTMLElement {
61-
const entryUrl = `${baseUrl}/entry/${entry.slug || entry.id}`;
61+
const entryUrl = `${baseUrl}/entry/${encodeURIComponent(entry.slug || entry.id)}`;
6262
const date = entry.publishedAt ? formatDate(entry.publishedAt) : formatDate(entry.createdAt);
6363

6464
const metaChildren: (Node | string)[] = [];

0 commit comments

Comments
 (0)