pake-cli 3.2.0-beta15 → 3.2.12

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/dist/dev.js CHANGED
@@ -4,13 +4,14 @@ import chalk from 'chalk';
4
4
  import path from 'path';
5
5
  import axios from 'axios';
6
6
  import { dir } from 'tmp-promise';
7
- import { fileURLToPath } from 'url';
7
+ import { fileTypeFromBuffer } from 'file-type';
8
+ import icongen from 'icon-gen';
9
+ import sharp from 'sharp';
8
10
  import crypto from 'crypto';
9
11
  import prompts from 'prompts';
10
12
  import ora from 'ora';
11
- import { fileTypeFromBuffer } from 'file-type';
13
+ import { fileURLToPath } from 'url';
12
14
  import * as psl from 'psl';
13
- import icongen from 'icon-gen';
14
15
  import { execa, execaSync } from 'execa';
15
16
  import dns from 'dns';
16
17
  import http from 'http';
@@ -68,17 +69,6 @@ const logger = {
68
69
  },
69
70
  };
70
71
 
71
- // Convert the current module URL to a file path
72
- const currentModulePath = fileURLToPath(import.meta.url);
73
- // Resolve the parent directory of the current module
74
- const npmDirectory = path.join(path.dirname(currentModulePath), '..');
75
- const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri', '.pake');
76
-
77
- const { platform: platform$2 } = process;
78
- const IS_MAC = platform$2 === 'darwin';
79
- const IS_WIN = platform$2 === 'win32';
80
- const IS_LINUX = platform$2 === 'linux';
81
-
82
72
  // Generates an identifier based on the given URL.
83
73
  function getIdentifier(url) {
84
74
  const postFixHash = crypto
@@ -112,66 +102,94 @@ function getSpinner(text) {
112
102
  }).start();
113
103
  }
114
104
 
