@choochmeque/tauri-windows-bundle 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/LICENSE +21 -0
- package/README.md +320 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1551 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/extension.d.ts +22 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/core/appx-content.d.ts +2 -0
- package/dist/core/manifest.d.ts +3 -0
- package/dist/core/project-discovery.d.ts +6 -0
- package/dist/generators/assets.d.ts +1 -0
- package/dist/generators/config.d.ts +3 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1406 -0
- package/dist/types.d.ts +114 -0
- package/dist/utils/exec.d.ts +11 -0
- package/dist/utils/template.d.ts +1 -0
- package/package.json +59 -0
- package/templates/AppxManifest.xml.template +51 -0
- package/templates/extensions/app-execution-alias.xml +5 -0
- package/templates/extensions/app-service.xml +3 -0
- package/templates/extensions/autoplay-device.xml +5 -0
- package/templates/extensions/autoplay.xml +5 -0
- package/templates/extensions/background-task.xml +5 -0
- package/templates/extensions/context-menu.xml +9 -0
- package/templates/extensions/file-association.xml +7 -0
- package/templates/extensions/preview-handler.xml +8 -0
- package/templates/extensions/print-task-settings.xml +3 -0
- package/templates/extensions/protocol.xml +5 -0
- package/templates/extensions/share-target.xml +9 -0
- package/templates/extensions/startup-task.xml +3 -0
- package/templates/extensions/thumbnail-handler.xml +8 -0
- package/templates/extensions/toast-activation.xml +3 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1551 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { glob } from 'glob';
|
|
7
|
+
import { exec } from 'node:child_process';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
import * as readline from 'node:readline';
|
|
10
|
+
|
|
11
|
+
function findProjectRoot(startDir) {
|
|
12
|
+
let dir = startDir || process.cwd();
|
|
13
|
+
while (dir !== path.dirname(dir)) {
|
|
14
|
+
// Check for tauri.conf.json in src-tauri
|
|
15
|
+
const tauriConfPath = path.join(dir, 'src-tauri', 'tauri.conf.json');
|
|
16
|
+
if (fs.existsSync(tauriConfPath)) {
|
|
17
|
+
return dir;
|
|
18
|
+
}
|
|
19
|
+
// Check for package.json as fallback
|
|
20
|
+
const packageJsonPath = path.join(dir, 'package.json');
|
|
21
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
22
|
+
// Verify it's a Tauri project
|
|
23
|
+
if (fs.existsSync(path.join(dir, 'src-tauri'))) {
|
|
24
|
+
return dir;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
dir = path.dirname(dir);
|
|
28
|
+
}
|
|
29
|
+
throw new Error('Could not find Tauri project root. Make sure you are in a Tauri project directory.');
|
|
30
|
+
}
|
|
31
|
+
function readTauriConfig(projectRoot) {
|
|
32
|
+
const configPath = path.join(projectRoot, 'src-tauri', 'tauri.conf.json');
|
|
33
|
+
if (!fs.existsSync(configPath)) {
|
|
34
|
+
throw new Error(`tauri.conf.json not found at ${configPath}`);
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
38
|
+
return JSON.parse(content);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`Failed to parse tauri.conf.json: ${error instanceof Error ? error.message : error}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function readBundleConfig$1(windowsDir) {
|
|
45
|
+
const configPath = path.join(windowsDir, 'bundle.config.json');
|
|
46
|
+
if (!fs.existsSync(configPath)) {
|
|
47
|
+
throw new Error(`bundle.config.json not found. Run 'tauri-windows-bundle init' first.`);
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
51
|
+
return JSON.parse(content);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
throw new Error(`Failed to parse bundle.config.json: ${error instanceof Error ? error.message : error}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function getWindowsDir(projectRoot) {
|
|
58
|
+
return path.join(projectRoot, 'src-tauri', 'gen', 'windows');
|
|
59
|
+
}
|
|
60
|
+
function toFourPartVersion(version) {
|
|
61
|
+
const parts = version.split('.');
|
|
62
|
+
while (parts.length < 4)
|
|
63
|
+
parts.push('0');
|
|
64
|
+
return parts.slice(0, 4).join('.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const MSIX_ASSETS = [
|
|
68
|
+
{ name: 'StoreLogo.png', size: 50 },
|
|
69
|
+
{ name: 'Square44x44Logo.png', size: 44 },
|
|
70
|
+
{ name: 'Square150x150Logo.png', size: 150 },
|
|
71
|
+
{ name: 'Wide310x150Logo.png', width: 310, height: 150 },
|
|
72
|
+
{ name: 'LargeTile.png', size: 310 },
|
|
73
|
+
];
|
|
74
|
+
const DEFAULT_MIN_WINDOWS_VERSION = '10.0.17763.0';
|
|
75
|
+
const DEFAULT_CAPABILITIES = ['internetClient'];
|
|
76
|
+
|
|
77
|
+
function generateBundleConfig(windowsDir, _tauriConfig) {
|
|
78
|
+
const config = {
|
|
79
|
+
publisher: 'CN=YourCompany',
|
|
80
|
+
publisherDisplayName: 'Your Company Name',
|
|
81
|
+
capabilities: DEFAULT_CAPABILITIES,
|
|
82
|
+
extensions: {
|
|
83
|
+
shareTarget: false,
|
|
84
|
+
fileAssociations: [],
|
|
85
|
+
protocolHandlers: [],
|
|
86
|
+
},
|
|
87
|
+
signing: {
|
|
88
|
+
pfx: null,
|
|
89
|
+
pfxPassword: null,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
const configPath = path.join(windowsDir, 'bundle.config.json');
|
|
93
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
94
|
+
}
|
|
95
|
+
function generateGitignore(windowsDir) {
|
|
96
|
+
const gitignorePath = path.join(windowsDir, '.gitignore');
|
|
97
|
+
const content = `# Generated files
|
|
98
|
+
# Keep bundle.config.json and templates in git
|
|
99
|
+
`;
|
|
100
|
+
fs.writeFileSync(gitignorePath, content);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function generateAssets(windowsDir) {
|
|
104
|
+
const assetsDir = path.join(windowsDir, 'Assets');
|
|
105
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
106
|
+
for (const asset of MSIX_ASSETS) {
|
|
107
|
+
const width = asset.width || asset.size || 50;
|
|
108
|
+
const height = asset.height || asset.size || 50;
|
|
109
|
+
const assetPath = path.join(assetsDir, asset.name);
|
|
110
|
+
// Generate a simple placeholder PNG
|
|
111
|
+
const pngData = createPlaceholderPng(width, height);
|
|
112
|
+
fs.writeFileSync(assetPath, pngData);
|
|
113
|
+
}
|
|
114
|
+
console.log(' Generated placeholder assets - replace with real icons before publishing');
|
|
115
|
+
}
|
|
116
|
+
function createPlaceholderPng(width, height) {
|
|
117
|
+
// Create a minimal valid PNG file (solid gray square)
|
|
118
|
+
// PNG signature
|
|
119
|
+
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
120
|
+
// IHDR chunk
|
|
121
|
+
const ihdrData = Buffer.alloc(13);
|
|
122
|
+
ihdrData.writeUInt32BE(width, 0);
|
|
123
|
+
ihdrData.writeUInt32BE(height, 4);
|
|
124
|
+
ihdrData.writeUInt8(8, 8); // bit depth
|
|
125
|
+
ihdrData.writeUInt8(2, 9); // color type (RGB)
|
|
126
|
+
ihdrData.writeUInt8(0, 10); // compression
|
|
127
|
+
ihdrData.writeUInt8(0, 11); // filter
|
|
128
|
+
ihdrData.writeUInt8(0, 12); // interlace
|
|
129
|
+
const ihdrChunk = createChunk('IHDR', ihdrData);
|
|
130
|
+
// IDAT chunk - raw image data (gray pixels)
|
|
131
|
+
const rawData = [];
|
|
132
|
+
for (let y = 0; y < height; y++) {
|
|
133
|
+
rawData.push(0); // filter byte
|
|
134
|
+
for (let x = 0; x < width; x++) {
|
|
135
|
+
rawData.push(128, 128, 128); // gray RGB
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Simple deflate compression (store block)
|
|
139
|
+
const uncompressed = Buffer.from(rawData);
|
|
140
|
+
const compressed = deflateStore(uncompressed);
|
|
141
|
+
const idatChunk = createChunk('IDAT', compressed);
|
|
142
|
+
// IEND chunk
|
|
143
|
+
const iendChunk = createChunk('IEND', Buffer.alloc(0));
|
|
144
|
+
return Buffer.concat([signature, ihdrChunk, idatChunk, iendChunk]);
|
|
145
|
+
}
|
|
146
|
+
function createChunk(type, data) {
|
|
147
|
+
const length = Buffer.alloc(4);
|
|
148
|
+
length.writeUInt32BE(data.length, 0);
|
|
149
|
+
const typeBuffer = Buffer.from(type, 'ascii');
|
|
150
|
+
const crcData = Buffer.concat([typeBuffer, data]);
|
|
151
|
+
const crc = crc32(crcData);
|
|
152
|
+
const crcBuffer = Buffer.alloc(4);
|
|
153
|
+
crcBuffer.writeUInt32BE(crc >>> 0, 0);
|
|
154
|
+
return Buffer.concat([length, typeBuffer, data, crcBuffer]);
|
|
155
|
+
}
|
|
156
|
+
function deflateStore(data) {
|
|
157
|
+
// Zlib header + store blocks
|
|
158
|
+
const result = [0x78, 0x01]; // zlib header
|
|
159
|
+
let remaining = data.length;
|
|
160
|
+
let offset = 0;
|
|
161
|
+
while (remaining > 0) {
|
|
162
|
+
const blockSize = Math.min(remaining, 65535);
|
|
163
|
+
const isLast = remaining <= 65535;
|
|
164
|
+
result.push(isLast ? 0x01 : 0x00); // BFINAL + BTYPE=00
|
|
165
|
+
result.push(blockSize & 0xff);
|
|
166
|
+
result.push((blockSize >> 8) & 0xff);
|
|
167
|
+
result.push(~blockSize & 0xff);
|
|
168
|
+
result.push((~blockSize >> 8) & 0xff);
|
|
169
|
+
for (let i = 0; i < blockSize; i++) {
|
|
170
|
+
result.push(data[offset + i]);
|
|
171
|
+
}
|
|
172
|
+
offset += blockSize;
|
|
173
|
+
remaining -= blockSize;
|
|
174
|
+
}
|
|
175
|
+
// Adler-32 checksum
|
|
176
|
+
const adler = adler32(data);
|
|
177
|
+
result.push((adler >> 24) & 0xff);
|
|
178
|
+
result.push((adler >> 16) & 0xff);
|
|
179
|
+
result.push((adler >> 8) & 0xff);
|
|
180
|
+
result.push(adler & 0xff);
|
|
181
|
+
return Buffer.from(result);
|
|
182
|
+
}
|
|
183
|
+
function crc32(data) {
|
|
184
|
+
let crc = 0xffffffff;
|
|
185
|
+
for (let i = 0; i < data.length; i++) {
|
|
186
|
+
crc ^= data[i];
|
|
187
|
+
for (let j = 0; j < 8; j++) {
|
|
188
|
+
crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return crc ^ 0xffffffff;
|
|
192
|
+
}
|
|
193
|
+
function adler32(data) {
|
|
194
|
+
let a = 1;
|
|
195
|
+
let b = 0;
|
|
196
|
+
for (let i = 0; i < data.length; i++) {
|
|
197
|
+
a = (a + data[i]) % 65521;
|
|
198
|
+
b = (b + a) % 65521;
|
|
199
|
+
}
|
|
200
|
+
return (b << 16) | a;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function replaceTemplateVariables(template, variables) {
|
|
204
|
+
let result = template;
|
|
205
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
206
|
+
const placeholder = `{{${key}}}`;
|
|
207
|
+
result = result.replaceAll(placeholder, value);
|
|
208
|
+
}
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const __filename$1 = fileURLToPath(import.meta.url);
|
|
213
|
+
const __dirname$1 = path.dirname(__filename$1);
|
|
214
|
+
function findPackageRoot(startDir) {
|
|
215
|
+
let dir = startDir;
|
|
216
|
+
while (dir !== path.dirname(dir)) {
|
|
217
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) {
|
|
218
|
+
return dir;
|
|
219
|
+
}
|
|
220
|
+
dir = path.dirname(dir);
|
|
221
|
+
}
|
|
222
|
+
throw new Error('Could not find package root');
|
|
223
|
+
}
|
|
224
|
+
const PACKAGE_ROOT = findPackageRoot(__dirname$1);
|
|
225
|
+
const TEMPLATES_DIR = path.join(PACKAGE_ROOT, 'templates');
|
|
226
|
+
const EXTENSIONS_DIR = path.join(TEMPLATES_DIR, 'extensions');
|
|
227
|
+
function loadTemplate(templatePath) {
|
|
228
|
+
return fs.readFileSync(templatePath, 'utf-8');
|
|
229
|
+
}
|
|
230
|
+
function getManifestTemplate() {
|
|
231
|
+
return loadTemplate(path.join(TEMPLATES_DIR, 'AppxManifest.xml.template'));
|
|
232
|
+
}
|
|
233
|
+
function getExtensionTemplate(name) {
|
|
234
|
+
return loadTemplate(path.join(EXTENSIONS_DIR, `${name}.xml`));
|
|
235
|
+
}
|
|
236
|
+
function generateManifestTemplate(windowsDir) {
|
|
237
|
+
const templatePath = path.join(windowsDir, 'AppxManifest.xml.template');
|
|
238
|
+
const template = getManifestTemplate();
|
|
239
|
+
fs.writeFileSync(templatePath, template);
|
|
240
|
+
}
|
|
241
|
+
function generateManifest(config, arch, minVersion) {
|
|
242
|
+
const variables = {
|
|
243
|
+
PACKAGE_NAME: config.identifier.replace(/\./g, ''),
|
|
244
|
+
PUBLISHER: config.publisher,
|
|
245
|
+
VERSION: config.version,
|
|
246
|
+
ARCH: arch,
|
|
247
|
+
DISPLAY_NAME: config.displayName,
|
|
248
|
+
PUBLISHER_DISPLAY_NAME: config.publisherDisplayName,
|
|
249
|
+
MIN_VERSION: minVersion,
|
|
250
|
+
EXECUTABLE: `${config.displayName.replace(/\s+/g, '')}.exe`,
|
|
251
|
+
DESCRIPTION: config.description || config.displayName,
|
|
252
|
+
EXTENSIONS: generateExtensions(config),
|
|
253
|
+
CAPABILITIES: generateCapabilities(config.capabilities || []),
|
|
254
|
+
};
|
|
255
|
+
return replaceTemplateVariables(getManifestTemplate(), variables);
|
|
256
|
+
}
|
|
257
|
+
function generateExtensions(config) {
|
|
258
|
+
const extensions = [];
|
|
259
|
+
if (config.extensions?.shareTarget) {
|
|
260
|
+
const template = getExtensionTemplate('share-target');
|
|
261
|
+
extensions.push(template.trimEnd());
|
|
262
|
+
}
|
|
263
|
+
if (config.extensions?.fileAssociations) {
|
|
264
|
+
const template = getExtensionTemplate('file-association');
|
|
265
|
+
for (const assoc of config.extensions.fileAssociations) {
|
|
266
|
+
const fileTypes = assoc.extensions
|
|
267
|
+
.map((ext) => ` <uap:FileType>${ext}</uap:FileType>`)
|
|
268
|
+
.join('\n');
|
|
269
|
+
const result = replaceTemplateVariables(template, {
|
|
270
|
+
NAME: assoc.name,
|
|
271
|
+
FILE_TYPES: fileTypes,
|
|
272
|
+
});
|
|
273
|
+
extensions.push(result.trimEnd());
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (config.extensions?.protocolHandlers) {
|
|
277
|
+
const template = getExtensionTemplate('protocol');
|
|
278
|
+
for (const handler of config.extensions.protocolHandlers) {
|
|
279
|
+
const result = replaceTemplateVariables(template, {
|
|
280
|
+
NAME: handler.name,
|
|
281
|
+
DISPLAY_NAME: handler.displayName || handler.name,
|
|
282
|
+
});
|
|
283
|
+
extensions.push(result.trimEnd());
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (config.extensions?.startupTask?.enabled) {
|
|
287
|
+
const template = getExtensionTemplate('startup-task');
|
|
288
|
+
const taskId = config.extensions.startupTask.taskId || 'StartupTask';
|
|
289
|
+
const result = replaceTemplateVariables(template, {
|
|
290
|
+
TASK_ID: taskId,
|
|
291
|
+
DISPLAY_NAME: config.displayName,
|
|
292
|
+
});
|
|
293
|
+
extensions.push(result.trimEnd());
|
|
294
|
+
}
|
|
295
|
+
if (config.extensions?.contextMenus) {
|
|
296
|
+
const template = getExtensionTemplate('context-menu');
|
|
297
|
+
for (const menu of config.extensions.contextMenus) {
|
|
298
|
+
const fileTypes = menu.fileTypes
|
|
299
|
+
.map((ft) => ` <desktop:FileType>${ft}</desktop:FileType>`)
|
|
300
|
+
.join('\n');
|
|
301
|
+
const result = replaceTemplateVariables(template, {
|
|
302
|
+
NAME: menu.name,
|
|
303
|
+
FILE_TYPES: fileTypes,
|
|
304
|
+
});
|
|
305
|
+
extensions.push(result.trimEnd());
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (config.extensions?.backgroundTasks) {
|
|
309
|
+
const template = getExtensionTemplate('background-task');
|
|
310
|
+
for (const task of config.extensions.backgroundTasks) {
|
|
311
|
+
const triggerType = task.type === 'timer'
|
|
312
|
+
? 'TimeTrigger'
|
|
313
|
+
: task.type === 'systemEvent'
|
|
314
|
+
? 'SystemTrigger'
|
|
315
|
+
: 'PushNotificationTrigger';
|
|
316
|
+
const result = replaceTemplateVariables(template, {
|
|
317
|
+
ENTRY_POINT: task.name,
|
|
318
|
+
TRIGGER_TYPE: triggerType,
|
|
319
|
+
});
|
|
320
|
+
extensions.push(result.trimEnd());
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (config.extensions?.appExecutionAliases) {
|
|
324
|
+
const template = getExtensionTemplate('app-execution-alias');
|
|
325
|
+
const executable = `${config.displayName.replace(/\s+/g, '')}.exe`;
|
|
326
|
+
for (const alias of config.extensions.appExecutionAliases) {
|
|
327
|
+
const result = replaceTemplateVariables(template, {
|
|
328
|
+
ALIAS: alias.alias.endsWith('.exe') ? alias.alias : `${alias.alias}.exe`,
|
|
329
|
+
EXECUTABLE: executable,
|
|
330
|
+
});
|
|
331
|
+
extensions.push(result.trimEnd());
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
if (config.extensions?.appServices) {
|
|
335
|
+
const template = getExtensionTemplate('app-service');
|
|
336
|
+
for (const service of config.extensions.appServices) {
|
|
337
|
+
const result = replaceTemplateVariables(template, {
|
|
338
|
+
NAME: service.name,
|
|
339
|
+
});
|
|
340
|
+
extensions.push(result.trimEnd());
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (config.extensions?.toastActivation) {
|
|
344
|
+
const template = getExtensionTemplate('toast-activation');
|
|
345
|
+
// Generate a CLSID from the identifier
|
|
346
|
+
const clsid = generateClsid(config.identifier + '.toast');
|
|
347
|
+
const result = replaceTemplateVariables(template, {
|
|
348
|
+
CLSID: clsid,
|
|
349
|
+
});
|
|
350
|
+
extensions.push(result.trimEnd());
|
|
351
|
+
}
|
|
352
|
+
if (config.extensions?.autoplayHandlers) {
|
|
353
|
+
for (const handler of config.extensions.autoplayHandlers) {
|
|
354
|
+
if (handler.contentEvent) {
|
|
355
|
+
const template = getExtensionTemplate('autoplay');
|
|
356
|
+
const result = replaceTemplateVariables(template, {
|
|
357
|
+
VERB: handler.verb,
|
|
358
|
+
ACTION_DISPLAY_NAME: handler.actionDisplayName,
|
|
359
|
+
CONTENT_EVENT: handler.contentEvent,
|
|
360
|
+
});
|
|
361
|
+
extensions.push(result.trimEnd());
|
|
362
|
+
}
|
|
363
|
+
if (handler.deviceEvent) {
|
|
364
|
+
const template = getExtensionTemplate('autoplay-device');
|
|
365
|
+
const result = replaceTemplateVariables(template, {
|
|
366
|
+
VERB: handler.verb,
|
|
367
|
+
ACTION_DISPLAY_NAME: handler.actionDisplayName,
|
|
368
|
+
DEVICE_EVENT: handler.deviceEvent,
|
|
369
|
+
});
|
|
370
|
+
extensions.push(result.trimEnd());
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (config.extensions?.printTaskSettings) {
|
|
375
|
+
const template = getExtensionTemplate('print-task-settings');
|
|
376
|
+
extensions.push(template.trimEnd());
|
|
377
|
+
}
|
|
378
|
+
if (config.extensions?.thumbnailHandlers) {
|
|
379
|
+
const template = getExtensionTemplate('thumbnail-handler');
|
|
380
|
+
for (const handler of config.extensions.thumbnailHandlers) {
|
|
381
|
+
const fileTypes = handler.fileTypes
|
|
382
|
+
.map((ext) => ` <uap:FileType>${ext}</uap:FileType>`)
|
|
383
|
+
.join('\n');
|
|
384
|
+
const result = replaceTemplateVariables(template, {
|
|
385
|
+
NAME: `thumbnail-${handler.clsid.replace(/[{}]/g, '').slice(0, 8)}`,
|
|
386
|
+
FILE_TYPES: fileTypes,
|
|
387
|
+
CLSID: handler.clsid,
|
|
388
|
+
});
|
|
389
|
+
extensions.push(result.trimEnd());
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (config.extensions?.previewHandlers) {
|
|
393
|
+
const template = getExtensionTemplate('preview-handler');
|
|
394
|
+
for (const handler of config.extensions.previewHandlers) {
|
|
395
|
+
const fileTypes = handler.fileTypes
|
|
396
|
+
.map((ext) => ` <uap:FileType>${ext}</uap:FileType>`)
|
|
397
|
+
.join('\n');
|
|
398
|
+
const result = replaceTemplateVariables(template, {
|
|
399
|
+
NAME: `preview-${handler.clsid.replace(/[{}]/g, '').slice(0, 8)}`,
|
|
400
|
+
FILE_TYPES: fileTypes,
|
|
401
|
+
CLSID: handler.clsid,
|
|
402
|
+
});
|
|
403
|
+
extensions.push(result.trimEnd());
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return extensions.length > 0 ? extensions.join('\n\n') : '';
|
|
407
|
+
}
|
|
408
|
+
function generateCapabilities(capabilities) {
|
|
409
|
+
return capabilities.map((cap) => ` <Capability Name="${cap}" />`).join('\n');
|
|
410
|
+
}
|
|
411
|
+
function generateClsid(seed) {
|
|
412
|
+
// Generate a deterministic GUID-like string from the seed
|
|
413
|
+
let hash = 0;
|
|
414
|
+
for (let i = 0; i < seed.length; i++) {
|
|
415
|
+
const char = seed.charCodeAt(i);
|
|
416
|
+
hash = (hash << 5) - hash + char;
|
|
417
|
+
hash = hash & hash;
|
|
418
|
+
}
|
|
419
|
+
const hex = Math.abs(hash).toString(16).padStart(8, '0');
|
|
420
|
+
const hex2 = Math.abs(hash * 31)
|
|
421
|
+
.toString(16)
|
|
422
|
+
.padStart(8, '0');
|
|
423
|
+
const hex3 = Math.abs(hash * 37)
|
|
424
|
+
.toString(16)
|
|
425
|
+
.padStart(8, '0');
|
|
426
|
+
const hex4 = Math.abs(hash * 41)
|
|
427
|
+
.toString(16)
|
|
428
|
+
.padStart(12, '0');
|
|
429
|
+
return `{${hex.slice(0, 8)}-${hex2.slice(0, 4)}-${hex2.slice(4, 8)}-${hex3.slice(0, 4)}-${hex4.slice(0, 12)}}`.toUpperCase();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function init(options) {
|
|
433
|
+
console.log('Initializing Windows bundle configuration...\n');
|
|
434
|
+
const projectRoot = findProjectRoot(options.path);
|
|
435
|
+
readTauriConfig(projectRoot);
|
|
436
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
437
|
+
// Create directories
|
|
438
|
+
fs.mkdirSync(path.join(windowsDir, 'Assets'), { recursive: true });
|
|
439
|
+
fs.mkdirSync(path.join(windowsDir, 'extensions'), { recursive: true });
|
|
440
|
+
// Generate bundle.config.json
|
|
441
|
+
generateBundleConfig(windowsDir);
|
|
442
|
+
console.log(' Created bundle.config.json');
|
|
443
|
+
// Generate AppxManifest.xml template
|
|
444
|
+
generateManifestTemplate(windowsDir);
|
|
445
|
+
console.log(' Created AppxManifest.xml.template');
|
|
446
|
+
// Generate placeholder assets
|
|
447
|
+
await generateAssets(windowsDir);
|
|
448
|
+
// Generate .gitignore
|
|
449
|
+
generateGitignore(windowsDir);
|
|
450
|
+
// Update package.json with build script
|
|
451
|
+
updatePackageJson(projectRoot);
|
|
452
|
+
console.log(' Added tauri:windows:build script to package.json');
|
|
453
|
+
console.log('\n Windows bundle configuration created!');
|
|
454
|
+
console.log(`\nNext steps:`);
|
|
455
|
+
console.log(` 1. Edit src-tauri/gen/windows/bundle.config.json`);
|
|
456
|
+
console.log(` - Set your publisher CN (from your code signing certificate)`);
|
|
457
|
+
console.log(` - Set your publisher display name`);
|
|
458
|
+
console.log(` 2. Replace placeholder icons in src-tauri/gen/windows/Assets/`);
|
|
459
|
+
console.log(` 3. Run: pnpm tauri:windows:build`);
|
|
460
|
+
}
|
|
461
|
+
function updatePackageJson(projectRoot) {
|
|
462
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
463
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
464
|
+
console.log(' Warning: package.json not found, skipping script update');
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
try {
|
|
468
|
+
const content = fs.readFileSync(packageJsonPath, 'utf-8');
|
|
469
|
+
const pkg = JSON.parse(content);
|
|
470
|
+
if (!pkg.scripts) {
|
|
471
|
+
pkg.scripts = {};
|
|
472
|
+
}
|
|
473
|
+
if (!pkg.scripts['tauri:windows:build']) {
|
|
474
|
+
pkg.scripts['tauri:windows:build'] = 'tauri-windows-bundle build';
|
|
475
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
console.log(` Warning: Could not update package.json: ${error instanceof Error ? error.message : error}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function prepareAppxContent(projectRoot, arch, config, tauriConfig, minVersion) {
|
|
484
|
+
const target = arch === 'x64' ? 'x86_64-pc-windows-msvc' : 'aarch64-pc-windows-msvc';
|
|
485
|
+
const buildDir = path.join(projectRoot, 'target', target, 'release');
|
|
486
|
+
const appxDir = path.join(projectRoot, 'target', 'appx', arch);
|
|
487
|
+
// Create directories
|
|
488
|
+
fs.mkdirSync(path.join(appxDir, 'Assets'), { recursive: true });
|
|
489
|
+
// Copy exe
|
|
490
|
+
const exeName = `${config.displayName.replace(/\s+/g, '')}.exe`;
|
|
491
|
+
const srcExe = path.join(buildDir, exeName);
|
|
492
|
+
if (!fs.existsSync(srcExe)) {
|
|
493
|
+
throw new Error(`Executable not found: ${srcExe}`);
|
|
494
|
+
}
|
|
495
|
+
fs.copyFileSync(srcExe, path.join(appxDir, exeName));
|
|
496
|
+
// Generate AppxManifest.xml
|
|
497
|
+
const manifest = generateManifest(config, arch, minVersion);
|
|
498
|
+
fs.writeFileSync(path.join(appxDir, 'AppxManifest.xml'), manifest);
|
|
499
|
+
// Copy MSIX Assets
|
|
500
|
+
const windowsAssetsDir = path.join(projectRoot, 'src-tauri', 'gen', 'windows', 'Assets');
|
|
501
|
+
if (fs.existsSync(windowsAssetsDir)) {
|
|
502
|
+
fs.cpSync(windowsAssetsDir, path.join(appxDir, 'Assets'), {
|
|
503
|
+
recursive: true,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
// Copy bundled resources from tauri.conf.json
|
|
507
|
+
copyBundledResources(projectRoot, appxDir, tauriConfig);
|
|
508
|
+
return appxDir;
|
|
509
|
+
}
|
|
510
|
+
function copyBundledResources(projectRoot, appxDir, tauriConfig) {
|
|
511
|
+
const resources = tauriConfig.bundle?.resources;
|
|
512
|
+
if (!resources || resources.length === 0)
|
|
513
|
+
return;
|
|
514
|
+
const srcDir = path.join(projectRoot, 'src-tauri');
|
|
515
|
+
for (const resource of resources) {
|
|
516
|
+
if (typeof resource === 'string') {
|
|
517
|
+
// Glob pattern like "assets/*" or specific file
|
|
518
|
+
const files = glob.sync(resource, { cwd: srcDir });
|
|
519
|
+
for (const file of files) {
|
|
520
|
+
const src = path.join(srcDir, file);
|
|
521
|
+
const dest = path.join(appxDir, file);
|
|
522
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
523
|
+
if (fs.statSync(src).isDirectory()) {
|
|
524
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
fs.copyFileSync(src, dest);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
else if (typeof resource === 'object' && resource.src && resource.target) {
|
|
532
|
+
const src = path.join(srcDir, resource.src);
|
|
533
|
+
const dest = path.join(appxDir, resource.target);
|
|
534
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
535
|
+
if (fs.statSync(src).isDirectory()) {
|
|
536
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
fs.copyFileSync(src, dest);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const execPromise = promisify(exec);
|
|
546
|
+
async function execAsync(command, options) {
|
|
547
|
+
const result = await execPromise(command, { ...options, encoding: 'utf8' });
|
|
548
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
549
|
+
}
|
|
550
|
+
async function isMsixbundleCliInstalled() {
|
|
551
|
+
try {
|
|
552
|
+
await execAsync('msixbundle-cli --version');
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async function getMsixbundleCliVersion() {
|
|
560
|
+
try {
|
|
561
|
+
const result = await execAsync('msixbundle-cli --version');
|
|
562
|
+
// Output format: "msixbundle-cli 1.0.0" or just "1.0.0"
|
|
563
|
+
const match = result.stdout.trim().match(/(\d+\.\d+\.\d+)/);
|
|
564
|
+
return match ? match[1] : null;
|
|
565
|
+
}
|
|
566
|
+
catch {
|
|
567
|
+
return null;
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
function isVersionSufficient(version, minVersion) {
|
|
571
|
+
const parse = (v) => v.split('.').map((n) => parseInt(n, 10));
|
|
572
|
+
const [major, minor, patch] = parse(version);
|
|
573
|
+
const [minMajor, minMinor, minPatch] = parse(minVersion);
|
|
574
|
+
if (major > minMajor)
|
|
575
|
+
return true;
|
|
576
|
+
if (major < minMajor)
|
|
577
|
+
return false;
|
|
578
|
+
if (minor > minMinor)
|
|
579
|
+
return true;
|
|
580
|
+
if (minor < minMinor)
|
|
581
|
+
return false;
|
|
582
|
+
return patch >= minPatch;
|
|
583
|
+
}
|
|
584
|
+
const MIN_MSIXBUNDLE_CLI_VERSION = '1.0.0';
|
|
585
|
+
async function promptInstall(message) {
|
|
586
|
+
const rl = readline.createInterface({
|
|
587
|
+
input: process.stdin,
|
|
588
|
+
output: process.stdout,
|
|
589
|
+
});
|
|
590
|
+
return new Promise((resolve) => {
|
|
591
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
592
|
+
rl.close();
|
|
593
|
+
resolve(answer.toLowerCase() === 'y');
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
async function build(options) {
|
|
599
|
+
console.log('Building MSIX package...\n');
|
|
600
|
+
// Check if msixbundle-cli is installed
|
|
601
|
+
if (!(await isMsixbundleCliInstalled())) {
|
|
602
|
+
const shouldInstall = await promptInstall('msixbundle-cli is required but not installed.\n' + 'Install it now? (requires Rust/Cargo)');
|
|
603
|
+
if (shouldInstall) {
|
|
604
|
+
console.log('Installing msixbundle-cli...');
|
|
605
|
+
try {
|
|
606
|
+
await execAsync('cargo install msixbundle-cli');
|
|
607
|
+
console.log(' msixbundle-cli installed\n');
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
console.error('Failed to install msixbundle-cli:', error);
|
|
611
|
+
console.log('\nInstall manually: cargo install msixbundle-cli');
|
|
612
|
+
console.log('Or from: https://github.com/Choochmeque/msixbundle-rs');
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else {
|
|
617
|
+
console.log('\nInstall manually: cargo install msixbundle-cli');
|
|
618
|
+
console.log('Or from: https://github.com/Choochmeque/msixbundle-rs');
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
// Check msixbundle-cli version
|
|
623
|
+
const version = await getMsixbundleCliVersion();
|
|
624
|
+
if (!version) {
|
|
625
|
+
console.error('Could not determine msixbundle-cli version');
|
|
626
|
+
process.exit(1);
|
|
627
|
+
}
|
|
628
|
+
if (!isVersionSufficient(version, MIN_MSIXBUNDLE_CLI_VERSION)) {
|
|
629
|
+
console.error(`msixbundle-cli version ${version} is too old. Minimum required: ${MIN_MSIXBUNDLE_CLI_VERSION}`);
|
|
630
|
+
console.log('Update with: cargo install msixbundle-cli --force');
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
const projectRoot = findProjectRoot();
|
|
634
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
635
|
+
// Read configs
|
|
636
|
+
const tauriConfig = readTauriConfig(projectRoot);
|
|
637
|
+
const bundleConfig = readBundleConfig$1(windowsDir);
|
|
638
|
+
// Merge config
|
|
639
|
+
const config = {
|
|
640
|
+
displayName: tauriConfig.productName || 'App',
|
|
641
|
+
version: toFourPartVersion(tauriConfig.version || '1.0.0'),
|
|
642
|
+
description: tauriConfig.bundle?.shortDescription || '',
|
|
643
|
+
identifier: tauriConfig.identifier || 'com.example.app',
|
|
644
|
+
...bundleConfig,
|
|
645
|
+
};
|
|
646
|
+
// Architectures from CLI flag
|
|
647
|
+
const architectures = options.arch?.split(',') || ['x64'];
|
|
648
|
+
const minVersion = options.minWindows || DEFAULT_MIN_WINDOWS_VERSION;
|
|
649
|
+
const appxDirs = [];
|
|
650
|
+
for (const arch of architectures) {
|
|
651
|
+
console.log(`Building for ${arch}...`);
|
|
652
|
+
// Build Tauri app
|
|
653
|
+
const target = arch === 'x64' ? 'x86_64-pc-windows-msvc' : 'aarch64-pc-windows-msvc';
|
|
654
|
+
const releaseFlag = options.release ? '--release' : '';
|
|
655
|
+
try {
|
|
656
|
+
console.log(` Running: cargo tauri build --target ${target} ${releaseFlag}`);
|
|
657
|
+
await execAsync(`cargo tauri build --target ${target} ${releaseFlag}`.trim(), {
|
|
658
|
+
cwd: projectRoot,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
catch (error) {
|
|
662
|
+
console.error(`Failed to build for ${arch}:`, error);
|
|
663
|
+
process.exit(1);
|
|
664
|
+
}
|
|
665
|
+
// Prepare AppxContent directory
|
|
666
|
+
console.log(` Preparing AppxContent for ${arch}...`);
|
|
667
|
+
const appxDir = prepareAppxContent(projectRoot, arch, config, tauriConfig, minVersion);
|
|
668
|
+
appxDirs.push({ arch, dir: appxDir });
|
|
669
|
+
console.log(` AppxContent ready: ${appxDir}`);
|
|
670
|
+
}
|
|
671
|
+
// Call msixbundle-cli
|
|
672
|
+
console.log('\nCreating MSIX package...');
|
|
673
|
+
const outDir = path.join(projectRoot, 'target', 'msix');
|
|
674
|
+
const args = [
|
|
675
|
+
'--out-dir',
|
|
676
|
+
outDir,
|
|
677
|
+
...appxDirs.flatMap(({ arch, dir }) => [`--dir-${arch}`, dir]),
|
|
678
|
+
];
|
|
679
|
+
// Signing
|
|
680
|
+
if (bundleConfig.signing?.pfx) {
|
|
681
|
+
args.push('--pfx', bundleConfig.signing.pfx);
|
|
682
|
+
const password = bundleConfig.signing.pfxPassword || process.env.MSIX_PFX_PASSWORD;
|
|
683
|
+
if (password) {
|
|
684
|
+
args.push('--pfx-password', password);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else if (tauriConfig.bundle?.windows?.certificateThumbprint) {
|
|
688
|
+
args.push('--thumbprint', tauriConfig.bundle.windows.certificateThumbprint);
|
|
689
|
+
}
|
|
690
|
+
try {
|
|
691
|
+
console.log(` Running: msixbundle-cli ${args.join(' ')}`);
|
|
692
|
+
const result = await execAsync(`msixbundle-cli ${args.join(' ')}`);
|
|
693
|
+
if (result.stdout)
|
|
694
|
+
console.log(result.stdout);
|
|
695
|
+
}
|
|
696
|
+
catch (error) {
|
|
697
|
+
console.error('Failed to create MSIX:', error);
|
|
698
|
+
process.exit(1);
|
|
699
|
+
}
|
|
700
|
+
console.log('\n MSIX bundle created!');
|
|
701
|
+
console.log(`Output: ${outDir}`);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function readBundleConfig(windowsDir) {
|
|
705
|
+
const configPath = path.join(windowsDir, 'bundle.config.json');
|
|
706
|
+
if (!fs.existsSync(configPath)) {
|
|
707
|
+
throw new Error(`bundle.config.json not found. Run 'tauri-windows-bundle init' first.`);
|
|
708
|
+
}
|
|
709
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
710
|
+
}
|
|
711
|
+
function writeBundleConfig(windowsDir, config) {
|
|
712
|
+
const configPath = path.join(windowsDir, 'bundle.config.json');
|
|
713
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
714
|
+
}
|
|
715
|
+
function prompt(question) {
|
|
716
|
+
const rl = readline.createInterface({
|
|
717
|
+
input: process.stdin,
|
|
718
|
+
output: process.stdout,
|
|
719
|
+
});
|
|
720
|
+
return new Promise((resolve) => {
|
|
721
|
+
rl.question(question, (answer) => {
|
|
722
|
+
rl.close();
|
|
723
|
+
resolve(answer.trim());
|
|
724
|
+
});
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
async function extensionList(options) {
|
|
728
|
+
const projectRoot = findProjectRoot(options.path);
|
|
729
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
730
|
+
const config = readBundleConfig(windowsDir);
|
|
731
|
+
console.log('\nConfigured extensions:\n');
|
|
732
|
+
// Share Target
|
|
733
|
+
const shareTarget = config.extensions?.shareTarget ?? false;
|
|
734
|
+
console.log(` Share Target: ${shareTarget ? 'enabled' : 'disabled'}`);
|
|
735
|
+
// File Associations
|
|
736
|
+
const fileAssociations = config.extensions?.fileAssociations ?? [];
|
|
737
|
+
if (fileAssociations.length > 0) {
|
|
738
|
+
console.log('\n File Associations:');
|
|
739
|
+
for (const assoc of fileAssociations) {
|
|
740
|
+
console.log(` - ${assoc.name}: ${assoc.extensions.join(', ')}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
console.log('\n File Associations: none');
|
|
745
|
+
}
|
|
746
|
+
// Protocol Handlers
|
|
747
|
+
const protocolHandlers = config.extensions?.protocolHandlers ?? [];
|
|
748
|
+
if (protocolHandlers.length > 0) {
|
|
749
|
+
console.log('\n Protocol Handlers:');
|
|
750
|
+
for (const handler of protocolHandlers) {
|
|
751
|
+
console.log(` - ${handler.name}:// (${handler.displayName || handler.name})`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
else {
|
|
755
|
+
console.log('\n Protocol Handlers: none');
|
|
756
|
+
}
|
|
757
|
+
// Startup Task
|
|
758
|
+
const startupTask = config.extensions?.startupTask;
|
|
759
|
+
console.log(`\n Startup Task: ${startupTask?.enabled ? 'enabled' : 'disabled'}`);
|
|
760
|
+
// Context Menus
|
|
761
|
+
const contextMenus = config.extensions?.contextMenus ?? [];
|
|
762
|
+
if (contextMenus.length > 0) {
|
|
763
|
+
console.log('\n Context Menus:');
|
|
764
|
+
for (const menu of contextMenus) {
|
|
765
|
+
console.log(` - ${menu.name}: ${menu.fileTypes.join(', ')}`);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
else {
|
|
769
|
+
console.log('\n Context Menus: none');
|
|
770
|
+
}
|
|
771
|
+
// Background Tasks
|
|
772
|
+
const backgroundTasks = config.extensions?.backgroundTasks ?? [];
|
|
773
|
+
if (backgroundTasks.length > 0) {
|
|
774
|
+
console.log('\n Background Tasks:');
|
|
775
|
+
for (const task of backgroundTasks) {
|
|
776
|
+
console.log(` - ${task.name} (${task.type})`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
console.log('\n Background Tasks: none');
|
|
781
|
+
}
|
|
782
|
+
// App Execution Aliases
|
|
783
|
+
const aliases = config.extensions?.appExecutionAliases ?? [];
|
|
784
|
+
if (aliases.length > 0) {
|
|
785
|
+
console.log('\n App Execution Aliases:');
|
|
786
|
+
for (const alias of aliases) {
|
|
787
|
+
console.log(` - ${alias.alias}`);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
else {
|
|
791
|
+
console.log('\n App Execution Aliases: none');
|
|
792
|
+
}
|
|
793
|
+
// App Services
|
|
794
|
+
const appServices = config.extensions?.appServices ?? [];
|
|
795
|
+
if (appServices.length > 0) {
|
|
796
|
+
console.log('\n App Services:');
|
|
797
|
+
for (const service of appServices) {
|
|
798
|
+
console.log(` - ${service.name}`);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
console.log('\n App Services: none');
|
|
803
|
+
}
|
|
804
|
+
// Toast Activation
|
|
805
|
+
const toastActivation = config.extensions?.toastActivation;
|
|
806
|
+
console.log(`\n Toast Activation: ${toastActivation ? 'enabled' : 'disabled'}`);
|
|
807
|
+
// Autoplay Handlers
|
|
808
|
+
const autoplayHandlers = config.extensions?.autoplayHandlers ?? [];
|
|
809
|
+
if (autoplayHandlers.length > 0) {
|
|
810
|
+
console.log('\n Autoplay Handlers:');
|
|
811
|
+
for (const handler of autoplayHandlers) {
|
|
812
|
+
console.log(` - ${handler.verb}: ${handler.actionDisplayName}`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
else {
|
|
816
|
+
console.log('\n Autoplay Handlers: none');
|
|
817
|
+
}
|
|
818
|
+
// Print Task Settings
|
|
819
|
+
const printTaskSettings = config.extensions?.printTaskSettings;
|
|
820
|
+
console.log(`\n Print Task Settings: ${printTaskSettings ? 'enabled' : 'disabled'}`);
|
|
821
|
+
// Thumbnail Handlers
|
|
822
|
+
const thumbnailHandlers = config.extensions?.thumbnailHandlers ?? [];
|
|
823
|
+
if (thumbnailHandlers.length > 0) {
|
|
824
|
+
console.log('\n Thumbnail Handlers:');
|
|
825
|
+
for (const handler of thumbnailHandlers) {
|
|
826
|
+
console.log(` - ${handler.fileTypes.join(', ')}`);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
else {
|
|
830
|
+
console.log('\n Thumbnail Handlers: none');
|
|
831
|
+
}
|
|
832
|
+
// Preview Handlers
|
|
833
|
+
const previewHandlers = config.extensions?.previewHandlers ?? [];
|
|
834
|
+
if (previewHandlers.length > 0) {
|
|
835
|
+
console.log('\n Preview Handlers:');
|
|
836
|
+
for (const handler of previewHandlers) {
|
|
837
|
+
console.log(` - ${handler.fileTypes.join(', ')}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
console.log('\n Preview Handlers: none');
|
|
842
|
+
}
|
|
843
|
+
console.log('');
|
|
844
|
+
}
|
|
845
|
+
async function extensionAddFileAssociation(options) {
|
|
846
|
+
const projectRoot = findProjectRoot(options.path);
|
|
847
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
848
|
+
const config = readBundleConfig(windowsDir);
|
|
849
|
+
console.log('\nAdd File Association\n');
|
|
850
|
+
const name = await prompt('Association name (e.g., myfiles): ');
|
|
851
|
+
if (!name) {
|
|
852
|
+
console.log('Cancelled.');
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
const extensionsInput = await prompt('File extensions (comma-separated, e.g., .myf,.myx): ');
|
|
856
|
+
if (!extensionsInput) {
|
|
857
|
+
console.log('Cancelled.');
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const extensions = extensionsInput.split(',').map((ext) => {
|
|
861
|
+
ext = ext.trim();
|
|
862
|
+
return ext.startsWith('.') ? ext : `.${ext}`;
|
|
863
|
+
});
|
|
864
|
+
const description = await prompt('Description (optional): ');
|
|
865
|
+
const fileAssociation = {
|
|
866
|
+
name,
|
|
867
|
+
extensions,
|
|
868
|
+
};
|
|
869
|
+
if (description) {
|
|
870
|
+
fileAssociation.description = description;
|
|
871
|
+
}
|
|
872
|
+
if (!config.extensions) {
|
|
873
|
+
config.extensions = {};
|
|
874
|
+
}
|
|
875
|
+
if (!config.extensions.fileAssociations) {
|
|
876
|
+
config.extensions.fileAssociations = [];
|
|
877
|
+
}
|
|
878
|
+
// Check for duplicate
|
|
879
|
+
const existing = config.extensions.fileAssociations.find((a) => a.name === name);
|
|
880
|
+
if (existing) {
|
|
881
|
+
console.log(`\nFile association '${name}' already exists. Updating...`);
|
|
882
|
+
Object.assign(existing, fileAssociation);
|
|
883
|
+
}
|
|
884
|
+
else {
|
|
885
|
+
config.extensions.fileAssociations.push(fileAssociation);
|
|
886
|
+
}
|
|
887
|
+
writeBundleConfig(windowsDir, config);
|
|
888
|
+
console.log(`\nFile association '${name}' added successfully.`);
|
|
889
|
+
}
|
|
890
|
+
async function extensionAddProtocol(options) {
|
|
891
|
+
const projectRoot = findProjectRoot(options.path);
|
|
892
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
893
|
+
const config = readBundleConfig(windowsDir);
|
|
894
|
+
console.log('\nAdd Protocol Handler\n');
|
|
895
|
+
const name = await prompt('Protocol name (e.g., myapp): ');
|
|
896
|
+
if (!name) {
|
|
897
|
+
console.log('Cancelled.');
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
const displayName = await prompt(`Display name (default: ${name}): `);
|
|
901
|
+
const protocolHandler = {
|
|
902
|
+
name,
|
|
903
|
+
};
|
|
904
|
+
if (displayName) {
|
|
905
|
+
protocolHandler.displayName = displayName;
|
|
906
|
+
}
|
|
907
|
+
if (!config.extensions) {
|
|
908
|
+
config.extensions = {};
|
|
909
|
+
}
|
|
910
|
+
if (!config.extensions.protocolHandlers) {
|
|
911
|
+
config.extensions.protocolHandlers = [];
|
|
912
|
+
}
|
|
913
|
+
// Check for duplicate
|
|
914
|
+
const existing = config.extensions.protocolHandlers.find((p) => p.name === name);
|
|
915
|
+
if (existing) {
|
|
916
|
+
console.log(`\nProtocol handler '${name}' already exists. Updating...`);
|
|
917
|
+
Object.assign(existing, protocolHandler);
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
config.extensions.protocolHandlers.push(protocolHandler);
|
|
921
|
+
}
|
|
922
|
+
writeBundleConfig(windowsDir, config);
|
|
923
|
+
console.log(`\nProtocol handler '${name}://' added successfully.`);
|
|
924
|
+
}
|
|
925
|
+
async function extensionEnableShareTarget(options) {
|
|
926
|
+
const projectRoot = findProjectRoot(options.path);
|
|
927
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
928
|
+
const config = readBundleConfig(windowsDir);
|
|
929
|
+
if (!config.extensions) {
|
|
930
|
+
config.extensions = {};
|
|
931
|
+
}
|
|
932
|
+
config.extensions.shareTarget = true;
|
|
933
|
+
writeBundleConfig(windowsDir, config);
|
|
934
|
+
console.log('\nShare Target enabled.');
|
|
935
|
+
}
|
|
936
|
+
async function extensionDisableShareTarget(options) {
|
|
937
|
+
const projectRoot = findProjectRoot(options.path);
|
|
938
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
939
|
+
const config = readBundleConfig(windowsDir);
|
|
940
|
+
if (!config.extensions) {
|
|
941
|
+
config.extensions = {};
|
|
942
|
+
}
|
|
943
|
+
config.extensions.shareTarget = false;
|
|
944
|
+
writeBundleConfig(windowsDir, config);
|
|
945
|
+
console.log('\nShare Target disabled.');
|
|
946
|
+
}
|
|
947
|
+
async function extensionEnableStartupTask(options) {
|
|
948
|
+
const projectRoot = findProjectRoot(options.path);
|
|
949
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
950
|
+
const config = readBundleConfig(windowsDir);
|
|
951
|
+
if (!config.extensions) {
|
|
952
|
+
config.extensions = {};
|
|
953
|
+
}
|
|
954
|
+
config.extensions.startupTask = { enabled: true };
|
|
955
|
+
writeBundleConfig(windowsDir, config);
|
|
956
|
+
console.log('\nStartup Task enabled. App will run on Windows login.');
|
|
957
|
+
}
|
|
958
|
+
async function extensionDisableStartupTask(options) {
|
|
959
|
+
const projectRoot = findProjectRoot(options.path);
|
|
960
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
961
|
+
const config = readBundleConfig(windowsDir);
|
|
962
|
+
if (!config.extensions) {
|
|
963
|
+
config.extensions = {};
|
|
964
|
+
}
|
|
965
|
+
config.extensions.startupTask = { enabled: false };
|
|
966
|
+
writeBundleConfig(windowsDir, config);
|
|
967
|
+
console.log('\nStartup Task disabled.');
|
|
968
|
+
}
|
|
969
|
+
async function extensionAddContextMenu(options) {
|
|
970
|
+
const projectRoot = findProjectRoot(options.path);
|
|
971
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
972
|
+
const config = readBundleConfig(windowsDir);
|
|
973
|
+
console.log('\nAdd Context Menu\n');
|
|
974
|
+
const name = await prompt('Menu item name (e.g., open-with-myapp): ');
|
|
975
|
+
if (!name) {
|
|
976
|
+
console.log('Cancelled.');
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
const fileTypesInput = await prompt('File types (comma-separated, e.g., *, .txt, .doc): ');
|
|
980
|
+
if (!fileTypesInput) {
|
|
981
|
+
console.log('Cancelled.');
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const fileTypes = fileTypesInput.split(',').map((t) => t.trim());
|
|
985
|
+
const displayName = await prompt('Display name (shown in menu): ');
|
|
986
|
+
const contextMenu = {
|
|
987
|
+
name,
|
|
988
|
+
fileTypes,
|
|
989
|
+
};
|
|
990
|
+
if (displayName) {
|
|
991
|
+
contextMenu.displayName = displayName;
|
|
992
|
+
}
|
|
993
|
+
if (!config.extensions) {
|
|
994
|
+
config.extensions = {};
|
|
995
|
+
}
|
|
996
|
+
if (!config.extensions.contextMenus) {
|
|
997
|
+
config.extensions.contextMenus = [];
|
|
998
|
+
}
|
|
999
|
+
const existing = config.extensions.contextMenus.find((m) => m.name === name);
|
|
1000
|
+
if (existing) {
|
|
1001
|
+
console.log(`\nContext menu '${name}' already exists. Updating...`);
|
|
1002
|
+
Object.assign(existing, contextMenu);
|
|
1003
|
+
}
|
|
1004
|
+
else {
|
|
1005
|
+
config.extensions.contextMenus.push(contextMenu);
|
|
1006
|
+
}
|
|
1007
|
+
writeBundleConfig(windowsDir, config);
|
|
1008
|
+
console.log(`\nContext menu '${name}' added successfully.`);
|
|
1009
|
+
}
|
|
1010
|
+
async function extensionAddBackgroundTask(options) {
|
|
1011
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1012
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1013
|
+
const config = readBundleConfig(windowsDir);
|
|
1014
|
+
console.log('\nAdd Background Task\n');
|
|
1015
|
+
const name = await prompt('Task name (e.g., sync-task): ');
|
|
1016
|
+
if (!name) {
|
|
1017
|
+
console.log('Cancelled.');
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
console.log('Task types:');
|
|
1021
|
+
console.log(' 1. timer - Runs periodically');
|
|
1022
|
+
console.log(' 2. systemEvent - Runs on system events');
|
|
1023
|
+
console.log(' 3. pushNotification - Runs on push notification');
|
|
1024
|
+
const typeInput = await prompt('Task type (1/2/3): ');
|
|
1025
|
+
const typeMap = {
|
|
1026
|
+
'1': 'timer',
|
|
1027
|
+
'2': 'systemEvent',
|
|
1028
|
+
'3': 'pushNotification',
|
|
1029
|
+
timer: 'timer',
|
|
1030
|
+
systemevent: 'systemEvent',
|
|
1031
|
+
pushnotification: 'pushNotification',
|
|
1032
|
+
};
|
|
1033
|
+
const taskType = typeMap[typeInput.toLowerCase()];
|
|
1034
|
+
if (!taskType) {
|
|
1035
|
+
console.log('Invalid task type. Cancelled.');
|
|
1036
|
+
return;
|
|
1037
|
+
}
|
|
1038
|
+
const backgroundTask = {
|
|
1039
|
+
name,
|
|
1040
|
+
type: taskType,
|
|
1041
|
+
};
|
|
1042
|
+
if (!config.extensions) {
|
|
1043
|
+
config.extensions = {};
|
|
1044
|
+
}
|
|
1045
|
+
if (!config.extensions.backgroundTasks) {
|
|
1046
|
+
config.extensions.backgroundTasks = [];
|
|
1047
|
+
}
|
|
1048
|
+
const existing = config.extensions.backgroundTasks.find((t) => t.name === name);
|
|
1049
|
+
if (existing) {
|
|
1050
|
+
console.log(`\nBackground task '${name}' already exists. Updating...`);
|
|
1051
|
+
Object.assign(existing, backgroundTask);
|
|
1052
|
+
}
|
|
1053
|
+
else {
|
|
1054
|
+
config.extensions.backgroundTasks.push(backgroundTask);
|
|
1055
|
+
}
|
|
1056
|
+
writeBundleConfig(windowsDir, config);
|
|
1057
|
+
console.log(`\nBackground task '${name}' (${taskType}) added successfully.`);
|
|
1058
|
+
}
|
|
1059
|
+
async function extensionAddAppExecutionAlias(options) {
|
|
1060
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1061
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1062
|
+
const config = readBundleConfig(windowsDir);
|
|
1063
|
+
console.log('\nAdd App Execution Alias\n');
|
|
1064
|
+
const alias = await prompt('Alias name (e.g., myapp): ');
|
|
1065
|
+
if (!alias) {
|
|
1066
|
+
console.log('Cancelled.');
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
const appAlias = { alias };
|
|
1070
|
+
if (!config.extensions) {
|
|
1071
|
+
config.extensions = {};
|
|
1072
|
+
}
|
|
1073
|
+
if (!config.extensions.appExecutionAliases) {
|
|
1074
|
+
config.extensions.appExecutionAliases = [];
|
|
1075
|
+
}
|
|
1076
|
+
const existing = config.extensions.appExecutionAliases.find((a) => a.alias === alias);
|
|
1077
|
+
if (existing) {
|
|
1078
|
+
console.log(`\nAlias '${alias}' already exists.`);
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
config.extensions.appExecutionAliases.push(appAlias);
|
|
1082
|
+
writeBundleConfig(windowsDir, config);
|
|
1083
|
+
console.log(`\nApp execution alias '${alias}' added. You can run your app from command line.`);
|
|
1084
|
+
}
|
|
1085
|
+
async function extensionAddAppService(options) {
|
|
1086
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1087
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1088
|
+
const config = readBundleConfig(windowsDir);
|
|
1089
|
+
console.log('\nAdd App Service\n');
|
|
1090
|
+
const name = await prompt('Service name (e.g., com.myapp.service): ');
|
|
1091
|
+
if (!name) {
|
|
1092
|
+
console.log('Cancelled.');
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const appService = { name };
|
|
1096
|
+
if (!config.extensions) {
|
|
1097
|
+
config.extensions = {};
|
|
1098
|
+
}
|
|
1099
|
+
if (!config.extensions.appServices) {
|
|
1100
|
+
config.extensions.appServices = [];
|
|
1101
|
+
}
|
|
1102
|
+
const existing = config.extensions.appServices.find((s) => s.name === name);
|
|
1103
|
+
if (existing) {
|
|
1104
|
+
console.log(`\nApp service '${name}' already exists.`);
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1107
|
+
config.extensions.appServices.push(appService);
|
|
1108
|
+
writeBundleConfig(windowsDir, config);
|
|
1109
|
+
console.log(`\nApp service '${name}' added. Other apps can now call into your app.`);
|
|
1110
|
+
}
|
|
1111
|
+
async function extensionEnableToastActivation(options) {
|
|
1112
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1113
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1114
|
+
const config = readBundleConfig(windowsDir);
|
|
1115
|
+
if (!config.extensions) {
|
|
1116
|
+
config.extensions = {};
|
|
1117
|
+
}
|
|
1118
|
+
config.extensions.toastActivation = { activationType: 'foreground' };
|
|
1119
|
+
writeBundleConfig(windowsDir, config);
|
|
1120
|
+
console.log('\nToast Activation enabled. Your app will handle toast notification clicks.');
|
|
1121
|
+
}
|
|
1122
|
+
async function extensionDisableToastActivation(options) {
|
|
1123
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1124
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1125
|
+
const config = readBundleConfig(windowsDir);
|
|
1126
|
+
if (config.extensions) {
|
|
1127
|
+
delete config.extensions.toastActivation;
|
|
1128
|
+
}
|
|
1129
|
+
writeBundleConfig(windowsDir, config);
|
|
1130
|
+
console.log('\nToast Activation disabled.');
|
|
1131
|
+
}
|
|
1132
|
+
async function extensionAddAutoplay(options) {
|
|
1133
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1134
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1135
|
+
const config = readBundleConfig(windowsDir);
|
|
1136
|
+
console.log('\nAdd Autoplay Handler\n');
|
|
1137
|
+
const verb = await prompt('Verb (e.g., open, play): ');
|
|
1138
|
+
if (!verb) {
|
|
1139
|
+
console.log('Cancelled.');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const actionDisplayName = await prompt('Action display name (e.g., Open with MyApp): ');
|
|
1143
|
+
if (!actionDisplayName) {
|
|
1144
|
+
console.log('Cancelled.');
|
|
1145
|
+
return;
|
|
1146
|
+
}
|
|
1147
|
+
console.log('Event type:');
|
|
1148
|
+
console.log(' 1. Content event (e.g., PlayMusicFilesOnArrival)');
|
|
1149
|
+
console.log(' 2. Device event (e.g., WPD\\ImageSource)');
|
|
1150
|
+
const eventType = await prompt('Event type (1/2): ');
|
|
1151
|
+
const handler = { verb, actionDisplayName };
|
|
1152
|
+
if (eventType === '1') {
|
|
1153
|
+
const contentEvent = await prompt('Content event (e.g., PlayMusicFilesOnArrival): ');
|
|
1154
|
+
if (!contentEvent) {
|
|
1155
|
+
console.log('Cancelled.');
|
|
1156
|
+
return;
|
|
1157
|
+
}
|
|
1158
|
+
handler.contentEvent = contentEvent;
|
|
1159
|
+
}
|
|
1160
|
+
else if (eventType === '2') {
|
|
1161
|
+
const deviceEvent = await prompt('Device event (e.g., WPD\\\\ImageSource): ');
|
|
1162
|
+
if (!deviceEvent) {
|
|
1163
|
+
console.log('Cancelled.');
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
handler.deviceEvent = deviceEvent;
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
console.log('Invalid event type. Cancelled.');
|
|
1170
|
+
return;
|
|
1171
|
+
}
|
|
1172
|
+
if (!config.extensions) {
|
|
1173
|
+
config.extensions = {};
|
|
1174
|
+
}
|
|
1175
|
+
if (!config.extensions.autoplayHandlers) {
|
|
1176
|
+
config.extensions.autoplayHandlers = [];
|
|
1177
|
+
}
|
|
1178
|
+
config.extensions.autoplayHandlers.push(handler);
|
|
1179
|
+
writeBundleConfig(windowsDir, config);
|
|
1180
|
+
console.log(`\nAutoplay handler '${verb}' added.`);
|
|
1181
|
+
}
|
|
1182
|
+
async function extensionEnablePrintTaskSettings(options) {
|
|
1183
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1184
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1185
|
+
const config = readBundleConfig(windowsDir);
|
|
1186
|
+
if (!config.extensions) {
|
|
1187
|
+
config.extensions = {};
|
|
1188
|
+
}
|
|
1189
|
+
config.extensions.printTaskSettings = { displayName: 'Print Settings' };
|
|
1190
|
+
writeBundleConfig(windowsDir, config);
|
|
1191
|
+
console.log('\nPrint Task Settings enabled.');
|
|
1192
|
+
}
|
|
1193
|
+
async function extensionDisablePrintTaskSettings(options) {
|
|
1194
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1195
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1196
|
+
const config = readBundleConfig(windowsDir);
|
|
1197
|
+
if (config.extensions) {
|
|
1198
|
+
delete config.extensions.printTaskSettings;
|
|
1199
|
+
}
|
|
1200
|
+
writeBundleConfig(windowsDir, config);
|
|
1201
|
+
console.log('\nPrint Task Settings disabled.');
|
|
1202
|
+
}
|
|
1203
|
+
async function extensionAddThumbnailHandler(options) {
|
|
1204
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1205
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1206
|
+
const config = readBundleConfig(windowsDir);
|
|
1207
|
+
console.log('\nAdd Thumbnail Handler\n');
|
|
1208
|
+
const clsid = await prompt('CLSID (e.g., {12345678-1234-1234-1234-123456789012}): ');
|
|
1209
|
+
if (!clsid) {
|
|
1210
|
+
console.log('Cancelled.');
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
const fileTypesInput = await prompt('File types (comma-separated, e.g., .myf,.myx): ');
|
|
1214
|
+
if (!fileTypesInput) {
|
|
1215
|
+
console.log('Cancelled.');
|
|
1216
|
+
return;
|
|
1217
|
+
}
|
|
1218
|
+
const fileTypes = fileTypesInput.split(',').map((t) => t.trim());
|
|
1219
|
+
const handler = { clsid, fileTypes };
|
|
1220
|
+
if (!config.extensions) {
|
|
1221
|
+
config.extensions = {};
|
|
1222
|
+
}
|
|
1223
|
+
if (!config.extensions.thumbnailHandlers) {
|
|
1224
|
+
config.extensions.thumbnailHandlers = [];
|
|
1225
|
+
}
|
|
1226
|
+
config.extensions.thumbnailHandlers.push(handler);
|
|
1227
|
+
writeBundleConfig(windowsDir, config);
|
|
1228
|
+
console.log(`\nThumbnail handler added for ${fileTypes.join(', ')}.`);
|
|
1229
|
+
}
|
|
1230
|
+
async function extensionAddPreviewHandler(options) {
|
|
1231
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1232
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1233
|
+
const config = readBundleConfig(windowsDir);
|
|
1234
|
+
console.log('\nAdd Preview Handler\n');
|
|
1235
|
+
const clsid = await prompt('CLSID (e.g., {12345678-1234-1234-1234-123456789012}): ');
|
|
1236
|
+
if (!clsid) {
|
|
1237
|
+
console.log('Cancelled.');
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
const fileTypesInput = await prompt('File types (comma-separated, e.g., .myf,.myx): ');
|
|
1241
|
+
if (!fileTypesInput) {
|
|
1242
|
+
console.log('Cancelled.');
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
const fileTypes = fileTypesInput.split(',').map((t) => t.trim());
|
|
1246
|
+
const handler = { clsid, fileTypes };
|
|
1247
|
+
if (!config.extensions) {
|
|
1248
|
+
config.extensions = {};
|
|
1249
|
+
}
|
|
1250
|
+
if (!config.extensions.previewHandlers) {
|
|
1251
|
+
config.extensions.previewHandlers = [];
|
|
1252
|
+
}
|
|
1253
|
+
config.extensions.previewHandlers.push(handler);
|
|
1254
|
+
writeBundleConfig(windowsDir, config);
|
|
1255
|
+
console.log(`\nPreview handler added for ${fileTypes.join(', ')}.`);
|
|
1256
|
+
}
|
|
1257
|
+
async function extensionRemove(type, name, options) {
|
|
1258
|
+
const projectRoot = findProjectRoot(options.path);
|
|
1259
|
+
const windowsDir = getWindowsDir(projectRoot);
|
|
1260
|
+
const config = readBundleConfig(windowsDir);
|
|
1261
|
+
if (!config.extensions) {
|
|
1262
|
+
console.log('\nNo extensions configured.');
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
switch (type) {
|
|
1266
|
+
case 'file-association': {
|
|
1267
|
+
if (!config.extensions.fileAssociations) {
|
|
1268
|
+
console.log('\nNo file associations configured.');
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const index = config.extensions.fileAssociations.findIndex((a) => a.name === name);
|
|
1272
|
+
if (index === -1) {
|
|
1273
|
+
console.log(`\nFile association '${name}' not found.`);
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
config.extensions.fileAssociations.splice(index, 1);
|
|
1277
|
+
writeBundleConfig(windowsDir, config);
|
|
1278
|
+
console.log(`\nFile association '${name}' removed.`);
|
|
1279
|
+
break;
|
|
1280
|
+
}
|
|
1281
|
+
case 'protocol': {
|
|
1282
|
+
if (!config.extensions.protocolHandlers) {
|
|
1283
|
+
console.log('\nNo protocol handlers configured.');
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const index = config.extensions.protocolHandlers.findIndex((p) => p.name === name);
|
|
1287
|
+
if (index === -1) {
|
|
1288
|
+
console.log(`\nProtocol handler '${name}' not found.`);
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
config.extensions.protocolHandlers.splice(index, 1);
|
|
1292
|
+
writeBundleConfig(windowsDir, config);
|
|
1293
|
+
console.log(`\nProtocol handler '${name}' removed.`);
|
|
1294
|
+
break;
|
|
1295
|
+
}
|
|
1296
|
+
case 'context-menu': {
|
|
1297
|
+
if (!config.extensions.contextMenus) {
|
|
1298
|
+
console.log('\nNo context menus configured.');
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
const index = config.extensions.contextMenus.findIndex((m) => m.name === name);
|
|
1302
|
+
if (index === -1) {
|
|
1303
|
+
console.log(`\nContext menu '${name}' not found.`);
|
|
1304
|
+
return;
|
|
1305
|
+
}
|
|
1306
|
+
config.extensions.contextMenus.splice(index, 1);
|
|
1307
|
+
writeBundleConfig(windowsDir, config);
|
|
1308
|
+
console.log(`\nContext menu '${name}' removed.`);
|
|
1309
|
+
break;
|
|
1310
|
+
}
|
|
1311
|
+
case 'background-task': {
|
|
1312
|
+
if (!config.extensions.backgroundTasks) {
|
|
1313
|
+
console.log('\nNo background tasks configured.');
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const index = config.extensions.backgroundTasks.findIndex((t) => t.name === name);
|
|
1317
|
+
if (index === -1) {
|
|
1318
|
+
console.log(`\nBackground task '${name}' not found.`);
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
config.extensions.backgroundTasks.splice(index, 1);
|
|
1322
|
+
writeBundleConfig(windowsDir, config);
|
|
1323
|
+
console.log(`\nBackground task '${name}' removed.`);
|
|
1324
|
+
break;
|
|
1325
|
+
}
|
|
1326
|
+
case 'app-execution-alias': {
|
|
1327
|
+
if (!config.extensions.appExecutionAliases) {
|
|
1328
|
+
console.log('\nNo app execution aliases configured.');
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
const index = config.extensions.appExecutionAliases.findIndex((a) => a.alias === name);
|
|
1332
|
+
if (index === -1) {
|
|
1333
|
+
console.log(`\nApp execution alias '${name}' not found.`);
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
config.extensions.appExecutionAliases.splice(index, 1);
|
|
1337
|
+
writeBundleConfig(windowsDir, config);
|
|
1338
|
+
console.log(`\nApp execution alias '${name}' removed.`);
|
|
1339
|
+
break;
|
|
1340
|
+
}
|
|
1341
|
+
case 'app-service': {
|
|
1342
|
+
if (!config.extensions.appServices) {
|
|
1343
|
+
console.log('\nNo app services configured.');
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
const index = config.extensions.appServices.findIndex((s) => s.name === name);
|
|
1347
|
+
if (index === -1) {
|
|
1348
|
+
console.log(`\nApp service '${name}' not found.`);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
config.extensions.appServices.splice(index, 1);
|
|
1352
|
+
writeBundleConfig(windowsDir, config);
|
|
1353
|
+
console.log(`\nApp service '${name}' removed.`);
|
|
1354
|
+
break;
|
|
1355
|
+
}
|
|
1356
|
+
case 'autoplay': {
|
|
1357
|
+
if (!config.extensions.autoplayHandlers) {
|
|
1358
|
+
console.log('\nNo autoplay handlers configured.');
|
|
1359
|
+
return;
|
|
1360
|
+
}
|
|
1361
|
+
const index = config.extensions.autoplayHandlers.findIndex((h) => h.verb === name);
|
|
1362
|
+
if (index === -1) {
|
|
1363
|
+
console.log(`\nAutoplay handler '${name}' not found.`);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
config.extensions.autoplayHandlers.splice(index, 1);
|
|
1367
|
+
writeBundleConfig(windowsDir, config);
|
|
1368
|
+
console.log(`\nAutoplay handler '${name}' removed.`);
|
|
1369
|
+
break;
|
|
1370
|
+
}
|
|
1371
|
+
case 'thumbnail-handler': {
|
|
1372
|
+
if (!config.extensions.thumbnailHandlers) {
|
|
1373
|
+
console.log('\nNo thumbnail handlers configured.');
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
const index = config.extensions.thumbnailHandlers.findIndex((h) => h.clsid === name);
|
|
1377
|
+
if (index === -1) {
|
|
1378
|
+
console.log(`\nThumbnail handler '${name}' not found.`);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
config.extensions.thumbnailHandlers.splice(index, 1);
|
|
1382
|
+
writeBundleConfig(windowsDir, config);
|
|
1383
|
+
console.log(`\nThumbnail handler removed.`);
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
case 'preview-handler': {
|
|
1387
|
+
if (!config.extensions.previewHandlers) {
|
|
1388
|
+
console.log('\nNo preview handlers configured.');
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
const index = config.extensions.previewHandlers.findIndex((h) => h.clsid === name);
|
|
1392
|
+
if (index === -1) {
|
|
1393
|
+
console.log(`\nPreview handler '${name}' not found.`);
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
config.extensions.previewHandlers.splice(index, 1);
|
|
1397
|
+
writeBundleConfig(windowsDir, config);
|
|
1398
|
+
console.log(`\nPreview handler removed.`);
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
default:
|
|
1402
|
+
console.log(`\nUnknown extension type: ${type}`);
|
|
1403
|
+
console.log('Valid types: file-association, protocol, context-menu, background-task, ' +
|
|
1404
|
+
'app-execution-alias, app-service, autoplay, thumbnail-handler, preview-handler');
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
const program = new Command();
|
|
1409
|
+
program
|
|
1410
|
+
.name('tauri-windows-bundle')
|
|
1411
|
+
.description('MSIX packaging tool for Tauri apps')
|
|
1412
|
+
.version('0.1.0');
|
|
1413
|
+
program
|
|
1414
|
+
.command('init')
|
|
1415
|
+
.description('Initialize Windows bundle configuration')
|
|
1416
|
+
.option('-p, --path <path>', 'Path to Tauri project')
|
|
1417
|
+
.action(async (options) => {
|
|
1418
|
+
try {
|
|
1419
|
+
await init(options);
|
|
1420
|
+
}
|
|
1421
|
+
catch (error) {
|
|
1422
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
1423
|
+
process.exit(1);
|
|
1424
|
+
}
|
|
1425
|
+
});
|
|
1426
|
+
program
|
|
1427
|
+
.command('build')
|
|
1428
|
+
.description('Build MSIX package')
|
|
1429
|
+
.option('--arch <architectures>', 'Architectures to build (comma-separated: x64,arm64)', 'x64')
|
|
1430
|
+
.option('--release', 'Build in release mode')
|
|
1431
|
+
.option('--min-windows <version>', 'Minimum Windows version', '10.0.17763.0')
|
|
1432
|
+
.action(async (options) => {
|
|
1433
|
+
try {
|
|
1434
|
+
await build(options);
|
|
1435
|
+
}
|
|
1436
|
+
catch (error) {
|
|
1437
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
1438
|
+
process.exit(1);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
// Extension commands
|
|
1442
|
+
const extension = program.command('extension').description('Manage Windows app extensions');
|
|
1443
|
+
extension
|
|
1444
|
+
.command('list')
|
|
1445
|
+
.description('List configured extensions')
|
|
1446
|
+
.option('-p, --path <path>', 'Path to Tauri project')
|
|
1447
|
+
.action(async (options) => {
|
|
1448
|
+
try {
|
|
1449
|
+
await extensionList(options);
|
|
1450
|
+
}
|
|
1451
|
+
catch (error) {
|
|
1452
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
1453
|
+
process.exit(1);
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
extension
|
|
1457
|
+
.command('add')
|
|
1458
|
+
.description('Add an extension')
|
|
1459
|
+
.argument('<type>', 'Extension type')
|
|
1460
|
+
.option('-p, --path <path>', 'Path to Tauri project')
|
|
1461
|
+
.action(async (type, options) => {
|
|
1462
|
+
try {
|
|
1463
|
+
switch (type) {
|
|
1464
|
+
case 'file-association':
|
|
1465
|
+
await extensionAddFileAssociation(options);
|
|
1466
|
+
break;
|
|
1467
|
+
case 'protocol':
|
|
1468
|
+
await extensionAddProtocol(options);
|
|
1469
|
+
break;
|
|
1470
|
+
case 'share-target':
|
|
1471
|
+
await extensionEnableShareTarget(options);
|
|
1472
|
+
break;
|
|
1473
|
+
case 'startup-task':
|
|
1474
|
+
await extensionEnableStartupTask(options);
|
|
1475
|
+
break;
|
|
1476
|
+
case 'context-menu':
|
|
1477
|
+
await extensionAddContextMenu(options);
|
|
1478
|
+
break;
|
|
1479
|
+
case 'background-task':
|
|
1480
|
+
await extensionAddBackgroundTask(options);
|
|
1481
|
+
break;
|
|
1482
|
+
case 'app-execution-alias':
|
|
1483
|
+
await extensionAddAppExecutionAlias(options);
|
|
1484
|
+
break;
|
|
1485
|
+
case 'app-service':
|
|
1486
|
+
await extensionAddAppService(options);
|
|
1487
|
+
break;
|
|
1488
|
+
case 'toast-activation':
|
|
1489
|
+
await extensionEnableToastActivation(options);
|
|
1490
|
+
break;
|
|
1491
|
+
case 'autoplay':
|
|
1492
|
+
await extensionAddAutoplay(options);
|
|
1493
|
+
break;
|
|
1494
|
+
case 'print-task-settings':
|
|
1495
|
+
await extensionEnablePrintTaskSettings(options);
|
|
1496
|
+
break;
|
|
1497
|
+
case 'thumbnail-handler':
|
|
1498
|
+
await extensionAddThumbnailHandler(options);
|
|
1499
|
+
break;
|
|
1500
|
+
case 'preview-handler':
|
|
1501
|
+
await extensionAddPreviewHandler(options);
|
|
1502
|
+
break;
|
|
1503
|
+
default:
|
|
1504
|
+
console.error(`Unknown extension type: ${type}`);
|
|
1505
|
+
console.log('Valid types: file-association, protocol, share-target, startup-task, context-menu, ' +
|
|
1506
|
+
'background-task, app-execution-alias, app-service, toast-activation, autoplay, ' +
|
|
1507
|
+
'print-task-settings, thumbnail-handler, preview-handler');
|
|
1508
|
+
process.exit(1);
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
catch (error) {
|
|
1512
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
});
|
|
1516
|
+
extension
|
|
1517
|
+
.command('remove')
|
|
1518
|
+
.description('Remove an extension')
|
|
1519
|
+
.argument('<type>', 'Extension type')
|
|
1520
|
+
.argument('[name]', 'Extension name/identifier (required for most types)')
|
|
1521
|
+
.option('-p, --path <path>', 'Path to Tauri project')
|
|
1522
|
+
.action(async (type, name, options) => {
|
|
1523
|
+
try {
|
|
1524
|
+
// Toggle extensions (no name required)
|
|
1525
|
+
if (type === 'share-target') {
|
|
1526
|
+
await extensionDisableShareTarget(options);
|
|
1527
|
+
}
|
|
1528
|
+
else if (type === 'startup-task') {
|
|
1529
|
+
await extensionDisableStartupTask(options);
|
|
1530
|
+
}
|
|
1531
|
+
else if (type === 'toast-activation') {
|
|
1532
|
+
await extensionDisableToastActivation(options);
|
|
1533
|
+
}
|
|
1534
|
+
else if (type === 'print-task-settings') {
|
|
1535
|
+
await extensionDisablePrintTaskSettings(options);
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
// Extensions that require a name
|
|
1539
|
+
if (!name) {
|
|
1540
|
+
console.error('Name is required for this extension type');
|
|
1541
|
+
process.exit(1);
|
|
1542
|
+
}
|
|
1543
|
+
await extensionRemove(type, name, options);
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1546
|
+
catch (error) {
|
|
1547
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
1548
|
+
process.exit(1);
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
program.parse();
|