├── .gitignore
├── .travis.yml
├── .vscode
└── launch.json
├── ElectronInstaller
├── .gitignore
├── ElectronInstaller.sln
└── ElectronInstaller
│ ├── App.config
│ ├── ElectronInstaller.csproj
│ ├── Program.cs
│ ├── Properties
│ └── AssemblyInfo.cs
│ ├── atom.ico
│ └── packages.config
├── LICENSE
├── README.md
├── appveyor.yml
├── bin
├── ElectronInstaller.exe
└── windowsstore.js
├── commitlint.config.js
├── lib
├── assets.js
├── convert.js
├── deploy.js
├── dotfile.js
├── finalsay.js
├── index.js
├── makeappx.js
├── makepri.js
├── manifest.js
├── params.js
├── setup.js
├── sign.js
├── utils.js
├── vendor
│ └── tail.js
└── zip.js
├── package-lock.json
├── package.json
├── ps1
├── convert.ps1
└── zip.ps1
├── release.config.js
├── template
├── appxmanifest.xml
└── assets
│ ├── SampleAppx.150x150.png
│ ├── SampleAppx.310x150.png
│ ├── SampleAppx.44x44.png
│ └── SampleAppx.50x50.png
└── test
├── fixtures
├── assets-scaled
│ ├── AppNameMedTile.scale-100.png
│ ├── AppNameMedTile.scale-200.png
│ └── AppNameMedTile.scale-400.png
├── assets
│ └── AppNameMedTile.png
└── child_process.js
├── lib
├── assets.js
├── bogus-private-key.pvk
├── deploy.js
├── makeappx.js
├── makepri.js
├── manifest.js
├── sign.js
├── utils.js
└── zip.js
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 |
35 | # Vim
36 | *.swp
37 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js: "10"
3 |
4 | jobs:
5 | include:
6 | - stage: test
7 | script: npm run test
8 |
9 | cache:
10 | npm: true
11 |
12 | before_install:
13 | - npm config set no-progress
14 |
15 | install:
16 | - npm install
17 |
18 | script:
19 | - commitlint-travis
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Launch",
6 | "type": "node",
7 | "request": "launch",
8 | "program": "${workspaceRoot}/bin/windowsstore.js",
9 | "stopOnEntry": false,
10 | "args": [],
11 | "cwd": "${workspaceRoot}",
12 | "preLaunchTask": null,
13 | "runtimeExecutable": null,
14 | "runtimeArgs": [
15 | "--nolazy"
16 | ],
17 | "env": {
18 | "NODE_ENV": "development"
19 | },
20 | "externalConsole": true,
21 | "sourceMaps": false,
22 | "outDir": null
23 | },
24 | {
25 | "name": "Attach",
26 | "type": "node",
27 | "request": "attach",
28 | "port": 5858,
29 | "address": "localhost",
30 | "restart": false,
31 | "sourceMaps": false,
32 | "outDir": null,
33 | "localRoot": "${workspaceRoot}",
34 | "remoteRoot": null
35 | }
36 | ]
37 | }
--------------------------------------------------------------------------------
/ElectronInstaller/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
--------------------------------------------------------------------------------
/ElectronInstaller/ElectronInstaller.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.31101.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ElectronInstaller", "ElectronInstaller\ElectronInstaller.csproj", "{07C90BD6-F73F-44C4-B3F0-2CCEDE07E046}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {07C90BD6-F73F-44C4-B3F0-2CCEDE07E046}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {07C90BD6-F73F-44C4-B3F0-2CCEDE07E046}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {07C90BD6-F73F-44C4-B3F0-2CCEDE07E046}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {07C90BD6-F73F-44C4-B3F0-2CCEDE07E046}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/ElectronInstaller/ElectronInstaller/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/ElectronInstaller/ElectronInstaller/ElectronInstaller.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {07C90BD6-F73F-44C4-B3F0-2CCEDE07E046}
8 | WinExe
9 | Properties
10 | ElectronInstaller
11 | ElectronInstaller
12 | v4.6
13 | 512
14 |
15 | publish\
16 | true
17 | Disk
18 | false
19 | Foreground
20 | 7
21 | Days
22 | false
23 | false
24 | true
25 | 0
26 | 1.0.0.%2a
27 | false
28 | false
29 | true
30 |
31 |
32 | AnyCPU
33 | true
34 | full
35 | false
36 | bin\Debug\
37 | DEBUG;TRACE
38 | prompt
39 | 4
40 |
41 |
42 | AnyCPU
43 | pdbonly
44 | true
45 | bin\Release\
46 | TRACE
47 | prompt
48 | 4
49 |
50 |
51 | atom.ico
52 |
53 |
54 | false
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | False
84 | Microsoft .NET Framework 4.5 %28x86 and x64%29
85 | true
86 |
87 |
88 | False
89 | .NET Framework 3.5 SP1 Client Profile
90 | false
91 |
92 |
93 | False
94 | .NET Framework 3.5 SP1
95 | false
96 |
97 |
98 |
99 |
106 |
--------------------------------------------------------------------------------
/ElectronInstaller/ElectronInstaller/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.IO;
7 | using System.IO.Compression;
8 | using System.Reflection;
9 |
10 | namespace ElectronInstaller
11 | {
12 | class Program
13 | {
14 | public static string AssemblyDirectory
15 | {
16 | get
17 | {
18 | string codeBase = Assembly.GetExecutingAssembly().CodeBase;
19 | UriBuilder uri = new UriBuilder(codeBase);
20 | string path = Uri.UnescapeDataString(uri.Path);
21 | return Path.GetDirectoryName(path);
22 | }
23 | }
24 |
25 | static void Main(string[] args)
26 | {
27 | Console.WriteLine("This tool functions as a basic installer for Electron Apps, to be used with the Desktop to UWP converter (also known as Project Centennial).");
28 | Console.WriteLine("");
29 |
30 | var input = Path.Combine(AssemblyDirectory, "app.zip");
31 | var output = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "e");
32 |
33 | if (!Directory.Exists(output))
34 | {
35 | Directory.CreateDirectory(output);
36 | }
37 |
38 | Logger("Input:");
39 | Logger(input);
40 | Logger("Output:");
41 | Logger(output);
42 |
43 | Unzip(input, output);
44 | }
45 |
46 | static void Logger(String lines)
47 | {
48 | var log = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "e", "log.txt");
49 | System.IO.StreamWriter file = new System.IO.StreamWriter(log, true);
50 | file.WriteLine(lines);
51 |
52 | file.Close();
53 | }
54 |
55 | static void Unzip(string zip, string destination)
56 | {
57 |
58 | if (!File.Exists(zip))
59 | {
60 | Logger("app.zip does not exist or could not be found");
61 | Environment.Exit(1);
62 | return;
63 | }
64 |
65 | Logger("Unzipping " + zip + " to " + destination);
66 | ZipFile.ExtractToDirectory(zip, destination);
67 | Logger("Unzip operation successful");
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ElectronInstaller/ElectronInstaller/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Electron Installer")]
9 | [assembly: AssemblyDescription("Installs Electron apps into Project Centennial")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("Microsoft Corporation")]
12 | [assembly: AssemblyProduct("ElectronInstaller")]
13 | [assembly: AssemblyCopyright("Copyright © Microsoft Corporation 2016")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("fece1ea4-ea62-47bd-837f-bd0f8e312459")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/ElectronInstaller/ElectronInstaller/atom.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/ElectronInstaller/ElectronInstaller/atom.ico
--------------------------------------------------------------------------------
/ElectronInstaller/ElectronInstaller/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) Felix Rieseberg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Electron Apps in the Windows Store
2 |
3 |
4 | Electron-Windows-Store: A CLI that takes the packaged output of your Electron app, then converts it into an AppX package. This allows you to submit your Electron app to the Windows Store :package:. You can also distribute your app as an `.appx` without using the Windows Store, allowing users to just double-click your `.appx` to automatically install it.
5 |
6 | 
7 |
8 | To install this command line tool, get it directly from npm:
9 |
10 | ```
11 | npm install -g electron-windows-store
12 | ```
13 |
14 | Then, configure your PowerShell:
15 |
16 | ```
17 | Set-ExecutionPolicy -ExecutionPolicy RemoteSigned
18 | ```
19 |
20 | To turn an Electron app into an AppX package, run:
21 |
22 | ```
23 | electron-windows-store --input-directory C:\myelectronapp --output-directory C:\output\myelectronapp --package-version 1.0.0.0 --package-name myelectronapp
24 | ```
25 |
26 | This tool supports two methods to create AppX packages: Either using manual file copy operations, or using Windows Containers. The first option requires only the [Windows 10 SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk), while the second option also requires the [Desktop App Converter](https://docs.microsoft.com/en-us/windows/uwp/porting/desktop-to-uwp-run-desktop-app-converter).
27 |
28 | # Usage
29 | Before running the Electron-Windows-Store CLI, let's make sure we have all the prerequisites in place. You will need:
30 |
31 | * Windows 10 with at least the Anniversary Update (if your Windows has been updated before 2018, you're good).
32 | * Windows 10 SDK from [here](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk)
33 | * Node 8 or above (to check, run `node -v`)
34 |
35 | ## Package Your Electron Application
36 | Package the application using [electron-packager](https://github.com/electron-userland/electron-packager) (or something similar). Make sure to remove node_modules that you don't need in your final application.
37 |
38 | The output should look roughly like this:
39 | ```
40 | ├── Ghost.exe
41 | ├── LICENSE
42 | ├── content_resources_200_percent.pak
43 | ├── node.dll
44 | ├── pdf.dll
45 | ├── resources
46 | │ ├── app
47 | │ └── atom.asar
48 | ├── snapshot_blob.bin
49 | ├── [... and more files]
50 | ```
51 |
52 | ## Convert with File Copying
53 | **From an elevated PowerShell (run it "as Administrator")**, run `electron-windows-store` with the required parameters, passing both the input and output directories, the app's name and version. If you don't pass these parameters, we will simply ask you for them.
54 |
55 | ```
56 | electron-windows-store --input-directory C:\myelectronapp --output-directory C:\output\myelectronapp --package-version 1.0.0.0 --package-name myelectronapp
57 | ```
58 |
59 | These are all options for the CLI:
60 |
61 | ```
62 | -h, --help output usage information
63 | -V, --version output the version number
64 | -c, --container-virtualization Create package using Windows Container virtualization
65 | -b, --windows-build Display Windows Build information
66 | -i, --input-directory Directory containing your application
67 | -o, --output-directory Output directory for the appx
68 | -p, --package-version Version of the app package
69 | -n, --package-name Name of the app package
70 | --package-display-name Display name of the package
71 | --package-description Description of the package
72 | --package-background-color Background color for the app icon (example: #464646)
73 | -e, --package-executable Path to the package executable
74 | -a, --assets Path to the visual assets for the appx
75 | -m, --manifest Path to a manifest, if you want to be overwritten
76 | -d, --deploy Should the app be deployed after creation?
77 | --identity-name Name for identity
78 | --publisher Publisher to use (example: CN=developmentca)
79 | --publisher-display-name Publisher display name to use
80 | --make-pri Use makepri.exe (you don't need to unless you know you do)
81 | --windows-kit Path to the Windows Kit bin folder
82 | --dev-cert Path to the developer certificate to use
83 | --cert-pass Password to use when signing the application (only necessary if a p12 certication is used)
84 | --desktop-converter Path to the desktop converter tools
85 | --expanded-base-image Path to the expanded base image
86 | --makeappx-params Additional parameters for Make-AppXPackage (example: --makeappx-params "/l","/d")
87 | --signtool-params Additional parameters for signtool.exe (example: --makeappx-params "/l","/d")
88 | --create-config-params Additional parameters for makepri.exe "createconfig" (example: --create-config-params "/l","/d")')
89 | --create-pri-params Additional parameters for makepri.exe "new" (example: --create-pri-params "/l","/d")')
90 | --verbose Enable debugging (similar to setting a DEBUG=electron-windows-store environment variable)
91 | ```
92 |
93 | ## Programmatic Usage
94 | You can call this package directly. All options correspond to the CLI options and are equally optional. There is one exception: You can provide a `finalSay` function, which will be executed right before `makeappx.exe` is being called. This allows you to modify the output folder right before we turn it into a package.
95 |
96 | ```js
97 | const convertToWindowsStore = require('electron-windows-store')
98 |
99 | convertToWindowsStore({
100 | containerVirtualization: false,
101 | inputDirectory: 'C:\\input\\',
102 | outputDirectory: 'C:\\output\\',
103 | packageVersion: '1.0.0.0',
104 | packageName: 'Ghost',
105 | packageDisplayName: 'Ghost Desktop',
106 | packageDescription: 'Ghost for Desktops',
107 | packageExecutable: 'app/Ghost.exe',
108 | assets: 'C:\\assets\\',
109 | manifest: 'C:\\AppXManifest.xml',
110 | deploy: false,
111 | publisher: 'CN=developmentca',
112 | windowsKit: 'C:\\windowskit',
113 | devCert: 'C:\\devcert.pfx',
114 | certPass: 'abcd',
115 | desktopConverter: 'C:\\desktop-converter-tools',
116 | expandedBaseImage: 'C:\\base-image.wim',
117 | makeappxParams: ['/l'],
118 | signtoolParams: ['/p'],
119 | makePri: true,
120 | createConfigParams: ['/a'],
121 | createPriParams: ['/b'],
122 | protocol: "ghost-app",
123 | finalSay: function () {
124 | return new Promise((resolve, reject) => resolve())
125 | }
126 | })
127 | ```
128 |
129 | ## Convert with Container Virtualization
130 | The Desktop App Converter is capable of running an installer and your app during conversion inside a Windows Container. This requires installation of the Desktop App Converter and has more advanced requirements.
131 |
132 | :warning: The _vast majority_ of Electron apps should be packaged using "File Copying". Unless you know that you need your `appx` to be created using a Windows container, use the "File Copying" method described above.
133 |
134 | :computer: Ensure that your computer is capable of running containers: You'll need a 64 bit (x64) processor, hardware-assisted virtualization and second Level Address Translation (SLAT). You will also need Windows 10 Enterprise Edition.
135 |
136 | :bulb: Before running the CLI for the first time, you will have to setup the "Windows Desktop App Converter". This will take a few minutes, but don't worry - you only have to do this once. Download and the Desktop App Converter from [here](https://docs.microsoft.com/en-us/windows/uwp/porting/desktop-to-uwp-run-desktop-app-converter). You will receive two files: `DesktopAppConverter.zip` and `BaseImage-14316.wim`.
137 |
138 | 1. Unzip `DesktopAppConverter.zip`. From an elevated PowerShell (opened with "run as Administrator"., ensure that your systems execution policy allows us to run everything we intended to run by calling `Set-ExecutionPolicy bypass`.
139 | 2. Then, run the installation of the Desktop App Converter, passing in the location of the Windows .ase Image (downloaded as `BaseImage-14316.wim`), by calling `.\DesktopAppConverter.ps1 -Setup -BaseImage .\BaseImage-14316.wim`.
140 | 3. If running the above command prompts you for a reboot, please restart your machine and run the above command again after a successful restart.
141 |
142 | Then, run `electron-windows-store` with the `--container-virtualization` flag!
143 |
144 | #### What is the CLI Doing?
145 | Once executed, the tool goes to work: It accepts your Electron app as an input. Then, it archives your application as `app.zip`. Using an installer and a Windows Container, the tool creates an "expanded" AppX package - including the Windows Application Manifest (`AppXManifest.xml`) as well as the virtual file system and the virtual registry inside your output folder.
146 |
147 | Once we have the expanded AppX files, the tool uses the Windows App Packager (`MakeAppx.exe`) to create a single-file AppX package from those files on disk. Finally, the tool can be used to create a trusted certificate on your computer to sign the new AppX pacakge. With the signed AppX package, the CLI can also automatically install the package on your machine.
148 |
149 | ## Configuration
150 | :bulb: The first time you run this tool, it needs to know some settings. It will ask you only once and store your answers in your profile folder in a `.electron-windows-store` file. You can also provide these values as a parameter when running the CLI.
151 |
152 | ```json
153 | {
154 | "publisher": "CN=developmentca",
155 | "windowsKit": "C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64",
156 | "devCert": "C:\\Tools\\DesktopConverter\\Certs\\devcert.pfx",
157 | "desktopConverter": "C:\\Tools\\DesktopConverter",
158 | "expandedBaseImage": "C:\\ProgramData\\Microsoft\\Windows\\Images\\BaseImage-14316\\"
159 | }
160 | ```
161 |
162 | ## Using all the fancy Windows APIs
163 | You can pair up your Electron app with a little invisible UWP side-kick, enabling your Electron app to call all WinRT APIs. Check out [an example over here](https://github.com/felixrieseberg/electron-uwp-background).
164 |
165 | ## Devices
166 | The compiled AppX package still contains a win32 executable - and will therefore not run on Xbox, HoloLens, or Phones.
167 |
168 | ## Development
169 | `electron-windows-store` uses [Semantic Release](https://github.com/semantic-release/semantic-release) to
170 | automate the whole release process. In order to have a PR merged, please ensure that your PR
171 | follows the commit guidelines so that our robots can understand your change. This repository uses
172 | the [default `conventional-changelog` rules](https://www.conventionalcommits.org/en/v1.0.0-beta.2/).
173 |
174 | ## License
175 | Licensed using the MIT License (MIT); Copyright (c) Felix Rieseberg and Microsoft Corporation. For more information, please see [LICENSE](LICENSE).
176 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | init:
2 | - git config --global core.autocrlf input
3 |
4 | # Test against these versions of Node.js.
5 | environment:
6 | matrix:
7 | - nodejs_version: "8"
8 |
9 | deploy:
10 | on:
11 | branch: master
12 | provider: script
13 |
14 | # Install scripts. (runs after repo cloning)
15 | install:
16 | - ps: Install-Product node $env:nodejs_version
17 | - npm install
18 |
19 | skip_tags: true
20 | skip_branch_with_pr: true
21 | max_jobs: 1
22 |
23 | # Post-install test scripts.
24 | test_script:
25 | # Output useful info for debugging.
26 | - node --version
27 | - npm --version
28 | # run tests
29 | - npm test
30 |
31 | deploy_script:
32 | - npm run semantic-release
33 |
34 | # Don't actually build.
35 | build: off
36 |
37 | # Set build version format here instead of in the admin panel.
38 | version: "{build}"
39 |
40 | cache:
41 | - node_modules # local npm modules
42 | - '%APPDATA%\npm-cache' # npm cache
43 |
--------------------------------------------------------------------------------
/bin/ElectronInstaller.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/bin/ElectronInstaller.exe
--------------------------------------------------------------------------------
/bin/windowsstore.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var program = require('commander')
8 | var os = require('os')
9 | var chalk = require('chalk')
10 | var pack = require('../package.json')
11 |
12 | // Ensure Node 4
13 | if (parseInt(process.versions.node[0], 10) < 4) {
14 | console.log('You need at least Node 4.x to run this script')
15 | }
16 |
17 | // Little helper function turning string input into an array
18 | function list (val) {
19 | return val.split(',')
20 | }
21 |
22 | program
23 | .version(pack.version)
24 | .option('-c, --container-virtualization', 'Create package using Windows Container virtualization')
25 | .option('-b, --windows-build', 'Display Windows Build information')
26 | .option('-i, --input-directory ', 'Directory containing your application')
27 | .option('-o, --output-directory ', 'Output directory for the appx')
28 | .option('-p, --package-version ', 'Version of the app package')
29 | .option('-n, --package-name ', 'Name of the app package')
30 | .option('--identity-name ', 'Name for identity')
31 | .option('--package-display-name ', 'Display name of the package')
32 | .option('--package-description ', 'Description of the package')
33 | .option('--package-background-color ', 'Background color for the app icon (example: #464646)')
34 | .option('-e, --package-executable ', 'Path to the package executable')
35 | .option('-a, --assets ', 'Path to the visual assets for the appx')
36 | .option('-m, --manifest ', 'Path to a manifest, if you want to overwrite the default one')
37 | .option('-d, --deploy ', 'Should the app be deployed after creation?')
38 | .option('--publisher ', 'Publisher to use (example: CN=developmentca)')
39 | .option('--publisher-display-name ', 'Publisher display name to use')
40 | .option('--windows-kit ', 'Path to the Windows Kit bin folder')
41 | .option('--dev-cert ', 'Path to the developer certificate to use')
42 | .option('--cert-pass ', 'Certification password')
43 | .option('--desktop-converter ', 'Path to the desktop converter tools')
44 | .option('--expanded-base-image ', 'Path to the expanded base image')
45 | .option('--make-pri ', 'Use makepri.exe (you don\'t need to unless you know you do)', (i) => (i === 'true'))
46 | .option('--makeappx-params ', 'Additional parameters for Make-AppXPackage (example: --makeappx-params "/l","/d")', list)
47 | .option('--signtool-params ', 'Additional parameters for signtool.exe (example: --makeappx-params "/l","/d")', list)
48 | .option('--create-config-params ', 'Additional parameters for makepri.exe "createconfig" (example: --create-config-params "/l","/d")', list)
49 | .option('--create-pri-params ', 'Additional parameters for makepri.exe "new" (example: --create-pri-params "/l","/d")', list)
50 | .option('--protocol ', 'Protocol scheme to start the application with.')
51 | .option('--verbose ', 'Enable debug mode')
52 | .parse(process.argv)
53 |
54 | if (program.windowsBuild) {
55 | console.log(os.release())
56 | }
57 |
58 | if (program.verbose) {
59 | var debug = process.env.DEBUG || ''
60 | process.env.DEBUG = 'electron-windows-store,' + debug
61 | }
62 |
63 | var ensureParams = require('../lib/params')
64 | var zip = require('../lib/zip')
65 | var setup = require('../lib/setup')
66 | var sign = require('../lib/sign')
67 | var assets = require('../lib/assets')
68 | var convert = require('../lib/convert')
69 | var makeappx = require('../lib/makeappx')
70 | var manifest = require('../lib/manifest')
71 | var deploy = require('../lib/deploy')
72 | var makepri = require('../lib/makepri')
73 |
74 | setup(program)
75 | .then(() => ensureParams(program))
76 | .then(() => zip(program))
77 | .then(() => convert(program))
78 | .then(() => assets(program))
79 | .then(() => manifest(program))
80 | .then(() => makepri(program))
81 | .then(() => makeappx(program))
82 | .then(() => sign.signAppx(program))
83 | .then(() => deploy(program))
84 | .then(() => console.log(chalk.bold.green('All done!')))
85 | .catch(e => {
86 | console.log(e)
87 | console.log(e.stack)
88 | })
89 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional']
3 | }
4 |
--------------------------------------------------------------------------------
/lib/assets.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const fs = require('fs-extra')
5 | const chalk = require('chalk')
6 | const utils = require('./utils')
7 |
8 | module.exports = function (program) {
9 | if (!program.assets) {
10 | return Promise.resolve()
11 | }
12 |
13 | // Let's copy in the assets
14 | utils.log(chalk.bold.green('Copying visual assets into pre-appx folder...'))
15 |
16 | const source = path.normalize(program.assets)
17 | const destination = path.join(program.outputDirectory, 'pre-appx', 'Assets')
18 |
19 | utils.debug(`Copying visual assets from ${source} to ${destination}`)
20 |
21 | return fs.copy(source, destination)
22 | .catch(error => {
23 | utils.debug(`Copying visual assets failed: ${JSON.stringify(error)}`)
24 | throw error
25 | }).then(() => utils.debug('Copying visual assets succeeded'))
26 | }
27 |
--------------------------------------------------------------------------------
/lib/convert.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const spawn = require('child_process').spawn
5 | const chalk = require('chalk')
6 | const fs = require('fs-extra')
7 |
8 | const Tail = require('./vendor/tail').Tail
9 | const utils = require('./utils')
10 |
11 | /**
12 | * Converts the given Electron app using Project Centennial
13 | * Container Virtualization.
14 | *
15 | * @param program - Program object containing the user's instructions
16 | * @returns - Promise
17 | */
18 | function convertWithContainer (program) {
19 | return new Promise((resolve, reject) => {
20 | if (!program.desktopConverter) {
21 | utils.log('Could not find the Project Centennial Desktop App Converter, which is required to')
22 | utils.log('run the conversion to appx using a Windows Container.\n')
23 | utils.log('Consult the documentation at https://aka.ms/electron-windows-store for a tutorial.\n')
24 | utils.log('You can find the Desktop App Converter at https://www.microsoft.com/en-us/download/details.aspx?id=51691\n')
25 | utils.log('Exiting now - restart when you downloaded and unpacked the Desktop App Converter!')
26 |
27 | process.exit(0)
28 | }
29 |
30 | let preAppx = path.join(program.outputDirectory, 'pre-appx')
31 | let installer = path.join(program.outputDirectory, 'ElectronInstaller.exe')
32 | let logfile = path.join(program.outputDirectory, 'logs', 'conversion.log')
33 | let converterArgs = [
34 | `-LogFile ${logfile}`,
35 | `-Installer '${installer}'`,
36 | `-Converter '${path.join(program.desktopConverter, 'DesktopAppConverter.ps1')}'`,
37 | `-ExpandedBaseImage ${program.expandedBaseImage}`,
38 | `-Destination '${preAppx}'`,
39 | `-PackageName "${program.packageName}"`,
40 | `-Version ${program.packageVersion}`,
41 | `-Publisher "${program.publisher}"`,
42 | `-AppExecutable '${program.packageExecutable}'`
43 | ]
44 | let args = `& {& '${path.resolve(__dirname, '..', 'ps1', 'convert.ps1')}' ${converterArgs.join(' ')}}`
45 | let child, tail
46 |
47 | utils.log(chalk.green.bold('Starting Conversion...'))
48 | utils.debug(`Conversion parameters used: ${JSON.stringify(converterArgs)}`)
49 |
50 | try {
51 | child = spawn('powershell.exe', ['-NoProfile', '-NoLogo', args])
52 | } catch (error) {
53 | reject(error)
54 | }
55 |
56 | child.stdout.on('data', (data) => utils.debug(data.toString()))
57 | child.stderr.on('data', (data) => utils.debug(data.toString()))
58 | child.on('exit', () => {
59 | // The conversion process exited, let's look for a log file
60 | // However, give the PS process a 3s headstart, since we'll
61 | // crash if the logfile does not exist yet
62 | setTimeout(() => {
63 | tail = new Tail(logfile, {
64 | fromBeginning: true
65 | })
66 |
67 | tail.on('line', (data) => {
68 | utils.log(data)
69 |
70 | if (data.indexOf('Conversion complete') > -1) {
71 | utils.log('')
72 | tail.unwatch()
73 | resolve()
74 | } else if (data.indexOf('An error occurred') > -1) {
75 | tail.unwatch()
76 | reject(new Error('Detected error in conversion log'))
77 | }
78 | })
79 |
80 | tail.on('error', (err) => utils.log(err))
81 | }, 3000)
82 | })
83 |
84 | child.stdin.end()
85 | })
86 | }
87 |
88 | /**
89 | * Converts the given Electron app using simple file copy
90 | * mechanisms.
91 | *
92 | * @param program - Program object containing the user's instructions
93 | * @returns - Promise
94 | */
95 | function convertWithFileCopy (program) {
96 | return new Promise((resolve, reject) => {
97 | let preAppx = path.join(program.outputDirectory, 'pre-appx')
98 | let app = path.join(preAppx, 'app')
99 | let manifest = path.join(preAppx, 'AppXManifest.xml')
100 | let manifestTemplate = path.join(__dirname, '..', 'template', 'AppXManifest.xml')
101 | let assets = path.join(preAppx, 'assets')
102 | let assetsTemplate = path.join(__dirname, '..', 'template', 'assets')
103 |
104 | utils.log(chalk.green.bold('Starting Conversion...'))
105 | utils.debug(`Using pre-appx folder: ${preAppx}`)
106 | utils.debug(`Using app from: ${app}`)
107 | utils.debug(`Using manifest template from: ${manifestTemplate}`)
108 | utils.debug(`Using asset template from ${assetsTemplate}`)
109 |
110 | // Clean output folder
111 | utils.log(chalk.green.bold('Cleaning pre-appx output folder...'))
112 | fs.emptyDirSync(preAppx)
113 |
114 | // Copy in the new manifest, app, assets
115 | utils.log(chalk.green.bold('Copying data...'))
116 | fs.copySync(manifestTemplate, manifest)
117 | utils.debug('Copied manifest template to destination')
118 | fs.copySync(assetsTemplate, assets)
119 | utils.debug('Copied asset template to destination')
120 | fs.copySync(program.inputDirectory, app)
121 | utils.debug('Copied input app files to destination')
122 |
123 | // Then, overwrite the manifest
124 | fs.readFile(manifest, 'utf8', (err, data) => {
125 | utils.log(chalk.green.bold('Creating manifest..'))
126 | let result = data
127 | let executable = program.packageExecutable || `app\\${program.packageName}.exe`
128 |
129 | if (err) {
130 | utils.debug(`Could not read manifest template. Error: ${JSON.stringify(err)}`)
131 | return utils.log(err)
132 | }
133 |
134 | result = result.replace(/\${publisherName}/g, program.publisher)
135 | result = result.replace(/\${publisherDisplayName}/g, program.publisherDisplayName || 'Reserved')
136 | result = result.replace(/\${identityName}/g, program.identityName || program.packageName)
137 | result = result.replace(/\${packageVersion}/g, program.packageVersion)
138 | result = result.replace(/\${packageName}/g, program.packageName)
139 | result = result.replace(/\${packageExecutable}/g, executable)
140 | result = result.replace(/\${packageDisplayName}/g, program.packageDisplayName || program.packageName)
141 | result = result.replace(/\${packageDescription}/g, program.packageDescription || program.packageName)
142 | result = result.replace(/\${packageBackgroundColor}/g, program.packageBackgroundColor || '#464646')
143 | result = result.replace(/\${protocol}/g, program.protocol ? `
144 |
145 |
146 | ` : '')
147 |
148 | fs.writeFile(manifest, result, 'utf8', (err) => {
149 | if (err) {
150 | const errorMessage = `Could not write manifest file in pre-appx location. Error: ${JSON.stringify(err)}`
151 | utils.debug(errorMessage)
152 | return reject(new Error(errorMessage))
153 | }
154 |
155 | resolve()
156 | })
157 | })
158 | })
159 | }
160 |
161 | module.exports = function (program) {
162 | if (program.containerVirtualization) {
163 | return convertWithContainer(program)
164 | } else {
165 | return convertWithFileCopy(program)
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/lib/deploy.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const chalk = require('chalk')
4 |
5 | const utils = require('./utils')
6 |
7 | module.exports = function (program) {
8 | return new Promise((resolve, reject) => {
9 | if (!program.deploy) {
10 | return resolve()
11 | }
12 |
13 | utils.log(chalk.bold.green('Deploying package to system...'))
14 |
15 | let args = `& {& Add-AppxPackage '${program.outputDirectory}/${program.packageName}.appx'}`
16 |
17 | return utils.executeChildProcess('powershell.exe', ['-NoProfile', '-NoLogo', args])
18 | .then(() => resolve())
19 | .catch((err) => reject(err))
20 | })
21 | }
22 |
--------------------------------------------------------------------------------
/lib/dotfile.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const merge = require('lodash.merge')
5 | const fs = require('fs')
6 | const e = process.env
7 |
8 | let root = e.USERPROFILE || e.APPDATA || e.TMP || e.TEMP || e.HOME || e.PWD || '/tmp'
9 |
10 | function Config () {
11 | if (!(this instanceof Config)) {
12 | return new Config('.electron-windows-store')
13 | }
14 |
15 | this.path = path.join(root, '.electron-windows-store')
16 | }
17 |
18 | Config.prototype.get = function () {
19 | try {
20 | return JSON.parse(fs.readFileSync(this.path, 'utf8'))
21 | } catch (err) {
22 | return {}
23 | }
24 | }
25 |
26 | Config.prototype.set = function (config) {
27 | if (e.USER === 'root') {
28 | return
29 | }
30 | var properties = this.get()
31 | properties = merge(properties, config)
32 | try {
33 | fs.writeFileSync(this.path, JSON.stringify(properties, null, 2) + '\n')
34 | } catch (err) {
35 | }
36 | }
37 |
38 | Config.prototype.append = function (property, value) {
39 | if (e.USER === 'root') {
40 | return
41 | }
42 |
43 | var data = this.get()
44 | data[property] = value
45 |
46 | try {
47 | fs.writeFileSync(this.path, JSON.stringify(data, null, 2) + '\n')
48 | } catch (err) {
49 | }
50 | }
51 |
52 | module.exports = Config
53 |
--------------------------------------------------------------------------------
/lib/finalsay.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | module.exports = function (program) {
4 | if (program.finalSay) {
5 | return Promise.resolve(program.finalSay())
6 | } else {
7 | return Promise.resolve()
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const zip = require('./zip')
4 | const setup = require('./setup')
5 | const sign = require('./sign')
6 | const assets = require('./assets')
7 | const convert = require('./convert')
8 | const finalSay = require('./finalsay')
9 | const makeappx = require('./makeappx')
10 | const manifest = require('./manifest')
11 | const deploy = require('./deploy')
12 | const makepri = require('./makepri')
13 |
14 | /**
15 | * Transforms a given input directory into a Windows Store package.
16 | *
17 | * @param {WindowsStoreOptions} program
18 | *
19 | * @typedef WindowsStoreOptions
20 | * @type {Object}
21 | * @property {boolean} containerVirtualization - Create package using Windows Container virtualization
22 | * @property {string} inputDirectory - Directory containing your application
23 | * @property {string} outputDirectory - Output directory for the appx
24 | * @property {string} packageVersion - Version of the app package
25 | * @property {string} packageName - Name of the app package
26 | * @property {string} packageDisplayName - Dispay name of the package
27 | * @property {string} packageDescription - Description of the package
28 | * @property {string} packageBackgroundColor - Background color for the app icon (example: #464646)
29 | * @property {string} packageExecutable - Path to the package executable
30 | * @property {string} assets - Path to the visual assets for the appx
31 | * @property {string} manifest - Path to a manifest, if you want to overwrite the default one
32 | * @property {boolean} deploy - Should the app be deployed after creation?
33 | * @property {string} publisher - Publisher to use (example: CN=developmentca)
34 | * @property {string} windowsKit - Path to the Windows Kit bin folder
35 | * @property {string} devCert - Path to the developer certificate to use
36 | * @property {string} desktopConverter - Path to the desktop converter tools
37 | * @property {string} expandedBaseImage - Path to the expanded base image
38 | * @property {[string]} makeappxParams - Additional parameters for Make-AppXPackage
39 | * @property {[string]} signtoolParams - Additional parameters for signtool.exe
40 | * @property {[string]} createConfigParams - Additional parameters for makepri.exe createconfig
41 | * @property {[string]} createPriParams - Additional parameters for makepri.exe new
42 | * @property {function} finalSay - A function that is called before makeappx.exe executes. Accepts a promise.
43 | * @property {string} protocol - Protocol scheme to start the application with.
44 | *
45 | * @returns {Promise} - A promise that completes once the appx has been created
46 | */
47 | module.exports = function windowsStore (program) {
48 | program.isModuleUse = true
49 |
50 | return setup(program)
51 | .then(() => zip(program))
52 | .then(() => convert(program))
53 | .then(() => assets(program))
54 | .then(() => manifest(program))
55 | .then(() => makepri(program))
56 | .then(() => finalSay(program))
57 | .then(() => makeappx(program))
58 | .then(() => sign.signAppx(program))
59 | .then(() => deploy(program))
60 | }
61 |
--------------------------------------------------------------------------------
/lib/makeappx.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const utils = require('./utils')
5 | const chalk = require('chalk')
6 |
7 | module.exports = function (program) {
8 | return new Promise((resolve, reject) => {
9 | if (!program.windowsKit) {
10 | return reject(new Error('Path to Windows Kit not specified'))
11 | }
12 |
13 | utils.log(chalk.bold.green('Creating appx package...'))
14 |
15 | let makeappx = path.join(program.windowsKit, 'makeappx.exe')
16 | let source = path.join(program.outputDirectory, 'pre-appx')
17 | let destination = path.join(program.outputDirectory, `${program.packageName}.appx`)
18 | let params = ['pack', '/d', source, '/p', destination, '/o'].concat(program.makeappxParams || [])
19 |
20 | utils.debug(`Using makeappx.exe in: ${makeappx}`)
21 | utils.debug(`Using pre-appx folder in: ${source}`)
22 | utils.debug(`Using following destination: ${destination}`)
23 | utils.debug(`Using parameters: ${JSON.stringify(params)}`)
24 |
25 | if (program.assets) {
26 | let assetPath = path.normalize(program.assets)
27 |
28 | if (utils.hasVariableResources(assetPath)) {
29 | utils.debug(`Determined that package has variable resources, calling makeappx.exe with /l`)
30 | params.push('/l')
31 | }
32 | }
33 |
34 | return utils.executeChildProcess(makeappx, params)
35 | .then(() => resolve())
36 | .catch((err) => reject(err))
37 | })
38 | }
39 |
--------------------------------------------------------------------------------
/lib/makepri.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const utils = require('./utils')
5 | const chalk = require('chalk')
6 |
7 | function createConfig (program) {
8 | return new Promise((resolve, reject) => {
9 | if (!program.windowsKit) {
10 | return reject(new Error('Path to Windows Kit not specified'))
11 | }
12 |
13 | if (!program.makePri) {
14 | return resolve()
15 | }
16 |
17 | utils.log(chalk.bold.green('Creating priconfig...'))
18 |
19 | const makepri = path.join(program.windowsKit, 'makepri.exe')
20 | const source = path.join('pre-appx', 'priconfig.xml')
21 | const params = ['createconfig', '/cf', source, '/dq', 'en-US'].concat(program.createConfigParams || [])
22 | const options = { cwd: program.outputDirectory }
23 |
24 | utils.debug(`Using makepri.exe in: ${makepri}`)
25 | utils.debug(`Using pre-appx folder in: ${source}`)
26 | utils.debug(`Using parameters: ${JSON.stringify(params)}`)
27 |
28 | return utils.executeChildProcess(makepri, params, options)
29 | .then(() => resolve())
30 | .catch((err) => reject(err))
31 | })
32 | }
33 |
34 | function createPri (program) {
35 | return new Promise((resolve, reject) => {
36 | if (!program.windowsKit) {
37 | return reject(new Error('Path to Windows Kit not specified'))
38 | }
39 |
40 | if (!program.makePri) {
41 | return resolve()
42 | }
43 |
44 | utils.log(chalk.bold.green('Creating pri file...'))
45 |
46 | const makepri = path.join(program.windowsKit, 'makepri.exe')
47 | const source = path.join('pre-appx', 'priconfig.xml')
48 | const projectFolder = 'pre-appx'
49 | const outFile = path.join('pre-appx', 'resources.pri')
50 | const params = ['new', '/pr', projectFolder, '/cf', source, '/of', outFile].concat(program.createPriParams || [])
51 | const options = { cwd: program.outputDirectory }
52 |
53 | utils.debug(`Using makepri.exe in: ${makepri}`)
54 | utils.debug(`Using pre-appx folder in: ${source}`)
55 | utils.debug(`Using parameters: ${JSON.stringify(params)}`)
56 |
57 | return utils.executeChildProcess(makepri, params, options)
58 | .then(() => resolve())
59 | .catch((err) => reject(err))
60 | })
61 | }
62 |
63 | module.exports = function (program) {
64 | return createConfig(program).then(() => createPri(program))
65 | }
66 |
--------------------------------------------------------------------------------
/lib/manifest.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const fs = require('fs-extra')
5 | const chalk = require('chalk')
6 |
7 | const utils = require('./utils')
8 |
9 | module.exports = function (program) {
10 | if (!program.manifest) {
11 | return Promise.resolve()
12 | }
13 |
14 | // Let's copy in the new manifest
15 | utils.log(chalk.bold.green('Overwriting manifest...'))
16 |
17 | const source = path.normalize(program.manifest)
18 | const destination = path.join(program.outputDirectory, 'pre-appx', 'AppXManifest.xml')
19 |
20 | utils.debug(`Copying manifest from ${source} to ${destination}`)
21 |
22 | return fs.copy(source, destination)
23 | .catch(error => {
24 | utils.debug(`Could not overwrite manifest. Error: ${JSON.stringify(error)}`)
25 |
26 | throw error
27 | })
28 | }
29 |
--------------------------------------------------------------------------------
/lib/params.js:
--------------------------------------------------------------------------------
1 | // Ensures correct parameters
2 | 'use strict'
3 |
4 | const inquirer = require('inquirer')
5 | const fs = require('fs')
6 | const path = require('path')
7 |
8 | const utils = require('./utils')
9 | const cwd = process.cwd()
10 |
11 | module.exports = function (program) {
12 | return new Promise((resolve, reject) => {
13 | const questions = [
14 | {
15 | name: 'inputDirectory',
16 | type: 'input',
17 | message: 'Please enter the path to your built Electron app: ',
18 | validate: (input) => {
19 | if (!utils.isDirectory(input)) {
20 | // Not found, let's try the subdir
21 | return (utils.isDirectory(path.join(cwd, input)))
22 | }
23 |
24 | return true
25 | },
26 | filter: (input) => {
27 | if (!utils.isDirectory(input)) {
28 | return path.join(cwd, input)
29 | } else {
30 | return input
31 | }
32 | },
33 | when: () => (!program.inputDirectory)
34 | },
35 | {
36 | name: 'outputDirectory',
37 | type: 'input',
38 | message: 'Please enter the path to your output directory: ',
39 | default: path.join(cwd, 'windows-store'),
40 | validate: (input) => {
41 | utils.log(input)
42 | if (!utils.isDirectory(input) && !utils.isDirectory(path.join(cwd, input))) {
43 | try {
44 | fs.mkdirSync(input)
45 | return true
46 | } catch (err) {
47 | utils.log(err)
48 | return false
49 | }
50 | }
51 |
52 | return true
53 | },
54 | when: () => (!program.outputDirectory)
55 | },
56 | {
57 | name: 'packageName',
58 | type: 'input',
59 | message: "Please enter your app's package name (name of your exe - without '.exe'): ",
60 | when: () => (!program.packageName)
61 | },
62 | {
63 | name: 'packageVersion',
64 | type: 'input',
65 | default: '1.0.0.0',
66 | message: "Please enter your app's package version: ",
67 | when: () => (!program.packageVersion)
68 | }
69 | ]
70 |
71 | // First, ensure that we're even running Windows
72 | // (and the right version of it)
73 | utils.ensureWindows()
74 |
75 | // Then, let's ensure our parameters
76 | inquirer.prompt(questions)
77 | .then((answers) => {
78 | if (!program.packageExecutable && program.containerVirtualization) {
79 | program.packageExecutable = `C:\\Users\\ContainerAdministrator\\AppData\\Roaming\\e\\${program.packageName}.exe`
80 | }
81 |
82 | Object.assign(program, answers)
83 | })
84 | .then(() => {
85 | // Verify optional parameters
86 | if (program.devCert.match(/.p12$/i)) {
87 | if (!program.certPass) {
88 | utils.debug(`Error: Using a P12 certification file but the password is missing`)
89 | return reject(new Error('No certificate password specified!'))
90 | }
91 | }
92 | resolve()
93 | })
94 | })
95 | }
96 |
--------------------------------------------------------------------------------
/lib/setup.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | /**
4 | * For setup, we need a number of params:
5 | * - DesktopConverter "C:\Tools\DesktopConverter"
6 | * - ExpandedBaseImage "C:\ProgramData\Microsoft\Windows\Images\BaseImage-14316\"
7 | * - Publisher "CN=testca"
8 | * - DevCert "C:\Tools\DesktopConverter\Certs\devcert.pfx"
9 | */
10 |
11 | const path = require('path')
12 | const inquirer = require('inquirer')
13 | const pathExists = require('path-exists')
14 | const defaults = require('lodash.defaults')
15 | const multiline = require('multiline')
16 | const chalk = require('chalk')
17 |
18 | const utils = require('./utils')
19 | const sign = require('./sign')
20 | const dotfile = require('./dotfile')()
21 |
22 | /**
23 | * Determines whether all setup settings are okay.
24 | *
25 | * @returns {boolean} - Whether everything is setup correctly.
26 | */
27 | function isSetupRequired (program) {
28 | const config = dotfile.get() || {}
29 | const hasPublisher = (config.publisher || program.publisher)
30 | const hasDevCert = (config.devCert || program.devCert)
31 | const hasWindowsKit = (config.windowsKit || program.windowsKit)
32 | const hasBaseImage = (config.expandedBaseImage || program.expandedBaseImage)
33 | const hasConverterTools = (config.desktopConverter || program.desktopConverter)
34 |
35 | if (!program.containerVirtualization) {
36 | return (hasPublisher && hasDevCert && hasWindowsKit)
37 | } else {
38 | return (hasPublisher && hasDevCert && hasWindowsKit && hasBaseImage && hasConverterTools)
39 | }
40 | }
41 |
42 | /**
43 | * Asks the user if dependencies are installed. If she/he declines, we exit the process.
44 | *
45 | * @param program - Commander program object
46 | * @returns {Promise} - Promsise that returns once user responded
47 | */
48 | function askForDependencies (program) {
49 | if (program.isModuleUse) {
50 | return Promise.resolve(program)
51 | }
52 |
53 | const questions = [
54 | {
55 | name: 'didInstallDesktopAppConverter',
56 | type: 'confirm',
57 | message: 'Did you download and install the Desktop App Converter? It is *not* required to run this tool. '
58 | },
59 | {
60 | name: 'makeCertificate',
61 | type: 'confirm',
62 | message: 'You need to install a development certificate in order to run your app. Would you like us to create one? '
63 | }
64 | ]
65 |
66 | return inquirer.prompt(questions)
67 | .then((answers) => {
68 | program.didInstallDesktopAppConverter = answers.didInstallDesktopAppConverter
69 | program.makeCertificate = answers.makeCertificate
70 | })
71 | }
72 |
73 | /**
74 | * Runs a wizard, helping the user setup configuration
75 | *
76 | * @param program - Commander program object
77 | * @returns {Promise} - Promsise that returns once wizard completed
78 | */
79 | function wizardSetup (program) {
80 | const welcome = multiline.stripIndent(function () { /*
81 | Welcome to the Electron-Windows-Store tool!
82 |
83 | This tool will assist you with turning your Electron app into
84 | a swanky Windows Store app.
85 |
86 | We need to know some settings. We will ask you only once and store
87 | your answers in your profile folder in a .electron-windows-store
88 | file.
89 |
90 | */
91 | })
92 | const complete = multiline.stripIndent(function () { /*
93 |
94 | Setup complete, moving on to package your app!
95 |
96 | */
97 | })
98 |
99 | let questions = [
100 | {
101 | name: 'desktopConverter',
102 | type: 'input',
103 | message: 'Please enter the path to your Desktop App Converter (DesktopAppConverter.ps1): ',
104 | validate: (input) => pathExists.sync(input),
105 | when: () => (!program.desktopConverter)
106 | },
107 | {
108 | name: 'expandedBaseImage',
109 | type: 'input',
110 | message: 'Please enter the path to your Expanded Base Image: ',
111 | default: 'C:\\ProgramData\\Microsoft\\Windows\\Images\\BaseImage-14316\\',
112 | validate: (input) => pathExists.sync(input),
113 | when: () => (!program.expandedBaseImage)
114 | },
115 | {
116 | name: 'devCert',
117 | type: 'input',
118 | message: 'Please enter the path to your development PFX certficate: ',
119 | default: null,
120 | when: () => (!dotfile.get().makeCertificate || !program.devCert)
121 | },
122 | {
123 | name: 'publisher',
124 | type: 'input',
125 | message: 'Please enter your publisher identity: ',
126 | default: 'CN=developmentca',
127 | when: () => (!program.publisher)
128 | },
129 | {
130 | name: 'windowsKit',
131 | type: 'input',
132 | message: "Please enter the location of your Windows Kit's bin folder: ",
133 | default: utils.getDefaultWindowsKitLocation(),
134 | when: () => (!program.windowsKit)
135 | }
136 | ]
137 |
138 | if (!program.isModuleUse) {
139 | utils.log(welcome)
140 | }
141 |
142 | // Remove the Desktop Converter Questions if not installed
143 | if (program.didInstallDesktopAppConverter === false) {
144 | questions = questions.slice(3)
145 | }
146 |
147 | if (program.isModuleUse) {
148 | program.windowsKit = program.windowsKit || utils.getDefaultWindowsKitLocation()
149 |
150 | return Promise.resolve(program)
151 | }
152 |
153 | return inquirer.prompt(questions)
154 | .then((answers) => {
155 | dotfile.set({
156 | desktopConverter: answers.desktopConverter || false,
157 | expandedBaseImage: answers.expandedBaseImage || false,
158 | devCert: answers.devCert,
159 | publisher: answers.publisher,
160 | windowsKit: answers.windowsKit,
161 | makeCertificate: dotfile.get().makeCertificate
162 | })
163 |
164 | program.desktopConverter = answers.desktopConverter
165 | program.expandedBaseImage = answers.expandedBaseImage
166 | program.devCert = answers.devCert
167 | program.publisher = answers.publisher
168 | program.windowsKit = answers.windowsKit
169 |
170 | if (program.makeCertificate) {
171 | utils.log(chalk.bold.green('Creating Certficate'))
172 | let publisher = dotfile.get().publisher.split('=')[1]
173 | let certFolder = path.join(process.env.APPDATA, 'electron-windows-store', publisher)
174 |
175 | return sign.makeCert({ publisherName: publisher, certFilePath: certFolder, program: program })
176 | .then(pfxFile => {
177 | utils.log('Created and installed certificate:')
178 | utils.log(pfxFile)
179 | dotfile.set({ devCert: pfxFile })
180 | })
181 | }
182 |
183 | utils.log(complete)
184 | })
185 | }
186 |
187 | /**
188 | * Logs the current configuration to utils
189 | *
190 | * @param program - Commander program object
191 | */
192 | function logConfiguration (program) {
193 | utils.log(chalk.bold.green.underline('\nConfiguration: '))
194 | utils.log(`Desktop Converter Location: ${program.desktopConverter}`)
195 | utils.log(`Expanded Base Image: ${program.expandedBaseImage}`)
196 | utils.log(`Publisher: ${program.publisher}`)
197 | utils.log(`Dev Certificate: ${program.devCert}`)
198 | utils.log(`Windows Kit Location: ${program.windowsKit}
199 | `)
200 | }
201 |
202 | /**
203 | * Runs setup, checking if all configuration is existent,
204 | * and merging the dotfile with the program object
205 | *
206 | * @param program - Commander program object
207 | * @returns {Promise} - Promsise that returns once setup completed
208 | */
209 | function setup (program) {
210 | return new Promise((resolve, reject) => {
211 | if (isSetupRequired(program)) {
212 | // If we're setup, merge the dotfile configuration into the program
213 | defaults(program, dotfile.get())
214 | logConfiguration(program)
215 | resolve()
216 | } else {
217 | // We're not setup, let's do that now
218 | askForDependencies(program)
219 | .then(() => wizardSetup(program))
220 | .then(() => logConfiguration(program))
221 | .then(() => resolve())
222 | .catch((e) => reject(e))
223 | }
224 | })
225 | }
226 |
227 | module.exports = setup
228 |
--------------------------------------------------------------------------------
/lib/sign.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const fs = require('fs-extra')
5 | const chalk = require('chalk')
6 |
7 | const utils = require('./utils')
8 |
9 | const isValidPublisherName = (function () {
10 | // MakeCert looks like it accepts RFC1779 / X.500 distinguished names
11 | // See https://msdn.microsoft.com/en-us/library/windows/apps/br211441.aspx
12 | // https://msdn.microsoft.com/en-us/library/aa366101
13 | // http://www.itu.int/rec/T-REC-X.520-198811-S/en
14 | //
15 | // However, in practice there seem to be some discepencies, such as not supported comma/space escaping,
16 | // so we adapt this to match the observed behavior of makecert.exe.
17 | const validKeyPatterns = [
18 | 'CN', // commonName
19 | 'OU', // organizationalUnitName
20 | 'O', // organizationName
21 | 'STREET', // streetAddress
22 | 'L', // localityName
23 | 'ST', // stateOrProvinceName
24 | 'C', // countryName
25 | 'DC', // domainComponent
26 | 'SN', // surname
27 | 'GN', // given name
28 | 'E', // email
29 | 'S', // (non-standard) "State" used by MS Identity objects
30 | 'T', // ?? Title / telephone
31 | 'G', // ?? generationQualifier
32 | 'I', // ?? IP Address
33 | 'SERIALNUMBER', // serialNumber
34 | '(?:OID\\.(0|[1-9][0-9]*)(?:\\.(0|[1-9][0-9]*))+)' // Object IDentifier by explicit numeric code
35 | ]
36 | const validKeyPattern = validKeyPatterns.join('|')
37 | const doubleQuotedPatternWithoutEmbeddedDoubleQuote = '"[^"\\\\]*(?:[^"][^"\\\\]*)*"'
38 | const validKeyValuePairPattern = `(${validKeyPattern})=((?:${doubleQuotedPatternWithoutEmbeddedDoubleQuote})|[^,"]*)`
39 | const validSequencePattern = `${validKeyValuePairPattern}(:?\\s*[,;]\\s*${validKeyValuePairPattern})*,?`
40 | const validDNRegex = new RegExp(`^${validSequencePattern}$`, 'i')
41 |
42 | return function isValidPublisherName (publisherName) {
43 | return typeof publisherName === 'string' &&
44 | (publisherName.length === 0 || validDNRegex.test(publisherName))
45 | }
46 | }())
47 |
48 | function makeCert (parametersOrPublisherName, certFilePath, program) {
49 | let publisherName
50 | let certFileName
51 | let install = true
52 |
53 | // We accept both an object and a string here - a string was used
54 | // in the first release of electron-windows-store, while the object
55 | // was added later to allow additional flexibility for consuming apps.
56 | if (typeof parametersOrPublisherName === 'string') {
57 | publisherName = parametersOrPublisherName
58 | } else {
59 | publisherName = parametersOrPublisherName.publisherName
60 | certFilePath = parametersOrPublisherName.certFilePath
61 | certFileName = parametersOrPublisherName.certFileName || publisherName
62 | program = parametersOrPublisherName.program
63 | if (typeof parametersOrPublisherName.install === 'boolean') {
64 | install = parametersOrPublisherName.install
65 | }
66 | }
67 |
68 | if (typeof publisherName !== 'string') {
69 | throw new Error('publisherName must be a string')
70 | }
71 |
72 | if (!isValidPublisherName(publisherName)) {
73 | publisherName = `CN=${publisherName}`
74 | }
75 |
76 | certFilePath = certFilePath || ''
77 | const cer = path.join(certFilePath, `${certFileName}.cer`)
78 | const pvk = path.join(certFilePath, `${certFileName}.pvk`)
79 | const pfx = path.join(certFilePath, `${certFileName}.pfx`)
80 |
81 | const makecertExe = path.join(program.windowsKit, 'makecert.exe')
82 | const makecertArgs = ['-r', '-h', '0', '-n', publisherName, '-eku', '1.3.6.1.5.5.7.3.3', '-pe', '-sv', pvk, cer]
83 |
84 | const pk2pfx = path.join(program.windowsKit, 'pvk2pfx.exe')
85 | const pk2pfxArgs = ['-pvk', pvk, '-spc', cer, '-pfx', pfx]
86 | const installPfxArgs = ['Import-PfxCertificate', '-FilePath', pfx, '-CertStoreLocation', '"Cert:\\LocalMachine\\TrustedPeople"']
87 |
88 | // Ensure the target directory exists
89 | fs.ensureDirSync(certFilePath)
90 |
91 | // If the private key file doesn't exist, makecert.exe will generate one and prompt user to set password
92 | if (!fs.existsSync(pvk)) {
93 | utils.log(chalk.green.bold('When asked to enter a password, please select "None".'))
94 | }
95 |
96 | return utils.executeChildProcess(makecertExe, makecertArgs)
97 | .then(() => utils.executeChildProcess(pk2pfx, pk2pfxArgs))
98 | .then(() => {
99 | if (install) {
100 | utils.executeChildProcess('powershell.exe', installPfxArgs)
101 | }
102 | })
103 | .then(() => {
104 | program.devCert = pfx
105 | return pfx
106 | })
107 | }
108 |
109 | function signAppx (program) {
110 | return new Promise((resolve, reject) => {
111 | if (!program.devCert) {
112 | utils.debug(`Error: Tried to call signAppx, but program.devCert was undefined`)
113 | return reject(new Error('No developer certificate specified!'))
114 | }
115 |
116 | const pfxFile = program.devCert
117 | const appxFile = path.join(program.outputDirectory, `${program.packageName}.appx`)
118 | let params = ['sign', '-f', pfxFile, '-fd', 'SHA256', '-v']
119 | if (program.certPass) {
120 | params.push('-p', program.certPass)
121 | }
122 |
123 | params = params.concat(program.signtoolParams || [])
124 |
125 | utils.debug(`Using PFX certificate from: ${pfxFile}`)
126 | utils.debug(`Signing appx package: ${appxFile}`)
127 | utils.debug(`Using the following parameters for signtool.exe: ${JSON.stringify(params)}`)
128 |
129 | params.push(appxFile)
130 |
131 | utils.executeChildProcess(path.join(program.windowsKit, 'signtool.exe'), params)
132 | .then(() => resolve())
133 | .catch((err) => reject(err))
134 | })
135 | }
136 |
137 | module.exports = {
138 | isValidPublisherName,
139 | makeCert,
140 | signAppx
141 | }
142 |
--------------------------------------------------------------------------------
/lib/utils.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | let _debug = null
4 |
5 | /**
6 | * Ensures that the currently running platform is Windows,
7 | * exiting the process if it is not
8 | */
9 | function ensureWindows () {
10 | if (process.platform !== 'win32') {
11 | log('This tool requires Windows 10.\n')
12 | log('You can run a virtual machine using the free VirtualBox and')
13 | log('the free Windows Virtual Machines found at http://modern.ie.\n')
14 | log('For more information, please see the readme.')
15 | process.exit(1)
16 | }
17 |
18 | let release = require('os').release()
19 | let major = parseInt(release.slice(0, 2), 10)
20 | let minor = parseInt(release.slice(3, 4), 10)
21 | let build = parseInt(release.slice(5), 10)
22 |
23 | if (major < 10 || (minor === 0 && build < 14316)) {
24 | log(`You are running Windows ${release}. You need at least Windows 10.0.14316.`)
25 | log('We can\'t confirm that you\'re running the right version, but we won\'t stop')
26 | log('this process - should things fail though, you might have to update your')
27 | log('Windows.')
28 | }
29 | }
30 |
31 | /**
32 | * Makes an educated guess whether or not resources have
33 | * multiple variations or resource versions for
34 | * language, scale, contrast, etc
35 | *
36 | * @param assetsDirectory - Path to a the assets directory
37 | * @returns {boolean} - Are the assets variable?
38 | */
39 | function hasVariableResources (assetsDirectory) {
40 | const files = require('fs-extra').readdirSync(assetsDirectory)
41 | const hasScale = files.find(file => /\.scale-...\./g.test(file))
42 |
43 | return (!!hasScale)
44 | }
45 |
46 | /**
47 | * Tests a given directory for existence
48 | *
49 | * @param directory - Path to a directory
50 | * @returns {boolean} - Does the dir exist?
51 | */
52 | function isDirectory (directory) {
53 | return require('path-exists').sync(directory)
54 | }
55 |
56 | /**
57 | * Starts a child process using the provided executable
58 | *
59 | * @param fileName - Path to the executable to start
60 | * @param args - Arguments for spawn
61 | * @param options - Options passed to spawn
62 | * @returns {Promise} - A promise that resolves when the
63 | * process exits
64 | */
65 | function executeChildProcess (fileName, args, options) {
66 | return new Promise((resolve, reject) => {
67 | const child = require('child_process').spawn(fileName, args, options)
68 |
69 | child.stdout.on('data', (data) => log(data.toString()))
70 | child.stderr.on('data', (data) => log(data.toString()))
71 |
72 | child.on('exit', (code) => {
73 | if (code !== 0) {
74 | return reject(new Error(fileName + ' exited with code: ' + code))
75 | }
76 | return resolve()
77 | })
78 |
79 | child.stdin.end()
80 | })
81 | }
82 |
83 | /**
84 | * Logs to console, unless tests are running (or is used as module)
85 | *
86 | * @param message - Message to log
87 | */
88 | function log (message) {
89 | if (!global.testing && !global.isModuleUse) {
90 | console.log(message)
91 | } else {
92 | debug(message)
93 | }
94 | }
95 |
96 | /**
97 | * Logs debug message to console, unless tests are running
98 | *
99 | * @param message - Message to log
100 | */
101 | function debug (message) {
102 | _debug = _debug || require('debug')('electron-windows-store')
103 | _debug(message)
104 | }
105 |
106 | /**
107 | * Returns the default location of the Windows kit.
108 | *
109 | * @param {string} Architecture - either ia32 or x64
110 | * @returns {string} Windows Kit location
111 | */
112 | function getDefaultWindowsKitLocation (arch) {
113 | arch = arch || process.arch
114 |
115 | return 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\' + (arch === 'ia32' ? 'x86' : 'x64')
116 | }
117 |
118 | module.exports = {
119 | isDirectory,
120 | ensureWindows,
121 | executeChildProcess,
122 | hasVariableResources,
123 | log,
124 | debug,
125 | getDefaultWindowsKitLocation
126 | }
127 |
--------------------------------------------------------------------------------
/lib/vendor/tail.js:
--------------------------------------------------------------------------------
1 | // Taken from https://github.com/lucagrulla/node-tail
2 | // and hacked to accept UTF16LE
3 |
4 | var Tail, environment, events, fs,
5 | bind = function (fn, me) { return function () { return fn.apply(me, arguments); }; },
6 | extend = function (child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
7 | hasProp = {}.hasOwnProperty;
8 |
9 | events = require("events");
10 |
11 | fs = require('fs');
12 |
13 | environment = process.env['NODE_ENV'] || 'development';
14 |
15 | Tail = (function (superClass) {
16 | extend(Tail, superClass);
17 |
18 | Tail.prototype.readBlock = function () {
19 | var block, stream;
20 | if (this.queue.length >= 1) {
21 | block = this.queue.shift();
22 | if (block.end > block.start) {
23 | stream = fs.createReadStream(this.filename, {
24 | start: block.start,
25 | end: block.end - 1,
26 | encoding: "utf16le"
27 | });
28 | stream.on('error', (function (_this) {
29 | return function (error) {
30 | console.error("Tail error:" + error);
31 | return _this.emit('error', error);
32 | };
33 | })(this));
34 | stream.on('end', (function (_this) {
35 | return function () {
36 | if (_this.queue.length >= 1) {
37 | return _this.internalDispatcher.emit("next");
38 | }
39 | };
40 | })(this));
41 | return stream.on('data', (function (_this) {
42 | return function (data) {
43 | var chunk, i, len, parts, results;
44 | _this.buffer += data;
45 | parts = _this.buffer.split(_this.separator);
46 | _this.buffer = parts.pop();
47 | results = [];
48 | for (i = 0, len = parts.length; i < len; i++) {
49 | chunk = parts[i];
50 | results.push(_this.emit("line", chunk));
51 | }
52 | return results;
53 | };
54 | })(this));
55 | }
56 | }
57 | };
58 |
59 | function Tail(filename, options) {
60 | var pos, ref, ref1, ref2, ref3;
61 | this.filename = filename;
62 | if (options == null) {
63 | options = {};
64 | }
65 | this.readBlock = bind(this.readBlock, this);
66 | this.separator = (ref = options.separator) != null ? ref : /[\r]{0,1}\n/, this.fsWatchOptions = (ref1 = options.fsWatchOptions) != null ? ref1 : {}, this.fromBeginning = (ref2 = options.fromBeginning) != null ? ref2 : false, this.follow = (ref3 = options.follow) != null ? ref3 : true;
67 | this.buffer = '';
68 | this.internalDispatcher = new events.EventEmitter();
69 | this.queue = [];
70 | this.isWatching = false;
71 | this.internalDispatcher.on('next', (function (_this) {
72 | return function () {
73 | return _this.readBlock();
74 | };
75 | })(this));
76 | if (this.fromBeginning) {
77 | pos = 0;
78 | }
79 | this.watch(pos);
80 | }
81 |
82 | Tail.prototype.watch = function (pos) {
83 | var stats;
84 | if (this.isWatching) {
85 | return;
86 | }
87 | this.isWatching = true;
88 | stats = fs.statSync(this.filename);
89 | this.pos = pos != null ? pos : stats.size;
90 | if (fs.watch) {
91 | return this.watcher = fs.watch(this.filename, this.fsWatchOptions, (function (_this) {
92 | return function (e) {
93 | return _this.watchEvent(e);
94 | };
95 | })(this));
96 | } else {
97 | return fs.watchFile(this.filename, this.fsWatchOptions, (function (_this) {
98 | return function (curr, prev) {
99 | return _this.watchFileEvent(curr, prev);
100 | };
101 | })(this));
102 | }
103 | };
104 |
105 | Tail.prototype.watchEvent = function (e) {
106 | var stats;
107 | if (e === 'change') {
108 | stats = fs.statSync(this.filename);
109 | if (stats.size < this.pos) {
110 | this.pos = stats.size;
111 | }
112 | if (stats.size > this.pos) {
113 | this.queue.push({
114 | start: this.pos,
115 | end: stats.size
116 | });
117 | this.pos = stats.size;
118 | if (this.queue.length === 1) {
119 | return this.internalDispatcher.emit("next");
120 | }
121 | }
122 | } else if (e === 'rename') {
123 | this.unwatch();
124 | if (this.follow) {
125 | return setTimeout(((function (_this) {
126 | return function () {
127 | return _this.watch();
128 | };
129 | })(this)), 1000);
130 | } else {
131 | console.error("'rename' event for " + this.filename + ". File not available.");
132 | return this.emit("error", "'rename' event for " + this.filename + ". File not available.");
133 | }
134 | }
135 | };
136 |
137 | Tail.prototype.watchFileEvent = function (curr, prev) {
138 | if (curr.size > prev.size) {
139 | this.queue.push({
140 | start: prev.size,
141 | end: curr.size
142 | });
143 | if (this.queue.length === 1) {
144 | return this.internalDispatcher.emit("next");
145 | }
146 | }
147 | };
148 |
149 | Tail.prototype.unwatch = function () {
150 | if (fs.watch && this.watcher) {
151 | this.watcher.close();
152 | } else {
153 | fs.unwatchFile(this.filename);
154 | }
155 | this.isWatching = false;
156 | return this.queue = [];
157 | };
158 |
159 | return Tail;
160 |
161 | })(events.EventEmitter);
162 |
163 | exports.Tail = Tail;
164 |
--------------------------------------------------------------------------------
/lib/zip.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const chalk = require('chalk')
5 |
6 | const utils = require('./utils')
7 |
8 | module.exports = function (program) {
9 | return new Promise((resolve, reject) => {
10 | // If we do a simple conversion, we don't need to zip.
11 | if (!program.containerVirtualization) {
12 | return resolve()
13 | }
14 |
15 | let input = program.inputDirectory
16 | let output = program.outputDirectory
17 | let args = `& {& '${path.resolve(__dirname, '..', 'ps1', 'zip.ps1')}' -source '${input}' -destination '${output}'}`
18 |
19 | utils.log(chalk.green('Zipping up built Electron application...'))
20 |
21 | return utils.executeChildProcess('powershell.exe', ['-NoProfile', '-NoLogo', args])
22 | .then(() => resolve())
23 | .catch((err) => reject(err))
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "electron-windows-store",
3 | "version": "2.0.1",
4 | "description": "Compile Electron Apps into Windows Store AppX packages",
5 | "main": "lib/index.js",
6 | "bin": {
7 | "electron-windows-store": "bin/windowsstore.js"
8 | },
9 | "scripts": {
10 | "test": "standard \"lib/*.js\" \"bin/**/*.js\" && mocha",
11 | "semantic-release": "semantic-release",
12 | "commitlint": "commitlint"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/felixrieseberg/electron-windows-store.git"
17 | },
18 | "keywords": [
19 | "Electron",
20 | "App",
21 | "Windows",
22 | "Store",
23 | "Appx",
24 | "UWP"
25 | ],
26 | "engineStrict": true,
27 | "engines": {
28 | "node": ">=6.0.0"
29 | },
30 | "author": "",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/felixrieseberg/electron-windows-store/issues"
34 | },
35 | "homepage": "https://github.com/felixrieseberg/electron-windows-store#readme",
36 | "dependencies": {
37 | "chalk": "^2.4.1",
38 | "commander": "^2.19.0",
39 | "debug": "^4.1.0",
40 | "fs-extra": "^7.0.0",
41 | "inquirer": "^6.2.0",
42 | "lodash.defaults": "^4.2.0",
43 | "lodash.merge": "^4.6.1",
44 | "multiline": "^2.0.0",
45 | "path-exists": "^3.0.0"
46 | },
47 | "devDependencies": {
48 | "@commitlint/cli": "^7.2.1",
49 | "@commitlint/config-conventional": "^7.1.2",
50 | "@commitlint/travis-cli": "^7.2.1",
51 | "chai": "^4.2.0",
52 | "chai-as-promised": "^7.1.1",
53 | "mocha": "^5.2.0",
54 | "mockery": "^2.1.0",
55 | "semantic-release": "^15.10.7",
56 | "standard": "^12.0.1"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ps1/convert.ps1:
--------------------------------------------------------------------------------
1 | [CmdletBinding(DefaultParameterSetName="Convert")]
2 | Param(
3 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
4 | [string]
5 | [ValidateNotNullOrEmpty()]
6 | $LogFile,
7 |
8 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
9 | [string]
10 | [ValidateNotNullOrEmpty()]
11 | $Installer,
12 |
13 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
14 | [string]
15 | [ValidateNotNullOrEmpty()]
16 | $Converter,
17 |
18 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
19 | [string]
20 | [ValidateNotNullOrEmpty()]
21 | $ExpandedBaseImage,
22 |
23 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
24 | [string]
25 | [ValidateNotNullOrEmpty()]
26 | $Destination,
27 |
28 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
29 | [string]
30 | [ValidateNotNullOrEmpty()]
31 | $PackageName,
32 |
33 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
34 | [string]
35 | [ValidateNotNullOrEmpty()]
36 | $Version,
37 |
38 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
39 | [string]
40 | [ValidateNotNullOrEmpty()]
41 | $Publisher,
42 |
43 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
44 | [string]
45 | [ValidateNotNullOrEmpty()]
46 | $AppExecutable
47 | )
48 |
49 | Start-Process powershell -WindowStyle Hidden -ArgumentList "-noprofile -nologo -file $Converter -LogFile $LogFile -Installer $Installer -ExpandedBaseImage $ExpandedBaseImage -Destination $Destination -PackageName $PackageName -Version $Version -Publisher $Publisher -AppExecutable $AppExecutable -Verbose" -verb RunAs
--------------------------------------------------------------------------------
/ps1/zip.ps1:
--------------------------------------------------------------------------------
1 | #
2 | # Takes a folder, turns it into a zip file.
3 | #
4 |
5 | [CmdletBinding(DefaultParameterSetName="Convert")]
6 | Param(
7 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
8 | [string]
9 | [ValidateNotNullOrEmpty()]
10 | $Source,
11 |
12 | [Parameter(Mandatory=$True, ParameterSetName="Convert")]
13 | [string]
14 | [ValidateNotNullOrEmpty()]
15 | $Destination
16 | )
17 |
18 | If (-Not (Test-path $Source)) {
19 | return "Source directory cannot be found"
20 | }
21 |
22 | If (-Not (Test-path $Destination)) {
23 | return "Destination directory cannot be found"
24 | }
25 |
26 | # We're zipping using the sytem's compressor
27 | Add-Type -assembly "system.io.compression.filesystem"
28 | $Zip = Join-Path -Path $Destination -ChildPath app.zip
29 |
30 | If (Test-Path $Zip) {
31 | Remove-Item $Zip
32 | }
33 |
34 | [io.compression.zipfile]::CreateFromDirectory($Source, $Zip)
35 |
36 | # Copy over installer
37 | $Installer = (Get-Item $PSScriptRoot).parent.FullName + '\bin\ElectronInstaller.exe'
38 | Copy-item $Installer -Destination $Destination
--------------------------------------------------------------------------------
/release.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | branch: 'master',
3 | plugins: [
4 | '@semantic-release/commit-analyzer',
5 | '@semantic-release/release-notes-generator',
6 | '@semantic-release/npm',
7 | '@semantic-release/github',
8 | ],
9 | }
--------------------------------------------------------------------------------
/template/appxmanifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
10 |
11 | ${packageDisplayName}
12 | ${publisherDisplayName}
13 | No description entered
14 | assets\SampleAppx.50x50.png
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
33 |
34 |
35 |
36 | ${protocol}
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/template/assets/SampleAppx.150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/template/assets/SampleAppx.150x150.png
--------------------------------------------------------------------------------
/template/assets/SampleAppx.310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/template/assets/SampleAppx.310x150.png
--------------------------------------------------------------------------------
/template/assets/SampleAppx.44x44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/template/assets/SampleAppx.44x44.png
--------------------------------------------------------------------------------
/template/assets/SampleAppx.50x50.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/template/assets/SampleAppx.50x50.png
--------------------------------------------------------------------------------
/test/fixtures/assets-scaled/AppNameMedTile.scale-100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/test/fixtures/assets-scaled/AppNameMedTile.scale-100.png
--------------------------------------------------------------------------------
/test/fixtures/assets-scaled/AppNameMedTile.scale-200.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/test/fixtures/assets-scaled/AppNameMedTile.scale-200.png
--------------------------------------------------------------------------------
/test/fixtures/assets-scaled/AppNameMedTile.scale-400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/test/fixtures/assets-scaled/AppNameMedTile.scale-400.png
--------------------------------------------------------------------------------
/test/fixtures/assets/AppNameMedTile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/test/fixtures/assets/AppNameMedTile.png
--------------------------------------------------------------------------------
/test/fixtures/child_process.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const EventEmitter = require('events')
4 |
5 | module.exports = class ChildProcessMock extends EventEmitter {
6 | constructor() {
7 | super()
8 |
9 | this.stdout = {
10 | on() {}
11 | }
12 | this.stderr = {
13 | on() {}
14 | }
15 | this.stdin = {
16 | end: () => {
17 | this.emit('exit', 0)
18 | }
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/test/lib/assets.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const mockery = require('mockery')
3 |
4 | describe('Assets', () => {
5 | afterEach(() => mockery.deregisterAll())
6 |
7 | describe('assets()', () => {
8 | it('should attempt to copy assets if they have been passed', (done) => {
9 | const programMock = {
10 | assets: '/fakepath/to/assets',
11 | outputDirectory: '/fakepath/to/output'
12 | }
13 | const fsMock = {
14 | copy: function (source, destination, cb) {
15 | source.should.equal(path.normalize('/fakepath/to/assets'))
16 | destination.should.equal(path.normalize('/fakepath/to/output/pre-appx/Assets'))
17 |
18 | if (cb) {
19 | cb()
20 | } else {
21 | return Promise.resolve()
22 | }
23 | }
24 | }
25 |
26 | mockery.registerMock('fs-extra', fsMock)
27 | require('../../lib/assets')(programMock).should.be.fulfilled.and.notify(done)
28 | mockery.deregisterMock('fs-extra');
29 | })
30 |
31 | it('should resolve right away if no assets have been passed', (done) => {
32 | const programMock = {}
33 | require('../../lib/assets')(programMock).should.be.fulfilled.and.notify(done)
34 | })
35 | })
36 | })
37 |
--------------------------------------------------------------------------------
/test/lib/bogus-private-key.pvk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/electron-userland/electron-windows-store/7b45c54574596863b8c2f790cea09aabc03b6883/test/lib/bogus-private-key.pvk
--------------------------------------------------------------------------------
/test/lib/deploy.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const mockery = require('mockery')
5 | const ChildProcessMock = require('../fixtures/child_process')
6 |
7 | describe('Deploy', () => {
8 | afterEach(() => mockery.deregisterAll())
9 |
10 | describe('deploy()', () => {
11 | it('should attempt to deploy the app if requested', function (done) {
12 | let passedProcess
13 | let passedArgs
14 |
15 | const programMock = {
16 | deploy: true,
17 | outputDirectory: '/fakepath/to/output',
18 | packageName: 'testApp'
19 | }
20 | const cpMock = {
21 | spawn(_process, _args) {
22 | passedProcess = _process
23 | passedArgs = _args
24 |
25 | return new ChildProcessMock()
26 | }
27 | }
28 |
29 | mockery.registerMock('child_process', cpMock)
30 |
31 | require('../../lib/deploy')(programMock)
32 | .then(() => {
33 | passedProcess.should.equal('powershell.exe')
34 | passedArgs[2].should.equal(`& {& Add-AppxPackage '${programMock.outputDirectory}/${programMock.packageName}.appx'}`)
35 | done()
36 | })
37 | })
38 |
39 | it('should resolve right away if no deployment was requested', (done) => {
40 | const programMock = {}
41 | require('../../lib/deploy')(programMock).should.be.fulfilled.notify(done)
42 | })
43 | })
44 | })
45 |
--------------------------------------------------------------------------------
/test/lib/makeappx.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const mockery = require('mockery')
5 |
6 | const ChildProcessMock = require('../fixtures/child_process')
7 |
8 | describe('MakeAppX', () => {
9 | const cpMock = {
10 | spawn(_process, _args) {
11 | passedProcess = _process
12 | passedArgs = _args
13 |
14 | return new ChildProcessMock()
15 | }
16 | }
17 |
18 | let passedArgs
19 | let passedProcess
20 |
21 | afterEach(() => {
22 | mockery.deregisterAll()
23 | passedArgs = undefined
24 | passedProcess = undefined
25 | })
26 |
27 | describe('makeappx()', () => {
28 | it('should attempt to call makeappx.exe for a pre-appx folder', function (done) {
29 | const programMock = {
30 | deploy: true,
31 | inputDirectory: '/fakepath/to/input',
32 | outputDirectory: '/fakepath/to/output',
33 | windowsKit: '/fakepath/to/windows/kit/bin',
34 | packageName: 'testapp'
35 | }
36 |
37 | mockery.registerMock('child_process', cpMock)
38 |
39 | require('../../lib/makeappx')(programMock)
40 | .then(() => {
41 | const expectedScript = path.join(programMock.windowsKit, 'makeappx.exe')
42 | const expectedOutput = path.join(programMock.outputDirectory, 'testapp.appx')
43 | const expectedInput = path.join(programMock.outputDirectory, 'pre-appx')
44 | const expectedParams = ['pack', '/d', expectedInput, '/p', expectedOutput, '/o']
45 |
46 | passedProcess.should.equal(expectedScript)
47 | passedArgs.should.deep.equal(expectedParams)
48 | done()
49 | })
50 | })
51 |
52 | it('should pass the /l flag if the pre-appx folder contains variable assets', function (done) {
53 | const programMock = {
54 | deploy: true,
55 | assets: path.join(__dirname, '..', 'fixtures', 'assets-scaled'),
56 | inputDirectory: '/fakepath/to/input',
57 | outputDirectory: '/fakepath/to/output',
58 | windowsKit: '/fakepath/to/windows/kit/bin',
59 | packageName: 'testapp'
60 | }
61 |
62 | mockery.registerMock('child_process', cpMock)
63 |
64 | require('../../lib/makeappx')(programMock)
65 | .then(() => {
66 | const expectedScript = path.join(programMock.windowsKit, 'makeappx.exe')
67 | const expectedOutput = path.join(programMock.outputDirectory, 'testapp.appx')
68 | const expectedInput = path.join(programMock.outputDirectory, 'pre-appx')
69 | const expectedParams = ['pack', '/d', expectedInput, '/p', expectedOutput, '/o', '/l']
70 |
71 | passedProcess.should.equal(expectedScript)
72 | passedArgs.should.deep.equal(expectedParams)
73 | done()
74 | })
75 | })
76 |
77 | it('should not pass the /l flag if the pre-appx folder does not contain variable assets', function (done) {
78 | const programMock = {
79 | deploy: true,
80 | assets: path.join(__dirname, '..', 'fixtures', 'assets'),
81 | inputDirectory: '/fakepath/to/input',
82 | outputDirectory: '/fakepath/to/output',
83 | windowsKit: '/fakepath/to/windows/kit/bin',
84 | packageName: 'testapp'
85 | }
86 |
87 | mockery.registerMock('child_process', cpMock)
88 |
89 | require('../../lib/makeappx')(programMock)
90 | .then(() => {
91 | const expectedScript = path.join(programMock.windowsKit, 'makeappx.exe')
92 | const expectedOutput = path.join(programMock.outputDirectory, 'testapp.appx')
93 | const expectedInput = path.join(programMock.outputDirectory, 'pre-appx')
94 | const expectedParams = ['pack', '/d', expectedInput, '/p', expectedOutput, '/o']
95 |
96 | passedProcess.should.equal(expectedScript)
97 | passedArgs.should.deep.equal(expectedParams)
98 | done()
99 | })
100 | })
101 |
102 | it('should reject right away if no Windows Kit is available', (done) => {
103 | const programMock = {}
104 | require('../../lib/makeappx')(programMock).should.be.rejected.notify(done)
105 | })
106 | })
107 | })
108 |
--------------------------------------------------------------------------------
/test/lib/makepri.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const mockery = require('mockery')
5 |
6 | const ChildProcessMock = require('../fixtures/child_process')
7 |
8 | describe('Makepri', () => {
9 | let spawnedProcesses = []
10 |
11 | const cpMock = {
12 | spawn(_process, _args) {
13 | spawnedProcesses.push({
14 | passedProcess: _process,
15 | passedArgs: _args
16 | })
17 |
18 | return new ChildProcessMock()
19 | }
20 | }
21 |
22 | afterEach(() => {
23 | mockery.deregisterAll()
24 | spawnedProcesses = []
25 | })
26 |
27 | describe('makepri()', () => {
28 | it('should attempt to call makepri.exe with createconfig as parameter', function (done) {
29 | const programMock = {
30 | deploy: true,
31 | inputDirectory: '/fakepath/to/input',
32 | outputDirectory: '/fakepath/to/output',
33 | windowsKit: '/fakepath/to/windows/kit/bin',
34 | packageName: 'testapp',
35 | makePri: true
36 | }
37 |
38 | mockery.registerMock('child_process', cpMock)
39 |
40 | require('../../lib/makepri')(programMock)
41 | .then(() => {
42 | const exptectedTarget = path.join('pre-appx', 'priconfig.xml')
43 | const expectedScript = path.join(programMock.windowsKit, 'makepri.exe')
44 | const expectedParams = ['createconfig', '/cf', exptectedTarget, '/dq', 'en-US']
45 |
46 | spawnedProcesses[0].passedProcess.should.equal(expectedScript)
47 | spawnedProcesses[0].passedArgs.should.deep.equal(expectedParams)
48 | done()
49 | })
50 | })
51 |
52 | it('should attempt to call makepri.exe with new as parameter', function (done) {
53 | const programMock = {
54 | deploy: true,
55 | inputDirectory: '/fakepath/to/input',
56 | outputDirectory: '/fakepath/to/output',
57 | windowsKit: '/fakepath/to/windows/kit/bin',
58 | packageName: 'testapp',
59 | makePri: true
60 | }
61 |
62 | mockery.registerMock('child_process', cpMock)
63 |
64 | require('../../lib/makepri')(programMock)
65 | .then(() => {
66 | const expectedProject = 'pre-appx'
67 | const exptectedTarget = path.join('pre-appx', 'priconfig.xml')
68 | const expectedOutput = path.join('pre-appx', 'resources.pri')
69 | const expectedScript = path.join(programMock.windowsKit, 'makepri.exe')
70 | const expectedParams = ['new', '/pr', expectedProject, '/cf', exptectedTarget, '/of', expectedOutput]
71 |
72 | spawnedProcesses[1].passedProcess.should.equal(expectedScript)
73 | spawnedProcesses[1].passedArgs.should.deep.equal(expectedParams)
74 | done()
75 | })
76 | })
77 |
78 | it('should reject right away if no Windows Kit is available', (done) => {
79 | const programMock = {}
80 | require('../../lib/makepri')(programMock).should.be.rejected.notify(done)
81 | })
82 | })
83 | })
84 |
--------------------------------------------------------------------------------
/test/lib/manifest.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const mockery = require('mockery')
3 |
4 | describe('Manifest', () => {
5 | afterEach(() => mockery.deregisterAll())
6 |
7 | describe('manifest()', () => {
8 | it('should attempt to copy a manifest if it has been passed', () => {
9 | const programMock = {
10 | outputDirectory: '/fakepath/to/output',
11 | manifest: '/fakepath/to/manifest'
12 | }
13 | const fsMock = {
14 | copy: function (source, destination, cb) {
15 | const expectedSource = path.normalize(programMock.manifest)
16 | const expectedDestination = path.join(programMock.outputDirectory, 'pre-appx', 'AppXManifest.xml')
17 |
18 | source.should.equal(expectedSource)
19 | destination.should.equal(expectedDestination)
20 |
21 | if (cb) {
22 | cb()
23 | } else {
24 | return Promise.resolve()
25 | }
26 | }
27 | }
28 |
29 | mockery.registerMock('fs-extra', fsMock)
30 | return require('../../lib/manifest')(programMock).should.be.fulfilled
31 | })
32 |
33 | it('should resolve right away if no manifest was passed', () => {
34 | const programMock = {}
35 | return require('../../lib/manifest')(programMock).should.be.fulfilled
36 | })
37 | })
38 | })
39 |
--------------------------------------------------------------------------------
/test/lib/sign.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const fs = require('fs-extra')
4 | const path = require('path')
5 | const mockery = require('mockery')
6 |
7 | const ChildProcessMock = require('../fixtures/child_process')
8 | const sign = require('../../lib/sign')
9 | const utils = require('../../lib/utils')
10 |
11 | describe('Sign', () => {
12 | const tmpDir = path.join(require('os').tmpdir(), 'electron-windows-store-cert-test')
13 |
14 | let passedArgs = []
15 | let passedProcess = []
16 |
17 | const cpMock = {
18 | spawn(_process, _args) {
19 | passedProcess.push(_process)
20 | passedArgs.push(_args)
21 |
22 | return new ChildProcessMock()
23 | }
24 | }
25 |
26 | before(() => {
27 | fs.ensureDirSync(tmpDir)
28 | })
29 |
30 | after(() => {
31 | fs.removeSync(tmpDir)
32 | })
33 |
34 | afterEach(() => {
35 | mockery.deregisterAll()
36 | passedArgs = []
37 | passedProcess = []
38 | })
39 |
40 | describe('signappx()', () => {
41 | it('should attempt to sign the current app', function (done) {
42 | const programMock = {
43 | inputDirectory: '/fakepath/to/input',
44 | outputDirectory: '/fakepath/to/output',
45 | windowsKit: '/fakepath/to/windows/kit/bin',
46 | packageName: 'testapp',
47 | devCert: 'fakepath/to/devcert.pfx'
48 | }
49 |
50 | mockery.registerMock('child_process', cpMock)
51 |
52 | sign.signAppx(programMock)
53 | .then(() => {
54 | const expectedScript = path.join(programMock.windowsKit, 'signtool.exe')
55 | const expectedPfxFile = programMock.devCert
56 | const expectedAppx = path.join(programMock.outputDirectory, `${programMock.packageName}.appx`)
57 | const expectedParams = ['sign', '-f', expectedPfxFile, '-fd', 'SHA256', '-v', expectedAppx]
58 |
59 | passedProcess.length.should.equal(1)
60 | passedProcess[0].should.equal(expectedScript)
61 | passedArgs[0].should.deep.equal(expectedParams)
62 | done()
63 | })
64 | })
65 |
66 | it('should pass along the certificate password', function () {
67 | const programMock = {
68 | inputDirectory: '/fakepath/to/input',
69 | outputDirectory: '/fakepath/to/output',
70 | windowsKit: '/fakepath/to/windows/kit/bin',
71 | packageName: 'testapp',
72 | devCert: 'fakepath/to/devcert.p12',
73 | certPass: '12345'
74 | }
75 |
76 | mockery.registerMock('child_process', cpMock)
77 |
78 | return sign.signAppx(programMock)
79 | .then(() => {
80 | const expectedScript = path.join(programMock.windowsKit, 'signtool.exe')
81 | const expectedPfxFile = programMock.devCert
82 | const expectedAppx = path.join(programMock.outputDirectory, `${programMock.packageName}.appx`)
83 | const expectedParams = ['sign', '-f', expectedPfxFile, '-fd', 'SHA256', '-v', '-p', '12345', expectedAppx]
84 |
85 | passedProcess.length.should.equal(1)
86 | passedProcess[0].should.equal(expectedScript)
87 | passedArgs[0].should.deep.equal(expectedParams)
88 | })
89 | })
90 |
91 | it('should reject if no certificate is present', function () {
92 | const programMock = {}
93 | return sign.signAppx(programMock).should.be.rejected
94 | })
95 | })
96 |
97 | describe('makeCert()', () => {
98 | it('should not attempt to import certificate when install === false', function (done) {
99 | mockery.registerMock('child_process', cpMock)
100 |
101 | sign.makeCert({
102 | publisherName: 'CN=Test',
103 | certFilePath: tmpDir,
104 | install: false,
105 | program: {
106 | windowsKit: '/fake/kit'
107 | }
108 | })
109 | .then(() => {
110 | passedArgs.every((args) => {
111 | return args.every(arg => arg.indexOf('Import-PfxCertificate') === -1)
112 | }).should.equal(true)
113 | done()
114 | })
115 | .catch(() => {})
116 | })
117 | })
118 |
119 |
120 | describe('isValidPublisherName()', () => {
121 | const windowsSdkPath = process.arch === 'x64' ?
122 | 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64' :
123 | 'C:\\Program Files\\Windows Kits\\10\\bin\\x64';
124 | const makecertExe = path.join(windowsSdkPath, 'makecert.exe')
125 | const pvkFileName = path.resolve(__dirname, 'bogus-private-key.pvk');
126 | const skipMakeCertExecution = !fs.existsSync(makecertExe) || !fs.existsSync(pvkFileName)
127 |
128 | const scenarios = [{
129 | publisherName: ''
130 | }, {
131 | publisherName: 'CN='
132 | }, {
133 | publisherName: 'CN=-'
134 | }, {
135 | publisherName: 'cn=lower, ou=case'
136 | }, {
137 | publisherName: 'CN=first.last'
138 | }, {
139 | publisherName: 'CN="Pointlessly quoted"'
140 | }, {
141 | publisherName: 'CN=no,o=spaces'
142 | }, {
143 | publisherName: 'CN=" Leading and Trailing Spaces "'
144 | }, {
145 | publisherName: 'CN=Common Name,O=Some organization'
146 | }, {
147 | publisherName: 'O="Quoted comma, Inc."'
148 | }, {
149 | publisherName: 'CN=!@#$^&*()[]{}<>|\/.~\'-=,O="Symbols are Cool, LLC"'
150 | }, {
151 | publisherName: 'OU=Sales+CN=J. Smith,O=Multi-valued'
152 | }, {
153 | publisherName: 'CN=Duplicate+CN=Attribute'
154 | }, {
155 | publisherName: 'OU=Trailing plus+',
156 | }, {
157 | publisherName: 'CN=trailing comma,',
158 | }, {
159 | publisherName: 'CN="Escaped\\ and\\ quoted\\ spaces"'
160 | }, {
161 | publisherName: 'CN=First M Last, O="Acme, Inc."'
162 | }, {
163 | publisherName: 'CN=Marshall T. Rose, O=Dover Beach Consulting, L=Santa Clara,ST=California, C=US'
164 | }, {
165 | publisherName: 'CN=FTAM Service, CN=Bells, OU=Computer Science,\nO=University College London, C=GB'
166 | }, {
167 | publisherName: 'CN=Markus Kuhn, O=University of Erlangen, T=Mr., C=DE'
168 | }, {
169 | publisherName: 'CN=Steve Kille,\n O=ISODE Consortium,\n C=GB'
170 | }, {
171 | publisherName: 'CN=Christian Huitema; O=INRIA; C=FR'
172 | }, {
173 | publisherName: 'CN=This is a really long distinguished name: 2048 chars!........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................'
174 | }, {
175 | publisherName: 'CN=Even longer: 4096 chars 0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678'
176 | }, {
177 | publisherName: 'CN=Long enough for anyone: 8192 chars 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123'
178 | }, {
179 | publisherName: 'CN=L. Eagle, O="Sue, Grabbit and Runn", C=GB'
180 | }, {
181 | publisherName: 'O=No CN'
182 | }, {
183 | publisherName: 'SERIALNUMBER=1'
184 | }, {
185 | publisherName: 'DC=A,CN=B,OU=C,O=D,STREET=123 Main St.,L=Big City,ST=Nowhere,C=XX,SN=Surname,GN=Given name,E=nobody@example.com,S=E,T=F,G=G,I=1.2.3.4'
186 | }, {
187 | publisherName: 'CN="+"',
188 | }, {
189 | publisherName: 'CN=X+'
190 | }, {
191 | publisherName: 'X',
192 | expectInvalid: true
193 | }, {
194 | publisherName: 'CN=X,UID=userId',
195 | expectInvalid: true
196 | }, {
197 | publisherName: 'CN=\ Escaped leading space"',
198 | expectInvalid: true
199 | }, {
200 | publisherName: 'CN="Quotation \\" Mark"',
201 | expectInvalid: true
202 | }, {
203 | publisherName: 'CN=X,DNQ=qualifier',
204 | expectInvalid: true
205 | }, {
206 | // According to RFC1779 and RFC2243 this should be legal but MakeCert.exe does not seem to accept it
207 | publisherName: 'CN=Sue\\, Grabbit and Runn',
208 | expectInvalid: true
209 | }]
210 |
211 | scenarios.forEach((scenario) => {
212 | const actualResult = sign.isValidPublisherName(scenario.publisherName)
213 | let nameToPrint = scenario.publisherName.replace(/\n/g, '\\n')
214 | if (nameToPrint.length > 60)
215 | nameToPrint = nameToPrint.slice(0,61) + '...'
216 |
217 | // Compare pre-determined checks (previously confirmed with makecert.exe)
218 | it(`return ${scenario.expectInvalid ? 'false' : 'true'} for ${nameToPrint}`, () => {
219 | actualResult.should.equal(!scenario.expectInvalid, scenario.publisherName)
220 | })
221 |
222 | // Run makecert.exe and check whether or not it fails with this publisherName
223 | if (!skipMakeCertExecution) {
224 | const rnd = Date.now() + '-' + Math.floor(Math.random()*10000)
225 | const crtFileName = path.join(tmpDir, `makecert-${rnd}.crt`)
226 | const makecertArgs = ['-r', '-h', '0', '-n', scenario.publisherName, '-eku', '1.3.6.1.5.5.7.3.3', '-pe', '-sv', pvkFileName, crtFileName]
227 |
228 | it(`makecert.exe should ${scenario.expectInvalid ? 'fail' : 'succeed'} for ${nameToPrint}`, () => {
229 | return utils.executeChildProcess(makecertExe, makecertArgs)
230 | .then(() => { return true })
231 | .catch(() => { return false })
232 | .should.eventually.equal(!scenario.expectInvalid)
233 | })
234 | }
235 | })
236 |
237 | if (skipMakeCertExecution) {
238 | it.skip('should be re-tested against actual MakeCert.exe from SDK')
239 | }
240 | })
241 | })
242 |
--------------------------------------------------------------------------------
/test/lib/utils.js:
--------------------------------------------------------------------------------
1 | const utils = require('../../lib/utils')
2 | const path = require('path')
3 |
4 | describe('Utilities', () => {
5 | describe('hasVariableResources()', () => {
6 | it('should return true if files contain scale- indication', () => {
7 | return utils.hasVariableResources(path.join(__dirname, '..', 'fixtures', 'assets-scaled')).should.equal(true)
8 | })
9 |
10 | it('should return false if files do not contain scale- indication', () => {
11 | return utils.hasVariableResources(path.join(__dirname, '..', 'fixtures', 'assets')).should.equal(false)
12 | })
13 |
14 | it('should return the correct location for the default windows kit locataion (x86)', () => {
15 | return utils.getDefaultWindowsKitLocation('ia32').should.equal('C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x86')
16 | })
17 |
18 | it('should return the correct location for the default windows kit locataion (x64)', () => {
19 | return utils.getDefaultWindowsKitLocation('x64').should.equal('C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64')
20 | })
21 |
22 | it('should return the correct location for the default windows kit locataion (default)', () => {
23 | const x86 = 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x86'
24 | const x64 = 'C:\\Program Files (x86)\\Windows Kits\\10\\bin\\x64'
25 |
26 | if (process.arch === 'ia32') {
27 | return utils.getDefaultWindowsKitLocation().should.equal(x86)
28 | } else {
29 | return utils.getDefaultWindowsKitLocation().should.equal(x64)
30 | }
31 | })
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/test/lib/zip.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const path = require('path')
4 | const mockery = require('mockery')
5 | const should = require('chai').should()
6 |
7 | const ChildProcessMock = require('../fixtures/child_process')
8 |
9 | describe('Zip', () => {
10 | const cpMock = {
11 | spawn(_process, _args) {
12 | passedProcess = _process
13 | passedArgs = _args
14 |
15 | return new ChildProcessMock()
16 | }
17 | }
18 |
19 | let passedArgs
20 | let passedProcess
21 |
22 | afterEach(() => {
23 | mockery.deregisterAll()
24 | passedArgs = undefined
25 | passedProcess = undefined
26 | })
27 |
28 | describe('signappx()', () => {
29 | it('should attempt to sign the current app', (done) => {
30 | const programMock = {
31 | inputDirectory: '/fakepath/to/input',
32 | outputDirectory: '/fakepath/to/output',
33 | containerVirtualization: true
34 | }
35 |
36 | mockery.registerMock('child_process', cpMock)
37 |
38 | require('../../lib/zip')(programMock).should.be.fulfilled
39 | .then(() => {
40 | const expectedInput = programMock.inputDirectory
41 | const expectedOutput = programMock.outputDirectory
42 | const expectedScript = path.resolve(__dirname, '..', '..', 'ps1', 'zip.ps1')
43 | const expectedPsArgs = `& {& '${expectedScript}' -source '${expectedInput}' -destination '${expectedOutput}'}`
44 | const expectedArgs = ['-NoProfile', '-NoLogo', expectedPsArgs]
45 |
46 | passedProcess.should.equal('powershell.exe')
47 | passedArgs.should.deep.equal(expectedArgs)
48 | })
49 | .should.notify(done)
50 | })
51 |
52 | it('does not zip if using simple (non-container) conversion', done => {
53 | const programMock = {}
54 | require('../../lib/zip')(programMock).should.be.fulfilled
55 | .then(() => {
56 | should.not.exist(passedArgs)
57 | should.not.exist(passedProcess)
58 | })
59 | .should.notify(done)
60 | })
61 | })
62 | })
63 |
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai')
2 | const chaiAsPromised = require('chai-as-promised')
3 | const mockery = require('mockery')
4 |
5 | chai.should()
6 | chai.use(chaiAsPromised)
7 | mockery.enable({ warnOnUnregistered: false })
8 |
9 | global.testing = true
10 |
11 | // Run tests
12 | require('./lib/assets');
13 | require('./lib/deploy');
14 | require('./lib/makeappx');
15 | require('./lib/makepri');
16 | require('./lib/manifest');
17 | require('./lib/sign');
18 | require('./lib/zip');
19 | require('./lib/utils');
20 |
--------------------------------------------------------------------------------