feat: integrate Jieba support and enhance VC++ redistributable handling for Windows
This commit is contained in:
1 parent
0ea1860085
commit
e84f8360d5
12 files changed
+151
-108
No files matched your search
@@ -9,9 +9,9 @@ The following DLLs are redistributed unmodified from the official
|
||||
- `vcruntime140.dll`
|
||||
- `vcruntime140_1.dll`
|
||||
|
||||
They are deployed app-locally next to the `onnxruntime-node` native binaries so
|
||||
that the ONNX Runtime embedding model can load without requiring a separate
|
||||
system-wide VC++ Redistributable installation.
|
||||
They are deployed app-locally next to the `onnxruntime-node` and
|
||||
`@node-rs/jieba-win32-x64-msvc` native binaries so that ONNX Runtime and Jieba can
|
||||
load without requiring a separate system-wide VC++ Redistributable installation.
|
||||
|
||||
These files are Microsoft's "Visual C++ Redistributable" redistributable code
|
||||
and are governed by the Microsoft Software License Terms for the Visual Studio
|
||||
@@ -20,7 +20,7 @@ or repackage these binaries; updates must be sourced from Microsoft.
|
||||
|
||||
SHA-256 (for verification):
|
||||
|
||||
- msvcp140.dll 0f885b509a685d2bbfa652fed26b5fb31d88fbdab0a978c641d1c7b8aa460aa9
|
||||
- msvcp140_1.dll bfad5aef4c63a669e3c140655cdfdf395b6c979b400a447bd5dcb65ed8826c3d
|
||||
- vcruntime140.dll d5e4d9a3e835fa679450145d6a7d94e36573a509317111904d9b3712c30d9066
|
||||
- msvcp140.dll 0f885b509a685d2bbfa652fed26b5fb31d88fbdab0a978c641d1c7b8aa460aa9
|
||||
- msvcp140_1.dll bfad5aef4c63a669e3c140655cdfdf395b6c979b400a447bd5dcb65ed8826c3d
|
||||
- vcruntime140.dll d5e4d9a3e835fa679450145d6a7d94e36573a509317111904d9b3712c30d9066
|
||||
- vcruntime140_1.dll 1f2d41c4aa5db0bc33ebf7b66d72943a817d7ce6cbe880502a9403823633093f
|
||||
@@ -17,6 +17,7 @@ prepare 必须在 Apple Silicon Mac;需已信任的 `Ncurator Update Signing`
|
||||
- electron-builder 以 `release-stage/app` 为唯一应用输入,开发工作区不直接进入安装包。
|
||||
- 安装包包含构建后的应用、生产依赖与运行资源,排除开发和测试资料、`.env`、私钥、Source Map 及其他平台文件。`app.asar` 与 `VITE_*` 可由用户读取,不作为凭证保护手段。
|
||||
- 生产依赖按追踪结果裁剪,并验证关键读写路径;追踪触达待裁剪目录时构建失败,避免误删运行依赖。
|
||||
- Windows x64 在 ONNX Runtime 与 Jieba 原生模块旁各自部署 VC++ 运行库,不要求用户预装系统运行库;安装包审计检查两处实际解包的二进制与 DLL,staging 实际加载 Jieba 默认词典并验证中文分词。
|
||||
- 网页抓取 sidecar 保留 Playwright 驱动 JS,复用 Electron 提供的 Node 与浏览器,不重复打包这些运行时。构建、缓存复用和打包后检查 sidecar 能否实际启动。
|
||||
- 候选 bundle 绑定 Tag、commit、fragment、安装包、更新文件与哈希,测试和发布消费同一份可校验产物。
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ asarUnpack:
|
||||
- '**/node_modules/node-pty/prebuilds/**/*'
|
||||
- '**/node_modules/@napi-rs/canvas/**/*'
|
||||
- '**/node_modules/@napi-rs/canvas-*/**/*'
|
||||
- '**/node_modules/@node-rs/jieba-*/**/*'
|
||||
win:
|
||||
icon: build/icon.ico
|
||||
executableName: 馆长
|
||||
|
||||
@@ -571,7 +571,7 @@ const main = async () => {
|
||||
if (audit.status !== 0) throw new Error('staging package security audit failed')
|
||||
|
||||
process.stdout.write(
|
||||
`[package] staging ready for ${process.platform}-${process.arch}; runtime roots: ${runtimePackageNames.join(', ')}; packaged dependencies: ${String(Object.keys(packagedDependencies).length)}; targeted pruning: ${(runtimePruning.totalBytes / 1024 / 1024).toFixed(1)} MiB; runtime smoke: ${String(runtimeVerification.docxDocuments)} DOCX / ${String(runtimeVerification.pdfDocuments)} PDF document(s), MathJax SVG, ExcelJS round trip; obfuscated main: ${String(obfuscation.totals.files)} file(s)\n`
|
||||
`[package] staging ready for ${process.platform}-${process.arch}; runtime roots: ${runtimePackageNames.join(', ')}; packaged dependencies: ${String(Object.keys(packagedDependencies).length)}; targeted pruning: ${(runtimePruning.totalBytes / 1024 / 1024).toFixed(1)} MiB; runtime smoke: Jieba ${String(runtimeVerification.jiebaTokens)} token(s), ${String(runtimeVerification.docxDocuments)} DOCX / ${String(runtimeVerification.pdfDocuments)} PDF document(s), MathJax SVG, ExcelJS round trip; obfuscated main: ${String(obfuscation.totals.files)} file(s)\n`
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,16 +8,25 @@ export const VC_REDIST_DLL_NAMES = [
|
||||
'vcruntime140_1.dll'
|
||||
]
|
||||
|
||||
const ONNX_BIN_SEGMENTS = ['node_modules', 'onnxruntime-node', 'bin', 'napi-v3', 'win32', 'x64']
|
||||
const WINDOWS_NATIVE_BINARIES = [
|
||||
'node_modules/onnxruntime-node/bin/napi-v3/win32/x64/onnxruntime_binding.node',
|
||||
'node_modules/@node-rs/jieba-win32-x64-msvc/jieba.win32-x64-msvc.node'
|
||||
]
|
||||
|
||||
const isWindowsX64 = (platform, arch) => platform === 'win32' && arch === 'x64'
|
||||
|
||||
const assertDirectory = (directory, label) => {
|
||||
if (!fs.existsSync(directory) || !fs.statSync(directory).isDirectory()) {
|
||||
throw new Error(`${label} not found: ${directory}`)
|
||||
const assertFile = (filePath, label) => {
|
||||
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
||||
throw new Error(`${label} missing: ${filePath}`)
|
||||
}
|
||||
}
|
||||
|
||||
const resolveNativeBinaries = (root) =>
|
||||
WINDOWS_NATIVE_BINARIES.map((relativePath) => path.join(root, ...relativePath.split('/')))
|
||||
|
||||
export const resolvePackagedWindowsNativeBinaries = (appRoot) =>
|
||||
resolveNativeBinaries(path.join(appRoot, 'resources', 'app.asar.unpacked'))
|
||||
|
||||
export const installWindowsVcRuntime = ({
|
||||
stageDir,
|
||||
vcRedistDir,
|
||||
@@ -26,34 +35,34 @@ export const installWindowsVcRuntime = ({
|
||||
}) => {
|
||||
if (!isWindowsX64(platform, arch)) return
|
||||
|
||||
assertDirectory(vcRedistDir, 'VC++ redistributable source directory')
|
||||
const targetDirectory = path.join(stageDir, ...ONNX_BIN_SEGMENTS)
|
||||
assertDirectory(targetDirectory, 'ONNX Runtime target binary directory')
|
||||
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
const sources = VC_REDIST_DLL_NAMES.map((filename) => {
|
||||
const source = path.join(vcRedistDir, filename)
|
||||
if (!fs.existsSync(source) || !fs.statSync(source).isFile()) {
|
||||
throw new Error(`VC++ redistributable DLL missing: ${source}`)
|
||||
assertFile(source, 'VC++ redistributable DLL')
|
||||
return source
|
||||
})
|
||||
const binaries = resolveNativeBinaries(stageDir)
|
||||
for (const binary of binaries) assertFile(binary, 'Windows native binary')
|
||||
for (const binary of binaries) {
|
||||
for (const source of sources) {
|
||||
fs.copyFileSync(source, path.join(path.dirname(binary), path.basename(source)))
|
||||
}
|
||||
fs.copyFileSync(source, path.join(targetDirectory, filename))
|
||||
}
|
||||
process.stdout.write('[package] installed VC++ redistributable DLLs into ONNX Runtime binary directory\n')
|
||||
process.stdout.write(
|
||||
'[package] installed VC++ redistributable DLLs next to ONNX Runtime and Jieba binaries\n'
|
||||
)
|
||||
}
|
||||
|
||||
export const resolveVcRuntimeDllDirectory = (appRoot) =>
|
||||
path.join(appRoot, 'resources', 'app.asar.unpacked', ...ONNX_BIN_SEGMENTS)
|
||||
|
||||
export const assertVcRuntimeDllsPackaged = (
|
||||
appRoot,
|
||||
{ platform = process.platform, arch = process.arch } = {}
|
||||
) => {
|
||||
if (!isWindowsX64(platform, arch)) return
|
||||
|
||||
const directory = resolveVcRuntimeDllDirectory(appRoot)
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
const dllPath = path.join(directory, filename)
|
||||
if (!fs.existsSync(dllPath) || !fs.statSync(dllPath).isFile()) {
|
||||
throw new Error(`packaged VC++ redistributable DLL missing: ${filename}`)
|
||||
for (const binary of resolvePackagedWindowsNativeBinaries(appRoot)) {
|
||||
const directory = path.dirname(binary)
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
assertFile(path.join(directory, filename), 'packaged VC++ redistributable DLL')
|
||||
}
|
||||
assertFile(binary, 'packaged Windows native binary')
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,13 @@ export const verifyStagedRuntime = async ({ stageDirectory, reportPath, rootDire
|
||||
}
|
||||
}
|
||||
|
||||
const { Jieba } = loadStageModule(stageRequire, stageNodeModules, '@node-rs/jieba')
|
||||
const { dict } = loadStageModule(stageRequire, stageNodeModules, '@node-rs/jieba/dict')
|
||||
const jiebaTokens = Jieba.withDict(dict).cutForSearch('中文分词', true)
|
||||
if (!jiebaTokens.includes('中文') || !jiebaTokens.includes('分词')) {
|
||||
throw new Error('Jieba staged native runtime or default dictionary is not working')
|
||||
}
|
||||
|
||||
const docxFixture = path.join(root, 'tests', 'fixtures', 'workspace', 'sample-edit.docx')
|
||||
const pdfFixture = path.join(root, 'tests', 'fixtures', 'workspace', '学习笔记', '深度学习.pdf')
|
||||
for (const fixture of [docxFixture, pdfFixture]) {
|
||||
@@ -187,6 +194,7 @@ export const verifyStagedRuntime = async ({ stageDirectory, reportPath, rootDire
|
||||
return {
|
||||
packages: report.packages.length,
|
||||
requiredFiles: report.requiredFiles.length,
|
||||
jiebaTokens: jiebaTokens.length,
|
||||
docxDocuments: docxDocuments.length,
|
||||
pdfDocuments: pdfDocuments.length,
|
||||
formulaSvgBytes: Buffer.byteLength(formulaMarkup),
|
||||
|
||||
@@ -174,9 +174,6 @@ import type {
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
import { WEB_INGEST_CONCURRENCY } from '../utils/constant'
|
||||
import { Jieba } from '@node-rs/jieba'
|
||||
import { dict } from '@node-rs/jieba/dict'
|
||||
import { normalizeForIpc } from '../utils/serialization'
|
||||
import { writeTextFileAtomically } from '../services/files/TextFileWriteService'
|
||||
import { ConfirmDialogWindowService } from '../services/ConfirmDialogWindowService'
|
||||
import type { ConfirmDialogRequest, ConfirmDialogResponse } from '../../shared/types'
|
||||
@@ -222,7 +219,6 @@ const takeE2ENativeDialogSelection = (
|
||||
return selectedPath
|
||||
}
|
||||
|
||||
const jieba = Jieba.withDict(dict)
|
||||
const getErrorMessage = (error: unknown): string =>
|
||||
error instanceof Error ? error.message : String(error)
|
||||
|
||||
@@ -2610,36 +2606,6 @@ export function registerHandlers(services: {
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle(
|
||||
'search',
|
||||
async (_event, query: string, sourceType?: 'all' | 'file' | 'web', documentIds?: string[]) => {
|
||||
try {
|
||||
const { data: queryVector } = await embeddingService.embed(query)
|
||||
const filter = sourceType && sourceType !== 'all' ? sourceType : undefined
|
||||
const results = await documentsStore.search(
|
||||
queryVector,
|
||||
query,
|
||||
5,
|
||||
filter,
|
||||
undefined,
|
||||
documentIds
|
||||
)
|
||||
|
||||
const tokens = jieba.cutForSearch(query, true)
|
||||
|
||||
const finalResults = results.map(normalizeForIpc)
|
||||
|
||||
return {
|
||||
results: finalResults,
|
||||
tokens
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('? [SEARCH] ERROR:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
ipcMain.handle('read-file', async (_event, filePath: string) => {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
|
||||
@@ -93,8 +93,6 @@ import type {
|
||||
LlmModelListResponse,
|
||||
PrepareWorkspaceRequest,
|
||||
RefreshWebSourceRequest,
|
||||
SearchResponse,
|
||||
SearchSourceFilter,
|
||||
PickDirectoryResponse,
|
||||
PickFileResponse,
|
||||
ListOpenWithApplicationsRequest,
|
||||
@@ -190,11 +188,6 @@ export type DesktopApi = {
|
||||
revealPath: (filePath: string) => Promise<{ success: boolean; error?: string }>
|
||||
pickDirectory: () => Promise<PickDirectoryResponse>
|
||||
pickFile: (defaultPath?: string) => Promise<PickFileResponse>
|
||||
search: (
|
||||
query: string,
|
||||
sourceType?: SearchSourceFilter,
|
||||
documentIds?: string[]
|
||||
) => Promise<SearchResponse>
|
||||
workspaceList: () => Promise<WorkspaceListResponse>
|
||||
workspaceCreate: (payload: CreateWorkspaceSourceRequest) => Promise<WorkspaceMutationResponse>
|
||||
workspaceSetRootPath: (payload: SetWorkspaceRootPathRequest) => Promise<WorkspaceMutationResponse>
|
||||
|
||||
@@ -8,7 +8,6 @@ import type {
|
||||
AppUpdateSnapshot,
|
||||
BrowserTabState,
|
||||
JsonValue,
|
||||
SearchSourceFilter,
|
||||
WorkspaceIndexJobRecord
|
||||
} from '../shared/types'
|
||||
import {
|
||||
@@ -204,8 +203,6 @@ const api: DesktopApi = {
|
||||
})
|
||||
return ipcRenderer.invoke('open-file-with-application', payload)
|
||||
},
|
||||
search: (query: string, sourceType?: SearchSourceFilter, documentIds?: string[]) =>
|
||||
ipcRenderer.invoke('search', query, sourceType, documentIds),
|
||||
workspaceList: () => ipcRenderer.invoke('workspace-list'),
|
||||
workspaceCreate: (payload) => ipcRenderer.invoke('workspace-create', payload),
|
||||
workspaceSetRootPath: (payload) => ipcRenderer.invoke('workspace-set-root-path', payload),
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { AppLanguage, AppRuntimeSnapshot, ServiceRoute } from './appPrefere
|
||||
|
||||
export type DocumentSourceType = 'file' | 'web'
|
||||
export type DocumentImportStatus = 1 | 2 | 3
|
||||
export type SearchSourceFilter = 'all' | DocumentSourceType
|
||||
export type WebSourceIdentityKind = 'normalized_url'
|
||||
export type WebSourceIndexStatus =
|
||||
| 'needs_index'
|
||||
@@ -501,11 +500,6 @@ export interface SearchResult {
|
||||
document?: DocumentRecord
|
||||
}
|
||||
|
||||
export interface SearchResponse {
|
||||
results: SearchResult[]
|
||||
tokens: string[]
|
||||
}
|
||||
|
||||
export interface ChunkListItem {
|
||||
id: string
|
||||
text: string
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { PROTECTED_MAIN_PLAINTEXT_MARKERS } from '../../../../scripts/build/package/obfuscation.mjs'
|
||||
import {
|
||||
VC_REDIST_DLL_NAMES,
|
||||
resolveVcRuntimeDllDirectory
|
||||
resolvePackagedWindowsNativeBinaries
|
||||
} from '../../../../scripts/build/package/vc-runtime.mjs'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
@@ -118,10 +118,13 @@ describe('package content policy paths', () => {
|
||||
await writeFile(nodePtyHelperPath, '', { mode: 0o755 })
|
||||
await writeFile(sidecarExecutablePath, '', { mode: 0o755 })
|
||||
await writeFile(playwrightDriverPath, 'module.exports = {}\n')
|
||||
const vcRuntimeDllDirectory = resolveVcRuntimeDllDirectory(packagedApp)
|
||||
await mkdir(vcRuntimeDllDirectory, { recursive: true })
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
await writeFile(path.join(vcRuntimeDllDirectory, filename), 'dummy')
|
||||
for (const binary of resolvePackagedWindowsNativeBinaries(packagedApp)) {
|
||||
const vcRuntimeDllDirectory = path.dirname(binary)
|
||||
await mkdir(vcRuntimeDllDirectory, { recursive: true })
|
||||
await writeFile(binary, 'native fixture')
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
await writeFile(path.join(vcRuntimeDllDirectory, filename), 'dummy')
|
||||
}
|
||||
}
|
||||
await writeFile(path.join(appSource, 'out', 'main', 'index.js'), 'export const ready = true\n')
|
||||
await writeFile(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import { mkdtemp, mkdir, readdir, rm, stat, writeFile } from 'node:fs/promises'
|
||||
import { mkdtemp, mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import path from 'node:path'
|
||||
import { promisify } from 'node:util'
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
VC_REDIST_DLL_NAMES,
|
||||
assertVcRuntimeDllsPackaged,
|
||||
installWindowsVcRuntime,
|
||||
resolveVcRuntimeDllDirectory
|
||||
resolvePackagedWindowsNativeBinaries
|
||||
} from '../../../../scripts/build/package/vc-runtime.mjs'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
@@ -19,27 +19,66 @@ afterEach(async () => {
|
||||
if (temporaryDirectory) await rm(temporaryDirectory, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
const writeDummyDll = async (directory, filename) => {
|
||||
const writeDummyDll = async (directory: string, filename: string) => {
|
||||
await mkdir(directory, { recursive: true })
|
||||
await writeFile(path.join(directory, filename), 'dummy')
|
||||
}
|
||||
|
||||
const stageTargetDirectory = (stageDir) =>
|
||||
path.join(stageDir, 'node_modules', 'onnxruntime-node', 'bin', 'napi-v3', 'win32', 'x64')
|
||||
const stageNativeBinaries = (stageDir: string) => [
|
||||
path.join(
|
||||
stageDir,
|
||||
'node_modules',
|
||||
'onnxruntime-node',
|
||||
'bin',
|
||||
'napi-v3',
|
||||
'win32',
|
||||
'x64',
|
||||
'onnxruntime_binding.node'
|
||||
),
|
||||
path.join(
|
||||
stageDir,
|
||||
'node_modules',
|
||||
'@node-rs',
|
||||
'jieba-win32-x64-msvc',
|
||||
'jieba.win32-x64-msvc.node'
|
||||
)
|
||||
]
|
||||
|
||||
const writeNativeBinaries = async (binaries: string[]) => {
|
||||
for (const binary of binaries) {
|
||||
await mkdir(path.dirname(binary), { recursive: true })
|
||||
await writeFile(binary, 'native fixture')
|
||||
}
|
||||
}
|
||||
|
||||
const writePackagedRuntime = async (appRoot: string) => {
|
||||
const binaries = resolvePackagedWindowsNativeBinaries(appRoot)
|
||||
await writeNativeBinaries(binaries)
|
||||
for (const binary of binaries) {
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
await writeDummyDll(path.dirname(binary), filename)
|
||||
}
|
||||
}
|
||||
return binaries
|
||||
}
|
||||
|
||||
describe('VC++ redistributable packaging', () => {
|
||||
it('installs the redistributable DLLs next to the ONNX Runtime binaries', async () => {
|
||||
it('installs the redistributable DLLs next to both ONNX Runtime and Jieba binaries', async () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const stageDir = path.join(temporaryDirectory, 'stage')
|
||||
const vcRedistDir = path.join(temporaryDirectory, 'vc-redist')
|
||||
const targetDir = stageTargetDirectory(stageDir)
|
||||
await mkdir(targetDir, { recursive: true })
|
||||
const binaries = stageNativeBinaries(stageDir)
|
||||
await writeNativeBinaries(binaries)
|
||||
for (const filename of VC_REDIST_DLL_NAMES) await writeDummyDll(vcRedistDir, filename)
|
||||
|
||||
installWindowsVcRuntime({ stageDir, vcRedistDir, platform: 'win32', arch: 'x64' })
|
||||
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
expect((await stat(path.join(targetDir, filename))).isFile()).toBe(true)
|
||||
for (const binary of binaries) {
|
||||
for (const filename of VC_REDIST_DLL_NAMES) {
|
||||
expect(await readFile(path.join(path.dirname(binary), filename))).toEqual(
|
||||
await readFile(path.join(vcRedistDir, filename))
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -47,7 +86,7 @@ describe('VC++ redistributable packaging', () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const stageDir = path.join(temporaryDirectory, 'stage')
|
||||
const vcRedistDir = path.join(temporaryDirectory, 'vc-redist')
|
||||
await mkdir(stageTargetDirectory(stageDir), { recursive: true })
|
||||
await writeNativeBinaries(stageNativeBinaries(stageDir))
|
||||
for (const filename of VC_REDIST_DLL_NAMES.slice(0, -1)) {
|
||||
await writeDummyDll(vcRedistDir, filename)
|
||||
}
|
||||
@@ -57,37 +96,62 @@ describe('VC++ redistributable packaging', () => {
|
||||
).toThrow('VC++ redistributable DLL missing')
|
||||
})
|
||||
|
||||
it('rejects a stage missing the Windows Jieba optional binary package', async () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const stageDir = path.join(temporaryDirectory, 'stage')
|
||||
const vcRedistDir = path.join(temporaryDirectory, 'vc-redist')
|
||||
await writeNativeBinaries(stageNativeBinaries(stageDir).slice(0, 1))
|
||||
for (const filename of VC_REDIST_DLL_NAMES) await writeDummyDll(vcRedistDir, filename)
|
||||
|
||||
expect(() =>
|
||||
installWindowsVcRuntime({ stageDir, vcRedistDir, platform: 'win32', arch: 'x64' })
|
||||
).toThrow('jieba.win32-x64-msvc.node')
|
||||
})
|
||||
|
||||
it('skips installation on non-Windows x64 platforms', async () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const stageDir = path.join(temporaryDirectory, 'stage')
|
||||
const vcRedistDir = path.join(temporaryDirectory, 'vc-redist')
|
||||
const targetDir = stageTargetDirectory(stageDir)
|
||||
await mkdir(targetDir, { recursive: true })
|
||||
const binaries = stageNativeBinaries(stageDir)
|
||||
await writeNativeBinaries(binaries)
|
||||
|
||||
installWindowsVcRuntime({ stageDir, vcRedistDir, platform: 'darwin', arch: 'arm64' })
|
||||
|
||||
expect(await readdir(targetDir)).toEqual([])
|
||||
for (const binary of binaries) {
|
||||
expect(await readdir(path.dirname(binary))).toEqual([path.basename(binary)])
|
||||
}
|
||||
})
|
||||
|
||||
it('asserts the redistributable DLLs are present in a packaged Windows app', async () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const appRoot = path.join(temporaryDirectory, 'win-unpacked')
|
||||
const dllDir = resolveVcRuntimeDllDirectory(appRoot)
|
||||
for (const filename of VC_REDIST_DLL_NAMES) await writeDummyDll(dllDir, filename)
|
||||
await writePackagedRuntime(appRoot)
|
||||
|
||||
expect(() => assertVcRuntimeDllsPackaged(appRoot, { platform: 'win32', arch: 'x64' })).not.toThrow()
|
||||
expect(() =>
|
||||
assertVcRuntimeDllsPackaged(appRoot, { platform: 'win32', arch: 'x64' })
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
it('throws when a packaged Windows app omits a redistributable DLL', async () => {
|
||||
it.each([0, 1])('rejects a missing DLL in packaged native target %i', async (index) => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const appRoot = path.join(temporaryDirectory, 'win-unpacked')
|
||||
const dllDir = resolveVcRuntimeDllDirectory(appRoot)
|
||||
for (const filename of VC_REDIST_DLL_NAMES.slice(0, -1)) {
|
||||
await writeDummyDll(dllDir, filename)
|
||||
}
|
||||
const binaries = await writePackagedRuntime(appRoot)
|
||||
const omittedDll = path.join(path.dirname(binaries[index]!), 'vcruntime140.dll')
|
||||
await rm(omittedDll)
|
||||
|
||||
expect(() => assertVcRuntimeDllsPackaged(appRoot, { platform: 'win32', arch: 'x64' })).toThrow(
|
||||
'packaged VC++ redistributable DLL missing'
|
||||
omittedDll
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects a packaged Jieba binary that is absent from app.asar.unpacked', async () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const appRoot = path.join(temporaryDirectory, 'win-unpacked')
|
||||
const binaries = await writePackagedRuntime(appRoot)
|
||||
await rm(binaries[1]!)
|
||||
|
||||
expect(() => assertVcRuntimeDllsPackaged(appRoot, { platform: 'win32', arch: 'x64' })).toThrow(
|
||||
'packaged Windows native binary missing'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -98,7 +162,7 @@ describe('VC++ redistributable packaging', () => {
|
||||
})
|
||||
|
||||
it.skipIf(process.platform !== 'win32' || process.arch !== 'x64')(
|
||||
'audit-app rejects a Windows package missing the redistributable DLLs',
|
||||
'audit-app rejects a Windows package with ONNX DLLs but missing Jieba runtime',
|
||||
async () => {
|
||||
temporaryDirectory = await mkdtemp(path.join(os.tmpdir(), 'ncurator-vc-runtime-'))
|
||||
const appSource = path.join(temporaryDirectory, 'app-source')
|
||||
@@ -106,7 +170,10 @@ describe('VC++ redistributable packaging', () => {
|
||||
const archivePath = path.join(packageDirectory, 'win-unpacked', 'resources', 'app.asar')
|
||||
await mkdir(path.join(appSource, 'out', 'main'), { recursive: true })
|
||||
await mkdir(path.dirname(archivePath), { recursive: true })
|
||||
await writeFile(path.join(appSource, 'out', 'main', 'index.js'), 'export const ready = true\n')
|
||||
await writeFile(
|
||||
path.join(appSource, 'out', 'main', 'index.js'),
|
||||
'export const ready = true\n'
|
||||
)
|
||||
await writeFile(
|
||||
path.join(appSource, 'package.json'),
|
||||
`${JSON.stringify({
|
||||
@@ -118,6 +185,9 @@ describe('VC++ redistributable packaging', () => {
|
||||
})}\n`
|
||||
)
|
||||
await createPackage(appSource, archivePath)
|
||||
const appRoot = path.join(packageDirectory, 'win-unpacked')
|
||||
const binaries = await writePackagedRuntime(appRoot)
|
||||
await rm(path.join(path.dirname(binaries[1]!), 'vcruntime140.dll'))
|
||||
|
||||
let stderr = ''
|
||||
try {
|
||||
@@ -129,6 +199,7 @@ describe('VC++ redistributable packaging', () => {
|
||||
stderr = String((error as { stderr?: string }).stderr || error)
|
||||
}
|
||||
expect(stderr).toContain('packaged VC++ redistributable DLL missing')
|
||||
expect(stderr).toContain('jieba-win32-x64-msvc')
|
||||
}
|
||||
)
|
||||
})
|
||||
Reference in new issue
Block a user