115
- // Extracts the domain from a given URL.
116
- function getDomain(inputUrl) {
105
+ // Convert the current module URL to a file path
106
+ const currentModulePath = fileURLToPath(import.meta.url);
107
+ // Resolve the parent directory of the current module
108
+ const npmDirectory = path.join(path.dirname(currentModulePath), '..');
109
+ const tauriConfigDirectory = path.join(npmDirectory, 'src-tauri', '.pake');
110
+
111
+ const { platform: platform$2 } = process;
112
+ const IS_MAC = platform$2 === 'darwin';
113
+ const IS_WIN = platform$2 === 'win32';
114
+ const IS_LINUX = platform$2 === 'linux';
115
+
116
+ // Constants
117
+ const ICON_CONFIG = {
118
+ minFileSize: 100,
119
+ downloadTimeout: 10000,
120
+ supportedFormats: ['png', 'ico', 'jpeg', 'jpg', 'webp'],
121
+ whiteBackground: { r: 255, g: 255, b: 255 },
122
+ };
123
+ // API Configuration
124
+ const API_TOKENS = {
125
+ // cspell:disable-next-line
126
+ logoDev: ['pk_JLLMUKGZRpaG5YclhXaTkg', 'pk_Ph745P8mQSeYFfW2Wk039A'],
127
+ // cspell:disable-next-line
128
+ brandfetch: ['1idqvJC0CeFSeyp3Yf7', '1idej-yhU_ThggIHFyG'],
129
+ };
130
+ /**
131
+ * Adds white background to transparent icons only
132
+ */
133
+ async function preprocessIcon(inputPath) {
117
134
  try {
118
- const url = new URL(inputUrl);
119
- // Use PSL to parse domain names.
120
- const parsed = psl.parse(url.hostname);
121
- // If domain is available, split it and return the SLD.
122
- if ('domain' in parsed && parsed.domain) {
123
- return parsed.domain.split('.')[0];
124
- }
125
- else {
126
- return null;
127
- }
135
+ const metadata = await sharp(inputPath).metadata();
136
+ if (metadata.channels !== 4)
137
+ return inputPath; // No transparency
138
+ const { path: tempDir } = await dir();
139
+ const outputPath = path.join(tempDir, 'icon-with-background.png');
140
+ await sharp({
141
+ create: {
142
+ width: metadata.width || 512,
143
+ height: metadata.height || 512,
144
+ channels: 3,
145
+ background: ICON_CONFIG.whiteBackground,
146
+ },
147
+ })
148
+ .composite([{ input: inputPath }])
149
+ .png()
150
+ .toFile(outputPath);
151
+ return outputPath;
128
152
  }
129
153
  catch (error) {
130
- return null;
154
+ logger.warn(`Failed to add background to icon: ${error.message}`);
155
+ return inputPath;
131
156
  }
132
157
  }
133
-
134
- // Helper function to convert icon to required platform format
158
+ /**
159
+ * Converts icon to platform-specific format
160
+ */
135
161
  async function convertIconFormat(inputPath, appName) {
136
162
  try {
163
+ if (!(await fsExtra.pathExists(inputPath)))
164
+ return null;
137
165
  const { path: outputDir } = await dir();
138
166
  const platformOutputDir = path.join(outputDir, 'converted-icons');
139
167
  await fsExtra.ensureDir(platformOutputDir);
140
- // Get the required format based on current platform
141
- const requiredFormat = IS_WIN ? 'ico' : IS_LINUX ? 'png' : 'icns';
142
- const outputFileName = IS_WIN
143
- ? `${appName.toLowerCase()}_256.ico`
144
- : IS_LINUX
145
- ? `${appName.toLowerCase()}_512.png`
146
- : `${appName.toLowerCase()}.icns`;
147
- const outputPath = path.join(platformOutputDir, outputFileName);
148
- // Convert using icon-gen
149
- if (requiredFormat === 'icns') {
150
- await icongen(inputPath, platformOutputDir, {
151
- report: false,
152
- icns: {
153
- name: appName.toLowerCase(),
154
- sizes: [16, 32, 64, 128, 256, 512, 1024]
155
- }
156
- });
157
- }
158
- else if (requiredFormat === 'ico') {
159
- await icongen(inputPath, platformOutputDir, {
168
+ const processedInputPath = await preprocessIcon(inputPath);
169
+ const iconName = appName.toLowerCase();
170
+ // Generate platform-specific format
171
+ if (IS_WIN) {
172
+ await icongen(processedInputPath, platformOutputDir, {
160
173
  report: false,
161
- ico: {
162
- name: `${appName.toLowerCase()}_256`,
163
- sizes: [16, 24, 32, 48, 64, 128, 256]
164
- }
174
+ ico: { name: `${iconName}_256`, sizes: [256] },
165
175
  });
176
+ return path.join(platformOutputDir, `${iconName}_256.ico`);
166
177
  }
167
- else {
168
- // For Linux, just copy the PNG file
169
- await fsExtra.copy(inputPath, outputPath);
178
+ if (IS_LINUX) {
179
+ const outputPath = path.join(platformOutputDir, `${iconName}_512.png`);
180
+ await fsExtra.copy(processedInputPath, outputPath);
181
+ return outputPath;
170
182
  }
171
- return outputPath;
183
+ // macOS
184
+ await icongen(processedInputPath, platformOutputDir, {
185
+ report: false,
186
+ icns: { name: iconName, sizes: [16, 32, 64, 128, 256, 512, 1024] },
187
+ });
188
+ const outputPath = path.join(platformOutputDir, `${iconName}.icns`);
189
+ return (await fsExtra.pathExists(outputPath)) ? outputPath : null;
172
190
  }
173
191
  catch (error) {
174
- logger.warn(`✼ Icon format conversion failed: ${error.message}`);
192
+ logger.warn(`Icon format conversion failed: ${error.message}`);
175
193
  return null;
176
194
  }
177
195
  }
@@ -180,118 +198,169 @@ async function handleIcon(options, url) {
180
198
  if (options.icon.startsWith('http')) {
181
199
  return downloadIcon(options.icon);
182
200
  }
183
- else {
184
- return path.resolve(options.icon);
185
- }
201
+ return path.resolve(options.icon);
186
202
  }
187
- else {
188
- // Try to get favicon from website if URL is provided and it's a web URL
189
- if (url && url.startsWith('http') && options.name) {
190
- const faviconPath = await tryGetFavicon(url, options.name);
191
- if (faviconPath) {
192
- return faviconPath;
203
+ // Try to get favicon from website if URL is provided
204
+ if (url && url.startsWith('http') && options.name) {
205
+ const faviconPath = await tryGetFavicon(url, options.name);
206
+ if (faviconPath)
207
+ return faviconPath;
208
+ }
209
+ logger.info('✼ No icon provided, using default icon.');
210
+ // For Windows, ensure we have proper fallback handling
211
+ if (IS_WIN) {
212
+ const defaultIcoPath = path.join(npmDirectory, 'src-tauri/png/icon_256.ico');
213
+ const defaultPngPath = path.join(npmDirectory, 'src-tauri/png/icon_512.png');
214
+ // First try default ico
215
+ if (await fsExtra.pathExists(defaultIcoPath)) {
216
+ return defaultIcoPath;
217
+ }
218
+ // If ico doesn't exist, try to convert from png
219
+ if (await fsExtra.pathExists(defaultPngPath)) {
220
+ logger.info('✼ Default ico not found, converting from png...');
221
+ try {
222
+ const convertedPath = await convertIconFormat(defaultPngPath, 'icon');
223
+ if (convertedPath && (await fsExtra.pathExists(convertedPath))) {
224
+ return convertedPath;
225
+ }
226
+ }
227
+ catch (error) {
228
+ logger.warn(`Failed to convert default png to ico: ${error.message}`);
193
229
  }
194
230
  }
195
- logger.warn('✼ No icon given, default in use. For a custom icon, use --icon option.');
196
- const iconPath = IS_WIN
197
- ? 'src-tauri/png/icon_256.ico'
198
- : IS_LINUX
199
- ? 'src-tauri/png/icon_512.png'
200
- : 'src-tauri/icons/icon.icns';
201
- return path.join(npmDirectory, iconPath);
231
+ // Last resort: return png path if it exists (Windows can handle png in some cases)
232
+ if (await fsExtra.pathExists(defaultPngPath)) {
233
+ logger.warn('✼ Using png as fallback for Windows (may cause issues).');
234
+ return defaultPngPath;
235
+ }
236
+ // If nothing exists, let the error bubble up
237
+ throw new Error('No default icon found for Windows build');
202
238
  }
239
+ const iconPath = IS_LINUX
240
+ ? 'src-tauri/png/icon_512.png'
241
+ : 'src-tauri/icons/icon.icns';
242
+ return path.join(npmDirectory, iconPath);
203
243
  }
204
- async function tryGetFavicon(url, appName) {
205
- const domain = getDomain(url);
206
- if (!domain) {
207
- return null;
208
- }
209
- const spinner = getSpinner('Fetching website favicon...');
210
- // List of logo/favicon services to try in order of preference
211
- const logoServices = [
212
- // Professional Logo APIs (free tier, higher quality)
244
+ /**
245
+ * Generates icon service URLs for a domain
246
+ */
247
+ function generateIconServiceUrls(domain) {
248
+ const logoDevUrls = API_TOKENS.logoDev
249
+ .sort(() => Math.random() - 0.5)
250
+ .map((token) => `https://img.logo.dev/${domain}?token=${token}&format=png&size=256`);
251
+ const brandfetchUrls = API_TOKENS.brandfetch
252
+ .sort(() => Math.random() - 0.5)
253
+ .map((key) => `https://cdn.brandfetch.io/${domain}/w/400/h/400?c=${key}`);
254
+ return [
255
+ ...logoDevUrls,
256
+ ...brandfetchUrls,
213
257
  `https://logo.clearbit.com/${domain}?size=256`,
214
258
  `https://logo.uplead.com/${domain}`,
215
- // Google's reliable service with larger size
216
259
  `https://www.google.com/s2/favicons?domain=${domain}&sz=256`,
217
- // Other favicon services
218
260
  `https://favicon.is/${domain}`,
219
261
  `https://icons.duckduckgo.com/ip3/${domain}.ico`,
220
262
  `https://icon.horse/icon/${domain}`,
221
- // Direct favicon checks
222
263
  `https://${domain}/favicon.ico`,
223
264
  `https://www.${domain}/favicon.ico`,
224
265
  `https://${domain}/apple-touch-icon.png`,
225
266
  `https://${domain}/apple-touch-icon-precomposed.png`,
226
267
  ];
227
- for (const logoUrl of logoServices) {
228
- try {
229
- const faviconPath = await downloadIcon(logoUrl, false);
230
- if (faviconPath) {
231
- spinner.text = 'Converting favicon to platform format...';
232
- // Convert to the correct format for the current platform
268
+ }
269
+ /**
270
+ * Attempts to fetch favicon from website
271
+ */
272
+ async function tryGetFavicon(url, appName) {
273
+ try {
274
+ const domain = new URL(url).hostname;
275
+ const spinner = getSpinner(`Fetching icon from ${domain}...`);
276
+ const serviceUrls = generateIconServiceUrls(domain);
277
+ // Use shorter timeout for CI environments
278
+ const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';
279
+ const downloadTimeout = isCI ? 5000 : ICON_CONFIG.downloadTimeout;
280
+ for (const serviceUrl of serviceUrls) {
281
+ try {
282
+ const faviconPath = await downloadIcon(serviceUrl, false, downloadTimeout);
283
+ if (!faviconPath)
284
+ continue;
233
285
  const convertedPath = await convertIconFormat(faviconPath, appName);
234
286
  if (convertedPath) {
235
- spinner.succeed(chalk.green(`Favicon downloaded and converted for ${domain}!`));
287
+ spinner.succeed(chalk.green('Icon fetched and converted successfully!'));
236
288
  return convertedPath;
237
289
  }
238
- else {
239
- // If conversion fails, try the next service
240
- continue;
290
+ }
291
+ catch (error) {
292
+ // Log specific errors in CI for debugging
293
+ if (isCI) {
294
+ logger.debug(`Icon service ${serviceUrl} failed: ${error.message}`);
241
295
  }
296
+ continue;
242
297
  }
243
298
  }
244
- catch (error) {
245
- // Continue to next service
246
- continue;
247
- }
299
+ spinner.warn(`✼ No favicon found for ${domain}. Using default.`);
300
+ return null;
301
+ }
302
+ catch (error) {
303
+ logger.warn(`Failed to fetch favicon: ${error.message}`);
304
+ return null;
248
305
  }
249
- spinner.fail(chalk.yellow(`No favicon found for ${domain}, using default icon.`));
250
- return null;
251
306
  }
252
- async function downloadIcon(iconUrl, showSpinner = true) {
253
- const spinner = showSpinner ? getSpinner('Downloading icon...') : null;
307
+ /**
308
+ * Downloads icon from URL
309
+ */
310
+ async function downloadIcon(iconUrl, showSpinner = true, customTimeout) {
254
311
  try {
255
- const iconResponse = await axios.get(iconUrl, {
312
+ const response = await axios.get(iconUrl, {
256
313
  responseType: 'arraybuffer',
257
- timeout: 10000, // 10 second timeout
314
+ timeout: customTimeout || ICON_CONFIG.downloadTimeout,
258
315
  });
259
- const iconData = await iconResponse.data;
260
- if (!iconData || iconData.byteLength < 100) {
261
- // Skip very small responses (likely error pages)
316
+ const iconData = response.data;
317
+ if (!iconData || iconData.byteLength < ICON_CONFIG.minFileSize)
262
318
  return null;
263
- }
264
319
  const fileDetails = await fileTypeFromBuffer(iconData);
265
- if (!fileDetails || !['png', 'ico', 'jpeg', 'jpg', 'gif', 'webp'].includes(fileDetails.ext)) {
266
- // Only accept common image formats
320
+ if (!fileDetails ||
321
+ !ICON_CONFIG.supportedFormats.includes(fileDetails.ext)) {
267
322
  return null;
268
323
  }
269
- const { path: tempPath } = await dir();
270
- let iconPath = `${tempPath}/icon.${fileDetails.ext}`;
271
- // Fix this for linux
272
- if (IS_LINUX) {
273
- iconPath = 'png/linux_temp.png';
274
- await fsExtra.outputFile(`${npmDirectory}/src-tauri/${iconPath}`, iconData);
275
- }
276
- else {
277
- await fsExtra.outputFile(iconPath, iconData);
278
- }
279
- await fsExtra.outputFile(iconPath, iconData);
280
- if (spinner) {
281
- spinner.succeed(chalk.green('Icon downloaded successfully!'));
324
+ return await saveIconFile(iconData, fileDetails.ext);
325
+ }
326
+ catch (error) {
327
+ if (showSpinner && !(error.response?.status === 404)) {
328
+ throw error;
282
329
  }
330
+ return null;
331
+ }
332
+ }
333
+ /**
334
+ * Saves icon file to temporary location
335
+ */
336
+ async function saveIconFile(iconData, extension) {
337
+ const buffer = Buffer.from(iconData);
338
+ if (IS_LINUX) {
339
+ const iconPath = 'png/linux_temp.png';
340
+ await fsExtra.outputFile(`${npmDirectory}/src-tauri/${iconPath}`, buffer);
283
341
  return iconPath;
284
342
  }
285
- catch (error) {
286
- if (spinner) {
287
- spinner.fail(chalk.red('Icon download failed!'));
343
+ const { path: tempPath } = await dir();
344
+ const iconPath = `${tempPath}/icon.${extension}`;
345
+ await fsExtra.outputFile(iconPath, buffer);
346
+ return iconPath;
347
+ }
348
+
349
+ // Extracts the domain from a given URL.
350
+ function getDomain(inputUrl) {
351
+ try {
352
+ const url = new URL(inputUrl);
353
+ // Use PSL to parse domain names.
354
+ const parsed = psl.parse(url.hostname);
355
+ // If domain is available, split it and return the SLD.
356
+ if ('domain' in parsed && parsed.domain) {
357
+ return parsed.domain.split('.')[0];
288
358
  }
289
- if (error.response && error.response.status === 404) {
359
+ else {
290
360
  return null;
291
361
  }
292
- if (showSpinner) {
293
- throw error;
294
- }
362
+ }
363
+ catch (error) {
295
364
  return null;
296
365
  }
297
366
  }
@@ -346,136 +415,13 @@ async function handleOptions(options, url) {
346
415
  return appOptions;
347
416
  }
348
417
 
349
- var windows = [
350
- {
351
- url: "https://weekly.tw93.fun/",
352
- url_type: "web",
353
- hide_title_bar: true,
354
- fullscreen: false,
355
- width: 1200,
356
- height: 780,
357
- resizable: true,
358
- always_on_top: false,
359
- dark_mode: false,
360
- activation_shortcut: "",
361
- disabled_web_shortcuts: false,
362
- hide_on_close: true,
363
- incognito: false
364
- }
365
- ];
366
- var user_agent = {
367
- macos: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.1 Safari/605.1.15",
368
- linux: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36",
369
- windows: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36"
370
- };
371
- var system_tray = {
372
- macos: false,
373
- linux: true,
374
- windows: true
375
- };
376
- var system_tray_path = "icons/icon.png";
377
- var inject = [
378
- ];
379
- var proxy_url = "";
380
- var pakeConf = {
381
- windows: windows,
382
- user_agent: user_agent,
383
- system_tray: system_tray,
384
- system_tray_path: system_tray_path,
385
- inject: inject,
386
- proxy_url: proxy_url
387
- };
388
-
389
- var productName$1 = "Weekly";
390
- var identifier = "com.pake.weekly";
391
- var version = "1.0.0";
392
- var app = {
393
- withGlobalTauri: true,
394
- trayIcon: {
395
- iconPath: "png/weekly_512.png",
396
- iconAsTemplate: false,
397
- id: "pake-tray"
398
- }
399
- };
400
- var build = {
401
- frontendDist: "../dist"
402
- };
403
- var CommonConf = {
404
- productName: productName$1,
405
- identifier: identifier,
406
- version: version,
407
- app: app,
408
- build: build
409
- };
410
-
411
- var bundle$2 = {
412
- icon: [
413
- "png/weekly_256.ico",
414
- "png/weekly_32.ico"
415
- ],
416
- active: true,
417
- resources: [
418
- "png/weekly_32.ico"
419
- ],
420
- targets: [
421
- "msi"
422
- ],
423
- windows: {
424
- digestAlgorithm: "sha256",
425
- wix: {
426
- language: [
427
- "en-US"
428
- ],
429
- template: "assets/main.wxs"
430
- }
431
- }
432
- };
433
- var WinConf = {
434
- bundle: bundle$2
435
- };
436
-
437
- var bundle$1 = {
438
- icon: [
439
- "icons/weekly.icns"
440
- ],
441
- active: true,
442
- macOS: {
443
- },
444
- targets: [
445
- "dmg"
446
- ]
447
- };
448
- var MacConf = {
449
- bundle: bundle$1
450
- };
451
-
452
- var productName = "weekly";
453
- var bundle = {
454
- icon: [
455
- "png/weekly_512.png"
456
- ],
457
- active: true,
458
- linux: {
459
- deb: {
460
- depends: [
461
- "curl",
462
- "wget"
463
- ],
464
- files: {
465
- "/usr/share/applications/com-pake-weekly.desktop": "assets/com-pake-weekly.desktop"
466
- }
467
- }
468
- },
469
- targets: [
470
- "deb",
471
- "appimage"
472
- ]
473
- };
474
- var LinuxConf = {
475
- productName: productName,
476
- bundle: bundle
477
- };
478
-
418
+ // Load configs from npm package directory, not from project source
419
+ const tauriSrcDir = path.join(npmDirectory, 'src-tauri');
420
+ const pakeConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'pake.json'));
421
+ const CommonConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.conf.json'));
422
+ const WinConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.windows.conf.json'));
423
+ const MacConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.macos.conf.json'));
424
+ const LinuxConf = fsExtra.readJSONSync(path.join(tauriSrcDir, 'tauri.linux.conf.json'));
479
425
  const platformConfigs = {
480
426
  win32: WinConf,
481
427
  darwin: MacConf,
@@ -497,13 +443,14 @@ let tauriConfig = {
497
443
  pake: pakeConf,
498
444
  };
499
445
 
500
- async function shellExec(command, timeout = 300000) {
446
+ async function shellExec(command, timeout = 300000, env) {
501
447
  try {
502
448
  const { exitCode } = await execa(command, {
503
449
  cwd: npmDirectory,
504
- stdio: 'inherit',
450
+ stdio: ['inherit', 'pipe', 'inherit'], // Hide stdout verbose, keep stderr
505
451
  shell: true,
506
452
  timeout,
453
+ env: env ? { ...process.env, ...env } : process.env,
507
454
  });
508
455
  return exitCode;
509
456
  }
@@ -572,11 +519,11 @@ async function installRust() {
572
519
  const spinner = getSpinner('Downloading Rust...');
573
520
  try {
574
521
  await shellExec(IS_WIN ? rustInstallScriptForWindows : rustInstallScriptForMac);
575
- spinner.succeed(chalk.green('Rust installed successfully!'));
522
+ spinner.succeed(chalk.green('Rust installed successfully!'));
576
523
  }
577
524
  catch (error) {
578
- console.error('Error installing Rust:', error.message);
579
- spinner.fail(chalk.red('Rust installation failed!'));
525
+ spinner.fail(chalk.red(' Rust installation failed!'));
526
+ console.error(error.message);
580
527
  process.exit(1);
581
528
  }
582
529
  }
@@ -689,7 +636,33 @@ async function mergeConfig(url, options, tauriConf) {
689
636
  tauriConf.pake.system_tray[currentPlatform] = showSystemTray;
690
637
  // Processing targets are currently only open to Linux.
691
638
  if (platform === 'linux') {
639
+ // Remove hardcoded desktop files and regenerate with correct app name
692
640
  delete tauriConf.bundle.linux.deb.files;
641
+ // Generate correct desktop file configuration
642
+ const appNameLower = name.toLowerCase();
643
+ const identifier = `com.pake.${appNameLower}`;
644
+ const desktopFileName = `${identifier}.desktop`;
645
+ // Create desktop file content
646
+ const desktopContent = `[Desktop Entry]
647
+ Version=1.0
648
+ Type=Application
649
+ Name=${name}
650
+ Comment=${name}
651
+ Exec=${appNameLower}
652
+ Icon=${appNameLower}
653
+ Categories=Network;WebBrowser;
654
+ MimeType=text/html;text/xml;application/xhtml_xml;
655
+ StartupNotify=true
656
+ `;
657
+ // Write desktop file to assets directory
658
+ const assetsDir = path.join(npmDirectory, 'src-tauri/assets');
659
+ const desktopFilePath = path.join(assetsDir, desktopFileName);
660
+ await fsExtra.ensureDir(assetsDir);
661
+ await fsExtra.writeFile(desktopFilePath, desktopContent);
662
+ // Set up desktop file in bundle configuration
663
+ tauriConf.bundle.linux.deb.files = {
664
+ [`/usr/share/applications/${desktopFileName}`]: `assets/${desktopFileName}`,
665
+ };
693
666
  const validTargets = ['deb', 'appimage', 'rpm'];
694
667
  if (validTargets.includes(options.targets)) {
695
668
  tauriConf.bundle.targets = [options.targets];
@@ -794,7 +767,6 @@ async function mergeConfig(url, options, tauriConf) {
794
767
  };
795
768
  const configPath = path.join(tauriConfigDirectory, platformConfigPaths[platform]);
796
769
  const bundleConf = { bundle: tauriConf.bundle };
797
- console.log('pakeConfig', tauriConf.pake);
798
770
  await fsExtra.outputJSON(configPath, bundleConf, { spaces: 4 });
799
771
  const pakeConfigPath = path.join(tauriConfigDirectory, 'pake.json');
800
772
  await fsExtra.outputJSON(pakeConfigPath, tauriConf.pake, { spaces: 4 });
@@ -812,6 +784,21 @@ class BaseBuilder {
812
784
  constructor(options) {
813
785
  this.options = options;
814
786
  }
787
+ getBuildEnvironment() {
788
+ return IS_MAC
789
+ ? {
790
+ CFLAGS: '-fno-modules',
791
+ CXXFLAGS: '-fno-modules',
792
+ MACOSX_DEPLOYMENT_TARGET: '14.0',
793
+ }
794
+ : undefined;
795
+ }
796
+ getInstallTimeout() {
797
+ return process.platform === 'win32' ? 600000 : 300000;
798
+ }
799
+ getBuildTimeout() {
800
+ return 900000; // 15 minutes for all builds
801
+ }
815
802
  async prepare() {
816
803
  const tauriSrcPath = path.join(npmDirectory, 'src-tauri');
817
804
  const tauriTargetPath = path.join(tauriSrcPath, 'target');
@@ -839,21 +826,22 @@ class BaseBuilder {
839
826
  const rustProjectDir = path.join(tauriSrcPath, '.cargo');
840
827
  const projectConf = path.join(rustProjectDir, 'config.toml');
841
828
  await fsExtra.ensureDir(rustProjectDir);
842
- // For global CLI installation, always use npm
829
+ // 统一使用npm,简单可靠
843
830
  const packageManager = 'npm';
844
831
  const registryOption = isChina
845
832
  ? ' --registry=https://registry.npmmirror.com'
846
833
  : '';
847
- // Windows环境下需要更长的超时时间
848
- const timeout = process.platform === 'win32' ? 600000 : 300000;
834
+ const legacyPeerDeps = ' --legacy-peer-deps'; // 解决dependency conflicts
835
+ const timeout = this.getInstallTimeout();
836
+ const buildEnv = this.getBuildEnvironment();
849
837
  if (isChina) {
850
838
  logger.info('✺ Located in China, using npm/rsProxy CN mirror.');
851
839
  const projectCnConf = path.join(tauriSrcPath, 'rust_proxy.toml');
852
840
  await fsExtra.copy(projectCnConf, projectConf);
853
- await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}`, timeout);
841
+ await shellExec(`cd "${npmDirectory}" && ${packageManager} install${registryOption}${legacyPeerDeps} --silent`, timeout, buildEnv);
854
842
  }
855
843
  else {
856
- await shellExec(`cd "${npmDirectory}" && ${packageManager} install`, timeout);
844
+ await shellExec(`cd "${npmDirectory}" && ${packageManager} install${legacyPeerDeps} --silent`, timeout, buildEnv);
857
845
  }
858
846
  spinner.succeed(chalk.green('Package installed!'));
859
847
  if (!tauriTargetPathExists) {
@@ -870,9 +858,14 @@ class BaseBuilder {
870
858
  const { name } = this.options;
871
859
  await mergeConfig(url, this.options, tauriConfig);
872
860
  // Build app
873
- const spinner = getSpinner('Building app...');
874
- setTimeout(() => spinner.stop(), 3000);
875
- await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand()}`);
861
+ const buildSpinner = getSpinner('Building app...');
862
+ // Let spinner run for a moment so user can see it, then stop before npm command
863
+ await new Promise((resolve) => setTimeout(resolve, 500));
864
+ buildSpinner.stop();
865
+ // Show static message to keep the status visible
866
+ logger.warn('✸ Building app...');
867
+ const buildEnv = this.getBuildEnvironment();
868
+ await shellExec(`cd "${npmDirectory}" && ${this.getBuildCommand()}`, this.getBuildTimeout(), buildEnv);
876
869
  // Copy app
877
870
  const fileName = this.getFileName();
878
871
  const fileType = this.getFileType(target);
@@ -897,13 +890,18 @@ class BaseBuilder {
897
890
  if (IS_MAC && this.options.targets === 'app') {
898
891
  fullCommand += ' --bundles app';
899
892
  }
893
+ // Add features
894
+ const features = ['cli-build'];
900
895
  // Add macos-proxy feature for modern macOS (Darwin 23+ = macOS 14+)
901
896
  if (IS_MAC) {
902
897
  const macOSVersion = this.getMacOSMajorVersion();
903
898
  if (macOSVersion >= 23) {
904
- fullCommand += ' --features macos-proxy';
899
+ features.push('macos-proxy');
905
900
  }
906
901
  }
902
+ if (features.length > 0) {
903
+ fullCommand += ` --features ${features.join(',')}`;
904
+ }
907
905
  return fullCommand;
908
906
  }
909
907
  getMacOSMajorVersion() {
@@ -957,9 +955,26 @@ class MacBuilder extends BaseBuilder {
957
955
  return `${name}_${tauriConfig.version}_${arch}`;
958
956
  }
959
957
  getBuildCommand() {
960
- return this.options.multiArch
961
- ? 'npm run build:mac'
962
- : super.getBuildCommand();
958
+ if (this.options.multiArch) {
959
+ const baseCommand = this.options.debug
960
+ ? 'npm run tauri build -- --debug'
961
+ : 'npm run tauri build --';
962
+ // Use temporary config directory to avoid modifying source files
963
+ const configPath = path.join('src-tauri', '.pake', 'tauri.conf.json');
964
+ let fullCommand = `${baseCommand} --target universal-apple-darwin -c "${configPath}"`;
965
+ // Add features
966
+ const features = ['cli-build'];
967
+ // Add macos-proxy feature for modern macOS (Darwin 23+ = macOS 14+)
968
+ const macOSVersion = this.getMacOSMajorVersion();
969
+ if (macOSVersion >= 23) {
970
+ features.push('macos-proxy');
971
+ }
972
+ if (features.length > 0) {
973
+ fullCommand += ` --features ${features.join(',')}`;
974
+ }
975
+ return fullCommand;
976
+ }
977
+ return super.getBuildCommand();
963
978
  }
964
979
  getBasePath() {
965
980
  return this.options.multiArch