@makefully/adaptfully 3.2.0 → 3.4.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/CHANGELOG.md +28 -0
- package/README.md +13 -2
- package/lib/node/index.js +10 -3
- package/lib/node/packagers/base.js +303 -0
- package/lib/node/packagers/capacitor.js +13 -0
- package/lib/node/packagers/cordova.js +44 -0
- package/lib/node/packagers/electron.js +222 -0
- package/lib/node/packagers/index.js +107 -0
- package/lib/node/packagers/web.js +13 -0
- package/lib/node/packagers.js +1 -157
- package/lib/node/paths.js +0 -5
- package/lib/node/prebuild.js +5 -3
- package/package.json +1 -1
- package/lib/templates/electron/main.js +0 -104
- package/lib/templates/electron/preload.js +0 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## 3.4.0 — 2026-06-25
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Electron **`main.js`** allows same-app `file://` navigation while still opening external http(s) links in the system browser.
|
|
10
|
+
- Steam builds (`steam-auth`) set renderer **`sandbox: false`** so the steamworks preload bridge can reach the main process; non-Steam Electron builds keep `sandbox: true`.
|
|
11
|
+
|
|
12
|
+
## 3.3.0 — 2026-06-23
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **Packager classes** — `Packager`, `WebPackager`, `ElectronPackager`, `CordovaPackager`, and `CapacitorPackager` encapsulate per-packager prebuild behavior behind a shared API (`validate`, `applyTemplates`, `applyHtmlExtras`, `prebuild`).
|
|
17
|
+
- **`createPackagerForPlatform(platformKey, pkg, options)`** — factory that resolves the configured packager and returns an instance. Options include `platforms` (target platform keys, e.g. `ios` + `android` for Cordova) and `platformKey` (active prebuild platform).
|
|
18
|
+
- **Plugin detection** — `collectUsedPlugins()` and `usesPlugin(id)` inspect standard Adaptfully auth/storage registrations across targeted platforms so packagers can adapt output (e.g. Electron injects steamworks init when `steam-auth` is registered).
|
|
19
|
+
- **Cordova prebuild** — writes `cordova.js` stub, injects CSP/viewport meta tags and `game-config.js` script tags into HTML.
|
|
20
|
+
- **`buildElectronMain()` / `buildElectronPreload()`** — exported helpers that compose Electron shell files (used by `ElectronPackager` and available for tests or extensions).
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Prebuild runs packager work through a single `packager.prebuild(dest, htmlPaths)` call instead of separate template/HTML helper functions.
|
|
25
|
+
- Electron **`main.js`** and **`preload.js`** are composed from embedded strings in `ElectronPackager` rather than read from disk templates.
|
|
26
|
+
- **`web`** packager writes **`game-config.js`** during prebuild when the platform key is **`uwp`**.
|
|
27
|
+
|
|
28
|
+
### Removed
|
|
29
|
+
|
|
30
|
+
- **`lib/templates/`** — packager output is generated in code; no template files are shipped with the package.
|
|
31
|
+
- **`getTemplatesDir()`**, **`resolvePackagerTemplateDir()`**, and **`applyTemplateMarker()`** from the public API.
|
|
32
|
+
|
|
5
33
|
## 3.2.0 — 2026-06-16
|
|
6
34
|
|
|
7
35
|
### Added
|
package/README.md
CHANGED
|
@@ -101,7 +101,16 @@ Standard plugin keys load bundled Adaptfully runtime scripts and emit an inline
|
|
|
101
101
|
|
|
102
102
|
Wrapfully builders (`steam`, `win`, `mac`, `android`, etc.) map to platform keys via defaults (`win` → `steam`) or an explicit `builders` array on the platform config.
|
|
103
103
|
|
|
104
|
-
Each platform entry may set a **`packager`** (`web`, `electron`, `cordova`, or
|
|
104
|
+
Each platform entry may set a **`packager`** (`web`, `electron`, `cordova`, or `capacitor`). Defaults to **`web`** — copy deploy and inject HTML only. Packagers are implemented as classes (`WebPackager`, `ElectronPackager`, `CordovaPackager`, `CapacitorPackager`) that handle prebuild output for their target platforms.
|
|
105
|
+
|
|
106
|
+
| Packager | Prebuild adds |
|
|
107
|
+
|----------|---------------|
|
|
108
|
+
| `web` | `game-config.js` for **`uwp`** platform prebuilds |
|
|
109
|
+
| `electron` | `main.js` (Electron shell); `preload.js` when **`steam-auth`** is registered |
|
|
110
|
+
| `cordova` | `cordova.js` stub, `game-config.js`, CSP/viewport HTML extras |
|
|
111
|
+
| `capacitor` | `game-config.js` (more Capacitor-specific output planned) |
|
|
112
|
+
|
|
113
|
+
**`steam-auth` requires `packager: "electron"`** and `config.steamId`.
|
|
105
114
|
|
|
106
115
|
```json
|
|
107
116
|
{
|
|
@@ -140,6 +149,7 @@ The renderer `steam-auth` plugin reads the Steam ID from that bridge. When Steam
|
|
|
140
149
|
```javascript
|
|
141
150
|
import {
|
|
142
151
|
prebuildPlatform,
|
|
152
|
+
createPackagerForPlatform,
|
|
143
153
|
resolveHtmlInjections,
|
|
144
154
|
runAdaptfullyStage,
|
|
145
155
|
buildAdaptfullyInjection,
|
|
@@ -155,6 +165,7 @@ import {
|
|
|
155
165
|
```
|
|
156
166
|
|
|
157
167
|
- **`prebuildPlatform(deployFolder, platformKey, pkg)`** — copy `deploy/` to `output/<platform>-prebuild/` and inject registrations into `config.htmlInjections` (default: `index.html`).
|
|
168
|
+
- **`createPackagerForPlatform(platformKey, pkg, { platforms, log })`** — get a packager instance for custom prebuild or future build/deploy integration. The instance exposes `usesPlugin('steam-auth')`, `collectUsedPlugins()`, and `prebuild(dest, htmlPaths)`.
|
|
158
169
|
- **`resolveRegistrationAssets(registrations)`** — resolve runtime script paths, inline registration JS, and external script tags for a registration map (useful for Vite dev servers).
|
|
159
170
|
- **`runAdaptfullyStage('prebuild' | 'build' | 'deploy', platformKey, options)`** — run a pipeline stage programmatically.
|
|
160
171
|
- **`platform.autoLogin(callback)`** — restore a prior session without UI when the auth plugin supports it (Google uses `lastLoggedIn` in storage and a cached OAuth token).
|
|
@@ -373,7 +384,7 @@ Standard npm fields (`name`, `version`, `description`) are used directly. Add a
|
|
|
373
384
|
| `platforms` | Prebuild | Per-platform registration maps (see [Adaptfully runtime](#adaptfully-runtime)) |
|
|
374
385
|
| `platforms.<name>.builder` | Build/deploy | Override Wrapfully builder for a platform (default: `web` → `webapp`, others match platform key) |
|
|
375
386
|
| `platforms.<name>.builders` | wrapfully-deploy | Map additional Wrapfully builder names to a platform |
|
|
376
|
-
| `platforms.<name>.packager` | Prebuild | `web` (default), `electron`, `cordova`, or `capacitor` —
|
|
387
|
+
| `platforms.<name>.packager` | Prebuild | `web` (default), `electron`, `cordova`, or `capacitor` — selects the packager class that adds platform-specific files during prebuild |
|
|
377
388
|
| `properties` | Cordova | Cordova config.xml entries (plugins, allow-navigation, etc.) |
|
|
378
389
|
|
|
379
390
|
### `wrapfully.json`
|
package/lib/node/index.js
CHANGED
|
@@ -3,13 +3,20 @@ export { loadProjectConfig, resolveServerUrl } from './config.js';
|
|
|
3
3
|
export { send } from './deploy.js';
|
|
4
4
|
export { adaptfullyFromCli, runAdaptfullyStage } from './pipeline.js';
|
|
5
5
|
export { prebuildPlatform, prebuildOutputDir, resolveHtmlInjections } from './prebuild.js';
|
|
6
|
-
export { getPackageRoot, getRuntimeDir,
|
|
6
|
+
export { getPackageRoot, getRuntimeDir, resolveRuntimeScript } from './paths.js';
|
|
7
7
|
export {
|
|
8
|
+
CapacitorPackager,
|
|
9
|
+
CordovaPackager,
|
|
10
|
+
ElectronPackager,
|
|
11
|
+
Packager,
|
|
8
12
|
VALID_PACKAGERS,
|
|
13
|
+
WebPackager,
|
|
14
|
+
applyPackagerHtmlExtras,
|
|
9
15
|
applyPackagerTemplates,
|
|
10
|
-
|
|
16
|
+
buildElectronMain,
|
|
17
|
+
buildElectronPreload,
|
|
18
|
+
createPackagerForPlatform,
|
|
11
19
|
resolvePlatformPackager,
|
|
12
|
-
resolvePackagerTemplateDir,
|
|
13
20
|
usesSteamAuth,
|
|
14
21
|
validatePlatformPackager,
|
|
15
22
|
} from './packagers.js';
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { STANDARD_PLUGINS } from '../registrations.js';
|
|
4
|
+
|
|
5
|
+
/** @typedef {import('../registrations.js').RegistrationMap} RegistrationMap */
|
|
6
|
+
/** @typedef {'web' | 'electron' | 'cordova' | 'capacitor'} PackagerName */
|
|
7
|
+
|
|
8
|
+
/** @type {PackagerName[]} */
|
|
9
|
+
export const VALID_PACKAGERS = ['web', 'electron', 'cordova', 'capacitor'];
|
|
10
|
+
|
|
11
|
+
const PACKAGER_MARKER = '<!-- adaptfully-packager -->';
|
|
12
|
+
const PACKAGER_END_MARKER = '<!-- /adaptfully-packager -->';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} PackagerOptions
|
|
16
|
+
* @property {string[]} [platforms] Platform keys this packager targets (e.g. ios + android for Cordova)
|
|
17
|
+
* @property {string} [platformKey] Active platform for a single prebuild run
|
|
18
|
+
* @property {(message: string) => void} [log]
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} value
|
|
23
|
+
*/
|
|
24
|
+
function isDeployPath(value) {
|
|
25
|
+
return value.startsWith('/') || value.startsWith('./') || value.includes('/');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} platformKey
|
|
30
|
+
*/
|
|
31
|
+
function gameConfigPlatform(platformKey) {
|
|
32
|
+
if (platformKey === 'uwp') {
|
|
33
|
+
return 'ms';
|
|
34
|
+
}
|
|
35
|
+
return platformKey;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {string} dest
|
|
40
|
+
* @param {string} platformKey
|
|
41
|
+
* @param {{ name: string, version: string, config?: { title?: string } }} pkg
|
|
42
|
+
* @param {(message: string) => void} log
|
|
43
|
+
*/
|
|
44
|
+
function writeGameConfig(dest, platformKey, pkg, log) {
|
|
45
|
+
const content = `window.gameConfig = ${JSON.stringify({
|
|
46
|
+
title: pkg.config?.title,
|
|
47
|
+
version: pkg.version,
|
|
48
|
+
id: pkg.name,
|
|
49
|
+
platform: gameConfigPlatform(platformKey),
|
|
50
|
+
}, null, 4)};`;
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(path.join(dest, 'game-config.js'), content);
|
|
53
|
+
log('adaptfully: write game-config.js');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} html
|
|
58
|
+
* @param {string} injection
|
|
59
|
+
*/
|
|
60
|
+
function injectPackagerExtras(html, injection) {
|
|
61
|
+
if (!injection) {
|
|
62
|
+
return html;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const markerPattern = new RegExp(
|
|
66
|
+
`${escapeRegExp(PACKAGER_MARKER)}[\\s\\S]*?${escapeRegExp(PACKAGER_END_MARKER)}\\n?`,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (markerPattern.test(html)) {
|
|
70
|
+
return html.replace(markerPattern, injection);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (html.includes(PACKAGER_MARKER)) {
|
|
74
|
+
return html.replace(PACKAGER_MARKER, `${injection}${PACKAGER_MARKER}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (html.includes('<!-- adaptfully -->')) {
|
|
78
|
+
return html.replace('<!-- adaptfully -->', `${injection}<!-- adaptfully -->`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (html.includes('<!-- scripts -->')) {
|
|
82
|
+
return html.replace('<!-- scripts -->', `${injection}<!-- scripts -->`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (html.includes('</head>')) {
|
|
86
|
+
return html.replace('</head>', `${injection}</head>`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error(
|
|
90
|
+
'Cannot inject packager extras: HTML needs '
|
|
91
|
+
+ '<!-- adaptfully-packager -->…<!-- /adaptfully-packager -->, '
|
|
92
|
+
+ '<!-- adaptfully -->, <!-- scripts -->, or </head>',
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function escapeRegExp(value) {
|
|
97
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class Packager {
|
|
101
|
+
/** @type {PackagerName} */
|
|
102
|
+
static id = 'web';
|
|
103
|
+
|
|
104
|
+
/** @type {string[]} */
|
|
105
|
+
static defaultPlatforms = [];
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {object} pkg
|
|
109
|
+
* @param {PackagerOptions} [options]
|
|
110
|
+
*/
|
|
111
|
+
constructor(pkg, options = {}) {
|
|
112
|
+
this.pkg = pkg;
|
|
113
|
+
this.log = options.log ?? (() => {});
|
|
114
|
+
this.platformKey = options.platformKey ?? null;
|
|
115
|
+
|
|
116
|
+
const configured = Packager.resolveConfiguredPlatforms(this.constructor.id, pkg);
|
|
117
|
+
if (options.platforms?.length) {
|
|
118
|
+
this.platforms = [...options.platforms];
|
|
119
|
+
} else if (configured.length) {
|
|
120
|
+
this.platforms = configured;
|
|
121
|
+
} else {
|
|
122
|
+
this.platforms = [...this.constructor.defaultPlatforms];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.platformKey && !this.platforms.includes(this.platformKey)) {
|
|
126
|
+
this.platforms.push(this.platformKey);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** @returns {PackagerName} */
|
|
131
|
+
get name() {
|
|
132
|
+
return /** @type {PackagerName} */ (this.constructor.id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {PackagerName} packagerId
|
|
137
|
+
* @param {{ config?: { platforms?: Record<string, { packager?: string }> } }} pkg
|
|
138
|
+
* @returns {string[]}
|
|
139
|
+
*/
|
|
140
|
+
static resolveConfiguredPlatforms(packagerId, pkg) {
|
|
141
|
+
const platforms = pkg.config?.platforms ?? {};
|
|
142
|
+
return Object.entries(platforms)
|
|
143
|
+
.filter(([, config]) => (config?.packager ?? 'web') === packagerId)
|
|
144
|
+
.map(([key]) => key);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @param {string} platformKey
|
|
149
|
+
* @param {{ config?: { platforms?: Record<string, { packager?: string }> } }} pkg
|
|
150
|
+
* @returns {PackagerName}
|
|
151
|
+
*/
|
|
152
|
+
static resolvePlatformPackager(platformKey, pkg) {
|
|
153
|
+
const platform = pkg.config?.platforms?.[platformKey];
|
|
154
|
+
const packager = platform?.packager ?? 'web';
|
|
155
|
+
return /** @type {PackagerName} */ (packager);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Merged registrations from every platform in {@link this.platforms}.
|
|
160
|
+
* @returns {RegistrationMap}
|
|
161
|
+
*/
|
|
162
|
+
collectRegistrations() {
|
|
163
|
+
/** @type {RegistrationMap} */
|
|
164
|
+
const merged = {};
|
|
165
|
+
for (const key of this.platforms) {
|
|
166
|
+
const registrations = this.pkg.config?.platforms?.[key]?.registrations ?? {};
|
|
167
|
+
Object.assign(merged, registrations);
|
|
168
|
+
}
|
|
169
|
+
return merged;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Registrations for the active prebuild platform, or merged when none is set.
|
|
174
|
+
* @returns {RegistrationMap}
|
|
175
|
+
*/
|
|
176
|
+
getActiveRegistrations() {
|
|
177
|
+
if (this.platformKey) {
|
|
178
|
+
return this.pkg.config?.platforms?.[this.platformKey]?.registrations ?? {};
|
|
179
|
+
}
|
|
180
|
+
return this.collectRegistrations();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Standard Adaptfully plugin ids (auth, storage, etc.) used across targeted platforms.
|
|
185
|
+
* Custom deploy paths are excluded.
|
|
186
|
+
* @returns {Set<string>}
|
|
187
|
+
*/
|
|
188
|
+
collectUsedPlugins() {
|
|
189
|
+
const plugins = new Set();
|
|
190
|
+
for (const key of this.platforms) {
|
|
191
|
+
const registrations = this.pkg.config?.platforms?.[key]?.registrations ?? {};
|
|
192
|
+
for (const value of Object.values(registrations)) {
|
|
193
|
+
if (typeof value === 'string' && !isDeployPath(value) && STANDARD_PLUGINS[value]) {
|
|
194
|
+
plugins.add(value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return plugins;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {string} pluginId Standard plugin id (e.g. steam-auth, dev-auth, localStorage)
|
|
203
|
+
* @param {{ scope?: 'active' | 'all' }} [options]
|
|
204
|
+
*/
|
|
205
|
+
usesPlugin(pluginId, options = {}) {
|
|
206
|
+
const registrations = options.scope === 'all'
|
|
207
|
+
? this.collectRegistrations()
|
|
208
|
+
: this.getActiveRegistrations();
|
|
209
|
+
return Object.values(registrations).includes(pluginId);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
validate() {
|
|
213
|
+
if (!VALID_PACKAGERS.includes(this.name)) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Invalid packager "${this.name}". Expected one of: ${VALID_PACKAGERS.join(', ')}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (this.usesPlugin('steam-auth', { scope: 'all' }) && this.name !== 'electron') {
|
|
220
|
+
const platformLabel = this.platformKey ?? this.platforms[0] ?? 'unknown';
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Platform "${platformLabel}" uses steam-auth but packager is "${this.name}". `
|
|
223
|
+
+ `Set config.platforms.${platformLabel}.packager to "electron".`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
needsGameConfig() {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* @param {string[]} headExtras
|
|
234
|
+
* @param {string[]} bodyScripts
|
|
235
|
+
* @returns {string}
|
|
236
|
+
*/
|
|
237
|
+
formatHtmlInjection(headExtras, bodyScripts) {
|
|
238
|
+
if (headExtras.length === 0 && bodyScripts.length === 0) {
|
|
239
|
+
return '';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
let block = `${PACKAGER_MARKER}\n`;
|
|
243
|
+
for (const extra of headExtras) {
|
|
244
|
+
block += `${extra}\n`;
|
|
245
|
+
}
|
|
246
|
+
for (const script of bodyScripts) {
|
|
247
|
+
block += `${script}\n`;
|
|
248
|
+
}
|
|
249
|
+
block += `${PACKAGER_END_MARKER}\n`;
|
|
250
|
+
return block;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** @param {string} dest */
|
|
254
|
+
applyTemplates(dest) {
|
|
255
|
+
if (this.needsGameConfig() && this.platformKey) {
|
|
256
|
+
writeGameConfig(dest, this.platformKey, this.pkg, this.log);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** @returns {string} */
|
|
261
|
+
buildHtmlInjection() {
|
|
262
|
+
const bodyScripts = [];
|
|
263
|
+
if (this.needsGameConfig()) {
|
|
264
|
+
bodyScripts.push('<script src="game-config.js"></script>');
|
|
265
|
+
}
|
|
266
|
+
return this.formatHtmlInjection([], bodyScripts);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* @param {string} dest
|
|
271
|
+
* @param {string[]} htmlPaths
|
|
272
|
+
*/
|
|
273
|
+
applyHtmlExtras(dest, htmlPaths) {
|
|
274
|
+
const injection = this.buildHtmlInjection();
|
|
275
|
+
if (!injection) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const relativePath of htmlPaths) {
|
|
280
|
+
const htmlPath = path.join(dest, relativePath);
|
|
281
|
+
if (!fs.existsSync(htmlPath)) {
|
|
282
|
+
throw new Error(`htmlInjections file not found in deploy output: ${relativePath}`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const html = fs.readFileSync(htmlPath, 'utf8');
|
|
286
|
+
const updated = injectPackagerExtras(html, injection);
|
|
287
|
+
if (updated !== html) {
|
|
288
|
+
this.log(`adaptfully: inject packager extras into ${relativePath}`);
|
|
289
|
+
fs.writeFileSync(htmlPath, updated);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @param {string} dest
|
|
296
|
+
* @param {string[]} htmlPaths
|
|
297
|
+
*/
|
|
298
|
+
prebuild(dest, htmlPaths) {
|
|
299
|
+
this.validate();
|
|
300
|
+
this.applyTemplates(dest);
|
|
301
|
+
this.applyHtmlExtras(dest, htmlPaths);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Packager } from './base.js';
|
|
2
|
+
|
|
3
|
+
export class CapacitorPackager extends Packager {
|
|
4
|
+
/** @type {'capacitor'} */
|
|
5
|
+
static id = 'capacitor';
|
|
6
|
+
|
|
7
|
+
/** @type {string[]} */
|
|
8
|
+
static defaultPlatforms = ['ios', 'android'];
|
|
9
|
+
|
|
10
|
+
needsGameConfig() {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Packager } from './base.js';
|
|
4
|
+
|
|
5
|
+
const CORDOVA_CSP = '<meta http-equiv="Content-Security-Policy" content="default-src * \'self\' data: gap: \'unsafe-inline\' \'unsafe-eval\'; style-src * \'self\' \'unsafe-inline\' \'unsafe-eval\' gap:; script-src * \'self\' \'unsafe-inline\' \'unsafe-eval\' gap:; frame-src *;" />';
|
|
6
|
+
|
|
7
|
+
const CORDOVA_VIEWPORT = '<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />';
|
|
8
|
+
|
|
9
|
+
const CORDOVA_STUB = `/**
|
|
10
|
+
* Stub for non-Cordova environments. Cordova replaces this at runtime on device builds.
|
|
11
|
+
*/
|
|
12
|
+
window.cordova = null;
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
export class CordovaPackager extends Packager {
|
|
16
|
+
/** @type {'cordova'} */
|
|
17
|
+
static id = 'cordova';
|
|
18
|
+
|
|
19
|
+
/** @type {string[]} */
|
|
20
|
+
static defaultPlatforms = ['ios', 'android'];
|
|
21
|
+
|
|
22
|
+
needsGameConfig() {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {string} dest */
|
|
27
|
+
applyTemplates(dest) {
|
|
28
|
+
fs.writeFileSync(path.join(dest, 'cordova.js'), CORDOVA_STUB);
|
|
29
|
+
this.log('adaptfully: write cordova.js');
|
|
30
|
+
|
|
31
|
+
super.applyTemplates(dest);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
buildHtmlInjection() {
|
|
35
|
+
const headExtras = [CORDOVA_CSP, CORDOVA_VIEWPORT];
|
|
36
|
+
const bodyScripts = ['<script src="cordova.js"></script>'];
|
|
37
|
+
|
|
38
|
+
if (this.needsGameConfig()) {
|
|
39
|
+
bodyScripts.push('<script src="game-config.js"></script>');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return this.formatHtmlInjection(headExtras, bodyScripts);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Packager } from './base.js';
|
|
4
|
+
|
|
5
|
+
const STEAM_INIT = `const path = require('path');
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
require('steamworks.js').electronEnableSteamOverlay();
|
|
9
|
+
} catch (err) {
|
|
10
|
+
console.error('Steam overlay unavailable:', err);
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
const STEAM_PRELOAD = `,
|
|
15
|
+
preload: path.join(__dirname, 'preload.js')`;
|
|
16
|
+
|
|
17
|
+
const ELECTRON_MAIN_HEAD = `/* eslint-disable no-sync */
|
|
18
|
+
/* global process, require */
|
|
19
|
+
const
|
|
20
|
+
{app, shell, BrowserWindow, Menu} = require('electron'),
|
|
21
|
+
fs = require('fs');
|
|
22
|
+
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
const ELECTRON_MAIN_BODY = `
|
|
26
|
+
/**
|
|
27
|
+
* Opens http(s) links in the system browser; allows same-app file:// navigation.
|
|
28
|
+
* @param {Electron.BrowserWindow} win
|
|
29
|
+
*/
|
|
30
|
+
function attachExternalLinkHandlers(win) {
|
|
31
|
+
win.webContents.setWindowOpenHandler(({url}) => {
|
|
32
|
+
if (url.startsWith('file:')) {
|
|
33
|
+
return {action: 'deny'};
|
|
34
|
+
}
|
|
35
|
+
shell.openExternal(url);
|
|
36
|
+
return {action: 'deny'};
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
win.webContents.on('will-navigate', (event, url) => {
|
|
40
|
+
if (url.startsWith('file:')) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
event.preventDefault();
|
|
44
|
+
shell.openExternal(url);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates window.
|
|
50
|
+
*/
|
|
51
|
+
function createWindow () {
|
|
52
|
+
// Create the browser window.
|
|
53
|
+
const
|
|
54
|
+
debugMode = false,
|
|
55
|
+
lastState = (function () {
|
|
56
|
+
let state = null;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
state = JSON.parse(fs.readFileSync(app.getPath('userData') + '/window-state.json', 'utf8'));
|
|
60
|
+
} catch (e) {}
|
|
61
|
+
|
|
62
|
+
return state || {
|
|
63
|
+
bounds: {
|
|
64
|
+
width: 800,
|
|
65
|
+
height: 600
|
|
66
|
+
},
|
|
67
|
+
maximized: false,
|
|
68
|
+
fullscreen: false
|
|
69
|
+
};
|
|
70
|
+
})(),
|
|
71
|
+
win = new BrowserWindow({
|
|
72
|
+
backgroundColor: '#E4378E',
|
|
73
|
+
width: lastState.bounds.width,
|
|
74
|
+
height: lastState.bounds.height,
|
|
75
|
+
minWidth: 200,
|
|
76
|
+
minHeight: 100,
|
|
77
|
+
webPreferences: {
|
|
78
|
+
devTools: debugMode,
|
|
79
|
+
nodeIntegration: false,
|
|
80
|
+
contextIsolation: true,
|
|
81
|
+
sandbox: __SANDBOX__`;
|
|
82
|
+
|
|
83
|
+
const ELECTRON_MAIN_TAIL = `
|
|
84
|
+
},
|
|
85
|
+
show: false
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (lastState.maximized) {
|
|
89
|
+
win.maximize();
|
|
90
|
+
}
|
|
91
|
+
if (lastState.fullscreen) {
|
|
92
|
+
win.setFullScreen(true);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!debugMode) {
|
|
96
|
+
Menu.setApplicationMenu(null);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
win.once('ready-to-show', () => { // helps prevent flicker-loading
|
|
100
|
+
win.show();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// and load the index.html of the app.
|
|
104
|
+
win.loadFile('index.html');
|
|
105
|
+
|
|
106
|
+
attachExternalLinkHandlers(win);
|
|
107
|
+
|
|
108
|
+
//win.webContents.openDevTools();
|
|
109
|
+
|
|
110
|
+
win.on('close', () => {
|
|
111
|
+
fs.writeFileSync(app.getPath('userData') + '/window-state.json', JSON.stringify({
|
|
112
|
+
bounds: win.getNormalBounds(),
|
|
113
|
+
maximized: win.isMaximized(),
|
|
114
|
+
fullscreen: win.isFullScreen()
|
|
115
|
+
}));
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// This method will be called when Electron has finished
|
|
120
|
+
// initialization and is ready to create browser windows.
|
|
121
|
+
// Some APIs can only be used after this event occurs.
|
|
122
|
+
app.whenReady().then(createWindow);
|
|
123
|
+
|
|
124
|
+
// Quit when all windows are closed.
|
|
125
|
+
app.on('window-all-closed', () => {
|
|
126
|
+
// On macOS it is common for applications and their menu bar
|
|
127
|
+
// to stay active until the user quits explicitly with Cmd + Q
|
|
128
|
+
if (process.platform !== 'darwin') {
|
|
129
|
+
app.quit();
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
app.on('activate', () => {
|
|
134
|
+
// On macOS it's common to re-create a window in the app when the
|
|
135
|
+
// dock icon is clicked and there are no other windows open.
|
|
136
|
+
if (BrowserWindow.getAllWindows().length === 0) {
|
|
137
|
+
createWindow();
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
`;
|
|
141
|
+
|
|
142
|
+
const ELECTRON_PRELOAD_HEAD = `/* global require */
|
|
143
|
+
const { contextBridge } = require('electron');
|
|
144
|
+
|
|
145
|
+
let steamClient = null;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
steamClient = require('steamworks.js').init(`;
|
|
149
|
+
|
|
150
|
+
const ELECTRON_PRELOAD_TAIL = `);
|
|
151
|
+
} catch (err) {
|
|
152
|
+
console.error('Steamworks init failed:', err);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (steamClient) {
|
|
156
|
+
contextBridge.exposeInMainWorld('__ADAPTFULLY_STEAMWORKS__', steamClient);
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {boolean} withSteam
|
|
162
|
+
*/
|
|
163
|
+
export function buildElectronMain(withSteam) {
|
|
164
|
+
const steamInit = withSteam ? STEAM_INIT : '';
|
|
165
|
+
const steamPreload = withSteam ? STEAM_PRELOAD : '';
|
|
166
|
+
const sandbox = withSteam ? 'false' : 'true';
|
|
167
|
+
const mainBody = ELECTRON_MAIN_BODY.replace('__SANDBOX__', sandbox);
|
|
168
|
+
return ELECTRON_MAIN_HEAD + steamInit + mainBody + steamPreload + ELECTRON_MAIN_TAIL;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* @param {number | string} steamAppId
|
|
173
|
+
*/
|
|
174
|
+
export function buildElectronPreload(steamAppId) {
|
|
175
|
+
return ELECTRON_PRELOAD_HEAD + steamAppId + ELECTRON_PRELOAD_TAIL;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export class ElectronPackager extends Packager {
|
|
179
|
+
/** @type {'electron'} */
|
|
180
|
+
static id = 'electron';
|
|
181
|
+
|
|
182
|
+
/** @type {string[]} */
|
|
183
|
+
static defaultPlatforms = ['steam'];
|
|
184
|
+
|
|
185
|
+
validate() {
|
|
186
|
+
super.validate();
|
|
187
|
+
|
|
188
|
+
if (this.usesPlugin('steam-auth') && !this.pkg.config?.steamId) {
|
|
189
|
+
const platformLabel = this.platformKey ?? this.platforms.join(', ');
|
|
190
|
+
throw new Error(
|
|
191
|
+
`Platform "${platformLabel}" uses steam-auth but config.steamId is not set.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
needsGameConfig() {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** @param {string} dest */
|
|
201
|
+
applyTemplates(dest) {
|
|
202
|
+
this.writeElectronMain(dest);
|
|
203
|
+
super.applyTemplates(dest);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** @param {string} dest */
|
|
207
|
+
writeElectronMain(dest) {
|
|
208
|
+
const withSteam = this.usesPlugin('steam-auth');
|
|
209
|
+
const mainContent = buildElectronMain(withSteam);
|
|
210
|
+
|
|
211
|
+
if (withSteam) {
|
|
212
|
+
fs.writeFileSync(
|
|
213
|
+
path.join(dest, 'preload.js'),
|
|
214
|
+
buildElectronPreload(this.pkg.config.steamId),
|
|
215
|
+
);
|
|
216
|
+
this.log('adaptfully: write preload.js (steam-auth)');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fs.writeFileSync(path.join(dest, 'main.js'), mainContent);
|
|
220
|
+
this.log('adaptfully: write main.js (electron)');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { CapacitorPackager } from './capacitor.js';
|
|
2
|
+
import { CordovaPackager } from './cordova.js';
|
|
3
|
+
import { ElectronPackager, buildElectronMain, buildElectronPreload } from './electron.js';
|
|
4
|
+
import { Packager, VALID_PACKAGERS } from './base.js';
|
|
5
|
+
import { WebPackager } from './web.js';
|
|
6
|
+
|
|
7
|
+
export { Packager, VALID_PACKAGERS } from './base.js';
|
|
8
|
+
export { WebPackager } from './web.js';
|
|
9
|
+
export { ElectronPackager, buildElectronMain, buildElectronPreload } from './electron.js';
|
|
10
|
+
export { CordovaPackager } from './cordova.js';
|
|
11
|
+
export { CapacitorPackager } from './capacitor.js';
|
|
12
|
+
|
|
13
|
+
/** @typedef {import('./base.js').PackagerName} PackagerName */
|
|
14
|
+
|
|
15
|
+
/** @type {Record<PackagerName, typeof Packager>} */
|
|
16
|
+
const PACKAGER_REGISTRY = {
|
|
17
|
+
web: WebPackager,
|
|
18
|
+
electron: ElectronPackager,
|
|
19
|
+
cordova: CordovaPackager,
|
|
20
|
+
capacitor: CapacitorPackager,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string} platformKey
|
|
25
|
+
* @param {{ config?: { platforms?: Record<string, { packager?: string, registrations?: Record<string, string> }>, steamId?: number } }} pkg
|
|
26
|
+
* @param {{ log?: (message: string) => void, platforms?: string[] }} [options]
|
|
27
|
+
* @returns {Packager}
|
|
28
|
+
*/
|
|
29
|
+
export function createPackagerForPlatform(platformKey, pkg, options = {}) {
|
|
30
|
+
const packagerId = Packager.resolvePlatformPackager(platformKey, pkg);
|
|
31
|
+
|
|
32
|
+
if (!VALID_PACKAGERS.includes(packagerId)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Invalid packager "${packagerId}" for platform "${platformKey}". `
|
|
35
|
+
+ `Expected one of: ${VALID_PACKAGERS.join(', ')}`,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const PackagerClass = PACKAGER_REGISTRY[packagerId];
|
|
40
|
+
return new PackagerClass(pkg, {
|
|
41
|
+
...options,
|
|
42
|
+
platformKey,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} platformKey
|
|
48
|
+
* @param {{ config?: { platforms?: Record<string, { packager?: string }> } }} pkg
|
|
49
|
+
* @returns {PackagerName}
|
|
50
|
+
*/
|
|
51
|
+
export function resolvePlatformPackager(platformKey, pkg) {
|
|
52
|
+
return Packager.resolvePlatformPackager(platformKey, pkg);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} platformKey
|
|
57
|
+
* @param {{ config?: { platforms?: Record<string, { packager?: string, registrations?: Record<string, string> }>, steamId?: number } }} pkg
|
|
58
|
+
*/
|
|
59
|
+
export function validatePlatformPackager(platformKey, pkg) {
|
|
60
|
+
createPackagerForPlatform(platformKey, pkg).validate();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {Record<string, string>} registrations
|
|
65
|
+
*/
|
|
66
|
+
export function usesSteamAuth(registrations) {
|
|
67
|
+
return Object.values(registrations).includes('steam-auth');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} dest
|
|
72
|
+
* @param {string} platformKey
|
|
73
|
+
* @param {{ config?: { platforms?: Record<string, { packager?: string, registrations?: Record<string, string> }>, steamId?: number } }} pkg
|
|
74
|
+
* @param {{ log?: (message: string) => void, platforms?: string[] }} [options]
|
|
75
|
+
*/
|
|
76
|
+
export function applyPackagerTemplates(dest, platformKey, pkg, options = {}) {
|
|
77
|
+
createPackagerForPlatform(platformKey, pkg, options).applyTemplates(dest);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} dest
|
|
82
|
+
* @param {string} platformKey
|
|
83
|
+
* @param {{ config?: { platforms?: Record<string, { packager?: string }> } }} pkg
|
|
84
|
+
* @param {string[]} htmlPaths
|
|
85
|
+
* @param {{ log?: (message: string) => void, platforms?: string[] }} [options]
|
|
86
|
+
*/
|
|
87
|
+
export function applyPackagerHtmlExtras(dest, platformKey, pkg, htmlPaths, options = {}) {
|
|
88
|
+
createPackagerForPlatform(platformKey, pkg, options).applyHtmlExtras(dest, htmlPaths);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {PackagerName} packager
|
|
93
|
+
* @param {string} platformKey
|
|
94
|
+
*/
|
|
95
|
+
export function needsGameConfig(packager, platformKey) {
|
|
96
|
+
const instance = new PACKAGER_REGISTRY[packager]({}, { platformKey });
|
|
97
|
+
return instance.needsGameConfig();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {PackagerName} packager
|
|
102
|
+
* @param {string} platformKey
|
|
103
|
+
*/
|
|
104
|
+
export function buildPackagerHtmlInjection(packager, platformKey) {
|
|
105
|
+
const instance = new PACKAGER_REGISTRY[packager]({}, { platformKey });
|
|
106
|
+
return instance.buildHtmlInjection();
|
|
107
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Packager } from './base.js';
|
|
2
|
+
|
|
3
|
+
export class WebPackager extends Packager {
|
|
4
|
+
/** @type {'web'} */
|
|
5
|
+
static id = 'web';
|
|
6
|
+
|
|
7
|
+
/** @type {string[]} */
|
|
8
|
+
static defaultPlatforms = ['web', 'uwp', 'pwa'];
|
|
9
|
+
|
|
10
|
+
needsGameConfig() {
|
|
11
|
+
return this.platformKey === 'uwp';
|
|
12
|
+
}
|
|
13
|
+
}
|
package/lib/node/packagers.js
CHANGED
|
@@ -1,157 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getTemplatesDir } from './paths.js';
|
|
4
|
-
|
|
5
|
-
/** @typedef {'web' | 'electron' | 'cordova' | 'capacitor'} PackagerName */
|
|
6
|
-
|
|
7
|
-
/** @type {PackagerName[]} */
|
|
8
|
-
export const VALID_PACKAGERS = ['web', 'electron', 'cordova', 'capacitor'];
|
|
9
|
-
|
|
10
|
-
const STEAM_INIT = `const path = require('path');
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
require('steamworks.js').electronEnableSteamOverlay();
|
|
14
|
-
} catch (err) {
|
|
15
|
-
console.error('Steam overlay unavailable:', err);
|
|
16
|
-
}
|
|
17
|
-
`;
|
|
18
|
-
|
|
19
|
-
const STEAM_PRELOAD = `,
|
|
20
|
-
preload: path.join(__dirname, 'preload.js')`;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {string} platformKey
|
|
24
|
-
* @param {{ config?: { platforms?: Record<string, { packager?: string, registrations?: Record<string, string> }>, steamId?: number } }} pkg
|
|
25
|
-
* @returns {PackagerName}
|
|
26
|
-
*/
|
|
27
|
-
export function resolvePlatformPackager(platformKey, pkg) {
|
|
28
|
-
const platform = pkg.config?.platforms?.[platformKey];
|
|
29
|
-
const packager = platform?.packager ?? 'web';
|
|
30
|
-
return /** @type {PackagerName} */ (packager);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {Record<string, string>} registrations
|
|
35
|
-
*/
|
|
36
|
-
export function usesSteamAuth(registrations) {
|
|
37
|
-
return Object.values(registrations).includes('steam-auth');
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* @param {string} platformKey
|
|
42
|
-
* @param {{ config?: { platforms?: Record<string, { packager?: string, registrations?: Record<string, string> }>, steamId?: number } }} pkg
|
|
43
|
-
*/
|
|
44
|
-
export function validatePlatformPackager(platformKey, pkg) {
|
|
45
|
-
const platform = pkg.config?.platforms?.[platformKey];
|
|
46
|
-
const packager = resolvePlatformPackager(platformKey, pkg);
|
|
47
|
-
|
|
48
|
-
if (!VALID_PACKAGERS.includes(packager)) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
`Invalid packager "${packager}" for platform "${platformKey}". `
|
|
51
|
-
+ `Expected one of: ${VALID_PACKAGERS.join(', ')}`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const registrations = platform?.registrations ?? {};
|
|
56
|
-
if (usesSteamAuth(registrations) && packager !== 'electron') {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`Platform "${platformKey}" uses steam-auth but packager is "${packager}". `
|
|
59
|
-
+ 'Set config.platforms.' + platformKey + '.packager to "electron".',
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (usesSteamAuth(registrations) && !pkg.config?.steamId) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`Platform "${platformKey}" uses steam-auth but config.steamId is not set.`,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* @param {string} content
|
|
72
|
-
* @param {string} markerName
|
|
73
|
-
* @param {string} replacement
|
|
74
|
-
*/
|
|
75
|
-
export function applyTemplateMarker(content, markerName, replacement) {
|
|
76
|
-
const pattern = new RegExp(
|
|
77
|
-
`/\\* adaptfully-${markerName} \\*/[\\s\\S]*?/\\* /adaptfully-${markerName} \\*/`,
|
|
78
|
-
'g',
|
|
79
|
-
);
|
|
80
|
-
return content.replace(pattern, replacement);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @param {PackagerName} packager
|
|
85
|
-
*/
|
|
86
|
-
export function resolvePackagerTemplateDir(packager) {
|
|
87
|
-
return path.join(getTemplatesDir(), packager);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @param {string} dest Prebuild output directory
|
|
92
|
-
* @param {string} platformKey
|
|
93
|
-
* @param {{ config?: { platforms?: Record<string, { packager?: string, registrations?: Record<string, string> }>, steamId?: number } }} pkg
|
|
94
|
-
* @param {{ log?: (message: string) => void }} [options]
|
|
95
|
-
*/
|
|
96
|
-
export function applyPackagerTemplates(dest, platformKey, pkg, options = {}) {
|
|
97
|
-
const log = options.log ?? (() => {});
|
|
98
|
-
const packager = resolvePlatformPackager(platformKey, pkg);
|
|
99
|
-
|
|
100
|
-
validatePlatformPackager(platformKey, pkg);
|
|
101
|
-
|
|
102
|
-
if (packager === 'web') {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const templateDir = resolvePackagerTemplateDir(packager);
|
|
107
|
-
if (!fs.existsSync(templateDir)) {
|
|
108
|
-
throw new Error(
|
|
109
|
-
`Packager "${packager}" has no templates yet (missing ${templateDir}).`,
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (packager === 'electron') {
|
|
114
|
-
applyElectronTemplates(dest, platformKey, pkg, templateDir, log);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* @param {string} dest
|
|
120
|
-
* @param {string} platformKey
|
|
121
|
-
* @param {{ config?: { platforms?: Record<string, { registrations?: Record<string, string> }>, steamId?: number } }} pkg
|
|
122
|
-
* @param {string} templateDir
|
|
123
|
-
* @param {(message: string) => void} log
|
|
124
|
-
*/
|
|
125
|
-
function applyElectronTemplates(dest, platformKey, pkg, templateDir, log) {
|
|
126
|
-
const registrations = pkg.config?.platforms?.[platformKey]?.registrations ?? {};
|
|
127
|
-
const withSteam = usesSteamAuth(registrations);
|
|
128
|
-
const mainTemplatePath = path.join(templateDir, 'main.js');
|
|
129
|
-
|
|
130
|
-
if (!fs.existsSync(mainTemplatePath)) {
|
|
131
|
-
throw new Error(`Electron packager template missing: ${mainTemplatePath}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let mainContent = fs.readFileSync(mainTemplatePath, 'utf8');
|
|
135
|
-
|
|
136
|
-
if (withSteam) {
|
|
137
|
-
mainContent = applyTemplateMarker(mainContent, 'steam-init', STEAM_INIT);
|
|
138
|
-
mainContent = applyTemplateMarker(mainContent, 'steam-preload', STEAM_PRELOAD);
|
|
139
|
-
|
|
140
|
-
const preloadTemplatePath = path.join(templateDir, 'preload.js');
|
|
141
|
-
if (!fs.existsSync(preloadTemplatePath)) {
|
|
142
|
-
throw new Error(`Electron steam-auth template missing: ${preloadTemplatePath}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const preloadContent = fs.readFileSync(preloadTemplatePath, 'utf8')
|
|
146
|
-
.replace(/\{\{STEAM_APP_ID\}\}/g, String(pkg.config.steamId));
|
|
147
|
-
|
|
148
|
-
fs.writeFileSync(path.join(dest, 'preload.js'), preloadContent);
|
|
149
|
-
log('adaptfully: write preload.js (steam-auth)');
|
|
150
|
-
} else {
|
|
151
|
-
mainContent = applyTemplateMarker(mainContent, 'steam-init', '');
|
|
152
|
-
mainContent = applyTemplateMarker(mainContent, 'steam-preload', '');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
fs.writeFileSync(path.join(dest, 'main.js'), mainContent);
|
|
156
|
-
log('adaptfully: write main.js (electron)');
|
|
157
|
-
}
|
|
1
|
+
export * from './packagers/index.js';
|
package/lib/node/paths.js
CHANGED
|
@@ -4,7 +4,6 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
5
|
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
6
6
|
const RUNTIME_DIR = path.join(PACKAGE_ROOT, 'lib', 'runtime');
|
|
7
|
-
const TEMPLATES_DIR = path.join(PACKAGE_ROOT, 'lib', 'templates');
|
|
8
7
|
|
|
9
8
|
export function getPackageRoot() {
|
|
10
9
|
return PACKAGE_ROOT;
|
|
@@ -14,10 +13,6 @@ export function getRuntimeDir() {
|
|
|
14
13
|
return RUNTIME_DIR;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
|
-
export function getTemplatesDir() {
|
|
18
|
-
return TEMPLATES_DIR;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
16
|
export function resolveRuntimeScript(relativePath) {
|
|
22
17
|
return path.join(RUNTIME_DIR, relativePath);
|
|
23
18
|
}
|
package/lib/node/prebuild.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
adaptfullyInjectionForPlatform,
|
|
5
5
|
injectAdaptfullyRegistrations,
|
|
6
6
|
} from './registrations.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createPackagerForPlatform } from './packagers.js';
|
|
8
8
|
import { copyRecursiveSync, emptyDirSync } from './fs-utils.js';
|
|
9
9
|
|
|
10
10
|
/** @typedef {'prebuild' | 'build' | 'deploy'} AdaptfullyStage */
|
|
@@ -74,11 +74,13 @@ export function prebuildPlatform(deployFolder, platformKey, pkg, options = {}) {
|
|
|
74
74
|
emptyDirSync(dest);
|
|
75
75
|
copyRecursiveSync(source, dest);
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
const packager = createPackagerForPlatform(platformKey, pkg, { log });
|
|
78
|
+
const htmlInjections = resolveHtmlInjections(pkg);
|
|
79
|
+
packager.prebuild(dest, htmlInjections);
|
|
78
80
|
|
|
79
81
|
const injection = adaptfullyInjectionForPlatform(platformKey, pkg, { log });
|
|
80
82
|
if (injection) {
|
|
81
|
-
for (const relativePath of
|
|
83
|
+
for (const relativePath of htmlInjections) {
|
|
82
84
|
const htmlPath = path.join(dest, relativePath);
|
|
83
85
|
if (!fs.existsSync(htmlPath)) {
|
|
84
86
|
throw new Error(`htmlInjections file not found in deploy output: ${relativePath}`);
|
package/package.json
CHANGED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-sync */
|
|
2
|
-
/* global process, require */
|
|
3
|
-
const
|
|
4
|
-
{app, shell, BrowserWindow, Menu} = require('electron'),
|
|
5
|
-
fs = require('fs');
|
|
6
|
-
|
|
7
|
-
/* adaptfully-steam-init */
|
|
8
|
-
/* /adaptfully-steam-init */
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Creates window.
|
|
12
|
-
*/
|
|
13
|
-
function createWindow () {
|
|
14
|
-
// Create the browser window.
|
|
15
|
-
const
|
|
16
|
-
debugMode = false,
|
|
17
|
-
lastState = (function () {
|
|
18
|
-
let state = null;
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
state = JSON.parse(fs.readFileSync(app.getPath('userData') + '/window-state.json', 'utf8'));
|
|
22
|
-
} catch (e) {}
|
|
23
|
-
|
|
24
|
-
return state || {
|
|
25
|
-
bounds: {
|
|
26
|
-
width: 800,
|
|
27
|
-
height: 600
|
|
28
|
-
},
|
|
29
|
-
maximized: false,
|
|
30
|
-
fullscreen: false
|
|
31
|
-
};
|
|
32
|
-
})(),
|
|
33
|
-
win = new BrowserWindow({
|
|
34
|
-
backgroundColor: '#E4378E',
|
|
35
|
-
width: lastState.bounds.width,
|
|
36
|
-
height: lastState.bounds.height,
|
|
37
|
-
minWidth: 200,
|
|
38
|
-
minHeight: 100,
|
|
39
|
-
webPreferences: {
|
|
40
|
-
devTools: debugMode,
|
|
41
|
-
nodeIntegration: false,
|
|
42
|
-
contextIsolation: true
|
|
43
|
-
/* adaptfully-steam-preload */
|
|
44
|
-
/* /adaptfully-steam-preload */
|
|
45
|
-
},
|
|
46
|
-
show: false
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (lastState.maximized) {
|
|
50
|
-
win.maximize();
|
|
51
|
-
}
|
|
52
|
-
if (lastState.fullscreen) {
|
|
53
|
-
win.setFullScreen(true);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!debugMode) {
|
|
57
|
-
Menu.setApplicationMenu(null);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
win.once('ready-to-show', () => { // helps prevent flicker-loading
|
|
61
|
-
win.show();
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
// and load the index.html of the app.
|
|
65
|
-
win.loadFile('index.html');
|
|
66
|
-
|
|
67
|
-
win.webContents.on('will-navigate', (event, url) => {
|
|
68
|
-
event.preventDefault();
|
|
69
|
-
shell.openExternal(url);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//win.webContents.openDevTools();
|
|
74
|
-
|
|
75
|
-
win.on('close', () => {
|
|
76
|
-
fs.writeFileSync(app.getPath('userData') + '/window-state.json', JSON.stringify({
|
|
77
|
-
bounds: win.getNormalBounds(),
|
|
78
|
-
maximized: win.isMaximized(),
|
|
79
|
-
fullscreen: win.isFullScreen()
|
|
80
|
-
}));
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// This method will be called when Electron has finished
|
|
85
|
-
// initialization and is ready to create browser windows.
|
|
86
|
-
// Some APIs can only be used after this event occurs.
|
|
87
|
-
app.whenReady().then(createWindow);
|
|
88
|
-
|
|
89
|
-
// Quit when all windows are closed.
|
|
90
|
-
app.on('window-all-closed', () => {
|
|
91
|
-
// On macOS it is common for applications and their menu bar
|
|
92
|
-
// to stay active until the user quits explicitly with Cmd + Q
|
|
93
|
-
if (process.platform !== 'darwin') {
|
|
94
|
-
app.quit();
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
app.on('activate', () => {
|
|
99
|
-
// On macOS it's common to re-create a window in the app when the
|
|
100
|
-
// dock icon is clicked and there are no other windows open.
|
|
101
|
-
if (BrowserWindow.getAllWindows().length === 0) {
|
|
102
|
-
createWindow();
|
|
103
|
-
}
|
|
104
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/* global require */
|
|
2
|
-
const { contextBridge } = require('electron');
|
|
3
|
-
|
|
4
|
-
let steamClient = null;
|
|
5
|
-
|
|
6
|
-
try {
|
|
7
|
-
steamClient = require('steamworks.js').init({{STEAM_APP_ID}});
|
|
8
|
-
} catch (err) {
|
|
9
|
-
console.error('Steamworks init failed:', err);
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (steamClient) {
|
|
13
|
-
contextBridge.exposeInMainWorld('__ADAPTFULLY_STEAMWORKS__', steamClient);
|
|
14
|
-
}
|