@driftengine/package 3.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +408 -0
- package/bin/drift-package.mjs +82 -0
- package/dist/android.d.ts +18 -0
- package/dist/android.js +207 -0
- package/dist/bootstrap.d.ts +17 -0
- package/dist/bootstrap.js +326 -0
- package/dist/build.d.ts +11 -0
- package/dist/build.js +248 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +343 -0
- package/dist/flags.d.ts +57 -0
- package/dist/flags.js +122 -0
- package/dist/identity.d.ts +51 -0
- package/dist/identity.js +75 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +24 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +45 -0
- package/dist/ios.d.ts +27 -0
- package/dist/ios.js +152 -0
- package/dist/macCrossSign.d.ts +34 -0
- package/dist/macCrossSign.js +298 -0
- package/dist/main/consoleForward.d.ts +43 -0
- package/dist/main/consoleForward.js +63 -0
- package/dist/main/csp.d.ts +4 -0
- package/dist/main/csp.js +55 -0
- package/dist/main/entry.d.ts +1 -0
- package/dist/main/entry.js +56 -0
- package/dist/main/ipc.d.ts +28 -0
- package/dist/main/ipc.js +284 -0
- package/dist/main/links.d.ts +1 -0
- package/dist/main/links.js +29 -0
- package/dist/main/menu.d.ts +17 -0
- package/dist/main/menu.js +49 -0
- package/dist/main/serve.d.ts +21 -0
- package/dist/main/serve.js +69 -0
- package/dist/main/shortcuts.d.ts +28 -0
- package/dist/main/shortcuts.js +26 -0
- package/dist/main/splash.d.ts +14 -0
- package/dist/main/splash.js +41 -0
- package/dist/main/splashTiming.d.ts +36 -0
- package/dist/main/splashTiming.js +24 -0
- package/dist/main/window.d.ts +19 -0
- package/dist/main/window.js +297 -0
- package/dist/manifest.d.ts +61 -0
- package/dist/manifest.js +91 -0
- package/dist/mobile/assetCheck.d.ts +1 -0
- package/dist/mobile/assetCheck.js +28 -0
- package/dist/mobile/bridgeShim.d.ts +83 -0
- package/dist/mobile/bridgeShim.js +208 -0
- package/dist/mobile/iosShim.d.ts +42 -0
- package/dist/mobile/iosShim.js +104 -0
- package/dist/mobile/report.d.ts +27 -0
- package/dist/mobile/report.js +122 -0
- package/dist/mobile/versionCode.d.ts +12 -0
- package/dist/mobile/versionCode.js +17 -0
- package/dist/outDir.d.ts +30 -0
- package/dist/outDir.js +32 -0
- package/dist/preload/bridge.d.ts +77 -0
- package/dist/preload/bridge.js +2 -0
- package/dist/preload/entry.d.ts +1 -0
- package/dist/preload/entry.js +77 -0
- package/dist/renderer/fileStore.d.ts +23 -0
- package/dist/renderer/fileStore.js +33 -0
- package/dist/renderer/host.d.ts +45 -0
- package/dist/renderer/host.js +30 -0
- package/dist/renderer/nativeHost.d.ts +83 -0
- package/dist/renderer/nativeHost.js +125 -0
- package/dist/resources.d.ts +71 -0
- package/dist/resources.js +83 -0
- package/dist/runtime.d.ts +38 -0
- package/dist/runtime.js +80 -0
- package/dist/runtimeVersion.d.ts +10 -0
- package/dist/runtimeVersion.js +18 -0
- package/dist/signing.d.ts +49 -0
- package/dist/signing.js +135 -0
- package/dist/stage.d.ts +22 -0
- package/dist/stage.js +138 -0
- package/dist/steam/cloudStore.d.ts +30 -0
- package/dist/steam/cloudStore.js +54 -0
- package/dist/steam/depot.d.ts +29 -0
- package/dist/steam/depot.js +39 -0
- package/dist/steam/main.d.ts +22 -0
- package/dist/steam/main.js +41 -0
- package/dist/templates/buildWindows.d.ts +16 -0
- package/dist/templates/buildWindows.js +161 -0
- package/dist/templates/packageWrapper.d.ts +9 -0
- package/dist/templates/packageWrapper.js +80 -0
- package/dist/toolchain.d.ts +101 -0
- package/dist/toolchain.js +186 -0
- package/package.json +73 -0
- package/src/android.ts +256 -0
- package/src/bootstrap.ts +389 -0
- package/src/build.ts +285 -0
- package/src/cli.ts +367 -0
- package/src/flags.ts +171 -0
- package/src/identity.ts +87 -0
- package/src/index.ts +31 -0
- package/src/init.ts +61 -0
- package/src/ios.ts +185 -0
- package/src/macCrossSign.ts +338 -0
- package/src/main/consoleForward.ts +93 -0
- package/src/main/csp.ts +57 -0
- package/src/main/entry.ts +63 -0
- package/src/main/ipc.ts +311 -0
- package/src/main/links.ts +29 -0
- package/src/main/menu.ts +53 -0
- package/src/main/serve.ts +70 -0
- package/src/main/shortcuts.ts +55 -0
- package/src/main/splash.ts +42 -0
- package/src/main/splashTiming.ts +51 -0
- package/src/main/window.ts +316 -0
- package/src/manifest.ts +157 -0
- package/src/mobile/assetCheck.ts +25 -0
- package/src/mobile/bridgeShim.ts +292 -0
- package/src/mobile/iosShim.ts +148 -0
- package/src/mobile/report.ts +121 -0
- package/src/mobile/versionCode.ts +17 -0
- package/src/outDir.ts +37 -0
- package/src/preload/bridge.ts +73 -0
- package/src/preload/entry.ts +91 -0
- package/src/renderer/fileStore.ts +38 -0
- package/src/renderer/host.ts +96 -0
- package/src/renderer/nativeHost.ts +146 -0
- package/src/resources.ts +150 -0
- package/src/runtime.ts +87 -0
- package/src/runtimeVersion.ts +16 -0
- package/src/signing.ts +206 -0
- package/src/stage.ts +180 -0
- package/src/steam/cloudStore.ts +73 -0
- package/src/steam/depot.ts +69 -0
- package/src/steam/main.ts +69 -0
- package/src/templates/buildWindows.ts +163 -0
- package/src/templates/packageWrapper.ts +82 -0
- package/src/toolchain.ts +210 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 Drift Technologies
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/NOTICE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
DriftEngine
|
|
2
|
+
Copyright 2026 Drift Technologies
|
|
3
|
+
|
|
4
|
+
This product includes software developed at Drift Technologies
|
|
5
|
+
(https://github.com/drftrun/driftengine).
|
|
6
|
+
|
|
7
|
+
Licensed under the Apache License, Version 2.0. Section 4(d) of that licence
|
|
8
|
+
requires this NOTICE to be reproduced in any derivative work you distribute,
|
|
9
|
+
in the source, the documentation, or a display generated by the work.
|
package/README.md
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# `@driftengine/package`
|
|
2
|
+
|
|
3
|
+
Turns a game built on this engine into an installable application, without the engine ever
|
|
4
|
+
learning what a shell is.
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
npx drift-package init # writes the two scripts your repository has to own
|
|
8
|
+
npx drift-package doctor # is this project packageable, and how would it be signed
|
|
9
|
+
npx drift-package run # the real shell, from source, no installer
|
|
10
|
+
npx drift-package build --target=linux-x64
|
|
11
|
+
npx drift-package verify --contains=<a string from your diff>
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Start here: `drift-package init`
|
|
15
|
+
|
|
16
|
+
It writes two files into your project and nothing else:
|
|
17
|
+
|
|
18
|
+
| | why it cannot live in the packager |
|
|
19
|
+
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
20
|
+
| `scripts/build-windows.ps1` | Something has to start Node on the Windows machine, and that something cannot be a Node program |
|
|
21
|
+
| `scripts/package.mjs` | A stable entry point, and the one that works when the engine is vendored instead of installed, where `npx drift-package` resolves nothing |
|
|
22
|
+
|
|
23
|
+
Both refuse to be overwritten without `--force`, because once you have edited one it is yours.
|
|
24
|
+
|
|
25
|
+
**They are short on purpose, and that is the whole design.** The first project to ship on three
|
|
26
|
+
platforms wrote **421 lines** of wrapper before this existed, and almost none of it was about their
|
|
27
|
+
game: it stripped `ELECTRON_RUN_AS_NODE`, launched a binary without `npx`, read an error field
|
|
28
|
+
`spawnSync` does not throw, asked whether the filesystem could tell a file from a directory,
|
|
29
|
+
checked an exit code PowerShell ignores, fetched a runtime npm had declined to fetch, and relocated
|
|
30
|
+
a build off a network drive. **All of that is now in `drift-package` itself**, which is why what it
|
|
31
|
+
writes for you is the part that genuinely has to be a file in your repository.
|
|
32
|
+
|
|
33
|
+
What the generated PowerShell carries that a hand-written one usually does not:
|
|
34
|
+
|
|
35
|
+
- Every native command through a step that checks `$LASTEXITCODE`. `$ErrorActionPreference = 'Stop'`
|
|
36
|
+
governs cmdlets and not `npm`, and PowerShell 5.1 — what a fresh Windows machine runs — has no
|
|
37
|
+
`$PSNativeCommandUseErrorActionPreference`. Unchecked, a failing `npm ci` arrives as a complaint
|
|
38
|
+
about a missing output directory with the real error scrolled off.
|
|
39
|
+
- Relocation off a mapped or network drive, **and the copy back**. Forgetting the second half is a
|
|
40
|
+
fix that creates the next problem: the build succeeds, says so, lists its files, and `out/` in
|
|
41
|
+
your checkout is empty.
|
|
42
|
+
- Pure ASCII, asserted by `scripts/packaging.test.mjs`. PowerShell 5.1 reads a BOM-less `.ps1` in
|
|
43
|
+
the system ANSI codepage, so one typographic character in a comment takes the _parser_ with it.
|
|
44
|
+
|
|
45
|
+
## `drift.package.json`
|
|
46
|
+
|
|
47
|
+
Beside your `package.json`, in the project that produces the web build:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"id": "dev.example.title",
|
|
52
|
+
"name": "Title",
|
|
53
|
+
"entry": "dist/index.html",
|
|
54
|
+
"publisher": "Example",
|
|
55
|
+
"icon": "art/icon.png",
|
|
56
|
+
"window": { "width": 1280, "height": 720, "mode": "windowed", "resizable": true },
|
|
57
|
+
"backend": { "webgpu": "prefer", "allowSoftwareRenderer": false },
|
|
58
|
+
"features": { "clipExport": false, "gamepad": true },
|
|
59
|
+
"splash": { "show": true, "minMs": 1400 },
|
|
60
|
+
"targets": ["linux-x64", "win-x64", "mac-arm64"],
|
|
61
|
+
"steam": { "appId": null }
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`id`, `name`, `entry` and `targets` are required; every rejection names the field it is about.
|
|
66
|
+
`icon` is a 1024x1024 PNG and defaults to the engine's own mark, because the alternative default is
|
|
67
|
+
Electron's logo. `window.mode` is what the window is _created_ as — `borderless` makes it
|
|
68
|
+
frameless — and is a different thing from the runtime mode a settings screen changes.
|
|
69
|
+
|
|
70
|
+
## Settings a game drives itself
|
|
71
|
+
|
|
72
|
+
`createHost` hands back every capability the engine takes, picked for wherever the build is
|
|
73
|
+
running. A game writes one settings screen and ships it to both:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { createHost } from '@driftengine/package';
|
|
77
|
+
|
|
78
|
+
const host = createHost(canvas);
|
|
79
|
+
|
|
80
|
+
await host.display.setMode('fullscreen'); // false in a browser outside a gesture
|
|
81
|
+
if (host.display.canSetSize()) {
|
|
82
|
+
// false in a browser, and while fullscreen
|
|
83
|
+
await host.display.setSize(1920, 1080);
|
|
84
|
+
}
|
|
85
|
+
for (const screen of host.display.displays()) {
|
|
86
|
+
console.log(screen.label, screen.width, screen.height, screen.refreshHz);
|
|
87
|
+
}
|
|
88
|
+
host.store.write('settings.volume', '0.8'); // a file in the shell, localStorage in a tab
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Every call that a platform cannot perform answers `false` or `null` rather than doing nothing
|
|
92
|
+
quietly, so a settings screen can grey the control instead of offering one that does not work.
|
|
93
|
+
**And it can ask before it draws**: `canSetSize()` answers the same question `setSize` answers by
|
|
94
|
+
performing it, which until 2026-08-28 was the only way to learn it — reported from outside, where a
|
|
95
|
+
screen that wanted to decide whether to draw a resolution control at all had to resize the window to
|
|
96
|
+
find out, with a no-op probe at boot and a paragraph explaining that it is not what it looks like. It
|
|
97
|
+
is read on each call rather than a constant, because a shell that can resize a window cannot resize
|
|
98
|
+
one that is covering a display; in a shell the question and the refusal are decided by one function,
|
|
99
|
+
so a greyed control cannot be one that would have worked.
|
|
100
|
+
**Render resolution is not here**: covering a display never changes the display's mode, so a game
|
|
101
|
+
that wants fewer pixels changes `RenderQuality.resolutionScale`, which is the engine's business.
|
|
102
|
+
|
|
103
|
+
On a phone, `host.screen` carries what a window does not have — orientation, safe-area insets and
|
|
104
|
+
the wake lock — and each of those can be refused too. iOS refuses an orientation lock outright.
|
|
105
|
+
|
|
106
|
+
## Leaving the game
|
|
107
|
+
|
|
108
|
+
A game decides how it is left, on every platform, through two members of `Lifecycle` and one
|
|
109
|
+
property that says whether an exit button belongs on screen at all.
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const host = createHost(canvas);
|
|
113
|
+
|
|
114
|
+
// Draw an Exit button only where one would do something: false in a browser tab, false on iOS.
|
|
115
|
+
if (host.lifecycle.canQuit) menu.addExitButton(() => host.lifecycle.requestQuit());
|
|
116
|
+
|
|
117
|
+
// Asked before the shell closes anything — a window's close button, or Android's back gesture.
|
|
118
|
+
host.lifecycle.onQuitRequest(() => {
|
|
119
|
+
pause();
|
|
120
|
+
confirm('Leave the run?').then((yes) => {
|
|
121
|
+
if (yes) host.lifecycle.requestQuit();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**On Android the back gesture goes to the game first**, in this order: a registered
|
|
127
|
+
`onQuitRequest` handler if there is one; failing that, a synthesised **Escape** keydown and keyup,
|
|
128
|
+
because a game with no quit handler almost always has a pause menu bound to that key — and
|
|
129
|
+
`preventDefault` is the page saying the keystroke was its own. Only if neither happens does the
|
|
130
|
+
host consider closing, and then it asks twice: two back presses inside two seconds with a line on
|
|
131
|
+
screen between them. A single gesture never ends a run.
|
|
132
|
+
|
|
133
|
+
So a game that does nothing at all still behaves: back opens its pause menu if Escape does, and
|
|
134
|
+
otherwise takes two presses to leave.
|
|
135
|
+
|
|
136
|
+
## Signing
|
|
137
|
+
|
|
138
|
+
Chosen from the environment and printed on every build. `doctor` says what a build would do before
|
|
139
|
+
it does it.
|
|
140
|
+
|
|
141
|
+
| mode | when | what the player sees |
|
|
142
|
+
| ------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
143
|
+
| `none` | no identity, and the target does not require one | Linux: nothing. Windows: SmartScreen warns on first run |
|
|
144
|
+
| `adhoc` | no identity, and the target requires a signature to run at all | macOS: runs locally; from a download, Gatekeeper refuses until the quarantine flag is cleared |
|
|
145
|
+
| `real` | an identity is in the environment | nothing: the artifact is trusted |
|
|
146
|
+
|
|
147
|
+
macOS with no certificate is `adhoc` rather than unsigned because Apple Silicon's **loader**
|
|
148
|
+
refuses an unsigned binary — that is not Gatekeeper, and no user gesture works around it.
|
|
149
|
+
|
|
150
|
+
Signing material is read from the environment and never from `drift.package.json`, which is
|
|
151
|
+
committed. The names are electron-builder's own: `CSC_LINK`, `CSC_KEY_PASSWORD`, `WIN_CSC_LINK`,
|
|
152
|
+
`WIN_CSC_KEY_PASSWORD`, and `APPLE_ID` / `APPLE_APP_SPECIFIC_PASSWORD` / `APPLE_TEAM_ID` for
|
|
153
|
+
notarisation.
|
|
154
|
+
|
|
155
|
+
**A Steam depot needs none of it**: the client delivering it is signed, so an unsigned Windows
|
|
156
|
+
build inside one raises no warning. A certificate buys direct downloads from a website.
|
|
157
|
+
|
|
158
|
+
## Steam
|
|
159
|
+
|
|
160
|
+
```jsonc
|
|
161
|
+
"steam": { "appId": 480 }
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
npm install steamworks.js # in the game, not here: a game that never ships on Steam ships no SDK
|
|
166
|
+
npx drift-package build --target=linux-x64
|
|
167
|
+
npx drift-package steam --target=linux-x64 # writes the two files steamcmd uploads with
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Three things happen once the manifest names an app.
|
|
171
|
+
|
|
172
|
+
**Saves go to Steam Cloud** and a game changes nothing to get them. `host.store` is the same
|
|
173
|
+
`KeyValueStore` it always was; whether those bytes land in a file beside the application or in the
|
|
174
|
+
cloud is the main process's decision, and the cloud is read first at boot so a player who changed
|
|
175
|
+
machines finds their settings. Cloud off for the title or the account degrades to the local file.
|
|
176
|
+
|
|
177
|
+
**Achievements and presence arrive** as `host.services`, which is `null` everywhere else — in a
|
|
178
|
+
browser, where the manifest named no app, and when the copy was launched outside Steam. That last
|
|
179
|
+
one is every run during development, so a game checks for null once rather than calling into
|
|
180
|
+
something that quietly does nothing.
|
|
181
|
+
|
|
182
|
+
**The SDK lives in the main process**, and it has to: `steamworks.js` is a native Node module and
|
|
183
|
+
the renderer runs sandboxed with no Node at all. The packager copies it out of the game's own
|
|
184
|
+
`node_modules` into the artifact and unpacks it from the asar archive, because a `.node` binary
|
|
185
|
+
inside one fails to load with a message about a missing path. If it is not installed, the build
|
|
186
|
+
says so and produces an artifact with no store rather than one whose achievements do nothing.
|
|
187
|
+
|
|
188
|
+
`drift-package steam` writes `app_build_<appid>.vdf` and `depot_build_<depotid>.vdf` beside the
|
|
189
|
+
artifacts, with **nothing set live** — an upload waits in Steam's admin for a person, which is
|
|
190
|
+
Valve's own default and the line people delete by accident. `--branch=beta` publishes to a branch;
|
|
191
|
+
`--depot=` overrides the depot, which otherwise defaults to the app id plus one.
|
|
192
|
+
|
|
193
|
+
## What a device says about itself
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
…/index.html?report=1
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Draws the renderer string, whether WebGPU is offered, the secure-context answer, the platform and
|
|
200
|
+
the WebView build **over** the running game. It is on every platform and it is deliberately not a
|
|
201
|
+
secret gesture: a tester who has to be told one reports the wrong thing.
|
|
202
|
+
|
|
203
|
+
It matters most on a phone. A pinned desktop Chromium cannot see the class of failure that tier
|
|
204
|
+
exists to catch — WebKit enforces a sixteen-byte uniform array stride that Dawn does not, so the
|
|
205
|
+
shaders that draw everything compiled on every desktop browser and on no iPhone. Every value is
|
|
206
|
+
what the device said, and one that could not be read is shown as `—` rather than filled in.
|
|
207
|
+
|
|
208
|
+
## Getting the toolchain, on any machine
|
|
209
|
+
|
|
210
|
+
```sh
|
|
211
|
+
npx drift-package bootstrap # for the targets in drift.package.json
|
|
212
|
+
npx drift-package bootstrap --target=android # or one of them
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Nothing is installed system-wide and nothing asks for a password. Everything lands under
|
|
216
|
+
`~/.cache/driftengine/toolchain` (`DRIFT_TOOLCHAIN_HOME` moves it), and a machine that already has
|
|
217
|
+
a working `JAVA_HOME` or `ANDROID_HOME` is used as it is rather than duplicated.
|
|
218
|
+
|
|
219
|
+
| target | what bootstrap fetches |
|
|
220
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
221
|
+
| `linux-x64`, `win-x64`, `mac-*` | the Electron runtime, which an `npm install` with scripts disabled leaves out |
|
|
222
|
+
| `android` | Temurin JDK 21, Google's command-line tools, the SDK licences, `platform-tools`, `platforms;android-35`, `build-tools;35.0.0`, and Gradle 8.9 |
|
|
223
|
+
| `ios` | nothing, and it says so: Xcode is an App Store install on a Mac |
|
|
224
|
+
|
|
225
|
+
Versions are pinned in `src/toolchain.ts`. Moving one is a commit with a reason in it, because a
|
|
226
|
+
build that quietly changed its build tools is a build whose output changed for no recorded reason.
|
|
227
|
+
|
|
228
|
+
`doctor` tells you which targets this machine is not ready for, and prints the command that fixes
|
|
229
|
+
each one.
|
|
230
|
+
|
|
231
|
+
## Android
|
|
232
|
+
|
|
233
|
+
```sh
|
|
234
|
+
npx drift-package bootstrap --target=android
|
|
235
|
+
npx drift-package build --target=android
|
|
236
|
+
adb install "out/android/<Game>-<version>.apk"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The host is one activity holding a WebView, serving the game from
|
|
240
|
+
`https://appassets.androidplatform.net/` — an https origin, so a secure context, so ES modules,
|
|
241
|
+
workers and `crossOriginIsolated` all work without a loopback server listening inside a game. The
|
|
242
|
+
bridge is injected before the game's own scripts through `addDocumentStartJavaScript`, which is the
|
|
243
|
+
mobile equivalent of the desktop preload, so `createHost` finds the same `__driftHost` it finds on
|
|
244
|
+
the desktop.
|
|
245
|
+
|
|
246
|
+
**Signing is not optional here and there is no unsigned option to offer**: Android refuses to
|
|
247
|
+
install an unsigned APK at all. Without a keystore the packager generates one and says so on every
|
|
248
|
+
build. That APK installs on a device with developer mode on; it cannot go to the Play Store.
|
|
249
|
+
|
|
250
|
+
**The generated key is in the toolchain cache, and it is the application's permanent identity:**
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
~/.cache/driftengine/toolchain/keystores/<your app id>.jks alias drift, password driftengine
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Not beside the artifact — this page said that until 2026-08-29 and it was wrong, which matters
|
|
257
|
+
because of what the file is. A device refuses an upgrade signed with a _different_ key, reading it
|
|
258
|
+
as a different application wearing the same name, and the only way out on the phone is to
|
|
259
|
+
uninstall, which throws away the player's save. Three things follow, and the first is the one to
|
|
260
|
+
act on today:
|
|
261
|
+
|
|
262
|
+
- **Back it up.** It lives under `~/.cache`, a directory whose entire purpose is being safe to
|
|
263
|
+
delete. Losing it means everybody who installed a build has to uninstall before they can install
|
|
264
|
+
the next one.
|
|
265
|
+
|
|
266
|
+
```sh
|
|
267
|
+
cp ~/.cache/driftengine/toolchain/keystores/<your app id>.jks ~/wherever-you-keep-things/
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
- **A second machine makes a second key, silently.** Build somewhere else and the packager finds no
|
|
271
|
+
keystore for that application id and generates a fresh one: a valid APK, signed by a stranger as
|
|
272
|
+
far as any phone holding the first is concerned. A game that will ever be built on two machines
|
|
273
|
+
needs an owned key, not the generated one.
|
|
274
|
+
|
|
275
|
+
- **Check before you send.** `doctor` says which key a build would use before it spends the minutes,
|
|
276
|
+
and the build prints it again on the way past. To verify an APK you are about to hand somebody:
|
|
277
|
+
|
|
278
|
+
```sh
|
|
279
|
+
~/.cache/driftengine/toolchain/android-sdk/build-tools/35.0.0/apksigner \
|
|
280
|
+
verify --print-certs out/android/<your game>.apk
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
If the digest is not the one you expect, **do not send that file** to anybody who already has a
|
|
284
|
+
build installed: it will not install over the top, and the message the phone shows says nothing
|
|
285
|
+
about signing.
|
|
286
|
+
|
|
287
|
+
`DRIFT_ANDROID_KEYSTORE`, `DRIFT_ANDROID_KEYSTORE_PASSWORD` and `DRIFT_ANDROID_KEY_ALIAS` take over
|
|
288
|
+
the moment there is a real one, through the environment and never through `drift.package.json`,
|
|
289
|
+
which is committed.
|
|
290
|
+
|
|
291
|
+
**The renderer is the platform's WebView, not a bundled Chromium.** That is the whole reason a
|
|
292
|
+
mobile build is 20 MB rather than 200, and it is also why WebGL2 is the baseline there: what the
|
|
293
|
+
device offers is what the game gets, and the acceptance probe decides.
|
|
294
|
+
|
|
295
|
+
## Where each artifact is built
|
|
296
|
+
|
|
297
|
+
An artifact is built on the platform it targets, with one exception, and `build` refuses the ones
|
|
298
|
+
this machine cannot honestly produce.
|
|
299
|
+
|
|
300
|
+
| target | built on | how |
|
|
301
|
+
| ---------------------- | ------------------------------- | --------------------------------------------------------------------------------------- |
|
|
302
|
+
| `linux-x64` | Linux | `npx drift-package build --target=linux-x64` |
|
|
303
|
+
| `win-x64` | Windows, a VM is fine | `drift-package init` writes `scripts/build-windows.ps1` into your project; run it there |
|
|
304
|
+
| `mac-arm64`, `mac-x64` | macOS, **or Linux** | [`scripts/build-macos.sh`](scripts/build-macos.sh), or `build --target=mac-arm64` |
|
|
305
|
+
| `android` | anywhere, the toolchain is Java | `npx drift-package build --target=android` |
|
|
306
|
+
| `ios` | macOS with Xcode | `npx drift-package build --target=ios` |
|
|
307
|
+
|
|
308
|
+
**macOS is the exception, and only from Linux.** Nothing in a Mac build is compiled: Electron ships
|
|
309
|
+
a prebuilt darwin runtime, so packing one is file copying and a plist. What stopped it was never
|
|
310
|
+
the artifact but the signature — electron-builder skips signing anywhere but a Mac, and Apple
|
|
311
|
+
Silicon's loader refuses an unsigned binary outright — so a cross-build applies an ad-hoc signature
|
|
312
|
+
with `rcodesign`, which `bootstrap` fetches. Two things a Linux host cannot do are named rather
|
|
313
|
+
than worked around: there is no `.dmg`, because that is `hdiutil`, so the artifact is a `.zip`; and
|
|
314
|
+
only the machine it runs on can prove it launches, so open one on a Mac before it reaches anybody
|
|
315
|
+
else. A Developer ID in `CSC_LINK` is **refused** here rather than ignored, because a build that
|
|
316
|
+
came out ad-hoc while reporting a certificate is the one that gets sent to strangers by mistake.
|
|
317
|
+
|
|
318
|
+
Windows deliberately does not go through Wine: the NSIS target can be cross-built and the signing
|
|
319
|
+
step cannot reach a Windows certificate store, so the platform whose warnings are hardest to shake
|
|
320
|
+
would be the one built with no way to sign it.
|
|
321
|
+
|
|
322
|
+
### `--out=<dir>`, for a project on a filesystem that misreports itself
|
|
323
|
+
|
|
324
|
+
Everything lands in `out/<target>/` inside the project unless `--out=` says otherwise:
|
|
325
|
+
|
|
326
|
+
```sh
|
|
327
|
+
npx drift-package build --target=win-x64 --out=C:\drift-out
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The layout under the root is the same, so that gives `C:\drift-out\win-x64\stage`. An absolute
|
|
331
|
+
path is used as it stands; a relative one resolves against wherever you ran the command, which is
|
|
332
|
+
not necessarily `--project=`.
|
|
333
|
+
|
|
334
|
+
**It exists because a staging copy can refuse itself.** Node's `fs.cp` will not copy a thing into
|
|
335
|
+
itself and decides that by walking the destination's ancestors comparing `dev` and `ino` against
|
|
336
|
+
the source. On a normal disk this never fires. Measured by a consumer on a Windows VM with the
|
|
337
|
+
project on a drive mapped to a folder on a Linux machine, `drift.package.json` and `out/` both
|
|
338
|
+
answered `dev=66313 ino=-112686486700016` — one identity for a file and a directory — and the very
|
|
339
|
+
first copy of the build failed with a sentence about copying a file into a subdirectory of itself,
|
|
340
|
+
which is the one thing that was not happening. Point `--out=` at a local disk and the condition
|
|
341
|
+
cannot arise.
|
|
342
|
+
|
|
343
|
+
## iOS
|
|
344
|
+
|
|
345
|
+
```sh
|
|
346
|
+
npx drift-package bootstrap --target=ios # fetches XcodeGen; Xcode is an App Store install
|
|
347
|
+
npx drift-package build --target=ios
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
One view controller holding a `WKWebView`, serving the game over a custom scheme through
|
|
351
|
+
`WKURLSchemeHandler` — `file://` is not an origin, and a game loaded from one fails to import its
|
|
352
|
+
own modules. The renderer is WebKit and there is no choice about that: Apple requires it, which is
|
|
353
|
+
why the engine's WebGL2 baseline matters here and why the acceptance probe decides whether WebGPU
|
|
354
|
+
is among what the device offers.
|
|
355
|
+
|
|
356
|
+
**The synchronous problem is worse than Android's.** A `WKScriptMessageHandler` is one-way and
|
|
357
|
+
there is no synchronous return from native code to JavaScript at all — so everything the engine
|
|
358
|
+
must read synchronously is _injected_ as a literal before the page's first line of script, and only
|
|
359
|
+
the mutations are messages. `WKScriptMessageHandlerWithReply` is what lets a save report whether it
|
|
360
|
+
worked, and it is why the deployment target is iOS 15.
|
|
361
|
+
|
|
362
|
+
Saving goes to the share sheet, because there is no Downloads folder on this platform: a clip is
|
|
363
|
+
written to the temporary directory and handed to `UIActivityViewController`, so the player sends it
|
|
364
|
+
to Photos, Files or a message.
|
|
365
|
+
|
|
366
|
+
**Unsigned unless `APPLE_TEAM_ID` is set.** Apple's free tier signs a build for your own device for
|
|
367
|
+
seven days at a time through Xcode's own interface rather than through anything a script can drive,
|
|
368
|
+
so the default output is an archive and an unsigned `.ipa` — the shape a sideloading tool re-signs.
|
|
369
|
+
|
|
370
|
+
**Not yet run on a Mac.** The Swift, the project spec and the build step are written against
|
|
371
|
+
Apple's documented behaviour and nothing here has compiled them. What is not guessed is the bridge,
|
|
372
|
+
which is shared with Android and tested, and the containment rule in the scheme handler, which
|
|
373
|
+
mirrors the desktop shell's. **One question still gates the tier**: whether WebKit treats a custom
|
|
374
|
+
scheme as a secure context. If it does not, `crossOriginIsolated` is false and the answer is a
|
|
375
|
+
loopback server — `SchemeHandler.swift` is the only file that changes, and
|
|
376
|
+
`drift://localhost/__drift/originProbe.html` answers it in one look on a device. The host must be
|
|
377
|
+
`localhost`, since that is what makes the origin trustworthy at all, and the shell's own assets
|
|
378
|
+
live under `__drift/` rather than behind a second host for the same reason.
|
|
379
|
+
|
|
380
|
+
## Three things that will bite
|
|
381
|
+
|
|
382
|
+
**A network share refuses the first copy of a build, and blames itself.** `fs.cp` walks the
|
|
383
|
+
destination's parents and compares each one's `dev` and `ino` with the source's, to avoid copying
|
|
384
|
+
something into itself. A redirector that gets no file index from the server invents a constant one,
|
|
385
|
+
and on Windows `dev` is already the volume's serial number — so a file and a folder on the same
|
|
386
|
+
share compare identical and a perfectly legitimate copy is refused, with `cannot copy … to a
|
|
387
|
+
subdirectory of self …` ten minutes into a build, about the thing furthest from the cause.
|
|
388
|
+
|
|
389
|
+
**`drift-package` already refuses this before a build starts, so you do not need to check for it.**
|
|
390
|
+
`doctor` refuses it first, which is where it belongs for a person's time — a build that cannot
|
|
391
|
+
succeed should stop before it downloads a runtime — and `stageApp` refuses it again immediately
|
|
392
|
+
before its first copy, which is where it belongs for correctness. The second one is not redundant:
|
|
393
|
+
this package's `exports` map is `"./*": "./*"`, so staging is importable directly and a route that
|
|
394
|
+
never passes through `doctor` would otherwise get the `cp` message. It also runs before the previous
|
|
395
|
+
stage directory is removed, so a refused build keeps its last good output.
|
|
396
|
+
|
|
397
|
+
What it prints names the volume, both witnesses with their `dev` and `ino`, and the remedy —
|
|
398
|
+
`drift-package build --out=/a/local/disk`, which is cheaper than moving a project with tens of
|
|
399
|
+
gigabytes of assets. If your launcher carries a copy of this check, it can go.
|
|
400
|
+
|
|
401
|
+
**`ELECTRON_RUN_AS_NODE`.** Any Electron-based terminal — an editor's integrated one is the common
|
|
402
|
+
case — exports this for the processes it spawns, and it turns Electron into plain Node. A packaged
|
|
403
|
+
game inheriting it prints a Node banner and dies on `Cannot find module 'electron'`. Both scripts
|
|
404
|
+
above unset it and so does `drift-package run`.
|
|
405
|
+
|
|
406
|
+
**A fresh `npm ci` may not fetch the Electron binary.** The package installs; its postinstall,
|
|
407
|
+
which downloads the runtime, is skipped by some environments. `node node_modules/electron/install.js`
|
|
408
|
+
fetches it.
|