electron-forge-maker-innosetup 0.3.3 → 1.0.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.
@@ -32,324 +32,396 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
- };
38
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
39
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
37
  };
41
38
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.InnoScriptParser = exports.InnoScriptGenerator = void 0;
43
39
  const path = __importStar(require("path"));
44
40
  const fs = __importStar(require("fs"));
45
41
  const child_process_1 = require("child_process");
46
42
  const maker_base_1 = __importDefault(require("@electron-forge/maker-base"));
43
+ const types_1 = require("./types");
47
44
  const generator_1 = require("./generator");
48
45
  const parser_1 = require("./parser");
46
+ const errors_1 = require("./errors");
47
+ const logger_1 = require("./logger");
48
+ const path_1 = require("./utils/path");
49
+ /** 默认编译超时时间(5 分钟) */
50
+ const DEFAULT_COMPILE_TIMEOUT = 300000;
51
+ /** 默认的 Inno Setup 编译器搜索路径 */
52
+ const DEFAULT_COMPILER_PATHS = [
53
+ // 内置便携版(相对于 dist 目录)
54
+ path.join(__dirname, "..", "vendor", "innosetup", "ISCC.exe"),
55
+ path.join(__dirname, "..", "vendor", "ISCC.exe"),
56
+ // 内置便携版(相对于 src 目录 - 用于开发环境)
57
+ path.join(__dirname, "..", "..", "vendor", "innosetup", "ISCC.exe"),
58
+ path.join(__dirname, "..", "..", "vendor", "ISCC.exe"),
59
+ // 系统安装路径
60
+ "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe",
61
+ "C:\\Program Files\\Inno Setup 6\\ISCC.exe",
62
+ "C:\\Program Files (x86)\\Inno Setup 5\\ISCC.exe",
63
+ "C:\\Program Files\\Inno Setup 5\\ISCC.exe",
64
+ ];
49
65
  /**
50
- * Electron Forge Maker for Innosetup
66
+ * Electron Forge Inno Setup Maker
67
+ *
68
+ * 使用 Inno Setup 编译器(ISCC.exe)创建 Windows 安装程序
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * import { MakerInnosetup } from 'electron-forge-maker-innosetup';
73
+ *
74
+ * // 在 forge.config.ts 中
75
+ * const config: ForgeConfig = {
76
+ * makers: [
77
+ * new MakerInnosetup({
78
+ * appName: 'My App',
79
+ * appVersion: '1.0.0',
80
+ * setupIconFile: './assets/icon.ico',
81
+ * config: {
82
+ * Setup: {
83
+ * AppName: 'My App',
84
+ * AppVersion: '1.0.0',
85
+ * DefaultDirName: '{autopf}\\MyApp',
86
+ * OutputDir: './out/installer',
87
+ * },
88
+ * },
89
+ * }),
90
+ * ],
91
+ * };
92
+ * ```
51
93
  */
