UNPKG

16.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const bluebird_lst_1 = require("bluebird-lst");
4const builder_util_1 = require("builder-util");
5const fs_1 = require("builder-util/out/fs");
6const fs_extra_1 = require("fs-extra");
7const path = require("path");
8const windowsCodeSign_1 = require("../codeSign/windowsCodeSign");
9const core_1 = require("../core");
10const pathManager_1 = require("../util/pathManager");
11const targetUtil_1 = require("./targetUtil");
12const APPX_ASSETS_DIR_NAME = "appx";
13const vendorAssetsForDefaultAssets = {
14 "StoreLogo.png": "SampleAppx.50x50.png",
15 "Square150x150Logo.png": "SampleAppx.150x150.png",
16 "Square44x44Logo.png": "SampleAppx.44x44.png",
17 "Wide310x150Logo.png": "SampleAppx.310x150.png",
18};
19const DEFAULT_RESOURCE_LANG = "en-US";
20class AppXTarget extends core_1.Target {
21 constructor(packager, outDir) {
22 super("appx");
23 this.packager = packager;
24 this.outDir = outDir;
25 this.options = (0, builder_util_1.deepAssign)({}, this.packager.platformSpecificBuildOptions, this.packager.config.appx);
26 if (process.platform !== "darwin" && (process.platform !== "win32" || (0, windowsCodeSign_1.isOldWin6)())) {
27 throw new Error("AppX is supported only on Windows 10 or Windows Server 2012 R2 (version number 6.3+)");
28 }
29 }
30 // https://docs.microsoft.com/en-us/windows/uwp/packaging/create-app-package-with-makeappx-tool#mapping-files
31 async build(appOutDir, arch) {
32 const packager = this.packager;
33 const artifactName = packager.expandArtifactBeautyNamePattern(this.options, "appx", arch);
34 const artifactPath = path.join(this.outDir, artifactName);
35 await packager.info.callArtifactBuildStarted({
36 targetPresentableName: "AppX",
37 file: artifactPath,
38 arch,
39 });
40 const vendorPath = await (0, windowsCodeSign_1.getSignVendorPath)();
41 const vm = await packager.vm.value;
42 const stageDir = await (0, targetUtil_1.createStageDir)(this, packager, arch);
43 const mappingFile = stageDir.getTempFile("mapping.txt");
44 const makeAppXArgs = ["pack", "/o" /* overwrite the output file if it exists */, "/f", vm.toVmFile(mappingFile), "/p", vm.toVmFile(artifactPath)];
45 if (packager.compression === "store") {
46 makeAppXArgs.push("/nc");
47 }
48 const mappingList = [];
49 mappingList.push(await bluebird_lst_1.default.map((0, fs_1.walk)(appOutDir), file => {
50 let appxPath = file.substring(appOutDir.length + 1);
51 if (path.sep !== "\\") {
52 appxPath = appxPath.replace(/\//g, "\\");
53 }
54 return `"${vm.toVmFile(file)}" "app\\${appxPath}"`;
55 }));
56 const userAssetDir = await this.packager.getResource(undefined, APPX_ASSETS_DIR_NAME);
57 const assetInfo = await AppXTarget.computeUserAssets(vm, vendorPath, userAssetDir);
58 const userAssets = assetInfo.userAssets;
59 const manifestFile = stageDir.getTempFile("AppxManifest.xml");
60 await this.writeManifest(manifestFile, arch, await this.computePublisherName(), userAssets);
61 await packager.info.callAppxManifestCreated(manifestFile);
62 mappingList.push(assetInfo.mappings);
63 mappingList.push([`"${vm.toVmFile(manifestFile)}" "AppxManifest.xml"`]);
64 const signToolArch = arch === builder_util_1.Arch.arm64 ? "x64" : builder_util_1.Arch[arch];
65 if (isScaledAssetsProvided(userAssets)) {
66 const outFile = vm.toVmFile(stageDir.getTempFile("resources.pri"));
67 const makePriPath = vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makepri.exe"));
68 const assetRoot = stageDir.getTempFile("appx/assets");
69 await (0, fs_extra_1.emptyDir)(assetRoot);
70 await bluebird_lst_1.default.map(assetInfo.allAssets, it => (0, fs_1.copyOrLinkFile)(it, path.join(assetRoot, path.basename(it))));
71 await vm.exec(makePriPath, [
72 "new",
73 "/Overwrite",
74 "/Manifest",
75 vm.toVmFile(manifestFile),
76 "/ProjectRoot",
77 vm.toVmFile(path.dirname(assetRoot)),
78 "/ConfigXml",
79 vm.toVmFile(path.join((0, pathManager_1.getTemplatePath)("appx"), "priconfig.xml")),
80 "/OutputFile",
81 outFile,
82 ]);
83 // in addition to resources.pri, resources.scale-140.pri and other such files will be generated
84 for (const resourceFile of (await (0, fs_extra_1.readdir)(stageDir.dir)).filter(it => it.startsWith("resources.")).sort()) {
85 mappingList.push([`"${vm.toVmFile(stageDir.getTempFile(resourceFile))}" "${resourceFile}"`]);
86 }
87 makeAppXArgs.push("/l");
88 }
89 let mapping = "[Files]";
90 for (const list of mappingList) {
91 mapping += "\r\n" + list.join("\r\n");
92 }
93 await (0, fs_extra_1.writeFile)(mappingFile, mapping);
94 packager.debugLogger.add("appx.mapping", mapping);
95 if (this.options.makeappxArgs != null) {
96 makeAppXArgs.push(...this.options.makeappxArgs);
97 }
98 await vm.exec(vm.toVmFile(path.join(vendorPath, "windows-10", signToolArch, "makeappx.exe")), makeAppXArgs);
99 await packager.sign(artifactPath);
100 await stageDir.cleanup();
101 await packager.info.callArtifactBuildCompleted({
102 file: artifactPath,
103 packager,
104 arch,
105 safeArtifactName: packager.computeSafeArtifactName(artifactName, "appx"),
106 target: this,
107 isWriteUpdateInfo: this.options.electronUpdaterAware,
108 });
109 }
110 static async computeUserAssets(vm, vendorPath, userAssetDir) {
111 const mappings = [];
112 let userAssets;
113 const allAssets = [];
114 if (userAssetDir == null) {
115 userAssets = [];
116 }
117 else {
118 userAssets = (await (0, fs_extra_1.readdir)(userAssetDir)).filter(it => !it.startsWith(".") && !it.endsWith(".db") && it.includes("."));
119 for (const name of userAssets) {
120 mappings.push(`"${vm.toVmFile(userAssetDir)}${vm.pathSep}${name}" "assets\\${name}"`);
121 allAssets.push(path.join(userAssetDir, name));
122 }
123 }
124 for (const defaultAsset of Object.keys(vendorAssetsForDefaultAssets)) {
125 if (userAssets.length === 0 || !isDefaultAssetIncluded(userAssets, defaultAsset)) {
126 const file = path.join(vendorPath, "appxAssets", vendorAssetsForDefaultAssets[defaultAsset]);
127 mappings.push(`"${vm.toVmFile(file)}" "assets\\${defaultAsset}"`);
128 allAssets.push(file);
129 }
130 }
131 // we do not use process.arch to build path to tools, because even if you are on x64, ia32 appx tool must be used if you build appx for ia32
132 return { userAssets, mappings, allAssets };
133 }
134 // https://github.com/electron-userland/electron-builder/issues/2108#issuecomment-333200711
135 async computePublisherName() {
136 if ((await this.packager.cscInfo.value) == null) {
137 builder_util_1.log.info({ reason: "Windows Store only build" }, "AppX is not signed");
138 return this.options.publisher || "CN=ms";
139 }
140 const certInfo = await this.packager.lazyCertInfo.value;
141 const publisher = this.options.publisher || (certInfo == null ? null : certInfo.bloodyMicrosoftSubjectDn);
142 if (publisher == null) {
143 throw new Error("Internal error: cannot compute subject using certificate info");
144 }
145 return publisher;
146 }
147 async writeManifest(outFile, arch, publisher, userAssets) {
148 const appInfo = this.packager.appInfo;
149 const options = this.options;
150 const executable = `app\\${appInfo.productFilename}.exe`;
151 const displayName = options.displayName || appInfo.productName;
152 const extensions = await this.getExtensions(executable, displayName);
153 const archSpecificMinVersion = arch === builder_util_1.Arch.arm64 ? "10.0.16299.0" : "10.0.14316.0";
154 const manifest = (await (0, fs_extra_1.readFile)(path.join((0, pathManager_1.getTemplatePath)("appx"), "appxmanifest.xml"), "utf8")).replace(/\${([a-zA-Z0-9]+)}/g, (match, p1) => {
155 switch (p1) {
156 case "publisher":
157 return publisher;
158 case "publisherDisplayName": {
159 const name = options.publisherDisplayName || appInfo.companyName;
160 if (name == null) {
161 throw new builder_util_1.InvalidConfigurationError(`Please specify "author" in the application package.json — it is required because "appx.publisherDisplayName" is not set.`);
162 }
163 return name;
164 }
165 case "version":
166 return appInfo.getVersionInWeirdWindowsForm(options.setBuildNumber === true);
167 case "applicationId": {
168 const result = options.applicationId || options.identityName || appInfo.name;
169 if (!isNaN(parseInt(result[0], 10))) {
170 let message = `AppX Application.Id can’t start with numbers: "${result}"`;
171 if (options.applicationId == null) {
172 message += `\nPlease set appx.applicationId (or correct appx.identityName or name)`;
173 }
174 throw new builder_util_1.InvalidConfigurationError(message);
175 }
176 return result;
177 }
178 case "identityName":
179 return options.identityName || appInfo.name;
180 case "executable":
181 return executable;
182 case "displayName":
183 return displayName;
184 case "description":
185 return appInfo.description || appInfo.productName;
186 case "backgroundColor":
187 return options.backgroundColor || "#464646";
188 case "logo":
189 return "assets\\StoreLogo.png";
190 case "square150x150Logo":
191 return "assets\\Square150x150Logo.png";
192 case "square44x44Logo":
193 return "assets\\Square44x44Logo.png";
194 case "lockScreen":
195 return lockScreenTag(userAssets);
196 case "defaultTile":
197 return defaultTileTag(userAssets, options.showNameOnTiles || false);
198 case "splashScreen":
199 return splashScreenTag(userAssets);
200 case "arch":
201 return arch === builder_util_1.Arch.ia32 ? "x86" : arch === builder_util_1.Arch.arm64 ? "arm64" : "x64";
202 case "resourceLanguages":
203 return resourceLanguageTag((0, builder_util_1.asArray)(options.languages));
204 case "extensions":
205 return extensions;
206 case "minVersion":
207 return options.minVersion || archSpecificMinVersion;
208 case "maxVersionTested":
209 return options.maxVersionTested || options.minVersion || archSpecificMinVersion;
210 default:
211 throw new Error(`Macro ${p1} is not defined`);
212 }
213 });
214 await (0, fs_extra_1.writeFile)(outFile, manifest);
215 }
216 async getExtensions(executable, displayName) {
217 const uriSchemes = (0, builder_util_1.asArray)(this.packager.config.protocols).concat((0, builder_util_1.asArray)(this.packager.platformSpecificBuildOptions.protocols));
218 const fileAssociations = (0, builder_util_1.asArray)(this.packager.config.fileAssociations).concat((0, builder_util_1.asArray)(this.packager.platformSpecificBuildOptions.fileAssociations));
219 let isAddAutoLaunchExtension = this.options.addAutoLaunchExtension;
220 if (isAddAutoLaunchExtension === undefined) {
221 const deps = this.packager.info.metadata.dependencies;
222 isAddAutoLaunchExtension = deps != null && deps["electron-winstore-auto-launch"] != null;
223 }
224 if (!isAddAutoLaunchExtension && uriSchemes.length === 0 && fileAssociations.length === 0 && this.options.customExtensionsPath === undefined) {
225 return "";
226 }
227 let extensions = "<Extensions>";
228 if (isAddAutoLaunchExtension) {
229 extensions += `
230 <desktop:Extension Category="windows.startupTask" Executable="${executable}" EntryPoint="Windows.FullTrustApplication">
231 <desktop:StartupTask TaskId="SlackStartup" Enabled="true" DisplayName="${displayName}" />
232 </desktop:Extension>`;
233 }
234 for (const protocol of uriSchemes) {
235 for (const scheme of (0, builder_util_1.asArray)(protocol.schemes)) {
236 extensions += `
237 <uap:Extension Category="windows.protocol">
238 <uap:Protocol Name="${scheme}">
239 <uap:DisplayName>${protocol.name}</uap:DisplayName>
240 </uap:Protocol>
241 </uap:Extension>`;
242 }
243 }
244 for (const fileAssociation of fileAssociations) {
245 for (const ext of (0, builder_util_1.asArray)(fileAssociation.ext)) {
246 extensions += `
247 <uap:Extension Category="windows.fileTypeAssociation">
248 <uap:FileTypeAssociation Name="${ext}">
249 <uap:SupportedFileTypes>
250 <uap:FileType>.${ext}</uap:FileType>
251 </uap:SupportedFileTypes>
252 </uap:FileTypeAssociation>
253 </uap:Extension>`;
254 }
255 }
256 if (this.options.customExtensionsPath !== undefined) {
257 const extensionsPath = path.resolve(this.packager.info.appDir, this.options.customExtensionsPath);
258 extensions += await (0, fs_extra_1.readFile)(extensionsPath, "utf8");
259 }
260 extensions += "</Extensions>";
261 return extensions;
262 }
263}
264exports.default = AppXTarget;
265// get the resource - language tag, see https://docs.microsoft.com/en-us/windows/uwp/globalizing/manage-language-and-region#specify-the-supported-languages-in-the-apps-manifest
266function resourceLanguageTag(userLanguages) {
267 if (userLanguages == null || userLanguages.length === 0) {
268 userLanguages = [DEFAULT_RESOURCE_LANG];
269 }
270 return userLanguages.map(it => `<Resource Language="${it.replace(/_/g, "-")}" />`).join("\n");
271}
272function lockScreenTag(userAssets) {
273 if (isDefaultAssetIncluded(userAssets, "BadgeLogo.png")) {
274 return '<uap:LockScreen Notification="badgeAndTileText" BadgeLogo="assets\\BadgeLogo.png" />';
275 }
276 else {
277 return "";
278 }
279}
280function defaultTileTag(userAssets, showNameOnTiles) {
281 const defaultTiles = ["<uap:DefaultTile", 'Wide310x150Logo="assets\\Wide310x150Logo.png"'];
282 if (isDefaultAssetIncluded(userAssets, "LargeTile.png")) {
283 defaultTiles.push('Square310x310Logo="assets\\LargeTile.png"');
284 }
285 if (isDefaultAssetIncluded(userAssets, "SmallTile.png")) {
286 defaultTiles.push('Square71x71Logo="assets\\SmallTile.png"');
287 }
288 if (showNameOnTiles) {
289 defaultTiles.push(">");
290 defaultTiles.push("<uap:ShowNameOnTiles>");
291 defaultTiles.push("<uap:ShowOn", 'Tile="wide310x150Logo"', "/>");
292 defaultTiles.push("<uap:ShowOn", 'Tile="square150x150Logo"', "/>");
293 defaultTiles.push("</uap:ShowNameOnTiles>");
294 defaultTiles.push("</uap:DefaultTile>");
295 }
296 else {
297 defaultTiles.push("/>");
298 }
299 return defaultTiles.join(" ");
300}
301function splashScreenTag(userAssets) {
302 if (isDefaultAssetIncluded(userAssets, "SplashScreen.png")) {
303 return '<uap:SplashScreen Image="assets\\SplashScreen.png" />';
304 }
305 else {
306 return "";
307 }
308}
309function isDefaultAssetIncluded(userAssets, defaultAsset) {
310 const defaultAssetName = defaultAsset.substring(0, defaultAsset.indexOf("."));
311 return userAssets.some(it => it.includes(defaultAssetName));
312}
313function isScaledAssetsProvided(userAssets) {
314 return userAssets.some(it => it.includes(".scale-") || it.includes(".targetsize-"));
315}
316//# sourceMappingURL=AppxTarget.js.map
\No newline at end of file