fix: address Copilot review (9 comments)
#1 GitView: hide/disable 'open in editor' for external linked worktrees (path outside session workspace is rejected by the fence); add isWithinWorkspace client helper for the gate. #2 DiffTab: untracked diff read resolves against diff.repoRoot (child-repo paths are repo-root-relative, not cwd-relative); fs.read route threads selectedRepoOf into resolveGitPath so the host resolves the right base. #3 GitView: chooseRepo handler mirrors chooseWorktree — clears status/ history/log and bumps generation before async refresh, preventing stale rows from acting on the newly selected repository. #4 git.ts: repoRoots/repoRoot compare roots via pathIdentity() (platform- aware) instead of raw includes(), fixing win32 forward/backslash mismatch that silently fell back to roots[0]. #5 markdown-images: mask fenced code blocks and inline code spans before rewriting; only rewrite reference definitions whose label is actually referenced by image syntax (not plain links). +3 tests. #6 package.json: downgrade unrun ^0.3.1 → ^0.2.39 (0.3.x requires Node ^22.13, incompatible with declared engines >=20). #7 market-manifest.spec: shell:true on win32 so execFileSync resolves the pnpm .cmd shim. #8 smoke.spec: build html-route URL via encodeHtmlUrl() (production encoder) so the Windows drive separator is correct. #9 index.ts git.worktrees route: thread selectedRepoOf so a workspace container's selected child repo's linked worktrees appear.
This commit is contained in:
@@ -70,4 +70,31 @@ describe('rewriteLocalImageUrls', () => {
|
||||
expect(url).toContain('/sidebar/file?')
|
||||
expect(decodeURIComponent(url)).toContain('C:\\repo\\img.png')
|
||||
})
|
||||
|
||||
it('does not rewrite image-looking text inside fenced code blocks', () => {
|
||||
const md = '```\n\n```\n'
|
||||
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
||||
// The fenced example must be preserved verbatim; only the real image is rewritten.
|
||||
expect(out).toContain('```\n\n```')
|
||||
expect(out).toContain('/sidebar/file?')
|
||||
})
|
||||
|
||||
it('does not rewrite image-looking text inside inline code spans', () => {
|
||||
const md = 'Use `` syntax.\n'
|
||||
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
||||
expect(out).toContain('``')
|
||||
expect(out).toContain('/sidebar/file?')
|
||||
})
|
||||
|
||||
it('does not rewrite a reference definition used only by a plain link', () => {
|
||||
// The `[manual][docs]` link references `[docs]: ./docs.md`, but that
|
||||
// definition must NOT be redirected to /sidebar/file because it is not
|
||||
// referenced by any image. Only image-referenced definitions are rewritten.
|
||||
const md = '[manual][docs]\n\n![pic][pic-def]\n\n[docs]: ./docs.md\n[pic-def]: ./pic.png'
|
||||
const out = rewriteLocalImageUrls(md, scope, '/repo/readme.md', ORIGIN)
|
||||
expect(out).toContain('[docs]: ./docs.md')
|
||||
expect(out).not.toMatch(/\[docs\]:.*\/sidebar\/file/)
|
||||
// The image-referenced definition IS rewritten.
|
||||
expect(out).toMatch(/\[pic-def\]:.*\/sidebar\/file/)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -50,7 +50,10 @@ const STABLE_SEMVER = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/u
|
||||
function packedManifest(): Record<string, unknown> {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'market-pack-'))
|
||||
try {
|
||||
execFileSync('pnpm', ['pack', '--pack-destination', dir], { cwd: ROOT, stdio: 'pipe' })
|
||||
// On Windows `pnpm` ships as a `.cmd` shim that `execFileSync` cannot
|
||||
// spawn directly (ENOENT). Run it through the shell so the same call
|
||||
// works on every platform; `tar` is available on all supported OSes.
|
||||
execFileSync('pnpm', ['pack', '--pack-destination', dir], { cwd: ROOT, stdio: 'pipe', shell: process.platform === 'win32' })
|
||||
execFileSync('tar', ['-xzf', join(dir, `${pkg.name}-${pkg.version}.tgz`), '-C', dir, 'package/package.json'], { stdio: 'pipe' })
|
||||
return JSON.parse(readFileSync(join(dir, 'package/package.json'), 'utf8')) as Record<string, unknown>
|
||||
} finally {
|
||||
|
||||
+5
-2
@@ -12,6 +12,7 @@ import { SettingsConflictError, settingsNamespace } from '@deepseek-ai/dsh-setti
|
||||
import { apply, mediaTypeForPath } from '../src/index.ts'
|
||||
import * as git from '../src/git.ts'
|
||||
import { listDirectory } from '../src/fs-tree.ts'
|
||||
import { encodeHtmlUrl } from '../src/html-route.ts'
|
||||
import { defaultShell, PtyManager, type SidebarPty } from '../src/pty-manager.ts'
|
||||
import type { SidebarWebRoute, SidebarWebUpgradeRoute } from '../src/context-types.ts'
|
||||
|
||||
@@ -636,8 +637,10 @@ describe('session cwd resolution over the API route', () => {
|
||||
const media = routes.find(route => route.path === '/sidebar/file')!
|
||||
const html = routes.find(route => route.path === '/sidebar/html')!
|
||||
const mediaResult = await invokeGet(media, `/sidebar/file?sessionId=security&path=${encodeURIComponent(join(workspace, 'link', 'secret.png'))}`)
|
||||
const htmlPathname = join(workspace, 'link', 'secret.html').replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/')
|
||||
const htmlResult = await invokeGet(html, `/sidebar/html/security${htmlPathname}`)
|
||||
// Use the production encoder so the URL is well-formed on every
|
||||
// platform (a Windows drive path needs the leading slash separator
|
||||
// that a naive join-without-separator drops).
|
||||
const htmlResult = await invokeGet(html, encodeHtmlUrl('security', join(workspace, 'link', 'secret.html')))
|
||||
expect(mediaResult).toMatchObject({ status: 403 })
|
||||
expect(JSON.parse(mediaResult.body)).toMatchObject({ ok: false, error: { code: 'forbidden' } })
|
||||
expect(htmlResult).toMatchObject({ status: 403 })
|
||||
|
||||
Reference in New Issue
Block a user