52
94
  class MakerInnosetup extends maker_base_1.default {
53
95
  /**
54
- * 获取项目根目录
96
+ * 从配置获取项目目录,若未配置则使用当前工作目录
55
97
  */
56
98
  getProjectDir() {
57
- return this.config?.paths?.projectDir || process.cwd();
99
+ return this.config.paths?.projectDir ?? process.cwd();
58
100
  }
59
101
  /**
60
- * 获取构建目录
102
+ * 从配置获取构建目录
61
103
  */
62
104
  getBuildDir() {
63
- return this.config?.paths?.buildDir;
105
+ return this.config.paths?.buildDir;
64
106
  }
65
107
  /**
66
- * 获取资源目录
108
+ * 从配置获取资源目录,若未配置则使用默认值 "assets"
67
109
  */
68
110
  getAssetsDir() {
69
- return this.config?.paths?.assetsDir || "assets";
111
+ return this.config.paths?.assetsDir ?? "assets";
70
112
  }
71
113
  constructor(config = {}, platforms) {
72
114
  // 默认启用相对路径解析
73
- if (config.resolveRelativePaths === undefined) {
74
- config.resolveRelativePaths = true;
75
- }
76
- super(config, platforms);
115
+ const finalConfig = {
116
+ resolveRelativePaths: true,
117
+ ...config,
118
+ };
119
+ super(finalConfig, platforms);
77
120
  this.name = "innosetup";
78
121
  this.defaultPlatforms = ["win32"];
79
122
  this.scriptGenerator = new generator_1.InnoScriptGenerator();
80
123
  }
81
124
  /**
82
- * 解析路径 - 支持相对路径转换为绝对路径
83
- * @param pathStr 输入路径
84
- * @param baseDir 基础目录(默认为 projectDir)
85
- * @returns 解析后的绝对路径
125
+ * ISS 文件创建 MakerInnosetupConfig
126
+ *
127
+ * @param issFilePath - ISS 文件路径
128
+ * @returns 解析后的配置对象
129
+ */
130
+ static fromIssFile(issFilePath) {
131
+ const config = parser_1.InnoScriptParser.parseFile(issFilePath);
132
+ return {
133
+ config,
134
+ scriptPath: issFilePath,
135
+ };
136
+ }
137
+ /**
138
+ * 从 ISS 内容字符串创建 MakerInnosetupConfig
139
+ *
140
+ * @param issContent - ISS 文件内容
141
+ * @returns 解析后的配置对象
142
+ */
143
+ static fromIssContent(issContent) {
144
+ const config = parser_1.InnoScriptParser.parse(issContent);
145
+ return { config };
146
+ }
147
+ /**
148
+ * 检查当前平台是否支持
149
+ *
150
+ * @returns 当前平台是否为 Windows
151
+ */
152
+ isSupportedOnCurrentPlatform() {
153
+ return process.platform === "win32";
154
+ }
155
+ /**
156
+ * 创建安装程序
157
+ *
158
+ * @param options - Electron Forge Maker 选项
159
+ * @returns 生成的安装程序文件路径数组
160
+ */
161
+ async make(options) {
162
+ const { appName, dir, makeDir, targetArch, packageJSON } = options;
163
+ const appVersion = packageJSON.version ?? "1.0.0";
164
+ const archId = (0, types_1.getArchIdentifier)(targetArch);
165
+ // 验证平台
166
+ if (!this.isSupportedOnCurrentPlatform()) {
167
+ throw new errors_1.PlatformNotSupportedError(process.platform);
168
+ }
169
+ // 设置构建目录用于路径解析
170
+ this.config.paths = {
171
+ ...this.config.paths,
172
+ buildDir: dir,
173
+ };
174
+ (0, logger_1.log)("正在创建 Inno Setup 安装程序: %s %s (%s)", appName, appVersion, targetArch);
175
+ (0, logger_1.logPath)("项目目录: %s", this.getProjectDir());
176
+ (0, logger_1.logPath)("构建目录: %s", this.getBuildDir());
177
+ // 查找编译器
178
+ const compilerPath = this.findInnosetupCompiler();
179
+ (0, logger_1.logCompile)("使用 Inno Setup 编译器: %s", compilerPath);
180
+ // 确定输出目录
181
+ const baseOutputDir = this.config.outputDir ?? path.join(makeDir, "innosetup.windows");
182
+ const outputDir = path.join(baseOutputDir, archId);
183
+ (0, path_1.ensureDir)(outputDir);
184
+ (0, logger_1.log)("输出目录: %s", outputDir);
185
+ // 生成或使用现有脚本
186
+ let scriptPath;
187
+ // actualOutputDir 用于查找生成的安装程序
188
+ let actualOutputDir = outputDir;
189
+ if (this.config.scriptPath) {
190
+ scriptPath = this.resolvePath(this.config.scriptPath, this.getProjectDir());
191
+ (0, logger_1.log)("使用自定义脚本: %s", scriptPath);
192
+ // 解析脚本以获取输出目录
193
+ try {
194
+ const parsedConfig = parser_1.InnoScriptParser.parseFile(scriptPath);
195
+ if (parsedConfig.Setup.OutputDir) {
196
+ actualOutputDir = path.isAbsolute(parsedConfig.Setup.OutputDir)
197
+ ? parsedConfig.Setup.OutputDir
198
+ : path.resolve(path.dirname(scriptPath), parsedConfig.Setup.OutputDir);
199
+ (0, logger_1.log)("脚本定义的 OutputDir: %s", actualOutputDir);
200
+ }
201
+ }
202
+ catch (err) {
203
+ (0, logger_1.logCompile)("解析脚本 OutputDir 失败: %s", err);
204
+ }
205
+ }
206
+ else {
207
+ // 生成脚本
208
+ const defaultConfig = this.generateDefaultConfig(dir, appName, appVersion, targetArch, outputDir);
209
+ const finalConfig = this.mergeConfig(defaultConfig, this.config.config);
210
+ // 解析配置中的路径
211
+ this.resolveConfigPaths(finalConfig, dir);
212
+ // 添加任务(桌面图标等)
213
+ this.addTasks(finalConfig, appName);
214
+ // 生成并保存脚本
215
+ const scriptContent = this.scriptGenerator.generate(finalConfig);
216
+ scriptPath = path.join(makeDir, `${appName}-setup.iss`);
217
+ this.scriptGenerator.saveToFile(scriptContent, scriptPath);
218
+ (0, logger_1.log)("已生成 Inno Setup 脚本: %s", scriptPath);
219
+ // 使用配置中的 OutputDir(如果有)
220
+ if (finalConfig.Setup.OutputDir) {
221
+ actualOutputDir = finalConfig.Setup.OutputDir;
222
+ }
223
+ }
224
+ // 编译
225
+ (0, logger_1.logCompile)("正在编译安装程序...");
226
+ await this.compileScript(scriptPath, compilerPath);
227
+ // 查找生成的安装程序
228
+ const installerPaths = this.findInstaller(actualOutputDir, appName, appVersion, archId);
229
+ // 输出成功信息
230
+ (0, logger_1.log)("✓ 安装程序创建成功!");
231
+ for (const installerPath of installerPaths) {
232
+ const stats = fs.statSync(installerPath);
233
+ const sizeMB = (stats.size / 1024 / 1024).toFixed(2);
234
+ (0, logger_1.log)(" 文件: %s", installerPath);
235
+ (0, logger_1.log)(" 大小: %s MB", sizeMB);
236
+ }
237
+ return installerPaths;
238
+ }
239
+ /**
240
+ * 查找 Inno Setup 编译器
241
+ *
242
+ * 按以下顺序查找:
243
+ * 1. 配置中指定的路径
244
+ * 2. 环境变量 INNOSETUP_PATH
245
+ * 3. 默认搜索路径
246
+ *
247
+ * @returns 编译器路径
248
+ * @throws CompilerNotFoundError 如果找不到编译器
86
249
  */
87
- resolvePath(pathStr, baseDir) {
88
- if (!pathStr) {
89
- return undefined;
250
+ findInnosetupCompiler() {
251
+ // 首先检查配置路径
252
+ if (this.config.innosetupPath) {
253
+ if ((0, path_1.fileExists)(this.config.innosetupPath)) {
254
+ return this.config.innosetupPath;
255
+ }
90
256
  }
91
- // 如果禁用了相对路径解析,直接返回
92
- if (this.config?.resolveRelativePaths === false) {
93
- return pathStr;
257
+ // 检查环境变量
258
+ const envPath = process.env.INNOSETUP_PATH;
259
+ if (envPath && (0, path_1.fileExists)(envPath)) {
260
+ return envPath;
94
261
  }
95
- // 如果已经是绝对路径,直接返回
96
- if (path.isAbsolute(pathStr)) {
97
- return pathStr;
262
+ // 搜索默认路径
263
+ for (const searchPath of DEFAULT_COMPILER_PATHS) {
264
+ if ((0, path_1.fileExists)(searchPath)) {
265
+ (0, logger_1.logCompile)("找到内置 Inno Setup: %s", searchPath);
266
+ return searchPath;
267
+ }
98
268
  }
99
- // 使用提供的 baseDir 或默认的 projectDir
100
- const base = baseDir || this.getProjectDir();
101
- return path.resolve(base, pathStr);
269
+ throw new errors_1.CompilerNotFoundError([this.config.innosetupPath, process.env.INNOSETUP_PATH, ...DEFAULT_COMPILER_PATHS].filter(Boolean));
102
270
  }
103
271
  /**
104
- * 解析配置中的路径占位符
105
- * 支持:{project}, {assets}, {build}, {app}
106
- * @param pathStr 包含占位符的路径字符串
107
- * @returns 解析后的路径
272
+ * 解析相对于基础目录的路径
273
+ *
274
+ * @param input - 输入路径
275
+ * @param baseDir - 基础目录
276
+ * @returns 解析后的绝对路径
108
277
  */
109
- resolvePathPlaceholders(pathStr) {
110
- if (!pathStr) {
111
- return undefined;
278
+ resolvePath(input, baseDir) {
279
+ if (!input) {
280
+ return "";
112
281
  }
113
- let resolved = pathStr;
282
+ // 如果禁用了相对路径解析则直接返回
283
+ if (this.config.resolveRelativePaths === false) {
284
+ return input;
285
+ }
286
+ // 已经是绝对路径,规范化分隔符
287
+ if (path.isAbsolute(input)) {
288
+ return path.normalize(input);
289
+ }
290
+ // 解析相对路径并规范化
291
+ return path.normalize(path.resolve(baseDir, input));
292
+ }
293
+ /**
294
+ * 解析字符串中的路径占位符
295
+ *
296
+ * 支持的占位符:
297
+ * - {project} - 项目根目录
298
+ * - {build} - 构建输出目录
299
+ * - {assets} - 资源文件目录
300
+ *
301
+ * @param input - 输入字符串
302
+ * @returns 替换占位符后的字符串
303
+ */
304
+ resolvePathPlaceholders(input) {
305
+ let result = input;
114
306
  const projectDir = this.getProjectDir();
115
307
  const buildDir = this.getBuildDir();
116
308
  const assetsDir = this.getAssetsDir();
117
309
  // {project} - 项目根目录
118
- resolved = resolved.replace(/\{project\}/g, projectDir);
310
+ result = result.replace(/\{project\}/g, projectDir);
119
311
  // {build} - 构建输出目录
120
312
  if (buildDir) {
121
- resolved = resolved.replace(/\{build\}/g, buildDir);
313
+ result = result.replace(/\{build\}/g, buildDir);
122
314
  }
123
315
  // {assets} - 资源目录
124
316
  const assetsPath = path.resolve(projectDir, assetsDir);
125
- resolved = resolved.replace(/\{assets\}/g, assetsPath);
126
- return resolved;
317
+ result = result.replace(/\{assets\}/g, assetsPath);
318
+ return result;
127
319
  }
128
320
  /**
129
- * 处理配置对象中的所有路径
130
- * @param config Innosetup 配置对象
131
- * @param appDir 应用目录
321
+ * 解析配置中的所有路径
322
+ *
323
+ * @param config - Inno Setup 配置
324
+ * @param appDir - 应用目录
132
325
  */
133
326
  resolveConfigPaths(config, appDir) {
134
- if (this.config?.resolveRelativePaths === false) {
327
+ if (this.config.resolveRelativePaths === false) {
135
328
  return;
136
329
  }
137
330
  const projectDir = this.getProjectDir();
138
- // 解析 Setup 部分的路径
331
+ // 解析 Setup 节中的路径字段
139
332
  if (config.Setup) {
140
- // SetupIconFile
141
- if (config.Setup.SetupIconFile) {
142
- config.Setup.SetupIconFile = this.resolvePath(this.resolvePathPlaceholders(config.Setup.SetupIconFile), projectDir);
143
- }
144
- // LicenseFile
145
- if (config.Setup.LicenseFile) {
146
- config.Setup.LicenseFile = this.resolvePath(this.resolvePathPlaceholders(config.Setup.LicenseFile), projectDir);
147
- }
148
- // InfoBeforeFile
149
- if (config.Setup.InfoBeforeFile) {
150
- config.Setup.InfoBeforeFile = this.resolvePath(this.resolvePathPlaceholders(config.Setup.InfoBeforeFile), projectDir);
151
- }
152
- // InfoAfterFile
153
- if (config.Setup.InfoAfterFile) {
154
- config.Setup.InfoAfterFile = this.resolvePath(this.resolvePathPlaceholders(config.Setup.InfoAfterFile), projectDir);
155
- }
156
- // WizardImageFile
157
- if (config.Setup.WizardImageFile) {
158
- config.Setup.WizardImageFile = this.resolvePath(this.resolvePathPlaceholders(config.Setup.WizardImageFile), projectDir);
159
- }
160
- // WizardSmallImageFile
161
- if (config.Setup.WizardSmallImageFile) {
162
- config.Setup.WizardSmallImageFile = this.resolvePath(this.resolvePathPlaceholders(config.Setup.WizardSmallImageFile), projectDir);
333
+ const pathFields = [
334
+ "SetupIconFile",
335
+ "LicenseFile",
336
+ "InfoBeforeFile",
337
+ "InfoAfterFile",
338
+ "WizardImageFile",
339
+ "WizardSmallImageFile",
340
+ ];
341
+ for (const field of pathFields) {
342
+ const value = config.Setup[field];
343
+ if (value && typeof value === "string") {
344
+ const resolved = this.resolvePathPlaceholders(value);
345
+ config.Setup = {
346
+ ...config.Setup,
347
+ [field]: this.resolvePath(resolved, projectDir),
348
+ };
349
+ }
163
350
  }
164
351
  }
165
- // 解析 Languages 部分的路径
352
+ // 解析 Languages 节中的路径
166
353
  if (config.Languages) {
167
- config.Languages.forEach((lang) => {
168
- if (lang.LicenseFile && !lang.LicenseFile.startsWith("compiler:")) {
169
- lang.LicenseFile = this.resolvePath(this.resolvePathPlaceholders(lang.LicenseFile), projectDir);
170
- }
171
- if (lang.InfoBeforeFile &&
172
- !lang.InfoBeforeFile.startsWith("compiler:")) {
173
- lang.InfoBeforeFile = this.resolvePath(this.resolvePathPlaceholders(lang.InfoBeforeFile), projectDir);
174
- }
175
- if (lang.InfoAfterFile && !lang.InfoAfterFile.startsWith("compiler:")) {
176
- lang.InfoAfterFile = this.resolvePath(this.resolvePathPlaceholders(lang.InfoAfterFile), projectDir);
354
+ config.Languages = config.Languages.map((lang) => {
355
+ const resolved = { ...lang };
356
+ const langPathFields = ["LicenseFile", "InfoBeforeFile", "InfoAfterFile"];
357
+ for (const field of langPathFields) {
358
+ const value = resolved[field];
359
+ if (value && !value.startsWith("compiler:")) {
360
+ const resolvedPath = this.resolvePathPlaceholders(value);
361
+ resolved[field] = this.resolvePath(resolvedPath, projectDir);
362
+ }
177
363
  }
364
+ return resolved;
178
365
  });
179
366
  }
180
- // 解析 Files 部分的路径
367
+ // 解析 Files 节中的路径
181
368
  if (config.Files) {
182
- config.Files.forEach((file) => {
183
- // Source 路径处理
184
- if (file.Source) {
185
- // 首先替换我们自定义的占位符
186
- let sourcePath = this.resolvePathPlaceholders(file.Source);
187
- // 如果替换后不是 Inno Setup 内置常量(如 {app}, {tmp}),则解析为绝对路径
188
- // Inno Setup 常量通常以 {app}, {tmp}, {src}, {win}, {sys} 等开头
189
- const isInnoConstant = sourcePath?.match(/^\{(app|tmp|src|win|sys|pf|cf|dao|fonts|userappdata|localappdata|group|autoprograms|autodesktop|commondocs)/i);
190
- if (sourcePath && !isInnoConstant) {
191
- // 如果包含 * 通配符,需要特殊处理
192
- if (sourcePath.includes("*")) {
193
- const basePath = sourcePath.split("*")[0];
194
- const wildcard = sourcePath.substring(basePath.length);
195
- const resolvedBase = this.resolvePath(basePath, appDir);
196
- sourcePath = resolvedBase ? resolvedBase + wildcard : sourcePath;
197
- }
198
- else {
199
- sourcePath = this.resolvePath(sourcePath, appDir);
200
- }
201
- file.Source = sourcePath;
202
- }
203
- else if (sourcePath) {
204
- // 是 Inno Setup 常量,保持不变
205
- file.Source = sourcePath;
206
- }
369
+ config.Files = config.Files.map((file) => {
370
+ if (!file.Source)
371
+ return file;
372
+ const sourcePath = this.resolvePathPlaceholders(file.Source);
373
+ // 跳过 Inno Setup 常量
374
+ if ((0, path_1.isInnoSetupConstant)(sourcePath)) {
375
+ return { ...file, Source: sourcePath };
376
+ }
377
+ // 处理通配符
378
+ if (sourcePath.includes("*") || sourcePath.includes("?")) {
379
+ const { basePath, wildcard } = (0, path_1.splitWildcardPath)(sourcePath);
380
+ const resolvedBase = this.resolvePath(basePath, appDir);
381
+ return { ...file, Source: resolvedBase + wildcard };
207
382
  }
383
+ return { ...file, Source: this.resolvePath(sourcePath, appDir) };
208
384
  });
209
385
  }
210
386
  }
211
387
  /**
212
- * ISS 文件解析配置
213
- * @param issFilePath ISS 文件路径
214
- * @returns MakerInnosetupConfig 配置对象
215
- */
216
- static fromIssFile(issFilePath) {
217
- const config = parser_1.InnoScriptParser.parseFile(issFilePath);
218
- return {
219
- config: config,
220
- scriptPath: issFilePath,
221
- };
222
- }
223
- /**
224
- * 从 ISS 脚本内容解析配置
225
- * @param issContent ISS 脚本内容
226
- * @returns MakerInnosetupConfig 配置对象
227
- */
228
- static fromIssContent(issContent) {
229
- const config = parser_1.InnoScriptParser.parse(issContent);
230
- return {
231
- config: config,
232
- };
233
- }
234
- /**
235
- * 检查是否支持当前平台
236
- */
237
- isSupportedOnCurrentPlatform() {
238
- return process.platform === "win32";
239
- }
240
- /**
241
- * 查找 Innosetup 编译器路径
242
- */
243
- findInnosetupCompiler() {
244
- if (this.config.innosetupPath) {
245
- return this.config.innosetupPath;
246
- }
247
- // 1. 优先查找内置的便携版(相对于包的位置)
248
- const builtinPaths = [
249
- // 相对于 dist 目录
250
- path.join(__dirname, "..", "vendor", "innosetup", "ISCC.exe"),
251
- path.join(__dirname, "..", "vendor", "ISCC.exe"),
252
- // 相对于 src 目录
253
- path.join(__dirname, "..", "..", "vendor", "innosetup", "ISCC.exe"),
254
- path.join(__dirname, "..", "..", "vendor", "ISCC.exe"),
255
- ];
256
- for (const builtinPath of builtinPaths) {
257
- if (fs.existsSync(builtinPath)) {
258
- console.log(`Using bundled Innosetup: ${builtinPath}`);
259
- return builtinPath;
260
- }
261
- }
262
- // 2. 尝试从环境变量中查找
263
- if (process.env.INNOSETUP_PATH &&
264
- fs.existsSync(process.env.INNOSETUP_PATH)) {
265
- return process.env.INNOSETUP_PATH;
266
- }
267
- // 3. 查找系统安装的 Innosetup
268
- const systemPaths = [
269
- "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe",
270
- "C:\\Program Files\\Inno Setup 6\\ISCC.exe",
271
- "C:\\Program Files (x86)\\Inno Setup 5\\ISCC.exe",
272
- "C:\\Program Files\\Inno Setup 5\\ISCC.exe",
273
- ];
274
- for (const systemPath of systemPaths) {
275
- if (fs.existsSync(systemPath)) {
276
- return systemPath;
277
- }
278
- }
279
- throw new Error("Innosetup compiler not found. Please:\n" +
280
- "1. Place Innosetup portable in vendor/innosetup/ directory, or\n" +
281
- "2. Install Innosetup to system, or\n" +
282
- "3. Set INNOSETUP_PATH environment variable, or\n" +
283
- "4. Set innosetupPath in config.");
284
- }
285
- /**
286
- * 根据架构获取架构标识符
287
- */
288
- getArchIdentifier(arch) {
289
- switch (arch) {
290
- case "x64":
291
- return "x64";
292
- case "ia32":
293
- case "x86":
294
- return "x86";
295
- case "arm64":
296
- return "arm64";
297
- default:
298
- return arch;
299
- }
300
- }
301
- /**
302
- * 根据架构获取 ArchitecturesAllowed 配置
303
- */
304
- getArchitecturesAllowed(arch) {
305
- switch (arch) {
306
- case "x64":
307
- return "x64compatible";
308
- case "ia32":
309
- case "x86":
310
- return "x86compatible";
311
- case "arm64":
312
- return "arm64";
313
- default:
314
- return arch;
315
- }
316
- }
317
- /**
318
- * 生成默认配置
388
+ * 生成默认的 Inno Setup 配置
389
+ *
390
+ * @param appDir - 应用目录
391
+ * @param appName - 应用名称
392
+ * @param appVersion - 应用版本
393
+ * @param arch - 目标架构
394
+ * @param outputDir - 输出目录
395
+ * @returns 默认配置对象
319
396
  */
320
397
  generateDefaultConfig(appDir, appName, appVersion, arch, outputDir) {
321
398
  const exeName = `${appName}.exe`;
322
- const archId = this.getArchIdentifier(arch);
399
+ const archId = (0, types_1.getArchIdentifier)(arch);
323
400
  const outputName = `${appName}-${appVersion}-${archId}-setup`;
401
+ const setup = {
402
+ AppName: this.config.appName ?? appName,
403
+ AppVersion: this.config.appVersion ?? appVersion,
404
+ AppPublisher: this.config.appPublisher ?? "",
405
+ AppId: this.config.appId ?? `{{${appName}}}`,
406
+ DefaultDirName: `{autopf}\\${appName}`,
407
+ DefaultGroupName: appName,
408
+ OutputDir: outputDir,
409
+ OutputBaseFilename: outputName,
410
+ Compression: "lzma2",
411
+ SolidCompression: true,
412
+ ArchitecturesAllowed: (0, types_1.getArchitecturesAllowed)(arch),
413
+ ArchitecturesInstallIn64BitMode: arch === "x64" ? "x64compatible" : "",
414
+ SetupIconFile: this.config.setupIconFile ?? "",
415
+ UninstallDisplayIcon: `{app}\\${exeName}`,
416
+ LicenseFile: this.config.licenseFile ?? "",
417
+ PrivilegesRequired: "admin",
418
+ WizardStyle: "modern",
419
+ };
324
420
  return {
325
- Setup: {
326
- AppName: this.config.appName || appName,
327
- AppVersion: this.config.appVersion || appVersion,
328
- AppPublisher: this.config.appPublisher || "",
329
- AppId: this.config.appId || `{{${appName}}}`,
330
- DefaultDirName: `{autopf}\\${appName}`,
331
- DefaultGroupName: appName,
332
- OutputDir: outputDir,
333
- OutputBaseFilename: outputName,
334
- Compression: "lzma2",
335
- SolidCompression: true,
336
- ArchitecturesAllowed: this.getArchitecturesAllowed(arch),
337
- ArchitecturesInstallIn64BitMode: arch === "x64" ? "x64compatible" : "",
338
- SetupIconFile: this.config.setupIconFile || "",
339
- UninstallDisplayIcon: `{app}\\${exeName}`,
340
- LicenseFile: this.config.licenseFile || "",
341
- PrivilegesRequired: "admin",
342
- WizardStyle: "modern",
343
- },
421
+ Setup: setup,
344
422
  Languages: [
345
- {
346
- Name: "english",
347
- MessagesFile: "compiler:Default.isl",
348
- },
349
- {
350
- Name: "chinesesimplified",
351
- MessagesFile: "compiler:Languages\\ChineseSimplified.isl",
352
- },
423
+ { Name: "english", MessagesFile: "compiler:Default.isl" },
424
+ { Name: "chinesesimplified", MessagesFile: "compiler:Languages\\ChineseSimplified.isl" },
353
425
  ],
354
426
  Tasks: [],
355
427
  Files: [
@@ -360,103 +432,102 @@ class MakerInnosetup extends maker_base_1.default {
360
432
  },
361
433
  ],
362
434
  Icons: [
363
- {
364
- Name: `{group}\\${appName}`,
365
- Filename: `{app}\\${exeName}`,
366
- },
367
- {
368
- Name: `{group}\\Uninstall ${appName}`,
369
- Filename: "{uninstallexe}",
370
- },
435
+ { Name: `{group}\\${appName}`, Filename: `{app}\\${exeName}` },
436
+ { Name: `{group}\\卸载 ${appName}`, Filename: "{uninstallexe}" },
371
437
  ],
372
438
  Run: [
373
439
  {
374
440
  Filename: `{app}\\${exeName}`,
375
- Description: `Launch ${appName}`,
441
+ Description: `启动 ${appName}`,
376
442
  Flags: "nowait postinstall skipifsilent",
377
443
  },
378
444
  ],
379
445
  };
380
446
  }
381
447
  /**
382
- * 合并用户配置和默认配置
448
+ * 合并用户配置与默认配置
449
+ *
450
+ * @param defaultConfig - 默认配置
451
+ * @param userConfig - 用户配置
452
+ * @returns 合并后的配置
383
453
  */
384
454
  mergeConfig(defaultConfig, userConfig) {
385
455
  if (!userConfig) {
386
456
  return defaultConfig;
387
457
  }
388
458
  return {
389
- Defines: userConfig.Defines || defaultConfig.Defines,
459
+ Defines: userConfig.Defines ?? defaultConfig.Defines,
390
460
  Setup: { ...defaultConfig.Setup, ...userConfig.Setup },
391
- Languages: userConfig.Languages || defaultConfig.Languages,
392
- Tasks: userConfig.Tasks || defaultConfig.Tasks,
393
- Types: userConfig.Types || defaultConfig.Types,
394
- Components: userConfig.Components || defaultConfig.Components,
395
- Files: userConfig.Files || defaultConfig.Files,
396
- Dirs: userConfig.Dirs || defaultConfig.Dirs,
397
- Icons: userConfig.Icons || defaultConfig.Icons,
398
- INI: userConfig.INI || defaultConfig.INI,
399
- InstallDelete: userConfig.InstallDelete || defaultConfig.InstallDelete,
400
- UninstallDelete: userConfig.UninstallDelete || defaultConfig.UninstallDelete,
401
- Registry: userConfig.Registry || defaultConfig.Registry,
402
- Run: userConfig.Run || defaultConfig.Run,
403
- UninstallRun: userConfig.UninstallRun || defaultConfig.UninstallRun,
404
- Messages: userConfig.Messages || defaultConfig.Messages,
405
- CustomMessages: userConfig.CustomMessages || defaultConfig.CustomMessages,
406
- Code: userConfig.Code || defaultConfig.Code,
461
+ Languages: userConfig.Languages ?? defaultConfig.Languages,
462
+ Tasks: userConfig.Tasks ?? defaultConfig.Tasks,
463
+ Types: userConfig.Types ?? defaultConfig.Types,
464
+ Components: userConfig.Components ?? defaultConfig.Components,
465
+ Files: userConfig.Files ?? defaultConfig.Files,
466
+ Dirs: userConfig.Dirs ?? defaultConfig.Dirs,
467
+ Icons: userConfig.Icons ?? defaultConfig.Icons,
468
+ INI: userConfig.INI ?? defaultConfig.INI,
469
+ InstallDelete: userConfig.InstallDelete ?? defaultConfig.InstallDelete,
470
+ UninstallDelete: userConfig.UninstallDelete ?? defaultConfig.UninstallDelete,
471
+ Registry: userConfig.Registry ?? defaultConfig.Registry,
472
+ Run: userConfig.Run ?? defaultConfig.Run,
473
+ UninstallRun: userConfig.UninstallRun ?? defaultConfig.UninstallRun,
474
+ Messages: userConfig.Messages ?? defaultConfig.Messages,
475
+ CustomMessages: userConfig.CustomMessages ?? defaultConfig.CustomMessages,
476
+ Code: userConfig.Code ?? defaultConfig.Code,
407
477
  };
408
478
  }
409
479
  /**
410
- * 添加任务(桌面图标、快速启动等)
480
+ * 添加桌面图标和快速启动任务
481
+ *
482
+ * @param config - Inno Setup 配置
483
+ * @param appName - 应用名称
411
484
  */
412
485
  addTasks(config, appName) {
413
- if (!config.Tasks) {
414
- config.Tasks = [];
415
- }
416
- // 检查用户是否已经自定义了桌面图标任务
417
- const hasDesktopTask = config.Tasks.some((task) => task.Name === "desktopicon");
418
- const hasDesktopIcon = config.Icons?.some((icon) => icon.Name?.includes("{autodesktop}") && icon.Tasks === "desktopicon");
419
- if (this.config.createDesktopIcon !== false &&
420
- !hasDesktopTask &&
421
- !hasDesktopIcon) {
422
- config.Tasks.push({
486
+ const tasks = [...(config.Tasks ?? [])];
487
+ const icons = [...(config.Icons ?? [])];
488
+ // 检查是否已存在桌面图标任务
489
+ const hasDesktopTask = tasks.some((t) => t.Name === "desktopicon");
490
+ const hasDesktopIcon = icons.some((i) => i.Name.includes("{autodesktop}") && i.Tasks === "desktopicon");
491
+ if (this.config.createDesktopIcon !== false && !hasDesktopTask && !hasDesktopIcon) {
492
+ tasks.push({
423
493
  Name: "desktopicon",
424
- Description: "Create a &desktop icon",
425
- GroupDescription: "Additional icons:",
494
+ Description: "创建桌面图标(&D)",
495
+ GroupDescription: "附加图标:",
426
496
  Flags: "unchecked",
427
497
  });
428
- if (config.Icons) {
429
- config.Icons.push({
430
- Name: "{autodesktop}\\" + appName,
431
- Filename: `{app}\\${appName}.exe`,
432
- Tasks: "desktopicon",
433
- });
434
- }
498
+ icons.push({
499
+ Name: `{autodesktop}\\${appName}`,
500
+ Filename: `{app}\\${appName}.exe`,
501
+ Tasks: "desktopicon",
502
+ });
435
503
  }
436
- // 检查用户是否已经自定义了快速启动任务
437
- const hasQuickLaunchTask = config.Tasks.some((task) => task.Name === "quicklaunchicon");
438
- const hasQuickLaunchIcon = config.Icons?.some((icon) => icon.Name?.includes("Quick Launch") && icon.Tasks === "quicklaunchicon");
439
- if (this.config.createQuickLaunchIcon &&
440
- !hasQuickLaunchTask &&
441
- !hasQuickLaunchIcon) {
442
- config.Tasks.push({
504
+ // 检查是否已存在快速启动任务
505
+ const hasQuickLaunchTask = tasks.some((t) => t.Name === "quicklaunchicon");
506
+ const hasQuickLaunchIcon = icons.some((i) => i.Name.includes("Quick Launch") && i.Tasks === "quicklaunchicon");
507
+ if (this.config.createQuickLaunchIcon && !hasQuickLaunchTask && !hasQuickLaunchIcon) {
508
+ tasks.push({
443
509
  Name: "quicklaunchicon",
444
- Description: "Create a &Quick Launch icon",
445
- GroupDescription: "Additional icons:",
510
+ Description: "创建快速启动图标(&Q)",
511
+ GroupDescription: "附加图标:",
446
512
  Flags: "unchecked",
447
513
  });
448
- if (config.Icons) {
449
- config.Icons.push({
450
- Name: "{userappdata}\\Microsoft\\Internet Explorer\\Quick Launch\\" +
451
- appName,
452
- Filename: `{app}\\${appName}.exe`,
453
- Tasks: "quicklaunchicon",
454
- });
455
- }
514
+ icons.push({
515
+ Name: `{userappdata}\\Microsoft\\Internet Explorer\\Quick Launch\\${appName}`,
516
+ Filename: `{app}\\${appName}.exe`,
517
+ Tasks: "quicklaunchicon",
518
+ });
456
519
  }
520
+ config.Tasks = tasks;
521
+ config.Icons = icons;
457
522
  }
458
523
  /**
459
- * 执行 Innosetup 编译
524
+ * 编译 ISS 脚本
525
+ *
526
+ * @param scriptPath - 脚本文件路径
527
+ * @param compilerPath - 编译器路径
528
+ * @returns 编译输出
529
+ * @throws CompilationError 编译失败
530
+ * @throws CompilationTimeoutError 编译超时
460
531
  */
461
532
  async compileScript(scriptPath, compilerPath) {
462
533
  return new Promise((resolve, reject) => {
@@ -464,44 +535,45 @@ class MakerInnosetup extends maker_base_1.default {
464
535
  if (this.config.isccOptions) {
465
536
  args.push(...this.config.isccOptions);
466
537
  }
467
- const iscc = (0, child_process_1.spawn)(compilerPath, args, {
468
- stdio: "pipe",
469
- });
538
+ const timeout = this.config.compileTimeout ?? DEFAULT_COMPILE_TIMEOUT;
470
539
  let output = "";
471
540
  let errorOutput = "";
472
541
  let isResolved = false;
473
- const timeout = this.config.compileTimeout || 300000;
474
- const timeoutHandle = setTimeout(() => {
475
- if (!isResolved) {
476
- isResolved = false;
477
- console.error("Innosetup compilation timeout, killing process...");
542
+ let iscc = null;
543
+ const cleanup = () => {
544
+ if (iscc && !iscc.killed) {
478
545
  try {
479
546
  iscc.kill("SIGKILL");
480
547
  }
481
- catch (e) {
482
- console.error("Failed to kill process", e);
548
+ catch {
549
+ // 忽略终止进程时的错误
483
550
  }
484
- reject(new Error(`Innosetup compilation timeout after ${timeout} ms`));
551
+ }
552
+ };
553
+ const timeoutHandle = setTimeout(() => {
554
+ if (!isResolved) {
555
+ isResolved = true;
556
+ cleanup();
557
+ reject(new errors_1.CompilationTimeoutError(timeout));
485
558
  }
486
559
  }, timeout);
487
- const cleanup = () => {
560
+ try {
561
+ iscc = (0, child_process_1.spawn)(compilerPath, args, { stdio: "pipe" });
562
+ }
563
+ catch (err) {
488
564
  clearTimeout(timeoutHandle);
489
- try {
490
- if (iscc && !iscc.killed) {
491
- iscc.kill("SIGKILL");
492
- }
493
- }
494
- catch (e) { }
495
- };
496
- iscc.stdout.on("data", (data) => {
565
+ reject(new Error(`启动 Inno Setup 编译器失败: ${err instanceof Error ? err.message : String(err)}`));
566
+ return;
567
+ }
568
+ iscc.stdout?.on("data", (data) => {
497
569
  const text = data.toString();
498
570
  output += text;
499
- console.log(text);
571
+ (0, logger_1.logCompile)("%s", text.trim());
500
572
  });
501
- iscc.stderr.on("data", (data) => {
573
+ iscc.stderr?.on("data", (data) => {
502
574
  const text = data.toString();
503
575
  errorOutput += text;
504
- console.error(text);
576
+ (0, logger_1.logCompile)("错误: %s", text.trim());
505
577
  });
506
578
  iscc.on("close", (code) => {
507
579
  if (isResolved)
@@ -513,121 +585,69 @@ class MakerInnosetup extends maker_base_1.default {
513
585
  }
514
586
  else {
515
587
  cleanup();
516
- reject(new Error(`Innosetup compilation failed with code ${code}\n${errorOutput}`));
588
+ reject(new errors_1.CompilationError(code ?? 1, errorOutput || output));
517
589
  }
518
590
  });
519
591
  iscc.on("error", (err) => {
520
592
  if (isResolved)
521
593
  return;
522
594
  isResolved = true;
595
+ clearTimeout(timeoutHandle);
523
596
  cleanup();
524
- reject(new Error(`Failed to start Innosetup compiler: ${err.message}`));
597
+ reject(new Error(`运行 Inno Setup 编译器失败: ${err.message}`));
525
598
  });
526
- // 处理进程意外退出
527
- process.on("exit", cleanup);
528
- process.on("SIGINT", cleanup);
529
- process.on("SIGTERM", cleanup);
530
599
  });
531
600
  }
532
601
  /**
533
- * 制作安装包
602
+ * 查找生成的安装程序文件
603
+ *
604
+ * @param searchDir - 搜索目录
605
+ * @param appName - 应用名称
606
+ * @param appVersion - 应用版本
607
+ * @param archId - 架构标识
608
+ * @returns 安装程序文件路径数组
609
+ * @throws InstallerNotFoundError 如果找不到安装程序
534
610
  */
535
- async make(options) {
536
- const { appName, dir, makeDir, targetArch, packageJSON } = options;
537
- this.config.paths = {
538
- buildDir: dir,
539
- ...this.config.config,
540
- };
541
- // 设置默认构建目录
542
- const appVersion = packageJSON.version || "1.0.0";
543
- const archId = this.getArchIdentifier(targetArch);
544
- console.log(`Creating Innosetup installer for ${appName} ${appVersion} (${targetArch})...`);
545
- console.log(`Project directory: ${this.getProjectDir()}`);
546
- console.log(`Build directory: ${this.getBuildDir()}`);
547
- // 查找编译器
548
- const compilerPath = this.findInnosetupCompiler();
549
- console.log(`Using Innosetup compiler: ${compilerPath}`);
550
- // 确定输出目录:根据架构生成子目录
551
- // 格式: makeDir/innosetup.windows/x64 或 makeDir/innosetup.windows/arm64
552
- const baseOutputDir = this.config.outputDir || path.join(makeDir, "innosetup.windows");
553
- const outputDir = path.join(baseOutputDir, archId);
554
- // 确保输出目录存在
555
- if (!fs.existsSync(outputDir)) {
556
- fs.mkdirSync(outputDir, { recursive: true });
557
- }
558
- console.log(`Output directory: ${outputDir}`);
559
- // 生成或使用脚本
560
- let scriptPath;
561
- let finalConfig;
562
- let actualOutputDir = outputDir; // 实际的输出目录
563
- if (this.config.scriptPath) {
564
- // 使用用户提供的脚本
565
- scriptPath = this.resolvePath(this.config.scriptPath, this.getProjectDir());
566
- console.log(`Using custom script: ${scriptPath}`);
567
- // 解析脚本以获取实际的输出目录
568
- try {
569
- const parsedConfig = parser_1.InnoScriptParser.parseFile(scriptPath);
570
- if (parsedConfig.Setup?.OutputDir) {
571
- // 如果脚本中定义了输出目录,使用绝对路径
572
- const scriptOutputDir = parsedConfig.Setup.OutputDir;
573
- actualOutputDir = path.isAbsolute(scriptOutputDir)
574
- ? scriptOutputDir
575
- : path.resolve(path.dirname(scriptPath), scriptOutputDir);
576
- console.log(`Script defines OutputDir: ${actualOutputDir}`);
577
- }
578
- }
579
- catch (err) {
580
- console.warn(`Failed to parse script for OutputDir: ${err}`);
581
- // 继续使用默认的 outputDir
611
+ findInstaller(searchDir, appName, appVersion, archId) {
612
+ (0, logger_1.log)("在目录中搜索安装程序: %s", searchDir);
613
+ // 尝试预期的文件名格式
614
+ const expectedNames = [
615
+ `${appName}-${appVersion}-${archId}-setup.exe`, // 默认格式
616
+ `${appName}_${appVersion}.exe`, // 用户自定义格式
617
+ `${appName}-${appVersion}.exe`, // 无架构格式
618
+ ];
619
+ for (const expectedName of expectedNames) {
620
+ const expectedPath = path.join(searchDir, expectedName);
621
+ if ((0, path_1.fileExists)(expectedPath)) {
622
+ (0, logger_1.log)("找到安装程序: %s", expectedPath);
623
+ return [expectedPath];
582
624
  }
583
625
  }
584
- else {
585
- // 生成脚本
586
- const defaultConfig = this.generateDefaultConfig(dir, appName, appVersion, targetArch, outputDir);
587
- finalConfig = this.mergeConfig(defaultConfig, this.config.config);
588
- // 解析配置中的路径
589
- this.resolveConfigPaths(finalConfig, dir);
590
- // 添加任务
591
- this.addTasks(finalConfig, appName);
592
- // 生成脚本内容
593
- const scriptContent = this.scriptGenerator.generate(finalConfig);
594
- // 保存脚本
595
- scriptPath = path.join(makeDir, `${appName}-setup.iss`);
596
- this.scriptGenerator.saveToFile(scriptContent, scriptPath);
597
- console.log(`Generated Innosetup script: ${scriptPath}`);
598
- }
599
- // 编译安装包
600
- console.log("Compiling installer...");
601
- await this.compileScript(scriptPath, compilerPath);
602
- // 查找生成的安装包
603
- // 如果使用自定义脚本,在实际输出目录中查找
604
- const searchDir = actualOutputDir;
605
- console.log(`Searching for installer in: ${searchDir}`);
606
- // 尝试匹配输出文件名模式
607
- const outputPattern = `${appName}-${appVersion}-${archId}-setup.exe`;
608
- const outputPath = path.join(searchDir, outputPattern);
609
- if (fs.existsSync(outputPath)) {
610
- console.log(`Installer created successfully: ${outputPath}`);
611
- return [outputPath];
612
- }
613
- // 如果没找到,尝试查找所有 exe 文件
614
- if (fs.existsSync(searchDir)) {
615
- const files = fs.readdirSync(searchDir);
616
- const exeFiles = files
617
- .filter((f) => f.endsWith(".exe"))
618
- .map((f) => path.join(searchDir, f));
626
+ // 搜索目录中的 .exe 文件
627
+ const searchInDir = (dir) => {
628
+ if (!(0, path_1.fileExists)(dir))
629
+ return [];
630
+ const files = fs.readdirSync(dir);
631
+ return files.filter((f) => f.endsWith(".exe")).map((f) => path.join(dir, f));
632
+ };
633
+ // 先在当前目录搜索
634
+ let exeFiles = searchInDir(searchDir);
635
+ // 如果没找到,尝试搜索子目录 (如 innosetup.windows/x64)
636
+ if (exeFiles.length === 0) {
637
+ const subDir = path.join(searchDir, "innosetup.windows", archId);
638
+ (0, logger_1.log)("尝试搜索子目录: %s", subDir);
639
+ exeFiles = searchInDir(subDir);
619
640
  if (exeFiles.length > 0) {
620
- console.log(`Installer created: ${exeFiles[0]}`);
641
+ (0, logger_1.log)("在子目录中找到安装程序: %s", exeFiles[0]);
621
642
  return exeFiles;
622
643
  }
623
644
  }
624
- throw new Error(`Failed to find generated installer in ${searchDir}`);
645
+ else {
646
+ (0, logger_1.log)("找到安装程序: %s", exeFiles[0]);
647
+ return exeFiles;
648
+ }
649
+ throw new errors_1.InstallerNotFoundError(searchDir);
625
650
  }
626
651
  }
627
652
  exports.default = MakerInnosetup;
628
- // 导出类型
629
- __exportStar(require("./types"), exports);
630
- var generator_2 = require("./generator");
631
- Object.defineProperty(exports, "InnoScriptGenerator", { enumerable: true, get: function () { return generator_2.InnoScriptGenerator; } });
632
- var parser_2 = require("./parser");
633
- Object.defineProperty(exports, "InnoScriptParser", { enumerable: true, get: function () { return parser_2.InnoScriptParser; } });
653
+ //# sourceMappingURL=MakerInnosetup.js.map