Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ description: Setup the environment
runs:
using: composite
steps:
- uses: pnpm/action-setup@v6.0.7
- uses: pnpm/action-setup@739bfe42ca9233c5e6aca07c1a25a9d34aca49b0 # v6.0.7
with:
run_install: false

- name: Setup node
uses: actions/setup-node@v6
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version-file: '.nvmrc'
cache: pnpm
Expand Down
7 changes: 6 additions & 1 deletion .github/renovate.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", ":preserveSemverRanges", "group:all"],
"extends": [
"config:recommended",
":preserveSemverRanges",
"group:all",
"helpers:pinGitHubActionDigestsToSemver"
],
"postUpdateOptions": ["pnpmDedupe"],
"packageRules": [
{
Expand Down
15 changes: 6 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,13 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CI: true

jobs:
install-deps:
runs-on: ubuntu-latest

steps:
- name: Checkout code repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

Expand All @@ -31,7 +28,7 @@ jobs:
needs: install-deps
steps:
- name: Checkout code repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

Expand All @@ -45,7 +42,7 @@ jobs:
needs: install-deps
steps:
- name: Checkout code repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

Expand All @@ -59,7 +56,7 @@ jobs:
needs: install-deps
steps:
- name: Checkout code repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

Expand Down Expand Up @@ -93,7 +90,7 @@ jobs:
shard: [1, 2, 3, 4, 5]
steps:
- name: Checkout code repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

Expand All @@ -112,7 +109,7 @@ jobs:

- name: Upload test results
if: ${{ failure() }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: test-results-e2e-${{ matrix.shard }}
path: e2e/test-results/
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ jobs:
language: ['javascript']
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
with:
languages: ${{ matrix.language }}
- name: Autobuild
uses: github/codeql-action/autobuild@v4
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4
with:
category: '/language:${{matrix.language}}'
5 changes: 1 addition & 4 deletions .github/workflows/fix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ on:
permissions:
contents: read

env:
CI: true

jobs:
fix:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- uses: ./.github/actions/setup

Expand Down
9 changes: 3 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ on:

concurrency: ${{ github.workflow }}-${{ github.ref }}

env:
CI: true

jobs:
prepare:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

Expand All @@ -35,7 +32,7 @@ jobs:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

Expand All @@ -53,7 +50,7 @@ jobs:
echo "MILKDOWN_RELEASE_TAG=v${MILKDOWN_VERSION}" >> "$GITHUB_ENV"

- name: Create GitHub release
uses: ncipollo/release-action@v1
uses: ncipollo/release-action@339a81892b84b4eeb0f6e744e4574d79d0d9b8dd # v1
with:
commit: main
tag: '${{ env.MILKDOWN_RELEASE_TAG }}'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,13 @@ function createSvgAwareSanitizer() {
return (dirty: string | Node) => purify.sanitize(dirty, config)
}

const sanitizeSvg = createSvgAwareSanitizer()
// Lazily initialize the sanitizer so that importing this module under SSR
// (where `DOMPurify()` returns a stub without `.addHook`) does not throw.
let cachedSanitizer: ReturnType<typeof createSvgAwareSanitizer> | undefined
function sanitizeSvg(dirty: string | Node) {
cachedSanitizer ??= createSvgAwareSanitizer()
return cachedSanitizer(dirty)
}

type PreviewPanelProps = Pick<
CodeBlockProps,
Expand Down
3 changes: 2 additions & 1 deletion packages/crepe/src/llm-providers/anthropic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
parseSSE,
readErrorBody,
resolveSystemPrompt,
stripTrailingSlashes,
} from '../shared'

const PROVIDER_NAME = 'milkdown/providers/anthropic'
Expand Down Expand Up @@ -75,7 +76,7 @@ export function createAnthropicProvider(
] satisfies AnthropicMessage[],
}

const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, '')
const baseURL = stripTrailingSlashes(config.baseURL ?? DEFAULT_BASE_URL)
const url = `${baseURL}/v1/messages`

const headers: Record<string, string> = {
Expand Down
3 changes: 2 additions & 1 deletion packages/crepe/src/llm-providers/openai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
parseSSE,
readErrorBody,
resolveSystemPrompt,
stripTrailingSlashes,
} from '../shared'

const PROVIDER_NAME = 'milkdown/providers/openai'
Expand Down Expand Up @@ -54,7 +55,7 @@ export function createOpenAIProvider(config: OpenAIProviderConfig): AIProvider {
? config.buildMessages(context, { systemPrompt, userMessage })
: defaultMessages(systemPrompt, userMessage)

const baseURL = (config.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, '')
const baseURL = stripTrailingSlashes(config.baseURL ?? DEFAULT_BASE_URL)
const url = `${baseURL}/v1/chat/completions`

const headers: Record<string, string> = {
Expand Down
10 changes: 10 additions & 0 deletions packages/crepe/src/llm-providers/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,16 @@ export function resolveSystemPrompt(
return systemPrompt ?? DEFAULT_SYSTEM_PROMPT
}

/// Strip trailing `/` characters from a URL. Uses a linear scan instead
/// of a regex like `/\/+$/` because the latter backtracks quadratically
/// on caller-supplied input ending in many slashes followed by a
/// non-slash (CodeQL js/polynomial-redos).
export function stripTrailingSlashes(url: string): string {
let end = url.length
while (end > 0 && url.charCodeAt(end - 1) === 47 /* '/' */) end--
return end === url.length ? url : url.slice(0, end)
}

/// Parse an SSE stream from `response.body`. Yields the payload after
/// `data: ` for each event; ignores `event:`, `id:`, `retry:`, and
/// comment lines. Stops cleanly when the signal aborts or the stream
Expand Down
Loading