From f836367aaa31bd11da03288bcf8e7ee91446b4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=B3=E4=BF=A1?= Date: Sat, 11 Jul 2026 12:13:46 +0800 Subject: [PATCH] fix(packaging): bundle clipboard addon in standalone builds --- .../standalone-clipboard-native-addon.md | 51 ++++++ .../assets/before-after.png | Bin 0 -> 97587 bytes packages/cli/src/i18n/locales/en.js | 2 + packages/cli/src/i18n/locales/zh-TW.js | 2 + packages/cli/src/i18n/locales/zh.js | 2 + packages/cli/src/ui/components/Composer.tsx | 4 +- .../src/ui/components/InputPrompt.test.tsx | 52 ++++++ .../cli/src/ui/components/InputPrompt.tsx | 75 ++++++--- .../src/ui/contexts/KeypressContext.test.tsx | 28 ++++ .../cli/src/ui/contexts/KeypressContext.tsx | 7 +- .../cli/src/ui/utils/clipboardUtils.test.ts | 67 +++++++- packages/cli/src/ui/utils/clipboardUtils.ts | 32 ++-- scripts/build-standalone-release.js | 74 ++++++++- scripts/create-standalone-package.js | 80 ++++++++- scripts/installation/INSTALLATION_GUIDE.md | 9 +- scripts/tests/install-script.test.js | 155 ++++++++++++++++-- 16 files changed, 561 insertions(+), 79 deletions(-) create mode 100644 docs/design/standalone-clipboard-native-addon.md create mode 100644 docs/design/standalone-clipboard-native-addon/assets/before-after.png diff --git a/docs/design/standalone-clipboard-native-addon.md b/docs/design/standalone-clipboard-native-addon.md new file mode 100644 index 00000000000..5c6a6f3f97a --- /dev/null +++ b/docs/design/standalone-clipboard-native-addon.md @@ -0,0 +1,51 @@ +# Standalone clipboard native addon + +## Problem + +The CLI bundle keeps `@teddyzhu/clipboard` external so npm installations can +load the platform-specific native package at runtime. Standalone archives also +keep the import external, but currently copy only the audio-capture native +addon into `lib/node_modules`. Clipboard image paste therefore fails silently +in every standalone archive. + +## Constraints + +- Each archive must contain the `@teddyzhu/clipboard` JavaScript package and + exactly one native package matching the archive target. +- The release job creates all supported targets on one Ubuntu runner. A normal + `npm ci` only installs the runner's optional native package, so packaging + cannot rely on the repository `node_modules` for cross-target artifacts. +- Clipboard package versions must come from the lockfile and remain aligned + with the CLI optional dependencies. +- Local packaging should continue to work when a non-host clipboard artifact + is unavailable, while release packaging must fail rather than publish a + partially functional archive. + +## Design + +Before building release archives, install the locked clipboard meta package +and every supported target package into a temporary staging directory. Pass +that directory explicitly to the per-target packaging command. + +The standalone packager maps each target to its native clipboard package and +copies only the meta package plus that target package into +`lib/node_modules/@teddyzhu`. When no explicit staging directory is supplied, +the packager uses the repository `node_modules`; a missing host artifact emits +a warning for local builds. Missing artifacts in an explicit staging directory +are fatal. + +If the runtime module still cannot load, the input prompt reports a single +user-visible error on the first clipboard-image paste attempt. Existing Linux +`wl-paste` and `xclip` paths are unchanged. + +## Verification + +- Packaging tests cover target selection, exclusion of other native targets, + and failure for incomplete explicit staging. +- Clipboard and input prompt tests cover the unavailable-module callback and + one-time UI error. +- A real macOS arm64 archive is unpacked outside the repository, loaded with + its bundled Node.js runtime, and exercised against an actual PNG in the + system clipboard. + +![Standalone clipboard paste before and after](standalone-clipboard-native-addon/assets/before-after.png) diff --git a/docs/design/standalone-clipboard-native-addon/assets/before-after.png b/docs/design/standalone-clipboard-native-addon/assets/before-after.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb4dfa2c009c40911f1f074af0d36d20481a89c GIT binary patch literal 97587 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%0DXBe8p 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 literal 0 HcmV?d00001 diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index b85c6007a69..a9575601931 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -21,6 +21,8 @@ export default { 'User level': 'User level', 'Project level': 'Project level', + 'Clipboard image paste is unavailable because the native clipboard module could not be loaded. Reinstall Qwen Code or use the npm installation method.': + 'Clipboard image paste is unavailable because the native clipboard module could not be loaded. Reinstall Qwen Code or use the npm installation method.', // ========================================================================== // Extensions manager dialog (Installed / Discover / Sources tabs) diff --git a/packages/cli/src/i18n/locales/zh-TW.js b/packages/cli/src/i18n/locales/zh-TW.js index 469fd486d2f..dcba2789bff 100644 --- a/packages/cli/src/i18n/locales/zh-TW.js +++ b/packages/cli/src/i18n/locales/zh-TW.js @@ -21,6 +21,8 @@ export default { 'User level': '使用者層級', 'Project level': '專案層級', + 'Clipboard image paste is unavailable because the native clipboard module could not be loaded. Reinstall Qwen Code or use the npm installation method.': + '剪貼簿圖片貼上功能無法使用,因為原生剪貼簿模組載入失敗。請重新安裝 Qwen Code,或改用 npm 安裝方式。', // ========================================================================== // Extensions manager dialog (Installed / Discover / Sources tabs) diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js index 826a2a6f688..ca4af92714a 100644 --- a/packages/cli/src/i18n/locales/zh.js +++ b/packages/cli/src/i18n/locales/zh.js @@ -19,6 +19,8 @@ export default { 'User level': '用户级', 'Project level': '项目级', + 'Clipboard image paste is unavailable because the native clipboard module could not be loaded. Reinstall Qwen Code or use the npm installation method.': + '剪贴板图片粘贴不可用,因为原生剪贴板模块加载失败。请重新安装 Qwen Code,或改用 npm 安装方式。', // ========================================================================== // Extensions manager dialog (Installed / Discover / Sources tabs) diff --git a/packages/cli/src/ui/components/Composer.tsx b/packages/cli/src/ui/components/Composer.tsx index 411046340f2..1c104a73f85 100644 --- a/packages/cli/src/ui/components/Composer.tsx +++ b/packages/cli/src/ui/components/Composer.tsx @@ -5,7 +5,7 @@ */ import { Box, Text, useIsScreenReaderEnabled } from 'ink'; -import { useCallback, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { LoadingIndicator } from './LoadingIndicator.js'; import { InputPrompt } from './InputPrompt.js'; import { Footer } from './Footer.js'; @@ -61,6 +61,7 @@ export const Composer = () => { // local to Composer because nothing outside this component needs the // narrow signal. const [showSuggestions, setShowSuggestions] = useState(false); + const clipboardUnavailableShownRef = useRef(false); // Broad signal — any input-area Tab consumer. Forwarded to AppContainer // via UIActionsContext so useAutoAcceptIndicator's `shouldBlockTab` can @@ -140,6 +141,7 @@ export const Composer = () => { } promptSuggestion={uiState.promptSuggestion} onPromptSuggestionDismiss={uiState.abortPromptSuggestion} + clipboardUnavailableShownRef={clipboardUnavailableShownRef} /> )} diff --git a/packages/cli/src/ui/components/InputPrompt.test.tsx b/packages/cli/src/ui/components/InputPrompt.test.tsx index b5070e53121..85f108d5b0c 100644 --- a/packages/cli/src/ui/components/InputPrompt.test.tsx +++ b/packages/cli/src/ui/components/InputPrompt.test.tsx @@ -1340,6 +1340,58 @@ describe('InputPrompt', () => { unmount(); }); + it('should show the native clipboard error only once across remounts', async () => { + const addItem = vi.fn(); + const clipboardUnavailableShownRef = { current: false }; + mockedUseUIState.mockReturnValue({ + isFeedbackDialogOpen: false, + messageQueue: [], + pendingGeminiHistoryItems: [], + historyManager: { addItem }, + } as unknown as ReturnType); + vi.mocked(clipboardUtils.clipboardHasImage).mockImplementation( + async (onUnavailable) => { + onUnavailable?.(); + return false; + }, + ); + + const first = renderWithProviders( + , + ); + await wait(); + + const pasteKey = isWindows ? '\x1Bv' : '\x16'; + first.stdin.write(pasteKey); + await wait(); + first.stdin.write(pasteKey); + await wait(); + first.unmount(); + + const second = renderWithProviders( + , + ); + await wait(); + second.stdin.write(pasteKey); + await wait(); + + expect(addItem).toHaveBeenCalledTimes(1); + expect(addItem).toHaveBeenCalledWith( + { + type: 'error', + text: 'Clipboard image paste is unavailable because the native clipboard module could not be loaded. Reinstall Qwen Code or use the npm installation method.', + }, + expect.any(Number), + ); + second.unmount(); + }); + it('should handle image save failure gracefully', async () => { vi.mocked(clipboardUtils.clipboardHasImage).mockResolvedValue(true); vi.mocked(clipboardUtils.saveClipboardImage).mockResolvedValue(null); diff --git a/packages/cli/src/ui/components/InputPrompt.tsx b/packages/cli/src/ui/components/InputPrompt.tsx index 64173908c31..9876c534bac 100644 --- a/packages/cli/src/ui/components/InputPrompt.tsx +++ b/packages/cli/src/ui/components/InputPrompt.tsx @@ -179,6 +179,7 @@ export interface InputPromptProps { promptSuggestion?: string | null; /** Called when prompt suggestion is dismissed (user typed) */ onPromptSuggestionDismiss?: () => void; + clipboardUnavailableShownRef?: React.MutableRefObject; } // Re-export from shared utils for backwards compatibility @@ -212,6 +213,7 @@ export const InputPrompt: React.FC = ({ isEmbeddedShellFocused, promptSuggestion, onPromptSuggestionDismiss, + clipboardUnavailableShownRef: sessionClipboardUnavailableShownRef, }) => { const isShellFocused = useShellFocusState(); const uiState = useUIState(); @@ -274,6 +276,9 @@ export const InputPrompt: React.FC = ({ const [attachments, setAttachments] = useState([]); const [isAttachmentMode, setIsAttachmentMode] = useState(false); const [selectedAttachmentIndex, setSelectedAttachmentIndex] = useState(-1); + const localClipboardUnavailableShownRef = useRef(false); + const clipboardUnavailableShownRef = + sessionClipboardUnavailableShownRef ?? localClipboardUnavailableShownRef; // Large paste placeholder handling const [pendingPastes, setPendingPastes] = useState>( new Map(), @@ -686,32 +691,54 @@ export const InputPrompt: React.FC = ({ resetCommandSearchCompletionState, ]); + const reportClipboardUnavailable = useCallback(() => { + if (clipboardUnavailableShownRef.current) { + return; + } + clipboardUnavailableShownRef.current = true; + uiState.historyManager?.addItem( + { + type: 'error', + text: t( + 'Clipboard image paste is unavailable because the native clipboard module could not be loaded. Reinstall Qwen Code or use the npm installation method.', + ), + }, + Date.now(), + ); + }, [clipboardUnavailableShownRef, uiState.historyManager]); + // Handle clipboard image pasting with Ctrl+V - const handleClipboardImage = useCallback(async (validated = false) => { - try { - const hasImage = validated || (await clipboardHasImage()); - if (hasImage) { - const imagePath = await saveClipboardImage(Storage.getGlobalTempDir()); - if (imagePath) { - // Clean up old images - cleanupOldClipboardImages(Storage.getGlobalTempDir()).catch(() => { - // Ignore cleanup errors - }); + const handleClipboardImage = useCallback( + async (validated = false) => { + try { + const hasImage = + validated || (await clipboardHasImage(reportClipboardUnavailable)); + if (hasImage) { + const imagePath = await saveClipboardImage( + Storage.getGlobalTempDir(), + ); + if (imagePath) { + // Clean up old images + cleanupOldClipboardImages(Storage.getGlobalTempDir()).catch(() => { + // Ignore cleanup errors + }); - // Add as attachment instead of inserting @reference into text - const filename = path.basename(imagePath); - const newAttachment: Attachment = { - id: String(Date.now()), - path: imagePath, - filename, - }; - setAttachments((prev) => [...prev, newAttachment]); + // Add as attachment instead of inserting @reference into text + const filename = path.basename(imagePath); + const newAttachment: Attachment = { + id: String(Date.now()), + path: imagePath, + filename, + }; + setAttachments((prev) => [...prev, newAttachment]); + } } + } catch (error) { + debugLogger.error('Error handling clipboard image:', error); } - } catch (error) { - debugLogger.error('Error handling clipboard image:', error); - } - }, []); + }, + [reportClipboardUnavailable], + ); // Promote a paste that is purely image-file path(s) (e.g. a terminal/clipboard // helper that injects `@` text on Cmd+V) into attachment chips, so the @@ -967,6 +994,9 @@ export const InputPrompt: React.FC = ({ // Ensure we never accidentally interpret paste as regular input. const pastedImagePaths = classifyPastedImagePaths(pasted); + if (key.clipboardImageUnavailable) { + reportClipboardUnavailable(); + } if (key.pasteImage) { handleClipboardImage(true); } else if ( @@ -1705,6 +1735,7 @@ export const InputPrompt: React.FC = ({ shellHistory, reverseSearchCompletion, handleClipboardImage, + reportClipboardUnavailable, promotePastedImagePaths, resetCompletionState, dismissCompletion, diff --git a/packages/cli/src/ui/contexts/KeypressContext.test.tsx b/packages/cli/src/ui/contexts/KeypressContext.test.tsx index a7648e22009..71ceae914b8 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.test.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.test.tsx @@ -22,6 +22,12 @@ import { import { useStdin } from 'ink'; import { EventEmitter } from 'node:events'; +const mockClipboardHasImage = vi.hoisted(() => vi.fn()); + +vi.mock('../utils/clipboardUtils.js', () => ({ + clipboardHasImage: mockClipboardHasImage, +})); + // Mock the 'ink' module to control stdin vi.mock('ink', async (importOriginal) => { const original = await importOriginal(); @@ -83,6 +89,7 @@ describe('KeypressContext - Kitty Protocol', () => { beforeEach(() => { vi.clearAllMocks(); + mockClipboardHasImage.mockResolvedValue(false); stdin = new MockStdin(); (useStdin as Mock).mockReturnValue({ stdin, @@ -607,6 +614,27 @@ describe('KeypressContext - Kitty Protocol', () => { ); }); + it('reports an unavailable native module for an empty paste', async () => { + const keyHandler = vi.fn(); + mockClipboardHasImage.mockImplementation(async (onUnavailable) => { + onUnavailable?.(); + return false; + }); + const { result } = renderHook(() => useKeypressContext(), { wrapper }); + act(() => result.current.subscribe(keyHandler)); + + act(() => stdin.sendPaste('')); + + await waitFor(() => { + expect(keyHandler).toHaveBeenCalledWith( + expect.objectContaining({ + paste: true, + clipboardImageUnavailable: true, + }), + ); + }); + }); + describe('paste mode markers', () => { // These tests use pasteWorkaround=true to force passthrough mode for raw keypress testing diff --git a/packages/cli/src/ui/contexts/KeypressContext.tsx b/packages/cli/src/ui/contexts/KeypressContext.tsx index 9c3f4d9dcb8..0089844b983 100644 --- a/packages/cli/src/ui/contexts/KeypressContext.tsx +++ b/packages/cli/src/ui/contexts/KeypressContext.tsx @@ -121,6 +121,7 @@ export interface Key { sequence: string; kittyProtocol?: boolean; pasteImage?: boolean; + clipboardImageUnavailable?: boolean; } export type KeypressHandler = (key: Key) => void; @@ -857,7 +858,10 @@ export function KeypressProvider({ sequence: pasteBuffer.toString(), }); } else { - const hasImage = await clipboardHasImage(); + let clipboardImageUnavailable = false; + const hasImage = await clipboardHasImage(() => { + clipboardImageUnavailable = true; + }); broadcast({ name: '', ctrl: false, @@ -865,6 +869,7 @@ export function KeypressProvider({ shift: false, paste: true, pasteImage: hasImage, + clipboardImageUnavailable, sequence: pasteBuffer.toString(), }); } diff --git a/packages/cli/src/ui/utils/clipboardUtils.test.ts b/packages/cli/src/ui/utils/clipboardUtils.test.ts index f37e4620528..cb466df657d 100644 --- a/packages/cli/src/ui/utils/clipboardUtils.test.ts +++ b/packages/cli/src/ui/utils/clipboardUtils.test.ts @@ -8,24 +8,35 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { EventEmitter } from 'node:events'; // Use vi.hoisted to define mock functions before vi.mock is hoisted -const { mockSpawn, mockExecSync } = vi.hoisted(() => ({ +const { mockSpawn, mockExecSync, clipboardMockState } = vi.hoisted(() => ({ mockSpawn: vi.fn(), mockExecSync: vi.fn(), + clipboardMockState: { failLoad: false, loadDelayMs: 0 }, })); // Mock @teddyzhu/clipboard -vi.mock('@teddyzhu/clipboard', () => ({ - default: { +vi.mock('@teddyzhu/clipboard', async () => { + if (clipboardMockState.loadDelayMs > 0) { + await new Promise((resolve) => + setTimeout(resolve, clipboardMockState.loadDelayMs), + ); + } + if (clipboardMockState.failLoad) { + throw new Error('native clipboard module missing'); + } + return { + default: { + ClipboardManager: vi.fn().mockImplementation(() => ({ + hasFormat: vi.fn().mockReturnValue(false), + getImageData: vi.fn().mockReturnValue({ data: null }), + })), + }, ClipboardManager: vi.fn().mockImplementation(() => ({ hasFormat: vi.fn().mockReturnValue(false), getImageData: vi.fn().mockReturnValue({ data: null }), })), - }, - ClipboardManager: vi.fn().mockImplementation(() => ({ - hasFormat: vi.fn().mockReturnValue(false), - getImageData: vi.fn().mockReturnValue({ data: null }), - })), -})); + }; +}); // Mock node:child_process vi.mock('node:child_process', () => ({ @@ -153,6 +164,8 @@ describe('clipboardUtils', () => { ); await realFs.rm('/tmp/test', { recursive: true, force: true }); + clipboardMockState.failLoad = false; + clipboardMockState.loadDelayMs = 0; vi.resetModules(); vi.clearAllMocks(); @@ -525,6 +538,42 @@ describe('clipboardUtils', () => { }); describe('macOS/Windows fallback', () => { + it('notifies after a cached native module load failure', async () => { + clipboardMockState.failLoad = true; + vi.resetModules(); + const mod = await import('./clipboardUtils.js'); + Object.defineProperty(process, 'platform', { + value: 'darwin', + configurable: true, + writable: true, + }); + + await expect(mod.clipboardHasImage()).resolves.toBe(false); + const onUnavailable = vi.fn(); + await expect(mod.clipboardHasImage(onUnavailable)).resolves.toBe(false); + expect(onUnavailable).toHaveBeenCalledOnce(); + }); + + it('shares an in-flight native module load without false errors', async () => { + clipboardMockState.loadDelayMs = 20; + vi.resetModules(); + const mod = await import('./clipboardUtils.js'); + Object.defineProperty(process, 'platform', { + value: 'darwin', + configurable: true, + writable: true, + }); + const onUnavailable = vi.fn(); + + await expect( + Promise.all([ + mod.clipboardHasImage(onUnavailable), + mod.clipboardHasImage(onUnavailable), + ]), + ).resolves.toEqual([false, false]); + expect(onUnavailable).not.toHaveBeenCalled(); + }); + it('should return false on non-linux platform when @teddyzhu/clipboard fails', async () => { const originalPlatform = process.platform; Object.defineProperty(process, 'platform', { diff --git a/packages/cli/src/ui/utils/clipboardUtils.ts b/packages/cli/src/ui/utils/clipboardUtils.ts index e8a7c99b355..b0ce01b7b38 100644 --- a/packages/cli/src/ui/utils/clipboardUtils.ts +++ b/packages/cli/src/ui/utils/clipboardUtils.ts @@ -65,8 +65,7 @@ let cachedWlPasteImageTypes: string[] | null = null; // Cache for @teddyzhu/clipboard module (macOS/Windows fallback) // eslint-disable-next-line @typescript-eslint/no-explicit-any -let cachedClipboardModule: any = null; -let clipboardLoadAttempted = false; +let clipboardModulePromise: Promise | null = null; /** * Get and cache the @teddyzhu/clipboard module. @@ -74,19 +73,16 @@ let clipboardLoadAttempted = false; */ // eslint-disable-next-line @typescript-eslint/no-explicit-any async function getClipboardModule(): Promise { - if (clipboardLoadAttempted) return cachedClipboardModule; - clipboardLoadAttempted = true; - - try { + if (!clipboardModulePromise) { const modName = '@teddyzhu/clipboard'; - cachedClipboardModule = await import(modName); - return cachedClipboardModule; - } catch (_e) { - debugLogger.error( - 'Failed to load @teddyzhu/clipboard native module. Clipboard image features will be unavailable.', - ); - return null; + clipboardModulePromise = import(modName).catch(() => { + debugLogger.error( + 'Failed to load @teddyzhu/clipboard native module. Clipboard image features will be unavailable.', + ); + return null; + }); } + return clipboardModulePromise; } /** @@ -301,9 +297,12 @@ async function checkClipboardForImage( /** * Checks if the system clipboard contains an image. * Uses platform-native tools (wl-paste/xclip) on Linux. + * @param onUnavailable Called when the macOS/Windows native module cannot load. * @returns true if clipboard contains an image */ -export async function clipboardHasImage(): Promise { +export async function clipboardHasImage( + onUnavailable?: () => void, +): Promise { cachedWlPasteImageTypes = null; // Fresh check each time if (process.platform === 'linux') { try { @@ -328,7 +327,10 @@ export async function clipboardHasImage(): Promise { try { const mod = await getClipboardModule(); - if (!mod) return false; + if (!mod) { + onUnavailable?.(); + return false; + } const clipboard = new mod.ClipboardManager(); return clipboard.hasFormat('image'); } catch (error) { diff --git a/scripts/build-standalone-release.js b/scripts/build-standalone-release.js index b0bf2bd1706..91066761355 100644 --- a/scripts/build-standalone-release.js +++ b/scripts/build-standalone-release.js @@ -14,7 +14,10 @@ import path from 'node:path'; import { Readable } from 'node:stream'; import { pipeline } from 'node:stream/promises'; import { fileURLToPath } from 'node:url'; -import { writeSha256Sums } from './create-standalone-package.js'; +import { + TARGET_CLIPBOARD_PACKAGE, + writeSha256Sums, +} from './create-standalone-package.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -79,6 +82,7 @@ async function main() { const checksumsPath = path.join(runtimeDir, 'SHASUMS256.txt'); await downloadFile(`${nodeDistUrl}/SHASUMS256.txt`, checksumsPath); const checksums = parseChecksums(fs.readFileSync(checksumsPath, 'utf8')); + const nativeModulesDir = stageClipboardPackages(runtimeDir); for (const target of RELEASE_TARGETS) { await packageTarget({ @@ -89,6 +93,7 @@ async function main() { releaseVersion: args.version, runtimeDir, checksums, + nativeModulesDir, }); } @@ -113,6 +118,7 @@ async function packageTarget({ releaseVersion, runtimeDir, checksums, + nativeModulesDir, }) { const archiveName = `node-v${nodeVersion}-${nodeTarget}.${nodeArchiveExtension}`; const archivePath = path.join(runtimeDir, archiveName); @@ -126,6 +132,8 @@ async function packageTarget({ qwenTarget, '--node-archive', archivePath, + '--native-modules-dir', + nativeModulesDir, '--out-dir', outDir, '--skip-checksums', @@ -140,6 +148,63 @@ async function packageTarget({ }); } +function readClipboardPackageSpecs() { + const packageLock = JSON.parse( + fs.readFileSync(path.join(rootDir, 'package-lock.json'), 'utf8'), + ); + const cliPackage = JSON.parse( + fs.readFileSync( + path.join(rootDir, 'packages', 'cli', 'package.json'), + 'utf8', + ), + ); + const packageNames = [ + '@teddyzhu/clipboard', + ...new Set(TARGET_CLIPBOARD_PACKAGE.values()), + ]; + + return packageNames.map((packageName) => { + const version = + packageLock.packages?.[`node_modules/${packageName}`]?.version; + const declaredVersion = cliPackage.optionalDependencies?.[packageName]; + if (!version || ![version, `^${version}`].includes(declaredVersion)) { + fail(`Clipboard package version is not locked for ${packageName}`); + } + return `${packageName}@${version}`; + }); +} + +function stageClipboardPackages(runtimeDir) { + const installDir = path.join(runtimeDir, 'clipboard-modules'); + fs.mkdirSync(installDir, { recursive: true }); + console.log('Staging standalone clipboard native packages'); + const npmExecPath = process.env.npm_execpath; + if (!npmExecPath) { + fail('npm_execpath is unavailable; run package:standalone:release via npm'); + } + execFileSync( + process.execPath, + [ + npmExecPath, + 'install', + '--prefix', + installDir, + '--package-lock=false', + '--no-save', + '--ignore-scripts', + '--force', + '--no-audit', + '--no-fund', + ...readClipboardPackageSpecs(), + ], + { + cwd: rootDir, + stdio: 'inherit', + }, + ); + return path.join(installDir, 'node_modules'); +} + async function downloadFile(url, destination) { console.log(`Downloading ${url}`); const response = await fetch(url); @@ -297,4 +362,9 @@ function fail(message) { throw new Error(`ERROR: ${message}`); } -export { assertStandaloneOutput, parseChecksums, RELEASE_TARGETS }; +export { + assertStandaloneOutput, + parseChecksums, + readClipboardPackageSpecs, + RELEASE_TARGETS, +}; diff --git a/scripts/create-standalone-package.js b/scripts/create-standalone-package.js index 66053c8e852..e3e35db606f 100644 --- a/scripts/create-standalone-package.js +++ b/scripts/create-standalone-package.js @@ -48,6 +48,14 @@ const TARGET_PREBUILD_DIR = new Map([ ['win-x64', 'win32-x64'], ]); +const TARGET_CLIPBOARD_PACKAGE = new Map([ + ['darwin-arm64', '@teddyzhu/clipboard-darwin-arm64'], + ['darwin-x64', '@teddyzhu/clipboard-darwin-x64'], + ['linux-arm64', '@teddyzhu/clipboard-linux-arm64-gnu'], + ['linux-x64', '@teddyzhu/clipboard-linux-x64-gnu'], + ['win-x64', '@teddyzhu/clipboard-win32-x64-msvc'], +]); + const DIST_REQUIRED_PATHS = [ 'cli.js', 'cli-entry.js', @@ -135,6 +143,7 @@ async function main() { copyRuntimeAssets(packageRoot, outDir); copyNativeAddon(packageRoot, target); + copyClipboardAddon(packageRoot, target, args.nativeModulesDir); extractNodeArchive(nodeArchive, runtimeExtractDir); const nodeDir = path.join(packageRoot, 'node'); copyExtractedNode(runtimeExtractDir, nodeDir); @@ -172,6 +181,7 @@ function isMainModule() { function parseArgs(argv) { const args = { help: false, + nativeModulesDir: undefined, outDir: undefined, nodeArchive: undefined, skipChecksums: false, @@ -194,6 +204,10 @@ function parseArgs(argv) { args.nodeArchive = readOptionValue(argv, index, arg); index += 1; break; + case '--native-modules-dir': + args.nativeModulesDir = readOptionValue(argv, index, arg); + index += 1; + break; case '--out-dir': args.outDir = readOptionValue(argv, index, arg); index += 1; @@ -229,11 +243,14 @@ Usage: Options: --target TARGET One of: ${Array.from(TARGETS.keys()).join(', ')} - --node-archive PATH Downloaded Node.js runtime archive. - --out-dir DIR Output directory. Defaults to dist/standalone. - --version VERSION Qwen Code version. Defaults to package.json version. - --skip-checksums Do not update SHA256SUMS. Used by release packaging. - -h, --help Show this help message.`); + --node-archive PATH Downloaded Node.js runtime archive. + --native-modules-dir DIR + Staged native node_modules directory. Missing + clipboard packages are fatal when this is supplied. + --out-dir DIR Output directory. Defaults to dist/standalone. + --version VERSION Qwen Code version. Defaults to package.json version. + --skip-checksums Do not update SHA256SUMS. Used by release packaging. + -h, --help Show this help message.`); } function assertRequiredInputs() { @@ -271,8 +288,8 @@ function copyRuntimeAssets(packageRoot, outDir) { fs.mkdirSync(libDir, { recursive: true }); for (const entry of fs.readdirSync(distDir)) { - // Standalone rebuilds a clean, target-trimmed lib/node_modules via - // copyNativeAddon(). If a local dist/node_modules exists from older + // Standalone rebuilds a clean, target-trimmed lib/node_modules via the + // native addon copy steps. If a local dist/node_modules exists from older // packaging output or manual testing, copying it would drag in unrelated // packages or every platform's native prebuild. if ( @@ -376,6 +393,53 @@ function copyNativeAddon(packageRoot, target) { assertNoSymlinks(modulesDir, 'Bundled native addon still contains symlinks.'); } +function copyClipboardAddon(packageRoot, target, nativeModulesDir) { + const modulesSrc = path.resolve( + nativeModulesDir || path.join(rootDir, 'node_modules'), + ); + const nativePackage = TARGET_CLIPBOARD_PACKAGE.get(target); + const packageNames = ['@teddyzhu/clipboard', nativePackage]; + const packageSources = packageNames.map((packageName) => + path.join(modulesSrc, packageName), + ); + const nativePackageSrc = packageSources[1]; + const hasRequiredFiles = + packageSources.every((packageSrc) => + fs.existsSync(path.join(packageSrc, 'package.json')), + ) && + fs.readdirSync(nativePackageSrc).some((entry) => entry.endsWith('.node')); + + if (!hasRequiredFiles) { + const message = `clipboard packages for ${target} are missing from ${modulesSrc}`; + if (nativeModulesDir) { + fail(`Required ${message}`); + } + console.warn( + `[standalone] ${message}; bundling without clipboard image support.`, + ); + return; + } + + const modulesDest = path.join(packageRoot, 'lib', 'node_modules'); + const copyOpts = { + recursive: true, + dereference: true, + verbatimSymlinks: false, + }; + for (let index = 0; index < packageNames.length; index += 1) { + fs.cpSync( + packageSources[index], + path.join(modulesDest, packageNames[index]), + copyOpts, + ); + } + + assertNoSymlinks( + modulesDest, + 'Bundled clipboard addon still contains symlinks.', + ); +} + function hasNativePrebuild(prebuildDir) { return ( fs.existsSync(prebuildDir) && @@ -733,4 +797,4 @@ function fail(message) { throw new Error(`Error: ${message}`); } -export { TARGETS, writeSha256Sums }; +export { TARGET_CLIPBOARD_PACKAGE, TARGETS, writeSha256Sums }; diff --git a/scripts/installation/INSTALLATION_GUIDE.md b/scripts/installation/INSTALLATION_GUIDE.md index f7ff9a1a70b..eba3b7599ec 100644 --- a/scripts/installation/INSTALLATION_GUIDE.md +++ b/scripts/installation/INSTALLATION_GUIDE.md @@ -143,11 +143,10 @@ install-qwen-standalone.bat --repair-path --path-scope machine ## Optional Native Modules -The standalone archives bundle Qwen Code and a private Node.js runtime. They do -not currently install npm optional native modules such as `node-pty` and -`@teddyzhu/clipboard`. Qwen Code is designed to degrade when these optional -modules are absent, but terminal pty behavior and clipboard image support may -not be identical to an npm installation. +The standalone archives bundle Qwen Code, a private Node.js runtime, and the +matching `@teddyzhu/clipboard` native package for clipboard image paste. They +do not currently install every npm optional native module, such as `node-pty`, +so terminal pty behavior may not be identical to an npm installation. Use `--method npm` if you specifically need npm to resolve optional native modules for the current machine. diff --git a/scripts/tests/install-script.test.js b/scripts/tests/install-script.test.js index f85ec20d2a5..5a9fdb36d1f 100644 --- a/scripts/tests/install-script.test.js +++ b/scripts/tests/install-script.test.js @@ -29,6 +29,9 @@ const readScript = (path) => readFileSync(path, 'utf8'); const standaloneReleaseScriptUrl = pathToFileURL( path.resolve('scripts/build-standalone-release.js'), ).href; +const standalonePackageScriptUrl = pathToFileURL( + path.resolve('scripts/create-standalone-package.js'), +).href; const hostedInstallationScriptUrl = pathToFileURL( path.resolve('scripts/build-hosted-installation-assets.js'), ).href; @@ -642,6 +645,7 @@ describe('standalone release packaging', () => { expect(packageScript).toContain('symlink cycle'); expect(packageScript).toContain('refusing to write empty SHA256SUMS'); expect(packageScript).toContain('--skip-checksums'); + expect(packageScript).toContain('--native-modules-dir'); expect(packageScript).toContain('dereference: true'); expect(packageScript).toContain('fs.createReadStream'); expect(packageScript).toContain('Expand-Archive'); @@ -796,6 +800,35 @@ describe('standalone release packaging', () => { expect(checksums.get('node-v22.0.0-win-x64.zip')).toBe('b'.repeat(64)); }); + it('stages the locked clipboard packages for every release target', async () => { + const { readClipboardPackageSpecs } = await import( + standaloneReleaseScriptUrl + ); + + expect(readClipboardPackageSpecs()).toEqual([ + '@teddyzhu/clipboard@0.0.5', + '@teddyzhu/clipboard-darwin-arm64@0.0.5', + '@teddyzhu/clipboard-darwin-x64@0.0.5', + '@teddyzhu/clipboard-linux-arm64-gnu@0.0.5', + '@teddyzhu/clipboard-linux-x64-gnu@0.0.5', + '@teddyzhu/clipboard-win32-x64-msvc@0.0.5', + ]); + }); + + it('maps every release target to its clipboard native package', async () => { + const { TARGET_CLIPBOARD_PACKAGE } = await import( + standalonePackageScriptUrl + ); + + expect([...TARGET_CLIPBOARD_PACKAGE]).toEqual([ + ['darwin-arm64', '@teddyzhu/clipboard-darwin-arm64'], + ['darwin-x64', '@teddyzhu/clipboard-darwin-x64'], + ['linux-arm64', '@teddyzhu/clipboard-linux-arm64-gnu'], + ['linux-x64', '@teddyzhu/clipboard-linux-x64-gnu'], + ['win-x64', '@teddyzhu/clipboard-win32-x64-msvc'], + ]); + }); + it('validates standalone release checksum output', async () => { const { assertStandaloneOutput, RELEASE_TARGETS } = await import( standaloneReleaseScriptUrl @@ -1962,6 +1995,65 @@ describe('standalone release packaging', () => { } }); + itOnUnix('packages only the matching clipboard native addon', () => { + const createdDist = ensureMinimalDist(); + const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); + + try { + const nativeModulesDir = createFakeClipboardModules(tmpDir, [ + '@teddyzhu/clipboard-linux-x64-gnu', + '@teddyzhu/clipboard-darwin-arm64', + ]); + const archive = packageFakeStandalone(tmpDir, {}, { nativeModulesDir }); + const extractDir = path.join(tmpDir, 'extract'); + mkdirSync(extractDir, { recursive: true }); + execFileSync('tar', ['-xzf', archive, '-C', extractDir], { + stdio: 'ignore', + }); + + const clipboardScope = path.join( + extractDir, + 'qwen-code', + 'lib', + 'node_modules', + '@teddyzhu', + ); + expect( + existsSync(path.join(clipboardScope, 'clipboard', 'index.js')), + ).toBe(true); + expect( + existsSync( + path.join( + clipboardScope, + 'clipboard-linux-x64-gnu', + 'clipboard.linux-x64-gnu.node', + ), + ), + ).toBe(true); + expect( + existsSync(path.join(clipboardScope, 'clipboard-darwin-arm64')), + ).toBe(false); + } finally { + restoreMinimalDist(createdDist); + rmSync(tmpDir, { recursive: true, force: true }); + } + }); + + itOnUnix('rejects incomplete explicit clipboard staging', () => { + const createdDist = ensureMinimalDist(); + const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); + + try { + const nativeModulesDir = createFakeClipboardModules(tmpDir, []); + expect(() => + packageFakeStandalone(tmpDir, {}, { nativeModulesDir }), + ).toThrow(/Required clipboard packages for linux-x64/); + } finally { + restoreMinimalDist(createdDist); + rmSync(tmpDir, { recursive: true, force: true }); + } + }); + itOnUnix('dereferences safe Node.js runtime symlinks', () => { const createdDist = ensureMinimalDist(); const tmpDir = mkdtempSync(path.join(tmpdir(), 'qwen-package-test-')); @@ -4426,27 +4518,58 @@ function extractZipForTest(archive, destination) { }); } -function packageFakeStandalone(tmpDir, nodeArchiveOptions = {}) { +function packageFakeStandalone( + tmpDir, + nodeArchiveOptions = {}, + { nativeModulesDir } = {}, +) { const outDir = path.join(tmpDir, 'out'); mkdirSync(outDir, { recursive: true }); - execFileSync( - 'node', - [ - 'scripts/create-standalone-package.js', - '--target', - 'linux-x64', - '--node-archive', - createFakeNodeArchive(tmpDir, nodeArchiveOptions), - '--out-dir', - outDir, - '--version', - '0.0.0-smoke', - ], - { stdio: 'pipe' }, - ); + const args = [ + 'scripts/create-standalone-package.js', + '--target', + 'linux-x64', + '--node-archive', + createFakeNodeArchive(tmpDir, nodeArchiveOptions), + '--out-dir', + outDir, + '--version', + '0.0.0-smoke', + ]; + if (nativeModulesDir) { + args.push('--native-modules-dir', nativeModulesDir); + } + execFileSync('node', args, { stdio: 'pipe' }); return path.join(outDir, 'qwen-code-linux-x64.tar.gz'); } +function createFakeClipboardModules(tmpDir, nativePackages) { + const modulesDir = path.join(tmpDir, 'clipboard-modules'); + const clipboardDir = path.join(modulesDir, '@teddyzhu', 'clipboard'); + mkdirSync(clipboardDir, { recursive: true }); + writeFileSync( + path.join(clipboardDir, 'package.json'), + JSON.stringify({ name: '@teddyzhu/clipboard', version: '0.0.5' }), + ); + writeFileSync(path.join(clipboardDir, 'index.js'), 'module.exports = {};\n'); + + for (const packageName of nativePackages) { + const packageDir = path.join(modulesDir, packageName); + const binaryName = packageName.replace( + '@teddyzhu/clipboard-', + 'clipboard.', + ); + mkdirSync(packageDir, { recursive: true }); + writeFileSync( + path.join(packageDir, 'package.json'), + JSON.stringify({ name: packageName, version: '0.0.5' }), + ); + writeFileSync(path.join(packageDir, `${binaryName}.node`), 'native\n'); + } + + return modulesDir; +} + function runUnixInstaller( archive, installRoot,