yy-tauri-macos-dmg 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/bin/cli.mjs +47 -0
- package/package.json +27 -0
- package/scripts/build-macos-dmg.sh +167 -0
- package/scripts/emit-dmg-env.mjs +22 -0
- package/scripts/lib/release-config.mjs +92 -0
- package/scripts/render-quarantine-script.mjs +24 -0
- package/templates/macos-quarantine-fix.command.in +44 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# yy-tauri-macos-dmg
|
|
2
|
+
|
|
3
|
+
在 **macOS** 上为 **Tauri 2** 项目生成带「隔离属性修复」`.command` 的 DMG(依赖本机已安装 [create-dmg](https://github.com/create-dmg/create-dmg))。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -D yy-tauri-macos-dmg
|
|
9
|
+
# 或
|
|
10
|
+
pnpm add -D yy-tauri-macos-dmg
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 使用
|
|
14
|
+
|
|
15
|
+
在 Tauri 项目根目录(含 `src-tauri/tauri.conf.json`)执行:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx yy-tauri-macos-dmg
|
|
19
|
+
# 或指定目录
|
|
20
|
+
npx yy-tauri-macos-dmg /path/to/your-tauri-app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
本机需已安装:`brew install create-dmg`,以及 `release.config.json` 里配置的构建命令所需工具(默认 `pnpm tauri build`)。
|
|
24
|
+
|
|
25
|
+
## 程序化读取发布配置
|
|
26
|
+
|
|
27
|
+
本包导出与 Tauri 项目根目录 `release.config.json` / `tauri.conf.json` 合并后的配置:
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { loadReleaseConfig } from 'yy-tauri-macos-dmg/release-config'
|
|
31
|
+
|
|
32
|
+
const c = loadReleaseConfig('/path/to/project')
|
|
33
|
+
console.log(c.productName, c.version)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 许可
|
|
37
|
+
|
|
38
|
+
MIT
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* 在任意 Tauri 项目根目录执行 macOS DMG 打包(含隔离修复 .command)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import { existsSync } from 'node:fs'
|
|
8
|
+
import { dirname, join, resolve } from 'node:path'
|
|
9
|
+
import { fileURLToPath } from 'node:url'
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
|
+
|
|
13
|
+
const args = process.argv.slice(2)
|
|
14
|
+
if (args[0] === '-h' || args[0] === '--help') {
|
|
15
|
+
console.log(`用法: yy-tauri-macos-dmg [项目根目录]
|
|
16
|
+
|
|
17
|
+
未指定目录时使用当前工作目录。
|
|
18
|
+
|
|
19
|
+
依赖(本机):
|
|
20
|
+
- macOS
|
|
21
|
+
- brew install create-dmg
|
|
22
|
+
- Node.js、pnpm 或 npm(与目标项目的 tauriBuildCommand 一致)
|
|
23
|
+
|
|
24
|
+
目标项目需包含 src-tauri/tauri.conf.json,且 bundle 已启用。`)
|
|
25
|
+
process.exit(0)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const project = resolve(args[0] || process.cwd())
|
|
29
|
+
const tauriConf = join(project, 'src-tauri', 'tauri.conf.json')
|
|
30
|
+
if (!existsSync(tauriConf)) {
|
|
31
|
+
console.error(`错误:未找到 Tauri 配置:${tauriConf}`)
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const script = join(__dirname, '..', 'scripts', 'build-macos-dmg.sh')
|
|
36
|
+
if (!existsSync(script)) {
|
|
37
|
+
console.error(`错误:未找到打包脚本:${script}`)
|
|
38
|
+
process.exit(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const r = spawnSync('bash', [script], {
|
|
42
|
+
env: { ...process.env, TAURI_PROJECT_ROOT: project },
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
cwd: project,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
process.exit(r.status ?? 1)
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yy-tauri-macos-dmg",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI:为 Tauri 2 项目生成带「隔离属性修复」脚本的 macOS DMG(create-dmg)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": "bin/cli.mjs",
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"scripts",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
"./release-config": "./scripts/lib/release-config.mjs"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"tauri",
|
|
21
|
+
"tauri2",
|
|
22
|
+
"dmg",
|
|
23
|
+
"macos",
|
|
24
|
+
"create-dmg",
|
|
25
|
+
"quarantine"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ============================================
|
|
3
|
+
# macOS 通用 DMG 打包(Tauri)
|
|
4
|
+
# 依赖目标项目根目录 release.config.json(可选)与 tauri.conf.json
|
|
5
|
+
# 流程:tauri build → 从 DMG 或 .app 取包 → 注入隔离修复脚本 → create-dmg
|
|
6
|
+
#
|
|
7
|
+
# 项目根目录:优先环境变量 TAURI_PROJECT_ROOT;否则自动探测(旧 scripts/ 布局或本 monorepo)
|
|
8
|
+
# ============================================
|
|
9
|
+
|
|
10
|
+
set -e
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
13
|
+
if [ -n "${TAURI_PROJECT_ROOT:-}" ]; then
|
|
14
|
+
PROJECT_DIR="$(cd "$TAURI_PROJECT_ROOT" && pwd)"
|
|
15
|
+
else
|
|
16
|
+
_parent="$(dirname "$SCRIPT_DIR")"
|
|
17
|
+
if [ -f "$_parent/src-tauri/tauri.conf.json" ]; then
|
|
18
|
+
PROJECT_DIR="$(cd "$_parent" && pwd)"
|
|
19
|
+
elif [ -f "$(cd "$SCRIPT_DIR/../../.." && pwd)/src-tauri/tauri.conf.json" ]; then
|
|
20
|
+
PROJECT_DIR="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
21
|
+
else
|
|
22
|
+
echo "错误:无法确定 Tauri 项目根目录。"
|
|
23
|
+
echo "请设置 TAURI_PROJECT_ROOT=/path/to/project后重试(目录下需有 src-tauri/tauri.conf.json)。"
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# 使用临时文件 source,避免 `source <(node …)` 在 sh / 部分环境下失效导致变量为空
|
|
29
|
+
DMG_ENV_FILE=$(mktemp)
|
|
30
|
+
trap 'rm -f "$DMG_ENV_FILE"' EXIT
|
|
31
|
+
node "$SCRIPT_DIR/emit-dmg-env.mjs" "$PROJECT_DIR" >"$DMG_ENV_FILE"
|
|
32
|
+
set -a
|
|
33
|
+
# shellcheck disable=SC1090
|
|
34
|
+
source "$DMG_ENV_FILE"
|
|
35
|
+
set +a
|
|
36
|
+
if [ -z "$APP_DISPLAY_NAME" ] || [ -z "$APP_VERSION" ]; then
|
|
37
|
+
echo "错误:未能从 Tauri / release 配置读取 APP_DISPLAY_NAME 或 APP_VERSION(请用 bash 执行本脚本)。"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
ARCH=$(uname -m)
|
|
42
|
+
BUNDLE_ROOT="$PROJECT_DIR/src-tauri/target/release/bundle"
|
|
43
|
+
DMG_DIR="$BUNDLE_ROOT/dmg"
|
|
44
|
+
FINAL_DMG="$DMG_DIR/${APP_DISPLAY_NAME}_${APP_VERSION}_${ARCH}.dmg"
|
|
45
|
+
QUARANTINE_TEMPLATE="$SCRIPT_DIR/../templates/macos-quarantine-fix.command.in"
|
|
46
|
+
|
|
47
|
+
echo ""
|
|
48
|
+
echo "========================================="
|
|
49
|
+
echo " ${APP_DISPLAY_NAME} macOS 打包"
|
|
50
|
+
echo "========================================="
|
|
51
|
+
echo ""
|
|
52
|
+
|
|
53
|
+
if ! command -v create-dmg &>/dev/null; then
|
|
54
|
+
echo "错误:未安装 create-dmg,请先执行:brew install create-dmg"
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if ! command -v node &>/dev/null; then
|
|
59
|
+
echo "错误:需要 Node.js 以读取 release 配置"
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
echo "步骤 1/4:执行构建 ..."
|
|
64
|
+
cd "$PROJECT_DIR"
|
|
65
|
+
eval "$TAURI_BUILD_CMD"
|
|
66
|
+
|
|
67
|
+
if [ ! -d "$BUNDLE_ROOT" ]; then
|
|
68
|
+
echo "错误:未找到打包目录:"
|
|
69
|
+
echo " $BUNDLE_ROOT"
|
|
70
|
+
echo "请确认 tauri build 已成功完成(含 bundle 阶段)。"
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
TAURI_DMG=$(find "$BUNDLE_ROOT" -name "*.dmg" -type f 2>/dev/null | head -n 1)
|
|
75
|
+
APP_FROM_BUNDLE=$(find "$BUNDLE_ROOT" -maxdepth 4 -name "*.app" -type d 2>/dev/null | head -n 1)
|
|
76
|
+
|
|
77
|
+
if [ -n "$TAURI_DMG" ]; then
|
|
78
|
+
echo "找到 Tauri DMG:$TAURI_DMG"
|
|
79
|
+
elif [ -n "$APP_FROM_BUNDLE" ]; then
|
|
80
|
+
echo "未找到 DMG,将使用 .app 继续打包:$APP_FROM_BUNDLE"
|
|
81
|
+
else
|
|
82
|
+
echo "错误:在 $BUNDLE_ROOT 下未找到 .dmg 或 .app。"
|
|
83
|
+
echo "目录内容:"
|
|
84
|
+
ls -laR "$BUNDLE_ROOT" || true
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
88
|
+
if [ ! -f "$QUARANTINE_TEMPLATE" ]; then
|
|
89
|
+
echo "错误:未找到隔离修复模板:$QUARANTINE_TEMPLATE"
|
|
90
|
+
exit 1
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
echo ""
|
|
94
|
+
echo "步骤 2/4:准备 .app 与卷图标 ..."
|
|
95
|
+
TEMP_DIR=$(mktemp -d)
|
|
96
|
+
STAGING_DIR="$TEMP_DIR/staging"
|
|
97
|
+
MOUNT_POINT="$TEMP_DIR/mount"
|
|
98
|
+
mkdir -p "$STAGING_DIR" "$MOUNT_POINT"
|
|
99
|
+
|
|
100
|
+
APP_BUNDLE_NAME=""
|
|
101
|
+
QUARANTINE_APP_STEM=""
|
|
102
|
+
VOL_ICON=""
|
|
103
|
+
|
|
104
|
+
if [ -n "$TAURI_DMG" ]; then
|
|
105
|
+
hdiutil attach "$TAURI_DMG" -mountpoint "$MOUNT_POINT" -quiet -nobrowse
|
|
106
|
+
APP_IN_DMG=$(find "$MOUNT_POINT" -maxdepth 1 -name "*.app" -type d | head -n 1)
|
|
107
|
+
if [ -z "$APP_IN_DMG" ] || [ ! -d "$APP_IN_DMG" ]; then
|
|
108
|
+
echo "错误:挂载的 DMG 内未找到 .app。"
|
|
109
|
+
ls -la "$MOUNT_POINT" || true
|
|
110
|
+
hdiutil detach "$MOUNT_POINT" -quiet || true
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
APP_BUNDLE_NAME=$(basename "$APP_IN_DMG")
|
|
114
|
+
cp -R "$APP_IN_DMG" "$STAGING_DIR/"
|
|
115
|
+
if [ -f "$MOUNT_POINT/.VolumeIcon.icns" ]; then
|
|
116
|
+
cp "$MOUNT_POINT/.VolumeIcon.icns" "$TEMP_DIR/VolumeIcon.icns"
|
|
117
|
+
VOL_ICON="$TEMP_DIR/VolumeIcon.icns"
|
|
118
|
+
fi
|
|
119
|
+
hdiutil detach "$MOUNT_POINT" -quiet
|
|
120
|
+
else
|
|
121
|
+
APP_BUNDLE_NAME=$(basename "$APP_FROM_BUNDLE")
|
|
122
|
+
cp -R "$APP_FROM_BUNDLE" "$STAGING_DIR/"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
QUARANTINE_APP_STEM="${APP_BUNDLE_NAME%.app}"
|
|
126
|
+
|
|
127
|
+
echo "步骤 3/4:生成隔离修复脚本并构建 DMG ..."
|
|
128
|
+
GEN_FIX="$STAGING_DIR/$DMG_QUARANTINE_SCRIPT_NAME"
|
|
129
|
+
node "$SCRIPT_DIR/render-quarantine-script.mjs" "$QUARANTINE_TEMPLATE" "$GEN_FIX" "$QUARANTINE_APP_STEM"
|
|
130
|
+
chmod +x "$GEN_FIX"
|
|
131
|
+
|
|
132
|
+
CREATE_DMG_ARGS=(
|
|
133
|
+
--volname "$APP_DISPLAY_NAME"
|
|
134
|
+
--window-pos 200 120
|
|
135
|
+
--window-size 700 340
|
|
136
|
+
--icon-size 80
|
|
137
|
+
--text-size 13
|
|
138
|
+
--icon "$APP_BUNDLE_NAME" 120 150
|
|
139
|
+
--icon "Applications" 350 150
|
|
140
|
+
--icon "$DMG_QUARANTINE_SCRIPT_NAME" 570 150
|
|
141
|
+
--app-drop-link 350 150
|
|
142
|
+
--no-internet-enable
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if [ -n "$VOL_ICON" ]; then
|
|
146
|
+
CREATE_DMG_ARGS+=(--volicon "$VOL_ICON")
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
rm -f "$TEMP_DIR/output.dmg"
|
|
150
|
+
create-dmg "${CREATE_DMG_ARGS[@]}" "$TEMP_DIR/output.dmg" "$STAGING_DIR"
|
|
151
|
+
|
|
152
|
+
echo ""
|
|
153
|
+
echo "步骤 4/4:写入最终 DMG ..."
|
|
154
|
+
mkdir -p "$DMG_DIR"
|
|
155
|
+
mv "$TEMP_DIR/output.dmg" "$FINAL_DMG"
|
|
156
|
+
rm -rf "$TEMP_DIR"
|
|
157
|
+
|
|
158
|
+
echo ""
|
|
159
|
+
echo "========================================="
|
|
160
|
+
echo "打包成功"
|
|
161
|
+
echo ""
|
|
162
|
+
echo "DMG 路径:"
|
|
163
|
+
echo " $FINAL_DMG"
|
|
164
|
+
echo ""
|
|
165
|
+
echo "内容:$APP_BUNDLE_NAME | Applications | $DMG_QUARANTINE_SCRIPT_NAME"
|
|
166
|
+
echo "========================================="
|
|
167
|
+
echo ""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 向 stdout 输出可被 bash `source` 的变量赋值(用于 build-macos-dmg.sh)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { loadReleaseConfig, shellSingleQuote } from './lib/release-config.mjs'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @returns {void}
|
|
9
|
+
*/
|
|
10
|
+
function main() {
|
|
11
|
+
const projectRoot = process.argv[2] || process.cwd()
|
|
12
|
+
const c = loadReleaseConfig(projectRoot)
|
|
13
|
+
const esc = shellSingleQuote
|
|
14
|
+
const folder = c.macos.applicationsAppFolderName
|
|
15
|
+
console.log(`APP_DISPLAY_NAME=${esc(c.productName)}`)
|
|
16
|
+
console.log(`APP_VERSION=${esc(c.version)}`)
|
|
17
|
+
console.log(`QUARANTINE_APP_FOLDER=${esc(folder)}`)
|
|
18
|
+
console.log(`TAURI_BUILD_CMD=${esc(c.tauriBuildCommand)}`)
|
|
19
|
+
console.log(`DMG_QUARANTINE_SCRIPT_NAME=${esc(c.macos.quarantineScriptDisplayName)}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
main()
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 跨项目复用的发布/打包配置加载器。
|
|
3
|
+
* 默认约定 Tauri 2 工程结构;可通过仓库根目录 release.config.json 覆盖路径与命令。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from 'fs'
|
|
7
|
+
import { resolve } from 'path'
|
|
8
|
+
|
|
9
|
+
const DEFAULTS = {
|
|
10
|
+
tauriConfigPath: 'src-tauri/tauri.conf.json',
|
|
11
|
+
changelogOutPath: 'src/data/changelog.ts',
|
|
12
|
+
cargoTomlPath: 'src-tauri/Cargo.toml',
|
|
13
|
+
/** @type {string[]} */
|
|
14
|
+
bumpExtraJsonVersionPaths: [],
|
|
15
|
+
tauriBuildCommand: 'pnpm tauri build',
|
|
16
|
+
genChangelogScript: 'scripts/gen-changelog.mjs',
|
|
17
|
+
macos: {
|
|
18
|
+
/** DMG 内「移除隔离属性」脚本在访达中显示的文件名 */
|
|
19
|
+
quarantineScriptDisplayName: '文件损坏修复.command',
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 读取并合并发布配置(defaults + release.config.json + tauri.conf.json 中的 productName/version)
|
|
25
|
+
*
|
|
26
|
+
* @param {string} projectRoot 仓库根目录(绝对或相对 cwd)
|
|
27
|
+
* @returns {object} 合并后的配置(含 productName、version、路径与 macOS 选项)
|
|
28
|
+
*/
|
|
29
|
+
export function loadReleaseConfig(projectRoot) {
|
|
30
|
+
const root = resolve(projectRoot)
|
|
31
|
+
const configPath = resolve(root, 'release.config.json')
|
|
32
|
+
/** @type {Record<string, unknown>} */
|
|
33
|
+
let fileCfg = {}
|
|
34
|
+
if (existsSync(configPath)) {
|
|
35
|
+
fileCfg = JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tauriRel =
|
|
39
|
+
typeof fileCfg.tauriConfigPath === 'string' ? fileCfg.tauriConfigPath : DEFAULTS.tauriConfigPath
|
|
40
|
+
const tauriPath = resolve(root, tauriRel)
|
|
41
|
+
if (!existsSync(tauriPath)) {
|
|
42
|
+
throw new Error(`[release-config] 未找到 Tauri 配置:${tauriPath}`)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const tauri = JSON.parse(readFileSync(tauriPath, 'utf-8'))
|
|
46
|
+
const productName = tauri.productName
|
|
47
|
+
const version = tauri.version
|
|
48
|
+
if (!productName || !version) {
|
|
49
|
+
throw new Error(`[release-config] ${tauriRel} 缺少 productName 或 version`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const macosFile = /** @type {Record<string, unknown>} */ (
|
|
53
|
+
fileCfg.macos && typeof fileCfg.macos === 'object' ? fileCfg.macos : {}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const applicationsAppFolderName =
|
|
57
|
+
typeof macosFile.applicationsAppFolderName === 'string' ? macosFile.applicationsAppFolderName : productName
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
projectRoot: root,
|
|
61
|
+
tauriConfigPath: tauriRel,
|
|
62
|
+
changelogOutPath:
|
|
63
|
+
typeof fileCfg.changelogOutPath === 'string' ? fileCfg.changelogOutPath : DEFAULTS.changelogOutPath,
|
|
64
|
+
cargoTomlPath: typeof fileCfg.cargoTomlPath === 'string' ? fileCfg.cargoTomlPath : DEFAULTS.cargoTomlPath,
|
|
65
|
+
bumpExtraJsonVersionPaths: Array.isArray(fileCfg.bumpExtraJsonVersionPaths)
|
|
66
|
+
? fileCfg.bumpExtraJsonVersionPaths.filter((p) => typeof p === 'string')
|
|
67
|
+
: DEFAULTS.bumpExtraJsonVersionPaths,
|
|
68
|
+
tauriBuildCommand:
|
|
69
|
+
typeof fileCfg.tauriBuildCommand === 'string' ? fileCfg.tauriBuildCommand : DEFAULTS.tauriBuildCommand,
|
|
70
|
+
genChangelogScript:
|
|
71
|
+
typeof fileCfg.genChangelogScript === 'string' ? fileCfg.genChangelogScript : DEFAULTS.genChangelogScript,
|
|
72
|
+
productName,
|
|
73
|
+
version,
|
|
74
|
+
macos: {
|
|
75
|
+
quarantineScriptDisplayName:
|
|
76
|
+
typeof macosFile.quarantineScriptDisplayName === 'string'
|
|
77
|
+
? macosFile.quarantineScriptDisplayName
|
|
78
|
+
: DEFAULTS.macos.quarantineScriptDisplayName,
|
|
79
|
+
applicationsAppFolderName,
|
|
80
|
+
},
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 将字符串转为可安全嵌入单引号 shell 赋值语句的字面量
|
|
86
|
+
*
|
|
87
|
+
* @param {string} s
|
|
88
|
+
* @returns {string}
|
|
89
|
+
*/
|
|
90
|
+
export function shellSingleQuote(s) {
|
|
91
|
+
return `'${String(s).replace(/'/g, `'\\''`)}'`
|
|
92
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 将模板中的占位符替换为实际「应用程序」目录名(不含 .app),写入 DMG 内 .command 文件
|
|
3
|
+
*
|
|
4
|
+
* @param {string} templatePath 模板路径
|
|
5
|
+
* @param {string} outputPath 输出路径
|
|
6
|
+
* @param {string} appFolderName Applications 下的 .app 目录名(不含后缀)
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @returns {void}
|
|
12
|
+
*/
|
|
13
|
+
function main() {
|
|
14
|
+
const [, , templatePath, outputPath, appFolderName] = process.argv
|
|
15
|
+
if (!templatePath || !outputPath || appFolderName === undefined) {
|
|
16
|
+
console.error('用法: node render-quarantine-script.mjs <模板> <输出> <App目录名>')
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}
|
|
19
|
+
let text = readFileSync(templatePath, 'utf8')
|
|
20
|
+
text = text.replace(/__QUARANTINE_APP_FOLDER__/g, appFolderName)
|
|
21
|
+
writeFileSync(outputPath, text, 'utf8')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# 由 build-macos-dmg.sh 从模板生成;占位符 __QUARANTINE_APP_FOLDER__ 为「应用程序」内 .app 主名
|
|
3
|
+
|
|
4
|
+
APP_FOLDER="__QUARANTINE_APP_FOLDER__"
|
|
5
|
+
APP_PATH="/Applications/${APP_FOLDER}.app"
|
|
6
|
+
|
|
7
|
+
echo ""
|
|
8
|
+
echo "========================================="
|
|
9
|
+
echo " 移除隔离属性:${APP_FOLDER}"
|
|
10
|
+
echo "========================================="
|
|
11
|
+
echo ""
|
|
12
|
+
|
|
13
|
+
if [ ! -d "$APP_PATH" ]; then
|
|
14
|
+
echo "错误:未在 /Applications 目录找到 ${APP_FOLDER}.app"
|
|
15
|
+
echo ""
|
|
16
|
+
echo "请确认应用已拖入「应用程序」文件夹,或手动输入路径:"
|
|
17
|
+
read -r -p "应用路径(直接回车跳过): " CUSTOM_PATH
|
|
18
|
+
if [ -n "$CUSTOM_PATH" ]; then
|
|
19
|
+
APP_PATH="$CUSTOM_PATH"
|
|
20
|
+
else
|
|
21
|
+
echo ""
|
|
22
|
+
echo "已取消操作。"
|
|
23
|
+
echo ""
|
|
24
|
+
read -n 1 -s -r -p "按任意键关闭窗口..."
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
echo "正在修复:$APP_PATH"
|
|
30
|
+
echo "(需要输入开机密码,输入时不会显示字符)"
|
|
31
|
+
echo ""
|
|
32
|
+
|
|
33
|
+
sudo xattr -rd com.apple.quarantine "$APP_PATH"
|
|
34
|
+
|
|
35
|
+
if [ $? -eq 0 ]; then
|
|
36
|
+
echo ""
|
|
37
|
+
echo "完成:已移除隔离属性,可尝试重新打开 ${APP_FOLDER}。"
|
|
38
|
+
else
|
|
39
|
+
echo ""
|
|
40
|
+
echo "失败:请检查密码是否正确或路径是否存在。"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
echo ""
|
|
44
|
+
read -n 1 -s -r -p "按任意键关闭窗口..."
|