@involvex/msix-packager-cli 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +360 -0
- package/package.json +57 -0
- package/src/certificates.js +320 -0
- package/src/cli.js +383 -0
- package/src/constants.js +140 -0
- package/src/index.js +414 -0
- package/src/manifest.js +389 -0
- package/src/package.js +909 -0
- package/src/sea-handler-new.js +301 -0
- package/src/sea-handler.js +1124 -0
- package/src/utils.js +292 -0
- package/src/validation.js +228 -0
package/src/manifest.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const fs = require("fs-extra");
|
|
3
|
+
const { CONSTANTS, ValidationError } = require("./constants");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a GUID for PhoneProductId
|
|
7
|
+
* @returns {string} A UUID v4 string
|
|
8
|
+
*/
|
|
9
|
+
function generateGuid() {
|
|
10
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
|
11
|
+
const r = (Math.random() * 16) | 0;
|
|
12
|
+
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
|
13
|
+
return v.toString(16);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Generates the AppxManifest.xml content
|
|
19
|
+
* @param {Object} config - Configuration object
|
|
20
|
+
* @param {Object} packageJson - package.json content
|
|
21
|
+
* @returns {string} XML manifest content
|
|
22
|
+
*/
|
|
23
|
+
function generateManifest(config, packageJson) {
|
|
24
|
+
// Validate required fields for manifest generation
|
|
25
|
+
validateManifestConfig(config);
|
|
26
|
+
|
|
27
|
+
const manifest = `<?xml version="1.0" encoding="utf-8"?>
|
|
28
|
+
|
|
29
|
+
<Package
|
|
30
|
+
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
|
31
|
+
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
|
|
32
|
+
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
|
33
|
+
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
|
34
|
+
xmlns:uap5="http://schemas.microsoft.com/appx/manifest/uap/windows10/5"
|
|
35
|
+
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
|
36
|
+
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
|
37
|
+
IgnorableNamespaces="uap uap3 uap5 desktop rescap">
|
|
38
|
+
|
|
39
|
+
<Identity
|
|
40
|
+
Name="${config.packageName}"
|
|
41
|
+
Publisher="${config.publisher}"
|
|
42
|
+
Version="${config.version}" />
|
|
43
|
+
|
|
44
|
+
<mp:PhoneIdentity PhoneProductId="${generateGuid()}" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
|
45
|
+
|
|
46
|
+
<Properties>
|
|
47
|
+
<DisplayName>${config.displayName}</DisplayName>
|
|
48
|
+
<PublisherDisplayName>${getPublisherDisplayName(config.publisher)}</PublisherDisplayName>
|
|
49
|
+
<Logo>Assets\\StoreLogo.png</Logo>
|
|
50
|
+
</Properties>
|
|
51
|
+
|
|
52
|
+
<Dependencies>
|
|
53
|
+
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
|
54
|
+
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
|
55
|
+
</Dependencies>
|
|
56
|
+
|
|
57
|
+
<Resources>
|
|
58
|
+
<Resource Language="en-US"/>
|
|
59
|
+
</Resources>
|
|
60
|
+
|
|
61
|
+
<Applications>
|
|
62
|
+
<Application Id="App"
|
|
63
|
+
Executable="${path.basename(config.executable)}"
|
|
64
|
+
EntryPoint="Windows.FullTrustApplication">
|
|
65
|
+
<uap:VisualElements
|
|
66
|
+
DisplayName="${config.displayName}"
|
|
67
|
+
Description="${config.description || config.displayName}"
|
|
68
|
+
BackgroundColor="${config.backgroundColor || "transparent"}"
|
|
69
|
+
Square150x150Logo="Assets\\Square150x150Logo.png"
|
|
70
|
+
Square44x44Logo="Assets\\Square44x44Logo.png">
|
|
71
|
+
<uap:DefaultTile Wide310x150Logo="Assets\\Wide310x150Logo.png" />
|
|
72
|
+
<uap:SplashScreen Image="Assets\\SplashScreen.png" />
|
|
73
|
+
</uap:VisualElements>
|
|
74
|
+
<Extensions>
|
|
75
|
+
${
|
|
76
|
+
config.mcpServer
|
|
77
|
+
? ` <uap3:Extension Category="windows.appExtension">
|
|
78
|
+
<uap3:AppExtension
|
|
79
|
+
Name="com.microsoft.windows.ai.mcpServer"
|
|
80
|
+
Id="MCPServer"
|
|
81
|
+
DisplayName="${config.displayName}"
|
|
82
|
+
PublicFolder="Assets">
|
|
83
|
+
<uap3:Properties>
|
|
84
|
+
<TrustedLaunch>true</TrustedLaunch>
|
|
85
|
+
<Registration>mcpServerConfig.json</Registration>
|
|
86
|
+
</uap3:Properties>
|
|
87
|
+
</uap3:AppExtension>
|
|
88
|
+
</uap3:Extension>`
|
|
89
|
+
: ""
|
|
90
|
+
}
|
|
91
|
+
<uap5:Extension Category="windows.appExecutionAlias">
|
|
92
|
+
<uap5:AppExecutionAlias>
|
|
93
|
+
<uap5:ExecutionAlias Alias="${config.alias || path.basename(config.executable)}"/>
|
|
94
|
+
</uap5:AppExecutionAlias>
|
|
95
|
+
</uap5:Extension>
|
|
96
|
+
${generateDesktopExtension(config)}
|
|
97
|
+
</Extensions>
|
|
98
|
+
</Application>
|
|
99
|
+
</Applications>
|
|
100
|
+
|
|
101
|
+
<Capabilities>
|
|
102
|
+
${generateCapabilities(config.capabilities)}
|
|
103
|
+
</Capabilities>
|
|
104
|
+
</Package>`;
|
|
105
|
+
|
|
106
|
+
return manifest;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validates configuration for manifest generation
|
|
111
|
+
* @param {Object} config - Configuration object
|
|
112
|
+
* @throws {ValidationError} When validation fails
|
|
113
|
+
*/
|
|
114
|
+
function validateManifestConfig(config) {
|
|
115
|
+
const requiredFields = [
|
|
116
|
+
"packageName",
|
|
117
|
+
"publisher",
|
|
118
|
+
"version",
|
|
119
|
+
"displayName",
|
|
120
|
+
"description",
|
|
121
|
+
"architecture",
|
|
122
|
+
"executable",
|
|
123
|
+
];
|
|
124
|
+
const missing = requiredFields.filter((field) => !config[field]);
|
|
125
|
+
|
|
126
|
+
if (missing.length > 0) {
|
|
127
|
+
throw new ValidationError(
|
|
128
|
+
"manifest config",
|
|
129
|
+
"all required fields",
|
|
130
|
+
`Missing fields: ${missing.join(", ")}`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Extracts publisher display name from publisher string
|
|
137
|
+
* @param {string} publisher - Publisher string (e.g., "CN=MyCompany, O=MyOrg")
|
|
138
|
+
* @returns {string} Display name
|
|
139
|
+
*/
|
|
140
|
+
function getPublisherDisplayName(publisher) {
|
|
141
|
+
// Extract CN (Common Name) from publisher string
|
|
142
|
+
const cnMatch = publisher.match(/CN=([^,]+)/);
|
|
143
|
+
return cnMatch ? cnMatch[1].trim() : publisher;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Generates capabilities XML section
|
|
148
|
+
* @param {Array} capabilities - Array of capabilities
|
|
149
|
+
* @returns {string} Capabilities XML
|
|
150
|
+
*/
|
|
151
|
+
function generateCapabilities(capabilities = ["internetClient"]) {
|
|
152
|
+
// Always ensure runFullTrust capability is included for Node.js apps
|
|
153
|
+
const allCapabilities = [...capabilities];
|
|
154
|
+
if (!allCapabilities.includes("runFullTrust")) {
|
|
155
|
+
allCapabilities.push("runFullTrust");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Always include internetClient if not specified
|
|
159
|
+
if (!allCapabilities.includes("internetClient")) {
|
|
160
|
+
allCapabilities.unshift("internetClient");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return allCapabilities
|
|
164
|
+
.map((capability) => {
|
|
165
|
+
// Handle different capability types
|
|
166
|
+
if (capability === "runFullTrust") {
|
|
167
|
+
return ' <rescap:Capability Name="runFullTrust" />';
|
|
168
|
+
} else if (capability.startsWith("rescap:")) {
|
|
169
|
+
return ` <rescap:Capability Name="${capability.replace("rescap:", "")}" />`;
|
|
170
|
+
} else if (capability.startsWith("uap:")) {
|
|
171
|
+
return ` <uap:Capability Name="${capability.replace("uap:", "")}" />`;
|
|
172
|
+
} else {
|
|
173
|
+
return ` <Capability Name="${capability}" />`;
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
.join("\n");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Generates desktop extension XML section for full trust process
|
|
181
|
+
* @param {Object} config - Configuration object
|
|
182
|
+
* @returns {string} Desktop extension XML
|
|
183
|
+
*/
|
|
184
|
+
function generateDesktopExtension(config) {
|
|
185
|
+
// Only generate desktop extension if we have arguments and executable is node.exe
|
|
186
|
+
if (config.arguments && config.executable === "node.exe") {
|
|
187
|
+
return ` <desktop:Extension Category="windows.fullTrustProcess" Executable="${config.executable}">
|
|
188
|
+
<desktop:FullTrustProcess>
|
|
189
|
+
<desktop:ParameterGroup GroupId="NodeJsGroup" Parameters="${config.arguments}" />
|
|
190
|
+
</desktop:FullTrustProcess>
|
|
191
|
+
</desktop:Extension>`;
|
|
192
|
+
}
|
|
193
|
+
return "";
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Creates default assets for the package
|
|
198
|
+
* @param {string} assetsDir - Directory to create assets in
|
|
199
|
+
* @param {string} iconPath - Path to source icon file (optional)
|
|
200
|
+
*/
|
|
201
|
+
async function createDefaultAssets(assetsDir, iconPath = null) {
|
|
202
|
+
await fs.ensureDir(assetsDir);
|
|
203
|
+
|
|
204
|
+
// Asset sizes required by MSIX
|
|
205
|
+
const requiredAssets = [
|
|
206
|
+
{ name: "Square44x44Logo.png", size: 44 },
|
|
207
|
+
{ name: "Square150x150Logo.png", size: 150 },
|
|
208
|
+
{ name: "Wide310x150Logo.png", width: 310, height: 150 },
|
|
209
|
+
{ name: "StoreLogo.png", size: 50 },
|
|
210
|
+
{ name: "SplashScreen.png", width: 620, height: 300 },
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
if (iconPath && (await fs.pathExists(iconPath))) {
|
|
214
|
+
// If source icon is provided, try to use it
|
|
215
|
+
await copyAndResizeIcon(iconPath, assetsDir, requiredAssets);
|
|
216
|
+
} else {
|
|
217
|
+
// Create placeholder assets
|
|
218
|
+
await createPlaceholderAssets(assetsDir, requiredAssets);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Copies and resizes icon for different asset sizes
|
|
224
|
+
* @param {string} iconPath - Source icon path
|
|
225
|
+
* @param {string} assetsDir - Assets directory
|
|
226
|
+
* @param {Array} requiredAssets - Required asset configurations
|
|
227
|
+
*/
|
|
228
|
+
async function copyAndResizeIcon(iconPath, assetsDir, requiredAssets) {
|
|
229
|
+
try {
|
|
230
|
+
// Check if we have ImageMagick available for resizing
|
|
231
|
+
const { executeCommand } = require("./utils");
|
|
232
|
+
|
|
233
|
+
for (const asset of requiredAssets) {
|
|
234
|
+
const outputPath = path.join(assetsDir, asset.name);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
if (asset.size) {
|
|
238
|
+
// Square asset
|
|
239
|
+
executeCommand(
|
|
240
|
+
`magick "${iconPath}" -resize ${asset.size}x${asset.size} "${outputPath}"`,
|
|
241
|
+
{ silent: true },
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
// Rectangular asset
|
|
245
|
+
executeCommand(
|
|
246
|
+
`magick "${iconPath}" -resize ${asset.width}x${asset.height} "${outputPath}"`,
|
|
247
|
+
{ silent: true },
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
console.log(`Created asset: ${asset.name}`);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
// Fallback to copying original icon
|
|
253
|
+
await fs.copy(iconPath, outputPath);
|
|
254
|
+
console.log(`Copied original icon as: ${asset.name} (resize failed)`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
console.warn("ImageMagick not available, creating placeholder assets");
|
|
259
|
+
await createPlaceholderAssets(assetsDir, requiredAssets);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Creates placeholder PNG assets
|
|
265
|
+
* @param {string} assetsDir - Assets directory
|
|
266
|
+
* @param {Array} requiredAssets - Required asset configurations
|
|
267
|
+
*/
|
|
268
|
+
async function createPlaceholderAssets(assetsDir, requiredAssets) {
|
|
269
|
+
// Create simple placeholder PNGs programmatically
|
|
270
|
+
for (const asset of requiredAssets) {
|
|
271
|
+
const outputPath = path.join(assetsDir, asset.name);
|
|
272
|
+
|
|
273
|
+
const width = asset.width || asset.size;
|
|
274
|
+
const height = asset.height || asset.size;
|
|
275
|
+
|
|
276
|
+
// Create a simple PNG placeholder
|
|
277
|
+
await createSimplePNG(outputPath, width, height);
|
|
278
|
+
console.log(
|
|
279
|
+
`Created placeholder asset: ${asset.name} (${width}x${height})`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Creates a simple PNG file programmatically
|
|
286
|
+
* @param {string} outputPath - Output file path
|
|
287
|
+
* @param {number} width - Image width
|
|
288
|
+
* @param {number} height - Image height
|
|
289
|
+
*/
|
|
290
|
+
async function createSimplePNG(outputPath, width, height) {
|
|
291
|
+
// Create a minimal PNG file
|
|
292
|
+
// This is a very basic 1x1 transparent PNG that MSIX will accept
|
|
293
|
+
const minimalPNG = Buffer.from([
|
|
294
|
+
0x89,
|
|
295
|
+
0x50,
|
|
296
|
+
0x4e,
|
|
297
|
+
0x47,
|
|
298
|
+
0x0d,
|
|
299
|
+
0x0a,
|
|
300
|
+
0x1a,
|
|
301
|
+
0x0a, // PNG signature
|
|
302
|
+
0x00,
|
|
303
|
+
0x00,
|
|
304
|
+
0x00,
|
|
305
|
+
0x0d, // IHDR chunk size
|
|
306
|
+
0x49,
|
|
307
|
+
0x48,
|
|
308
|
+
0x44,
|
|
309
|
+
0x52, // IHDR
|
|
310
|
+
0x00,
|
|
311
|
+
0x00,
|
|
312
|
+
0x00,
|
|
313
|
+
0x01, // width: 1
|
|
314
|
+
0x00,
|
|
315
|
+
0x00,
|
|
316
|
+
0x00,
|
|
317
|
+
0x01, // height: 1
|
|
318
|
+
0x08,
|
|
319
|
+
0x06,
|
|
320
|
+
0x00,
|
|
321
|
+
0x00,
|
|
322
|
+
0x00, // bit depth: 8, color type: 6 (RGBA), compression: 0, filter: 0, interlace: 0
|
|
323
|
+
0x1f,
|
|
324
|
+
0x15,
|
|
325
|
+
0xc4,
|
|
326
|
+
0x89, // IHDR CRC
|
|
327
|
+
0x00,
|
|
328
|
+
0x00,
|
|
329
|
+
0x00,
|
|
330
|
+
0x0a, // IDAT chunk size
|
|
331
|
+
0x49,
|
|
332
|
+
0x44,
|
|
333
|
+
0x41,
|
|
334
|
+
0x54, // IDAT
|
|
335
|
+
0x78,
|
|
336
|
+
0x9c,
|
|
337
|
+
0x62,
|
|
338
|
+
0x00,
|
|
339
|
+
0x00,
|
|
340
|
+
0x00,
|
|
341
|
+
0x02,
|
|
342
|
+
0x00,
|
|
343
|
+
0x01, // compressed data (transparent pixel)
|
|
344
|
+
0xe2,
|
|
345
|
+
0x21,
|
|
346
|
+
0xbc,
|
|
347
|
+
0x33, // IDAT CRC
|
|
348
|
+
0x00,
|
|
349
|
+
0x00,
|
|
350
|
+
0x00,
|
|
351
|
+
0x00, // IEND chunk size
|
|
352
|
+
0x49,
|
|
353
|
+
0x45,
|
|
354
|
+
0x4e,
|
|
355
|
+
0x44, // IEND
|
|
356
|
+
0xae,
|
|
357
|
+
0x42,
|
|
358
|
+
0x60,
|
|
359
|
+
0x82, // IEND CRC
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
await fs.writeFile(outputPath, minimalPNG);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Generates a resource configuration file
|
|
367
|
+
* @param {Object} config - Configuration object
|
|
368
|
+
* @returns {string} Resource configuration content
|
|
369
|
+
*/
|
|
370
|
+
function generateResourceConfig(config) {
|
|
371
|
+
const resourceConfig = {
|
|
372
|
+
packageName: config.packageName,
|
|
373
|
+
displayName: config.displayName,
|
|
374
|
+
description: config.description,
|
|
375
|
+
version: config.version,
|
|
376
|
+
architecture: config.architecture,
|
|
377
|
+
capabilities: config.capabilities,
|
|
378
|
+
generated: new Date().toISOString(),
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
return JSON.stringify(resourceConfig, null, 2);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = {
|
|
385
|
+
generateManifest,
|
|
386
|
+
createDefaultAssets,
|
|
387
|
+
generateResourceConfig,
|
|
388
|
+
validateManifestConfig,
|
|
389
|
+
};
|