diff --git a/.github/workflows/nextjs-tracker.yml b/.github/workflows/nextjs-tracker.yml index 5c2c87d45..3bafa8d00 100644 --- a/.github/workflows/nextjs-tracker.yml +++ b/.github/workflows/nextjs-tracker.yml @@ -39,8 +39,9 @@ jobs: id: commits env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SINCE_HOURS: ${{ github.event.inputs.since_hours || '24' }} run: | - HOURS="${{ github.event.inputs.since_hours || '24' }}" + HOURS="$SINCE_HOURS" SINCE=$(date -u -d "${HOURS} hours ago" +%Y-%m-%dT%H:%M:%SZ) echo "Fetching Next.js canary commits since $SINCE" @@ -70,8 +71,10 @@ jobs: - name: Build tracker prompt id: build_prompt if: steps.commits.outputs.has_commits == 'true' + env: + DRY_RUN_INPUT: ${{ github.event.inputs.dry_run || 'false' }} run: | - DRY_RUN="${{ github.event.inputs.dry_run || 'false' }}" + DRY_RUN="$DRY_RUN_INPUT" COMMITS=$(cat /tmp/nextjs-commits.json) if [ "$DRY_RUN" = "true" ]; then @@ -134,4 +137,6 @@ jobs: - name: Skip (no commits) if: steps.commits.outputs.has_commits == 'false' - run: echo "No Next.js canary commits in the last ${{ github.event.inputs.since_hours || '24' }} hours. Nothing to do." + env: + SINCE_HOURS: ${{ github.event.inputs.since_hours || '24' }} + run: echo "No Next.js canary commits in the last $SINCE_HOURS hours. Nothing to do." diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6db2a17e4..20064c796 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -44,13 +44,15 @@ jobs: - name: Bump version id: version working-directory: packages/vinext + env: + BUMP: ${{ inputs.bump }} run: | # vp wraps package-manager subcommands, but versioning still needs the real npm CLI # because we are mutating package.json in place before the publish step. LATEST=$(npm view vinext version 2>/dev/null || echo "0.0.0") IFS='.' read -r MAJOR MINOR PATCH <<< "$LATEST" - case "${{ inputs.bump }}" in + case "$BUMP" in major) MAJOR=$((MAJOR + 1)) MINOR=0 diff --git a/examples/hackernews/components/comment.jsx b/examples/hackernews/components/comment.jsx index 0176b5992..1e89094e1 100644 --- a/examples/hackernews/components/comment.jsx +++ b/examples/hackernews/components/comment.jsx @@ -1,6 +1,7 @@ 'use client'; import { useState } from 'react'; +import DOMPurify from 'dompurify'; import timeAgo from '../lib/time-ago'; @@ -26,7 +27,7 @@ export default function Comment({ user, text, date, comments, commentsCount }) {
,
{comments.map((comment) => ( diff --git a/examples/hackernews/package.json b/examples/hackernews/package.json index 1cd765f89..d98ba6433 100644 --- a/examples/hackernews/package.json +++ b/examples/hackernews/package.json @@ -1,26 +1,28 @@ { - "name": "vinext-hackernews", - "type": "module", - "private": true, - "scripts": { - "dev": "vp dev", - "build": "vp build", - "preview": "vp preview" - }, - "dependencies": { - "@vitejs/plugin-react": "catalog:", - "ms": "catalog:", - "react": "catalog:", - "react-dom": "catalog:", - "server-only": "catalog:", - "vite": "catalog:", - "vinext": "workspace:*", - "@vitejs/plugin-rsc": "catalog:", - "react-server-dom-webpack": "catalog:", - "@cloudflare/vite-plugin": "catalog:", - "wrangler": "catalog:" - }, - "devDependencies": { - "vite-plus": "catalog:" - } + "name": "vinext-hackernews", + "type": "module", + "private": true, + "scripts": { + "dev": "vp dev", + "build": "vp build", + "preview": "vp preview" + }, + "dependencies": { + "@vitejs/plugin-react": "catalog:", + "dompurify": "catalog:", + "ms": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "server-only": "catalog:", + "vite": "catalog:", + "vinext": "workspace:*", + "@vitejs/plugin-rsc": "catalog:", + "react-server-dom-webpack": "catalog:", + "@cloudflare/vite-plugin": "catalog:", + "wrangler": "catalog:" + }, + "devDependencies": { + "@cloudflare/workers-types": "catalog:", + "vite-plus": "catalog:" + } } diff --git a/examples/hackernews/tsconfig.json b/examples/hackernews/tsconfig.json index 7ccdce365..29b540b89 100644 --- a/examples/hackernews/tsconfig.json +++ b/examples/hackernews/tsconfig.json @@ -8,8 +8,15 @@ "esModuleInterop": true, "skipLibCheck": true, "allowJs": true, - "baseUrl": ".", - "types": ["@cloudflare/workers-types"] + "noEmit": true, + "types": [ + "@cloudflare/workers-types" + ] }, - "include": ["app", "components", "lib", "worker"] -} + "include": [ + "app", + "components", + "lib", + "worker" + ] +} \ No newline at end of file diff --git a/packages/vinext/src/shims/head.ts b/packages/vinext/src/shims/head.ts index cc7ecad21..0c9d4492d 100644 --- a/packages/vinext/src/shims/head.ts +++ b/packages/vinext/src/shims/head.ts @@ -272,7 +272,9 @@ export function escapeAttr(s: string): string { * context but prevents the HTML parser from seeing a closing tag. */ export function escapeInlineContent(content: string, tag: string): string { - // Build a pattern like `<\/script` or `<\/style`, case-insensitive + // Build a pattern like `<\/script` or `<\/style`, case-insensitive. + // `tag` is always a literal developer-controlled value ("script" or "style") + // guarded by the RAW_CONTENT_TAGS.has(tag) check at all call sites — never user input. const pattern = new RegExp(`<\\/(${tag})`, "gi"); return content.replace(pattern, "<\\/$1"); } diff --git a/packages/vinext/src/shims/script.tsx b/packages/vinext/src/shims/script.tsx index 02bb7bbbd..aa3a6395c 100644 --- a/packages/vinext/src/shims/script.tsx +++ b/packages/vinext/src/shims/script.tsx @@ -156,6 +156,11 @@ function Script(props: ScriptProps): React.ReactElement | null { } if (dangerouslySetInnerHTML?.__html) { + // Intentional: mirrors the Next.js