From 3b96c5f214679dc2ccd7b12402af2ff872887b79 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:23:51 +0000 Subject: [PATCH 1/5] Initial plan From 2ec2ee3801dfc64fde28e0b035e1c0c08dc84087 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:27:54 +0000 Subject: [PATCH 2/5] fix(webview): fall back to local diff2html assets --- CHANGELOG.md | 5 +++++ src/patchEditorProvider.ts | 35 +++++++++++++++++++++++++---- src/test/extension.test.ts | 46 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a45ddc..d112ebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## [Unreleased] + +### Fixed +- Load bundled `diff2html` webview assets from `node_modules` when the copied `media` assets are unavailable during local development and testing + ## [1.0.0] - 2026-01-30 ### Added diff --git a/src/patchEditorProvider.ts b/src/patchEditorProvider.ts index ee07025..c3140c8 100644 --- a/src/patchEditorProvider.ts +++ b/src/patchEditorProvider.ts @@ -1,5 +1,30 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; import * as vscode from 'vscode'; +const DIFF2HTML_ASSET_FILES = [ + path.join('css', 'diff2html.min.css'), + path.join('js', 'diff2html.min.js') +]; + +function hasDiff2HtmlAssets(assetRoot: string): boolean { + return DIFF2HTML_ASSET_FILES.every(file => fs.existsSync(path.join(assetRoot, file))); +} + +export function resolveDiff2HtmlAssetDirectory(extensionPath: string): string { + const mediaAssetRoot = path.join(extensionPath, 'media', 'diff2html'); + if (hasDiff2HtmlAssets(mediaAssetRoot)) { + return mediaAssetRoot; + } + + const nodeModulesAssetRoot = path.join(extensionPath, 'node_modules', 'diff2html', 'bundles'); + if (hasDiff2HtmlAssets(nodeModulesAssetRoot)) { + return nodeModulesAssetRoot; + } + + return mediaAssetRoot; +} + /** * Custom editor provider for patch/diff files */ @@ -59,19 +84,21 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { this.activeWebviewPanel = webviewPanel; // Setup webview options + const diff2htmlAssetRoot = vscode.Uri.file(resolveDiff2HtmlAssetDirectory(this.context.extensionUri.fsPath)); + webviewPanel.webview.options = { enableScripts: true, localResourceRoots: [ - vscode.Uri.joinPath(this.context.extensionUri, 'media') + diff2htmlAssetRoot ] }; - // Get URIs for diff2html resources from media folder + // Get URIs for diff2html resources from the packaged media folder or the local dependency fallback const diff2htmlCssUri = webviewPanel.webview.asWebviewUri( - vscode.Uri.joinPath(this.context.extensionUri, 'media', 'diff2html', 'css', 'diff2html.min.css') + vscode.Uri.joinPath(diff2htmlAssetRoot, 'css', 'diff2html.min.css') ); const diff2htmlJsUri = webviewPanel.webview.asWebviewUri( - vscode.Uri.joinPath(this.context.extensionUri, 'media', 'diff2html', 'js', 'diff2html.min.js') + vscode.Uri.joinPath(diff2htmlAssetRoot, 'js', 'diff2html.min.js') ); // Set initial HTML content diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index d5b27ca..5b9251f 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -1,5 +1,9 @@ import * as assert from 'assert'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; import * as vscode from 'vscode'; +import { resolveDiff2HtmlAssetDirectory } from '../patchEditorProvider'; suite('Extension Test Suite', () => { vscode.window.showInformationMessage('Start all tests.'); @@ -22,4 +26,46 @@ suite('Extension Test Suite', () => { assert.ok(commands.includes('tlcsdm.patchReader.toggleView')); }); }); + + test('Should prefer packaged diff2html assets when available', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'patch-reader-media-')); + const mediaCss = path.join(tempRoot, 'media', 'diff2html', 'css', 'diff2html.min.css'); + const mediaJs = path.join(tempRoot, 'media', 'diff2html', 'js', 'diff2html.min.js'); + const nodeModulesCss = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'css', 'diff2html.min.css'); + const nodeModulesJs = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'js', 'diff2html.min.js'); + + fs.mkdirSync(path.dirname(mediaCss), { recursive: true }); + fs.mkdirSync(path.dirname(mediaJs), { recursive: true }); + fs.mkdirSync(path.dirname(nodeModulesCss), { recursive: true }); + fs.mkdirSync(path.dirname(nodeModulesJs), { recursive: true }); + fs.writeFileSync(mediaCss, ''); + fs.writeFileSync(mediaJs, ''); + fs.writeFileSync(nodeModulesCss, ''); + fs.writeFileSync(nodeModulesJs, ''); + + assert.strictEqual( + resolveDiff2HtmlAssetDirectory(tempRoot), + path.join(tempRoot, 'media', 'diff2html') + ); + + fs.rmSync(tempRoot, { recursive: true, force: true }); + }); + + test('Should fall back to node_modules diff2html assets when media assets are missing', () => { + const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'patch-reader-node-modules-')); + const nodeModulesCss = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'css', 'diff2html.min.css'); + const nodeModulesJs = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'js', 'diff2html.min.js'); + + fs.mkdirSync(path.dirname(nodeModulesCss), { recursive: true }); + fs.mkdirSync(path.dirname(nodeModulesJs), { recursive: true }); + fs.writeFileSync(nodeModulesCss, ''); + fs.writeFileSync(nodeModulesJs, ''); + + assert.strictEqual( + resolveDiff2HtmlAssetDirectory(tempRoot), + path.join(tempRoot, 'node_modules', 'diff2html', 'bundles') + ); + + fs.rmSync(tempRoot, { recursive: true, force: true }); + }); }); From 748a234a23fcd9155596188c565906d0b4fbde34 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:29:11 +0000 Subject: [PATCH 3/5] test(webview): harden diff2html asset fallback checks --- src/patchEditorProvider.ts | 7 ++++- src/test/extension.test.ts | 64 ++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/patchEditorProvider.ts b/src/patchEditorProvider.ts index c3140c8..f251407 100644 --- a/src/patchEditorProvider.ts +++ b/src/patchEditorProvider.ts @@ -84,7 +84,12 @@ export class PatchEditorProvider implements vscode.CustomTextEditorProvider { this.activeWebviewPanel = webviewPanel; // Setup webview options - const diff2htmlAssetRoot = vscode.Uri.file(resolveDiff2HtmlAssetDirectory(this.context.extensionUri.fsPath)); + const diff2htmlAssetDirectory = resolveDiff2HtmlAssetDirectory(this.context.extensionUri.fsPath); + if (!hasDiff2HtmlAssets(diff2htmlAssetDirectory)) { + this.logError(`Diff2Html assets were not found in ${diff2htmlAssetDirectory}`); + } + + const diff2htmlAssetRoot = vscode.Uri.file(diff2htmlAssetDirectory); webviewPanel.webview.options = { enableScripts: true, diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 5b9251f..46a3b41 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -29,43 +29,47 @@ suite('Extension Test Suite', () => { test('Should prefer packaged diff2html assets when available', () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'patch-reader-media-')); - const mediaCss = path.join(tempRoot, 'media', 'diff2html', 'css', 'diff2html.min.css'); - const mediaJs = path.join(tempRoot, 'media', 'diff2html', 'js', 'diff2html.min.js'); - const nodeModulesCss = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'css', 'diff2html.min.css'); - const nodeModulesJs = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'js', 'diff2html.min.js'); + try { + const mediaCss = path.join(tempRoot, 'media', 'diff2html', 'css', 'diff2html.min.css'); + const mediaJs = path.join(tempRoot, 'media', 'diff2html', 'js', 'diff2html.min.js'); + const nodeModulesCss = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'css', 'diff2html.min.css'); + const nodeModulesJs = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'js', 'diff2html.min.js'); - fs.mkdirSync(path.dirname(mediaCss), { recursive: true }); - fs.mkdirSync(path.dirname(mediaJs), { recursive: true }); - fs.mkdirSync(path.dirname(nodeModulesCss), { recursive: true }); - fs.mkdirSync(path.dirname(nodeModulesJs), { recursive: true }); - fs.writeFileSync(mediaCss, ''); - fs.writeFileSync(mediaJs, ''); - fs.writeFileSync(nodeModulesCss, ''); - fs.writeFileSync(nodeModulesJs, ''); + fs.mkdirSync(path.dirname(mediaCss), { recursive: true }); + fs.mkdirSync(path.dirname(mediaJs), { recursive: true }); + fs.mkdirSync(path.dirname(nodeModulesCss), { recursive: true }); + fs.mkdirSync(path.dirname(nodeModulesJs), { recursive: true }); + fs.writeFileSync(mediaCss, ''); + fs.writeFileSync(mediaJs, ''); + fs.writeFileSync(nodeModulesCss, ''); + fs.writeFileSync(nodeModulesJs, ''); - assert.strictEqual( - resolveDiff2HtmlAssetDirectory(tempRoot), - path.join(tempRoot, 'media', 'diff2html') - ); - - fs.rmSync(tempRoot, { recursive: true, force: true }); + assert.strictEqual( + resolveDiff2HtmlAssetDirectory(tempRoot), + path.join(tempRoot, 'media', 'diff2html') + ); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } }); test('Should fall back to node_modules diff2html assets when media assets are missing', () => { const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'patch-reader-node-modules-')); - const nodeModulesCss = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'css', 'diff2html.min.css'); - const nodeModulesJs = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'js', 'diff2html.min.js'); + try { + const nodeModulesCss = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'css', 'diff2html.min.css'); + const nodeModulesJs = path.join(tempRoot, 'node_modules', 'diff2html', 'bundles', 'js', 'diff2html.min.js'); - fs.mkdirSync(path.dirname(nodeModulesCss), { recursive: true }); - fs.mkdirSync(path.dirname(nodeModulesJs), { recursive: true }); - fs.writeFileSync(nodeModulesCss, ''); - fs.writeFileSync(nodeModulesJs, ''); + fs.mkdirSync(path.dirname(nodeModulesCss), { recursive: true }); + fs.mkdirSync(path.dirname(nodeModulesJs), { recursive: true }); + fs.writeFileSync(nodeModulesCss, ''); + fs.writeFileSync(nodeModulesJs, ''); - assert.strictEqual( - resolveDiff2HtmlAssetDirectory(tempRoot), - path.join(tempRoot, 'node_modules', 'diff2html', 'bundles') - ); - - fs.rmSync(tempRoot, { recursive: true, force: true }); + assert.strictEqual( + resolveDiff2HtmlAssetDirectory(tempRoot), + path.join(tempRoot, 'node_modules', 'diff2html', 'bundles') + ); + } finally { + fs.rmSync(tempRoot, { recursive: true, force: true }); + } }); }); From 02835537a343f160f8e4256e665903e0bae8da9d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 3 Jul 2026 02:30:07 +0000 Subject: [PATCH 4/5] docs(webview): clarify diff2html asset fallback --- src/patchEditorProvider.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/patchEditorProvider.ts b/src/patchEditorProvider.ts index f251407..04f34fd 100644 --- a/src/patchEditorProvider.ts +++ b/src/patchEditorProvider.ts @@ -7,10 +7,18 @@ const DIFF2HTML_ASSET_FILES = [ path.join('js', 'diff2html.min.js') ]; +/** + * Check whether all required diff2html asset files exist under the given asset root. + */ function hasDiff2HtmlAssets(assetRoot: string): boolean { return DIFF2HTML_ASSET_FILES.every(file => fs.existsSync(path.join(assetRoot, file))); } +/** + * Resolve the diff2html asset directory for this extension installation. + * Prefers packaged media assets and falls back to the local dependency bundle + * when running from a source checkout where copied media assets are absent. + */ export function resolveDiff2HtmlAssetDirectory(extensionPath: string): string { const mediaAssetRoot = path.join(extensionPath, 'media', 'diff2html'); if (hasDiff2HtmlAssets(mediaAssetRoot)) { From 406948af49075cd5327c8ddf02bceda18542ed3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=A6=E9=87=8C=E4=B8=8D=E7=9F=A5=E8=BA=AB=E6=98=AF?= =?UTF-8?q?=E5=AE=A2?= Date: Fri, 3 Jul 2026 10:46:24 +0800 Subject: [PATCH 5/5] docs: Update CHANGELOG for version 1.0.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update changelog for version 1.0.2 release. Signed-off-by: 梦里不知身是客 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d112ebe..6583ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## [Unreleased] +## [1.0.2] - 2026-07-03 ### Fixed - Load bundled `diff2html` webview assets from `node_modules` when the copied `media` assets are unavailable during local development and testing