feat: 实现 Windows 可选字体包安装链
This commit is contained in:
@@ -1,3 +1,67 @@
|
|||||||
|
!ifdef FONT_PACK_COMPANION_FILE
|
||||||
|
!ifndef BUILD_UNINSTALLER
|
||||||
|
!include "FileFunc.nsh"
|
||||||
|
!include "nsDialogs.nsh"
|
||||||
|
|
||||||
|
Var fontPackCompanionAvailable
|
||||||
|
Var fontPackCompanionCheckbox
|
||||||
|
Var fontPackCompanionExitCode
|
||||||
|
Var fontPackCompanionHash
|
||||||
|
Var fontPackCompanionPath
|
||||||
|
Var fontPackCompanionSelected
|
||||||
|
|
||||||
|
!macro customInit
|
||||||
|
StrCpy $fontPackCompanionAvailable "0"
|
||||||
|
StrCpy $fontPackCompanionSelected "0"
|
||||||
|
StrCpy $fontPackCompanionPath "$EXEDIR\${FONT_PACK_COMPANION_FILE}"
|
||||||
|
IfFileExists "$fontPackCompanionPath" 0 font_pack_init_done
|
||||||
|
${StdUtils.HashFile} $fontPackCompanionHash "SHA2-256" "$fontPackCompanionPath"
|
||||||
|
StrCmp $fontPackCompanionHash "${FONT_PACK_COMPANION_SHA256}" 0 font_pack_init_done
|
||||||
|
StrCpy $fontPackCompanionAvailable "1"
|
||||||
|
IfSilent font_pack_silent font_pack_interactive
|
||||||
|
font_pack_interactive:
|
||||||
|
StrCpy $fontPackCompanionSelected "1"
|
||||||
|
Goto font_pack_init_done
|
||||||
|
font_pack_silent:
|
||||||
|
${GetParameters} $R0
|
||||||
|
${GetOptions} $R0 "/WITHFONTPACK=" $R1
|
||||||
|
StrCmp $R1 "1" 0 font_pack_init_done
|
||||||
|
StrCpy $fontPackCompanionSelected "1"
|
||||||
|
font_pack_init_done:
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
Function FontPackCompanionPage
|
||||||
|
StrCmp $fontPackCompanionAvailable "1" 0 font_pack_page_skip
|
||||||
|
nsDialogs::Create 1018
|
||||||
|
Pop $R0
|
||||||
|
StrCmp $R0 "error" 0 +2
|
||||||
|
Abort
|
||||||
|
${NSD_CreateLabel} 0 0 100% 28u "检测到经过完整性校验的可选字体包。安装后,政企公文主题可使用与 PDF 相同的原生中文字体资产。"
|
||||||
|
Pop $R0
|
||||||
|
${NSD_CreateCheckbox} 0 38u 100% 14u "安装 ${FONT_PACK_COMPANION_NAME}"
|
||||||
|
Pop $fontPackCompanionCheckbox
|
||||||
|
${NSD_Check} $fontPackCompanionCheckbox
|
||||||
|
nsDialogs::Show
|
||||||
|
Return
|
||||||
|
font_pack_page_skip:
|
||||||
|
Abort
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
Function FontPackCompanionPageLeave
|
||||||
|
${NSD_GetState} $fontPackCompanionCheckbox $R0
|
||||||
|
StrCmp $R0 ${BST_CHECKED} 0 font_pack_page_unchecked
|
||||||
|
StrCpy $fontPackCompanionSelected "1"
|
||||||
|
Return
|
||||||
|
font_pack_page_unchecked:
|
||||||
|
StrCpy $fontPackCompanionSelected "0"
|
||||||
|
FunctionEnd
|
||||||
|
|
||||||
|
!macro customPageAfterChangeDir
|
||||||
|
Page custom FontPackCompanionPage FontPackCompanionPageLeave
|
||||||
|
!macroend
|
||||||
|
!endif
|
||||||
|
!endif
|
||||||
|
|
||||||
!macro customInstall
|
!macro customInstall
|
||||||
WriteRegStr SHCTX \
|
WriteRegStr SHCTX \
|
||||||
"Software\Classes\MarkdownPdfExporter.Markdown" \
|
"Software\Classes\MarkdownPdfExporter.Markdown" \
|
||||||
@@ -43,6 +107,24 @@
|
|||||||
"" \
|
"" \
|
||||||
"$INSTDIR\md-to-pdf.exe"
|
"$INSTDIR\md-to-pdf.exe"
|
||||||
StrCpy $launchLink "$INSTDIR\md-to-pdf.exe"
|
StrCpy $launchLink "$INSTDIR\md-to-pdf.exe"
|
||||||
|
!ifdef FONT_PACK_COMPANION_FILE
|
||||||
|
StrCmp $fontPackCompanionAvailable "1" 0 font_pack_install_done
|
||||||
|
StrCmp $fontPackCompanionSelected "1" 0 font_pack_install_done
|
||||||
|
${StdUtils.HashFile} $fontPackCompanionHash "SHA2-256" "$fontPackCompanionPath"
|
||||||
|
StrCmp $fontPackCompanionHash "${FONT_PACK_COMPANION_SHA256}" 0 font_pack_integrity_failed
|
||||||
|
DetailPrint "正在安装可选字体包:${FONT_PACK_COMPANION_NAME}"
|
||||||
|
ExecWait '"$fontPackCompanionPath" /S' $fontPackCompanionExitCode
|
||||||
|
StrCmp $fontPackCompanionExitCode "0" font_pack_install_done
|
||||||
|
DetailPrint "可选字体包安装失败,退出码:$fontPackCompanionExitCode"
|
||||||
|
IfSilent font_pack_install_done 0
|
||||||
|
MessageBox MB_ICONEXCLAMATION|MB_OK "主程序已安装,但可选字体包安装失败(退出码 $fontPackCompanionExitCode)。您可以稍后单独运行字体包安装器。"
|
||||||
|
Goto font_pack_install_done
|
||||||
|
font_pack_integrity_failed:
|
||||||
|
DetailPrint "可选字体包完整性复核失败,已跳过安装。"
|
||||||
|
IfSilent font_pack_install_done 0
|
||||||
|
MessageBox MB_ICONEXCLAMATION|MB_OK "可选字体包完整性复核失败,主程序已安装,但字体包已跳过。"
|
||||||
|
font_pack_install_done:
|
||||||
|
!endif
|
||||||
System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0, i 0, i 0)'
|
System::Call 'shell32::SHChangeNotify(i 0x08000000, i 0, i 0, i 0)'
|
||||||
!macroend
|
!macroend
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ const windowsIcon = path.resolve(
|
|||||||
__dirname,
|
__dirname,
|
||||||
"../../logos/desktop/windows/app.ico"
|
"../../logos/desktop/windows/app.ico"
|
||||||
);
|
);
|
||||||
|
const nsisInclude = process.env.MD_TO_PDF_NSIS_INCLUDE?.trim() ||
|
||||||
|
"build/installer.nsh";
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
appId: "com.md-to-pdf.desktop",
|
appId: "com.md-to-pdf.desktop",
|
||||||
@@ -71,7 +73,7 @@ module.exports = {
|
|||||||
installerIcon: windowsIcon,
|
installerIcon: windowsIcon,
|
||||||
uninstallerIcon: windowsIcon,
|
uninstallerIcon: windowsIcon,
|
||||||
installerLanguages: ["zh_CN"],
|
installerLanguages: ["zh_CN"],
|
||||||
include: "build/installer.nsh",
|
include: nsisInclude,
|
||||||
artifactName: `md-to-pdf-${version}-x86_64-Setup.\${ext}`
|
artifactName: `md-to-pdf-${version}-x86_64-Setup.\${ext}`
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -58,6 +58,28 @@ describe("桌面发行构建链", () => {
|
|||||||
expect(builderConfig).toContain('to: "samples"');
|
expect(builderConfig).toContain('to: "samples"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("仅在组合构建时接入经过哈希校验的同目录字体包", () => {
|
||||||
|
const builderConfig = readFileSync(
|
||||||
|
`${desktopRoot}/electron-builder.config.cjs`,
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
const installerInclude = readFileSync(
|
||||||
|
`${desktopRoot}/build/installer.nsh`,
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
const companionBuild = readFileSync(
|
||||||
|
`${projectRoot}/scripts/build-desktop-with-font-pack.mjs`,
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(builderConfig).toContain("MD_TO_PDF_NSIS_INCLUDE");
|
||||||
|
expect(installerInclude).toContain("FONT_PACK_COMPANION_SHA256");
|
||||||
|
expect(installerInclude).toContain("StdUtils.HashFile");
|
||||||
|
expect(installerInclude).toContain("/WITHFONTPACK=");
|
||||||
|
expect(companionBuild).toContain("createDesktopCompanionInclude");
|
||||||
|
expect(companionBuild).toContain("MD_TO_PDF_NSIS_INCLUDE");
|
||||||
|
});
|
||||||
|
|
||||||
it("将 DOCX Lua Filter 复制到桌面主进程资源目录", () => {
|
it("将 DOCX Lua Filter 复制到桌面主进程资源目录", () => {
|
||||||
const buildScript = readFileSync(
|
const buildScript = readFileSync(
|
||||||
`${desktopRoot}/scripts/build-main.mjs`,
|
`${desktopRoot}/scripts/build-main.mjs`,
|
||||||
|
|||||||
+27
-2
@@ -323,6 +323,29 @@ FP3 只处理独立 NSIS 字体包、Docker 可选挂载及其安装/升级/卸
|
|||||||
本阶段未修改 NSIS 或 Docker;下一步 FP3-B 实现独立用户级字体包安装、
|
本阶段未修改 NSIS 或 Docker;下一步 FP3-B 实现独立用户级字体包安装、
|
||||||
升级、卸载和主安装器同目录可选入口。
|
升级、卸载和主安装器同目录可选入口。
|
||||||
|
|
||||||
|
阶段 12D-FP3-B 已实现 Windows 独立字体包安装与 Desktop 同目录可选入口。
|
||||||
|
字体包构建器复用 electron-builder `26.15.3` 锁定并校验的 NSIS 3.0.4.1,
|
||||||
|
不要求系统安装 NSIS,也不引入第二套 Electron。独立安装器以当前用户权限
|
||||||
|
将完整 `mdtp-serif-sc` ID 安装到
|
||||||
|
`%LOCALAPPDATA%/md-to-pdf/font-packs/`,不注册系统字体;安装使用暂存、
|
||||||
|
备份、原子提升和失败回滚,同版本重装幂等,独立卸载只清理该 ID。隔离
|
||||||
|
根目录验收中,首次静默安装、同版本重装和卸载退出码均为 0,注册器复核
|
||||||
|
的字体包指纹仍为
|
||||||
|
`67adac137fa74ca54b4ab07dd584ae87657a1aec413b7f125141c5351fa5b89d`。
|
||||||
|
|
||||||
|
主安装器通过生成的 NSIS include 绑定同目录字体包安装器的精确文件名与
|
||||||
|
SHA-256;交互模式仅在文件存在且哈希正确时显示默认勾选项,并在执行前
|
||||||
|
再次复核哈希。静默主安装默认跳过,只有 `/WITHFONTPACK=1` 才安装;字体
|
||||||
|
包失败不会回滚主程序。统一组合命令为
|
||||||
|
`npm run desktop:make:with-font-pack`,普通 Desktop 构建不启用伴随逻辑。
|
||||||
|
真实组合产物中,主安装器为 115,328,133 字节,独立字体包安装器为
|
||||||
|
24,489,062 字节;桌面解包目录未包含字体二进制或字体包安装器。Windows
|
||||||
|
实机安装向导确认可选页位于安装目录页之后,说明文字完整且字体包默认
|
||||||
|
勾选;未执行主程序重装。全仓 465 项测试、类型检查、生产构建和组合发行
|
||||||
|
构建通过。下一步 FP3-C 实现 Docker 可选字体包挂载与部署门禁。
|
||||||
|
本阶段按版本策略未提前修改应用的 `0.5.1` 版本号;正式 `v0.6.0` 发布
|
||||||
|
提交升级版本后,字体包才满足其 `minimumAppVersion=0.6.0` 运行时门禁。
|
||||||
|
|
||||||
## 2. 已完成
|
## 2. 已完成
|
||||||
|
|
||||||
### 2.1 项目骨架
|
### 2.1 项目骨架
|
||||||
@@ -1279,8 +1302,10 @@ ECharts 第二阶段浏览器与 PDF 验证结果:
|
|||||||
- 阶段 12D-FP2:已完成 Preview/PDF WOFF2 与 DOCX 静态 TrueType 的
|
- 阶段 12D-FP2:已完成 Preview/PDF WOFF2 与 DOCX 静态 TrueType 的
|
||||||
同源字体解析、跨端受限资源入口、缓存指纹、降级诊断和真实字体包验证;
|
同源字体解析、跨端受限资源入口、缓存指纹、降级诊断和真实字体包验证;
|
||||||
- 阶段 12D-FP3-A:已完成独立字体包构建器、冻结配方、许可证与来源追踪、
|
- 阶段 12D-FP3-A:已完成独立字体包构建器、冻结配方、许可证与来源追踪、
|
||||||
同版本不可变门禁和真实资产构建;下一步 FP3-B 实现独立 NSIS 字体包,
|
同版本不可变门禁和真实资产构建;
|
||||||
FP3-C 再实现 Docker 可选挂载,最终由 R4 完成 14 套主题深度视觉回归;
|
- 阶段 12D-FP3-B:已完成独立用户级 NSIS 字体包、原子安装/升级/卸载、
|
||||||
|
主安装器同目录哈希校验和显式静默开关;下一步 FP3-C 实现 Docker 可选
|
||||||
|
挂载,最终由 R4 完成 14 套主题深度视觉回归;
|
||||||
- 阶段 13:完成 Word/WPS 双向互存、外部主题兼容、体积和正式发布验收。
|
- 阶段 13:完成 Word/WPS 双向互存、外部主题兼容、体积和正式发布验收。
|
||||||
|
|
||||||
每个阶段验收通过后创建一个独立提交,再进入下一阶段。当前阶段不得混入
|
每个阶段验收通过后创建一个独立提交,再进入下一阶段。当前阶段不得混入
|
||||||
|
|||||||
@@ -45,3 +45,52 @@ root/<pack-id>/<semver>/font-pack.json
|
|||||||
当前 `mdtp-serif-sc@1.0.0` 只替换 `FandolSong` 与
|
当前 `mdtp-serif-sc@1.0.0` 只替换 `FandolSong` 与
|
||||||
`Mdpdf Fandol Song` 的 400/700 字重,不匹配 `SimSun` 等通用别名,避免
|
`Mdpdf Fandol Song` 的 400/700 字重,不匹配 `SimSun` 等通用别名,避免
|
||||||
错误替换仿宋、黑体、楷体或普通外部主题字体。
|
错误替换仿宋、黑体、楷体或普通外部主题字体。
|
||||||
|
|
||||||
|
## Windows 独立安装器
|
||||||
|
|
||||||
|
发布人员可以使用 electron-builder 锁定并校验的 NSIS 工具链构建独立
|
||||||
|
字体包安装器,不需要额外安装系统 NSIS:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run build:font-pack:windows
|
||||||
|
```
|
||||||
|
|
||||||
|
默认产物为:
|
||||||
|
|
||||||
|
```text
|
||||||
|
output/font-packs/windows/
|
||||||
|
└── md-to-pdf-font-pack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
安装器使用当前用户权限,将整个 `mdtp-serif-sc` ID 安装到:
|
||||||
|
|
||||||
|
```text
|
||||||
|
%LOCALAPPDATA%/md-to-pdf/font-packs/mdtp-serif-sc/1.0.0/
|
||||||
|
```
|
||||||
|
|
||||||
|
它不向 Windows 系统字体目录注册字体。同一版本重装保持幂等,升级时先把
|
||||||
|
该 ID 的现有目录移入备份,再原子提升新版本;失败会回滚。独立卸载项只
|
||||||
|
删除安装器拥有的 `mdtp-serif-sc` ID,不影响其他字体包 ID。安装器支持
|
||||||
|
`/S` 静默安装和卸载。
|
||||||
|
|
||||||
|
## Desktop 伴随发行
|
||||||
|
|
||||||
|
组合构建命令为:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
npm run desktop:make:with-font-pack
|
||||||
|
```
|
||||||
|
|
||||||
|
该命令把主安装器和字体包安装器作为两个同目录文件发行。主安装器只编译
|
||||||
|
字体包文件名与 SHA-256,不嵌入字体二进制;仅当同目录文件存在且哈希完全
|
||||||
|
一致时,交互安装向导才显示默认勾选的字体包选项。字体包失败不回滚已安装
|
||||||
|
的主程序。
|
||||||
|
|
||||||
|
主安装器静默运行时默认不安装伴随字体包;只有显式传入以下参数才会安装:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
md-to-pdf-<version>-x86_64-Setup.exe /S /WITHFONTPACK=1
|
||||||
|
```
|
||||||
|
|
||||||
|
普通 `npm run make -w @md-to-pdf/desktop` 不启用伴随逻辑,避免本地残留的
|
||||||
|
生成文件影响常规桌面构建。
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
Unicode true
|
||||||
|
RequestExecutionLevel user
|
||||||
|
SetCompressor /SOLID lzma
|
||||||
|
SetCompressorDictSize 64
|
||||||
|
|
||||||
|
!include "MUI2.nsh"
|
||||||
|
!include "LogicLib.nsh"
|
||||||
|
|
||||||
|
!ifndef PACK_DIRECTORY
|
||||||
|
!error "PACK_DIRECTORY is required"
|
||||||
|
!endif
|
||||||
|
!ifndef PACK_ID
|
||||||
|
!error "PACK_ID is required"
|
||||||
|
!endif
|
||||||
|
!ifndef PACK_VERSION
|
||||||
|
!error "PACK_VERSION is required"
|
||||||
|
!endif
|
||||||
|
!ifndef PACK_NAME
|
||||||
|
!error "PACK_NAME is required"
|
||||||
|
!endif
|
||||||
|
!ifndef PACK_LICENSE_FILE
|
||||||
|
!error "PACK_LICENSE_FILE is required"
|
||||||
|
!endif
|
||||||
|
!ifndef OUTPUT_FILE
|
||||||
|
!error "OUTPUT_FILE is required"
|
||||||
|
!endif
|
||||||
|
!ifndef FONT_PACK_ROOT
|
||||||
|
!define FONT_PACK_ROOT "$LOCALAPPDATA\md-to-pdf\font-packs"
|
||||||
|
!endif
|
||||||
|
!ifndef MANAGER_ROOT
|
||||||
|
!define MANAGER_ROOT "$LOCALAPPDATA\md-to-pdf\font-pack-installers"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
Name "Markdown PDF 导出器字体包 - ${PACK_NAME}"
|
||||||
|
OutFile "${OUTPUT_FILE}"
|
||||||
|
InstallDir "${MANAGER_ROOT}\${PACK_ID}"
|
||||||
|
ShowInstDetails show
|
||||||
|
ShowUninstDetails show
|
||||||
|
BrandingText "Markdown PDF 导出器"
|
||||||
|
|
||||||
|
!ifdef ICON_FILE
|
||||||
|
Icon "${ICON_FILE}"
|
||||||
|
UninstallIcon "${ICON_FILE}"
|
||||||
|
!define MUI_ICON "${ICON_FILE}"
|
||||||
|
!define MUI_UNICON "${ICON_FILE}"
|
||||||
|
!endif
|
||||||
|
|
||||||
|
!define MUI_ABORTWARNING
|
||||||
|
!insertmacro MUI_PAGE_WELCOME
|
||||||
|
!insertmacro MUI_PAGE_LICENSE "${PACK_LICENSE_FILE}"
|
||||||
|
!insertmacro MUI_PAGE_INSTFILES
|
||||||
|
!insertmacro MUI_PAGE_FINISH
|
||||||
|
!insertmacro MUI_UNPAGE_CONFIRM
|
||||||
|
!insertmacro MUI_UNPAGE_INSTFILES
|
||||||
|
!insertmacro MUI_LANGUAGE "SimpChinese"
|
||||||
|
|
||||||
|
VIProductVersion "1.0.0.0"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "ProductName" "Markdown PDF 导出器字体包"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileDescription" "${PACK_NAME}"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "FileVersion" "${PACK_VERSION}"
|
||||||
|
VIAddVersionKey /LANG=${LANG_SIMPCHINESE} "LegalCopyright" "Copyright © YIXIONG Tech.ltd"
|
||||||
|
|
||||||
|
Section "安装字体包" SEC_FONT_PACK
|
||||||
|
SetShellVarContext current
|
||||||
|
StrCpy $R0 "${FONT_PACK_ROOT}\.installing-${PACK_ID}"
|
||||||
|
StrCpy $R1 "${FONT_PACK_ROOT}\${PACK_ID}"
|
||||||
|
StrCpy $R2 "${FONT_PACK_ROOT}\.backup-${PACK_ID}"
|
||||||
|
|
||||||
|
CreateDirectory "${FONT_PACK_ROOT}"
|
||||||
|
RMDir /r "$R0"
|
||||||
|
RMDir /r "$R2"
|
||||||
|
CreateDirectory "$R0\${PACK_ID}\${PACK_VERSION}"
|
||||||
|
SetOutPath "$R0\${PACK_ID}\${PACK_VERSION}"
|
||||||
|
File /r "${PACK_DIRECTORY}\*.*"
|
||||||
|
SetOutPath "$TEMP"
|
||||||
|
IfFileExists "$R0\${PACK_ID}\${PACK_VERSION}\font-pack.json" +3 0
|
||||||
|
RMDir /r "$R0"
|
||||||
|
Abort "字体包暂存校验失败:缺少 font-pack.json"
|
||||||
|
|
||||||
|
ClearErrors
|
||||||
|
IfFileExists "$R1\*.*" 0 promote_pack
|
||||||
|
Rename "$R1" "$R2"
|
||||||
|
IfErrors install_failed
|
||||||
|
|
||||||
|
promote_pack:
|
||||||
|
ClearErrors
|
||||||
|
Rename "$R0\${PACK_ID}" "$R1"
|
||||||
|
IfErrors rollback_pack
|
||||||
|
RMDir /r "$R0"
|
||||||
|
RMDir /r "$R2"
|
||||||
|
|
||||||
|
SetOutPath "$INSTDIR"
|
||||||
|
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||||
|
FileOpen $R3 "$INSTDIR\installed-version.txt" w
|
||||||
|
FileWrite $R3 "${PACK_VERSION}$\r$\n"
|
||||||
|
FileClose $R3
|
||||||
|
|
||||||
|
!ifdef REGISTER_UNINSTALL
|
||||||
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}" "DisplayName" "Markdown PDF 导出器字体包 - ${PACK_NAME}"
|
||||||
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}" "DisplayVersion" "${PACK_VERSION}"
|
||||||
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}" "Publisher" "YIXIONG Tech.ltd"
|
||||||
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}" "InstallLocation" "$INSTDIR"
|
||||||
|
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}" "UninstallString" '"$INSTDIR\Uninstall.exe"'
|
||||||
|
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}" "NoModify" 1
|
||||||
|
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}" "NoRepair" 1
|
||||||
|
!endif
|
||||||
|
Goto install_done
|
||||||
|
|
||||||
|
rollback_pack:
|
||||||
|
RMDir /r "$R1"
|
||||||
|
IfFileExists "$R2\*.*" 0 install_failed
|
||||||
|
Rename "$R2" "$R1"
|
||||||
|
|
||||||
|
install_failed:
|
||||||
|
RMDir /r "$R0"
|
||||||
|
Abort "字体包安装失败,原版本已保留。"
|
||||||
|
|
||||||
|
install_done:
|
||||||
|
SectionEnd
|
||||||
|
|
||||||
|
Section "Uninstall"
|
||||||
|
SetShellVarContext current
|
||||||
|
RMDir /r "${FONT_PACK_ROOT}\${PACK_ID}"
|
||||||
|
RMDir /r "${FONT_PACK_ROOT}\.installing-${PACK_ID}"
|
||||||
|
RMDir /r "${FONT_PACK_ROOT}\.backup-${PACK_ID}"
|
||||||
|
!ifdef REGISTER_UNINSTALL
|
||||||
|
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\md-to-pdf-font-pack-${PACK_ID}"
|
||||||
|
!endif
|
||||||
|
Delete "$INSTDIR\installed-version.txt"
|
||||||
|
Delete "$INSTDIR\Uninstall.exe"
|
||||||
|
RMDir "$INSTDIR"
|
||||||
|
SectionEnd
|
||||||
Generated
+1
-1
@@ -5631,7 +5631,6 @@
|
|||||||
"integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==",
|
"integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"app-builder-lib": "26.15.3",
|
"app-builder-lib": "26.15.3",
|
||||||
"builder-util": "26.15.3",
|
"builder-util": "26.15.3",
|
||||||
@@ -10037,6 +10036,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
|
"app-builder-lib": "26.15.3",
|
||||||
"vitest": "^4.1.10"
|
"vitest": "^4.1.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
"desktop:package": "npm run package -w @md-to-pdf/desktop",
|
"desktop:package": "npm run package -w @md-to-pdf/desktop",
|
||||||
"build:font-pack": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-theme-engine && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/font-pack-builder && node packages/font-pack-builder/dist/cli.js build",
|
"build:font-pack": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-theme-engine && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/font-pack-builder && node packages/font-pack-builder/dist/cli.js build",
|
||||||
"verify:font-pack": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-theme-engine && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/font-pack-builder && node packages/font-pack-builder/dist/cli.js verify",
|
"verify:font-pack": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-theme-engine && npm run build -w @md-to-pdf/font-pack-registry && npm run build -w @md-to-pdf/docx-engine && npm run build -w @md-to-pdf/font-pack-builder && node packages/font-pack-builder/dist/cli.js verify",
|
||||||
|
"build:font-pack:windows": "npm run verify:font-pack && node packages/font-pack-builder/dist/cli.js windows-installer",
|
||||||
|
"desktop:make:with-font-pack": "npm run build:font-pack:windows && node scripts/build-desktop-with-font-pack.mjs",
|
||||||
"theme:import-typora": "node scripts/import-typora-theme.mjs",
|
"theme:import-typora": "node scripts/import-typora-theme.mjs",
|
||||||
"verify:docx-reference": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-engine && npm run verify:pandoc -w @md-to-pdf/docx-engine",
|
"verify:docx-reference": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-engine && npm run verify:pandoc -w @md-to-pdf/docx-engine",
|
||||||
"verify:docx-conversion": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-engine && npm run verify:conversion -w @md-to-pdf/docx-engine",
|
"verify:docx-conversion": "npm run build -w @md-to-pdf/core && npm run build -w @md-to-pdf/docx-engine && npm run verify:conversion -w @md-to-pdf/docx-engine",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^24.10.1",
|
"@types/node": "^24.10.1",
|
||||||
|
"app-builder-lib": "26.15.3",
|
||||||
"vitest": "^4.1.10"
|
"vitest": "^4.1.10"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import { buildFontPack, verifyFontPack } from "./builder.js";
|
import { buildFontPack, verifyFontPack } from "./builder.js";
|
||||||
|
import { buildWindowsFontPackInstaller } from "./windows-installer.js";
|
||||||
|
|
||||||
function argumentsMap(values: string[]) {
|
function argumentsMap(values: string[]) {
|
||||||
const result = new Map<string, string>();
|
const result = new Map<string, string>();
|
||||||
@@ -61,7 +62,31 @@ async function main() {
|
|||||||
packVersion: option(values, "pack-version", "1.0.0")
|
packVersion: option(values, "pack-version", "1.0.0")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error("命令必须是 build 或 verify");
|
if (command === "windows-installer") {
|
||||||
|
return buildWindowsFontPackInstaller({
|
||||||
|
packRoot: resolve(option(values, "root", "output/font-packs/root")),
|
||||||
|
appVersion: option(values, "app-version", "0.6.0"),
|
||||||
|
packId: option(values, "pack-id", "mdtp-serif-sc"),
|
||||||
|
packVersion: option(values, "pack-version", "1.0.0"),
|
||||||
|
scriptPath: resolve(
|
||||||
|
option(values, "script", "font-packs/windows/font-pack-installer.nsi")
|
||||||
|
),
|
||||||
|
outputDirectory: resolve(
|
||||||
|
option(values, "output", "output/font-packs/windows")
|
||||||
|
),
|
||||||
|
iconPath: resolve(
|
||||||
|
option(values, "icon", "logos/desktop/windows/app.ico")
|
||||||
|
),
|
||||||
|
reportPath: resolve(
|
||||||
|
option(
|
||||||
|
values,
|
||||||
|
"report",
|
||||||
|
"output/font-packs/windows-installer-report.json"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new Error("命令必须是 build、verify 或 windows-installer");
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./builder.js";
|
export * from "./builder.js";
|
||||||
export * from "./recipe.js";
|
export * from "./recipe.js";
|
||||||
|
export * from "./windows-installer.js";
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { lstat, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
||||||
|
import { basename, dirname, join, resolve } from "node:path";
|
||||||
|
import { getMakeNsisPath } from "app-builder-lib/out/toolsets/windows.js";
|
||||||
|
import { fontPackManifestSchema } from "@md-to-pdf/font-pack-registry";
|
||||||
|
import { verifyFontPack } from "./builder.js";
|
||||||
|
|
||||||
|
export const WINDOWS_FONT_PACK_ARCHITECTURE = "x86_64";
|
||||||
|
|
||||||
|
export interface BuildWindowsFontPackInstallerOptions {
|
||||||
|
packRoot: string;
|
||||||
|
appVersion: string;
|
||||||
|
packId: string;
|
||||||
|
packVersion: string;
|
||||||
|
scriptPath: string;
|
||||||
|
outputDirectory: string;
|
||||||
|
iconPath?: string;
|
||||||
|
reportPath?: string;
|
||||||
|
fontPackRoot?: string;
|
||||||
|
managerRoot?: string;
|
||||||
|
registerUninstall?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WindowsFontPackInstallerReport {
|
||||||
|
packId: string;
|
||||||
|
packVersion: string;
|
||||||
|
artifactPath: string;
|
||||||
|
artifactName: string;
|
||||||
|
bytes: number;
|
||||||
|
sha256: string;
|
||||||
|
nsisPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateDesktopCompanionIncludeOptions {
|
||||||
|
installerPath: string;
|
||||||
|
baseIncludePath: string;
|
||||||
|
outputPath: string;
|
||||||
|
displayName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sha256(content: Uint8Array) {
|
||||||
|
return createHash("sha256").update(content).digest("hex").toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertDefineValue(value: string, label: string) {
|
||||||
|
if (value.includes('"') || value.includes("\r") || value.includes("\n")) {
|
||||||
|
throw new Error(`${label} 包含 NSIS 定义不支持的字符`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function define(name: string, value: string) {
|
||||||
|
return `/D${name}=${assertDefineValue(value, name)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function windowsFontPackInstallerName(packId: string, version: string) {
|
||||||
|
return `md-to-pdf-font-pack-${packId}-${version}-${WINDOWS_FONT_PACK_ARCHITECTURE}-Setup.exe`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWindowsFontPackInstallerArguments(options: {
|
||||||
|
packDirectory: string;
|
||||||
|
packId: string;
|
||||||
|
packVersion: string;
|
||||||
|
packName: string;
|
||||||
|
licensePath: string;
|
||||||
|
outputPath: string;
|
||||||
|
scriptPath: string;
|
||||||
|
iconPath?: string;
|
||||||
|
fontPackRoot?: string;
|
||||||
|
managerRoot?: string;
|
||||||
|
registerUninstall?: boolean;
|
||||||
|
}) {
|
||||||
|
const argumentsList = [
|
||||||
|
"/V2",
|
||||||
|
"/INPUTCHARSET",
|
||||||
|
"UTF8",
|
||||||
|
define("PACK_DIRECTORY", resolve(options.packDirectory)),
|
||||||
|
define("PACK_ID", options.packId),
|
||||||
|
define("PACK_VERSION", options.packVersion),
|
||||||
|
define("PACK_NAME", options.packName),
|
||||||
|
define("PACK_LICENSE_FILE", resolve(options.licensePath)),
|
||||||
|
define("OUTPUT_FILE", resolve(options.outputPath))
|
||||||
|
];
|
||||||
|
if (options.iconPath) {
|
||||||
|
argumentsList.push(define("ICON_FILE", resolve(options.iconPath)));
|
||||||
|
}
|
||||||
|
if (options.fontPackRoot) {
|
||||||
|
argumentsList.push(define("FONT_PACK_ROOT", resolve(options.fontPackRoot)));
|
||||||
|
}
|
||||||
|
if (options.managerRoot) {
|
||||||
|
argumentsList.push(define("MANAGER_ROOT", resolve(options.managerRoot)));
|
||||||
|
}
|
||||||
|
if (options.registerUninstall !== false) {
|
||||||
|
argumentsList.push("/DREGISTER_UNINSTALL");
|
||||||
|
}
|
||||||
|
argumentsList.push(resolve(options.scriptPath));
|
||||||
|
return argumentsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runProcess(
|
||||||
|
executable: string,
|
||||||
|
argumentsList: string[],
|
||||||
|
environment?: NodeJS.ProcessEnv
|
||||||
|
) {
|
||||||
|
await new Promise<void>((resolvePromise, reject) => {
|
||||||
|
const child = spawn(executable, argumentsList, {
|
||||||
|
env: environment,
|
||||||
|
stdio: "inherit",
|
||||||
|
windowsHide: true
|
||||||
|
});
|
||||||
|
child.once("error", reject);
|
||||||
|
child.once("exit", (code, signal) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolvePromise();
|
||||||
|
} else {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`NSIS 编译失败:${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function buildWindowsFontPackInstaller(
|
||||||
|
options: BuildWindowsFontPackInstallerOptions
|
||||||
|
): Promise<WindowsFontPackInstallerReport> {
|
||||||
|
if (process.platform !== "win32") {
|
||||||
|
throw new Error("字体包 NSIS 安装器只能在 Windows 上构建");
|
||||||
|
}
|
||||||
|
const verified = await verifyFontPack({
|
||||||
|
root: options.packRoot,
|
||||||
|
appVersion: options.appVersion,
|
||||||
|
packId: options.packId,
|
||||||
|
packVersion: options.packVersion
|
||||||
|
});
|
||||||
|
const packDirectory = resolve(verified.outputDirectory);
|
||||||
|
const manifestPath = join(packDirectory, "font-pack.json");
|
||||||
|
const manifest = fontPackManifestSchema.parse(
|
||||||
|
JSON.parse(await readFile(manifestPath, "utf8")) as unknown
|
||||||
|
);
|
||||||
|
const scriptPath = resolve(options.scriptPath);
|
||||||
|
const scriptStatus = await lstat(scriptPath);
|
||||||
|
if (!scriptStatus.isFile() || scriptStatus.isSymbolicLink()) {
|
||||||
|
throw new Error("字体包 NSIS 脚本不是受信任的普通文件");
|
||||||
|
}
|
||||||
|
const outputDirectory = resolve(options.outputDirectory);
|
||||||
|
await mkdir(outputDirectory, { recursive: true });
|
||||||
|
const artifactName = windowsFontPackInstallerName(
|
||||||
|
options.packId,
|
||||||
|
options.packVersion
|
||||||
|
);
|
||||||
|
const artifactPath = join(outputDirectory, artifactName);
|
||||||
|
const nsis = await getMakeNsisPath(null);
|
||||||
|
const argumentsList = createWindowsFontPackInstallerArguments({
|
||||||
|
packDirectory,
|
||||||
|
packId: manifest.id,
|
||||||
|
packVersion: manifest.version,
|
||||||
|
packName: manifest.name,
|
||||||
|
licensePath: join(packDirectory, manifest.licenseFile),
|
||||||
|
outputPath: artifactPath,
|
||||||
|
scriptPath,
|
||||||
|
...(options.iconPath ? { iconPath: options.iconPath } : {}),
|
||||||
|
...(options.fontPackRoot ? { fontPackRoot: options.fontPackRoot } : {}),
|
||||||
|
...(options.managerRoot ? { managerRoot: options.managerRoot } : {}),
|
||||||
|
...(options.registerUninstall !== undefined
|
||||||
|
? { registerUninstall: options.registerUninstall }
|
||||||
|
: {})
|
||||||
|
});
|
||||||
|
await runProcess(nsis.path, argumentsList, {
|
||||||
|
...process.env,
|
||||||
|
...nsis.env
|
||||||
|
});
|
||||||
|
const artifact = await readFile(artifactPath);
|
||||||
|
const artifactStatus = await stat(artifactPath);
|
||||||
|
const report: WindowsFontPackInstallerReport = {
|
||||||
|
packId: manifest.id,
|
||||||
|
packVersion: manifest.version,
|
||||||
|
artifactPath,
|
||||||
|
artifactName,
|
||||||
|
bytes: artifactStatus.size,
|
||||||
|
sha256: sha256(artifact),
|
||||||
|
nsisPath: nsis.path
|
||||||
|
};
|
||||||
|
if (options.reportPath) {
|
||||||
|
const reportPath = resolve(options.reportPath);
|
||||||
|
await mkdir(dirname(reportPath), { recursive: true });
|
||||||
|
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8");
|
||||||
|
}
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createDesktopCompanionInclude(
|
||||||
|
options: CreateDesktopCompanionIncludeOptions
|
||||||
|
) {
|
||||||
|
const installerPath = resolve(options.installerPath);
|
||||||
|
const status = await lstat(installerPath);
|
||||||
|
if (!status.isFile() || status.isSymbolicLink()) {
|
||||||
|
throw new Error("字体包伴随安装器不是受信任的普通文件");
|
||||||
|
}
|
||||||
|
const installerHash = sha256(await readFile(installerPath));
|
||||||
|
const include = [
|
||||||
|
`!define FONT_PACK_COMPANION_FILE "${assertDefineValue(basename(installerPath), "伴随安装器文件名")}"`,
|
||||||
|
`!define FONT_PACK_COMPANION_SHA256 "${installerHash}"`,
|
||||||
|
`!define FONT_PACK_COMPANION_NAME "${assertDefineValue(options.displayName, "伴随安装器名称")}"`,
|
||||||
|
`!include "${assertDefineValue(resolve(options.baseIncludePath), "基础安装脚本路径")}"`,
|
||||||
|
""
|
||||||
|
].join("\n");
|
||||||
|
const outputPath = resolve(options.outputPath);
|
||||||
|
await mkdir(dirname(outputPath), { recursive: true });
|
||||||
|
await writeFile(outputPath, include, "utf8");
|
||||||
|
return {
|
||||||
|
outputPath,
|
||||||
|
installerPath,
|
||||||
|
installerHash
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { createHash } from "node:crypto";
|
||||||
|
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import {
|
||||||
|
createDesktopCompanionInclude,
|
||||||
|
createWindowsFontPackInstallerArguments,
|
||||||
|
windowsFontPackInstallerName
|
||||||
|
} from "../src/index.js";
|
||||||
|
|
||||||
|
const temporaryDirectories: string[] = [];
|
||||||
|
const repositoryRoot = fileURLToPath(new URL("../../../", import.meta.url));
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await Promise.all(
|
||||||
|
temporaryDirectories.splice(0).map((directory) =>
|
||||||
|
rm(directory, { recursive: true, force: true })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Windows 字体包安装器构建", () => {
|
||||||
|
it("保持用户级原子安装和精确卸载边界", async () => {
|
||||||
|
const script = await readFile(
|
||||||
|
join(repositoryRoot, "font-packs", "windows", "font-pack-installer.nsi"),
|
||||||
|
"utf8"
|
||||||
|
);
|
||||||
|
expect(script).toContain(
|
||||||
|
'$LOCALAPPDATA\\md-to-pdf\\font-packs'
|
||||||
|
);
|
||||||
|
expect(script).toContain(".installing-${PACK_ID}");
|
||||||
|
expect(script).toContain(".backup-${PACK_ID}");
|
||||||
|
expect(script).toContain('Rename "$R0\\${PACK_ID}" "$R1"');
|
||||||
|
expect(script).toContain(
|
||||||
|
'RMDir /r "${FONT_PACK_ROOT}\\${PACK_ID}"'
|
||||||
|
);
|
||||||
|
expect(script).not.toContain("Windows\\Fonts");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("生成稳定发行文件名和完整 NSIS 定义", () => {
|
||||||
|
expect(windowsFontPackInstallerName("mdtp-serif-sc", "1.0.0")).toBe(
|
||||||
|
"md-to-pdf-font-pack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe"
|
||||||
|
);
|
||||||
|
const args = createWindowsFontPackInstallerArguments({
|
||||||
|
packDirectory: "C:/pack",
|
||||||
|
packId: "mdtp-serif-sc",
|
||||||
|
packVersion: "1.0.0",
|
||||||
|
packName: "测试字体包",
|
||||||
|
licensePath: "C:/pack/LICENSE.txt",
|
||||||
|
outputPath: "C:/out/setup.exe",
|
||||||
|
scriptPath: "C:/repo/font-pack-installer.nsi",
|
||||||
|
registerUninstall: false
|
||||||
|
});
|
||||||
|
expect(args).toContain("/DPACK_ID=mdtp-serif-sc");
|
||||||
|
expect(args).toContain("/DPACK_VERSION=1.0.0");
|
||||||
|
expect(args.slice(0, 3)).toEqual(["/V2", "/INPUTCHARSET", "UTF8"]);
|
||||||
|
expect(args).not.toContain("/DREGISTER_UNINSTALL");
|
||||||
|
expect(args.at(-1)).toMatch(/font-pack-installer\.nsi$/u);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("生成绑定同目录文件名与 SHA-256 的桌面 include", async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), "mdtp-font-companion-"));
|
||||||
|
temporaryDirectories.push(root);
|
||||||
|
const installer = join(root, "font-setup.exe");
|
||||||
|
const baseInclude = join(root, "installer.nsh");
|
||||||
|
const output = join(root, "generated", "companion.nsh");
|
||||||
|
await Promise.all([
|
||||||
|
writeFile(installer, "installer", "utf8"),
|
||||||
|
writeFile(baseInclude, "!macro customInstall\n!macroend\n", "utf8")
|
||||||
|
]);
|
||||||
|
const result = await createDesktopCompanionInclude({
|
||||||
|
installerPath: installer,
|
||||||
|
baseIncludePath: baseInclude,
|
||||||
|
outputPath: output,
|
||||||
|
displayName: "测试字体包"
|
||||||
|
});
|
||||||
|
const expected = createHash("sha256")
|
||||||
|
.update("installer")
|
||||||
|
.digest("hex")
|
||||||
|
.toUpperCase();
|
||||||
|
const content = await readFile(output, "utf8");
|
||||||
|
expect(result.installerHash).toBe(expected);
|
||||||
|
expect(content).toContain('FONT_PACK_COMPANION_FILE "font-setup.exe"');
|
||||||
|
expect(content).toContain(`FONT_PACK_COMPANION_SHA256 "${expected}"`);
|
||||||
|
expect(content).toContain(baseInclude);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { copyFile, mkdir, readFile } from "node:fs/promises";
|
||||||
|
import { spawn } from "node:child_process";
|
||||||
|
import { dirname, join, resolve } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { createDesktopCompanionInclude } from "../packages/font-pack-builder/dist/index.js";
|
||||||
|
|
||||||
|
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
const desktopRoot = join(repositoryRoot, "apps", "desktop");
|
||||||
|
const desktopManifest = JSON.parse(
|
||||||
|
await readFile(join(desktopRoot, "package.json"), "utf8")
|
||||||
|
);
|
||||||
|
const fontInstallerName =
|
||||||
|
"md-to-pdf-font-pack-mdtp-serif-sc-1.0.0-x86_64-Setup.exe";
|
||||||
|
const fontInstallerPath = join(
|
||||||
|
repositoryRoot,
|
||||||
|
"output",
|
||||||
|
"font-packs",
|
||||||
|
"windows",
|
||||||
|
fontInstallerName
|
||||||
|
);
|
||||||
|
const generatedInclude = join(
|
||||||
|
repositoryRoot,
|
||||||
|
"output",
|
||||||
|
"font-packs",
|
||||||
|
"desktop-companion.nsh"
|
||||||
|
);
|
||||||
|
|
||||||
|
await createDesktopCompanionInclude({
|
||||||
|
installerPath: fontInstallerPath,
|
||||||
|
baseIncludePath: join(desktopRoot, "build", "installer.nsh"),
|
||||||
|
outputPath: generatedInclude,
|
||||||
|
displayName: "MdTP 中文宋体兼容包 1.0.0"
|
||||||
|
});
|
||||||
|
|
||||||
|
const npmCli =
|
||||||
|
process.env.npm_execpath ||
|
||||||
|
join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js");
|
||||||
|
await new Promise((resolvePromise, reject) => {
|
||||||
|
const child = spawn(
|
||||||
|
process.execPath,
|
||||||
|
[npmCli, "run", "make", "-w", "@md-to-pdf/desktop"],
|
||||||
|
{
|
||||||
|
cwd: repositoryRoot,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
MD_TO_PDF_NSIS_INCLUDE: generatedInclude
|
||||||
|
},
|
||||||
|
stdio: "inherit",
|
||||||
|
windowsHide: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
child.once("error", reject);
|
||||||
|
child.once("exit", (code, signal) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolvePromise();
|
||||||
|
} else {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
`桌面安装器构建失败:${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const desktopOutput = join(desktopRoot, "out", `v${desktopManifest.version}`);
|
||||||
|
await mkdir(desktopOutput, { recursive: true });
|
||||||
|
await copyFile(fontInstallerPath, join(desktopOutput, fontInstallerName));
|
||||||
|
process.stdout.write(
|
||||||
|
`主安装器与字体包伴随安装器已生成:${desktopOutput}\n`
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user