@makefully/adaptfully 2.1.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,314 @@
1
+ import fs from 'node:fs';
2
+ import { resolveRuntimeScript } from './paths.js';
3
+
4
+ /** @typedef {Record<string, string>} RegistrationMap */
5
+
6
+ /**
7
+ * @typedef {Object} StandardPlugin
8
+ * @property {string[]} scripts Runtime script paths relative to lib/runtime
9
+ * @property {(registerKey: string) => string} registration
10
+ * @property {string} [extScript] Optional HTML snippet injected before adaptfully scripts
11
+ */
12
+
13
+ /** @type {Record<string, StandardPlugin>} */
14
+ export const STANDARD_PLUGINS = {
15
+ 'google-auth': {
16
+ scripts: ['core.js', 'platform.js', 'auth/_helpers.js', 'auth/google-auth.js'],
17
+ registration: (key) => `adaptfully.register('${key}', adaptfully.auth.Google);`,
18
+ extScript: '<script src="https://accounts.google.com/gsi/client" async defer></script>',
19
+ },
20
+ 'steam-auth': {
21
+ scripts: ['core.js', 'platform.js', 'auth/_helpers.js', 'auth/steam-auth.js'],
22
+ registration: (key) => `adaptfully.register('${key}', adaptfully.auth.Steam);`,
23
+ },
24
+ 'dev-auth': {
25
+ scripts: ['core.js', 'platform.js', 'auth/_helpers.js', 'auth/dev-auth.js'],
26
+ registration: (key) => `adaptfully.register('${key}', adaptfully.auth.Dev);`,
27
+ },
28
+ };
29
+
30
+ /** Default Wrapfully builder → config.platforms key */
31
+ export const DEFAULT_BUILDER_PLATFORMS = {
32
+ steam: 'steam',
33
+ 'steam-dev': 'steam',
34
+ win: 'steam',
35
+ 'win-dev': 'steam',
36
+ mac: 'steam',
37
+ 'mac-dev': 'steam',
38
+ linux: 'steam',
39
+ 'linux-dev': 'steam',
40
+ android: 'android',
41
+ 'android-dev': 'android',
42
+ ios: 'ios',
43
+ 'ios-dev': 'ios',
44
+ 'ios-sim': 'ios',
45
+ webapp: 'web',
46
+ cordova: 'cordova',
47
+ 'cordova-dev': 'cordova',
48
+ apple: 'apple',
49
+ 'apple-dev': 'apple',
50
+ uwp: 'uwp',
51
+ };
52
+
53
+ const ADAPTFULLY_MARKER = '<!-- adaptfully -->';
54
+ const ADAPTFULLY_END_MARKER = '<!-- /adaptfully -->';
55
+
56
+ function readRuntimeScript(relativePath) {
57
+ return fs.readFileSync(resolveRuntimeScript(relativePath), 'utf8');
58
+ }
59
+
60
+ /**
61
+ * @param {RegistrationMap} registrations
62
+ * @returns {{
63
+ * runtimeScripts: string[],
64
+ * extScripts: string[],
65
+ * deployScripts: string[],
66
+ * inlineRegistrations: string[],
67
+ * }}
68
+ */
69
+ export function collectRegistrationParts(registrations) {
70
+ if (!registrations || typeof registrations !== 'object') {
71
+ throw new Error('registrations must be an object');
72
+ }
73
+
74
+ const entries = Object.entries(registrations);
75
+ const runtimeScripts = [];
76
+ const extScripts = [];
77
+ const deployScripts = [];
78
+ const inlineRegistrations = [];
79
+
80
+ for (const [registerKey, value] of entries) {
81
+ if (!value || typeof value !== 'string') {
82
+ throw new Error(`Invalid registration for "${registerKey}": expected a string`);
83
+ }
84
+
85
+ if (isDeployPath(value)) {
86
+ const src = value.startsWith('/') ? value : `/${value.replace(/^\.\//, '')}`;
87
+ deployScripts.push({ key: registerKey, src });
88
+ continue;
89
+ }
90
+
91
+ const plugin = STANDARD_PLUGINS[value];
92
+ if (!plugin) {
93
+ throw new Error(
94
+ `Unknown Adaptfully plugin "${value}" for "${registerKey}". `
95
+ + `Known plugins: ${Object.keys(STANDARD_PLUGINS).join(', ')}`,
96
+ );
97
+ }
98
+
99
+ for (const script of plugin.scripts) {
100
+ if (!runtimeScripts.includes(script)) {
101
+ runtimeScripts.push(script);
102
+ }
103
+ }
104
+
105
+ if (plugin.extScript && !extScripts.includes(plugin.extScript)) {
106
+ extScripts.push(plugin.extScript);
107
+ }
108
+
109
+ inlineRegistrations.push(plugin.registration(registerKey));
110
+ }
111
+
112
+ return { runtimeScripts, extScripts, deployScripts, inlineRegistrations };
113
+ }
114
+
115
+ /**
116
+ * @param {RegistrationMap} registrations
117
+ */
118
+ export function resolveRegistrationAssets(registrations) {
119
+ const { runtimeScripts, extScripts, deployScripts, inlineRegistrations } = collectRegistrationParts(registrations);
120
+
121
+ return {
122
+ runtimeScriptPaths: runtimeScripts.map((rel) => resolveRuntimeScript(rel)),
123
+ inlineScript: inlineRegistrations.join('\n'),
124
+ extScripts,
125
+ deployScriptSrcs: deployScripts.map(({ src }) => src),
126
+ };
127
+ }
128
+
129
+ function isDeployPath(value) {
130
+ return value.startsWith('/') || value.startsWith('./') || value.includes('/');
131
+ }
132
+
133
+ /**
134
+ * @param {string} builder
135
+ * @param {Record<string, { builders?: string[], registrations?: RegistrationMap }>} [platforms]
136
+ * @returns {string | null}
137
+ */
138
+ export function resolvePlatformKey(builder, platforms = {}) {
139
+ if (!platforms || Object.keys(platforms).length === 0) {
140
+ return null;
141
+ }
142
+
143
+ for (const [platformKey, platformConfig] of Object.entries(platforms)) {
144
+ if (platformConfig?.builders?.includes(builder)) {
145
+ return platformKey;
146
+ }
147
+ }
148
+
149
+ if (platforms[builder]?.registrations) {
150
+ return builder;
151
+ }
152
+
153
+ const mapped = DEFAULT_BUILDER_PLATFORMS[builder];
154
+ if (mapped && platforms[mapped]) {
155
+ return mapped;
156
+ }
157
+
158
+ if (platforms[builder]) {
159
+ return builder;
160
+ }
161
+
162
+ return mapped ?? null;
163
+ }
164
+
165
+ /**
166
+ * @param {RegistrationMap} registrations
167
+ * @param {{ log?: (message: string) => void }} [options]
168
+ */
169
+ export function buildAdaptfullyInjection(registrations, options = {}) {
170
+ const log = options.log ?? (() => {});
171
+ const parts = collectRegistrationParts(registrations);
172
+ if (parts.runtimeScripts.length === 0
173
+ && parts.extScripts.length === 0
174
+ && parts.deployScripts.length === 0
175
+ && parts.inlineRegistrations.length === 0) {
176
+ return '';
177
+ }
178
+
179
+ for (const [registerKey, value] of Object.entries(registrations)) {
180
+ if (isDeployPath(value)) {
181
+ const src = value.startsWith('/') ? value : `/${value.replace(/^\.\//, '')}`;
182
+ log(`adaptfully: registering ${registerKey} ← ${src}`);
183
+ } else {
184
+ log(`adaptfully: registering ${registerKey} ← ${value}`);
185
+ }
186
+ }
187
+
188
+ let block = `${ADAPTFULLY_MARKER}\n`;
189
+
190
+ for (const extScript of parts.extScripts) {
191
+ block += `${extScript}\n`;
192
+ }
193
+
194
+ for (const script of parts.runtimeScripts) {
195
+ block += `<script>\n${readRuntimeScript(script)}\n</script>\n`;
196
+ }
197
+
198
+ for (const { src } of parts.deployScripts) {
199
+ block += `<script src="${src}"></script>\n`;
200
+ }
201
+
202
+ if (parts.inlineRegistrations.length > 0) {
203
+ block += `<script>\n${parts.inlineRegistrations.join('\n')}\n</script>\n`;
204
+ }
205
+
206
+ block += `${ADAPTFULLY_END_MARKER}\n`;
207
+ return block;
208
+ }
209
+
210
+ /**
211
+ * @param {string} html
212
+ * @param {string} injection
213
+ */
214
+ export function injectAdaptfullyRegistrations(html, injection) {
215
+ if (!injection) {
216
+ return html;
217
+ }
218
+
219
+ const markerPattern = new RegExp(
220
+ `${escapeRegExp(ADAPTFULLY_MARKER)}[\\s\\S]*?${escapeRegExp(ADAPTFULLY_END_MARKER)}\\n?`,
221
+ );
222
+
223
+ if (markerPattern.test(html)) {
224
+ return html.replace(markerPattern, injection);
225
+ }
226
+
227
+ if (html.includes('<!-- scripts -->')) {
228
+ return html.replace('<!-- scripts -->', `${injection}<!-- scripts -->`);
229
+ }
230
+
231
+ if (html.includes('</head>')) {
232
+ return html.replace('</head>', `${injection}</head>`);
233
+ }
234
+
235
+ throw new Error(
236
+ 'Cannot inject Adaptfully registrations: HTML needs '
237
+ + '<!-- adaptfully -->…<!-- /adaptfully -->, <!-- scripts -->, or </head>',
238
+ );
239
+ }
240
+
241
+ /**
242
+ * @param {string} platformKey
243
+ * @param {{ config?: { platforms?: Record<string, { registrations?: RegistrationMap, builder?: string }> } }} pkg
244
+ * @returns {{ platformKey: string, registrations: RegistrationMap | null }}
245
+ */
246
+ export function resolvePlatformRegistrationsByKey(platformKey, pkg) {
247
+ const platforms = pkg.config?.platforms ?? {};
248
+ const registrations = platforms[platformKey]?.registrations ?? null;
249
+
250
+ if (!registrations || Object.keys(registrations).length === 0) {
251
+ return { platformKey, registrations: null };
252
+ }
253
+
254
+ return { platformKey, registrations };
255
+ }
256
+
257
+ /**
258
+ * @param {string} platformKey
259
+ * @param {{ config?: { platforms?: Record<string, { registrations?: RegistrationMap, builder?: string }> } }} pkg
260
+ * @param {{ log?: (message: string) => void }} [options]
261
+ * @returns {string}
262
+ */
263
+ export function adaptfullyInjectionForPlatform(platformKey, pkg, options = {}) {
264
+ const log = options.log ?? (() => {});
265
+ const { registrations } = resolvePlatformRegistrationsByKey(platformKey, pkg);
266
+
267
+ if (!registrations) {
268
+ log(`adaptfully: no registrations configured for platform "${platformKey}"; skipping injection`);
269
+ return '';
270
+ }
271
+
272
+ return buildAdaptfullyInjection(registrations, { log });
273
+ }
274
+
275
+ /**
276
+ * @param {string} platformKey
277
+ * @param {{ config?: { platforms?: Record<string, { builder?: string }> } }} pkg
278
+ */
279
+ export function resolveBuilderForPlatform(platformKey, pkg) {
280
+ const platform = pkg.config?.platforms?.[platformKey];
281
+ if (platform?.builder) {
282
+ return platform.builder;
283
+ }
284
+
285
+ if (platformKey === 'web') {
286
+ return 'webapp';
287
+ }
288
+
289
+ return platformKey;
290
+ }
291
+
292
+ /**
293
+ * @param {string} arg CLI platform or Wrapfully builder name
294
+ * @param {{ config?: { platforms?: Record<string, { builder?: string }> } }} pkg
295
+ */
296
+ export function resolveCliPlatformAndBuilder(arg, pkg) {
297
+ const platforms = pkg.config?.platforms ?? {};
298
+
299
+ if (platforms[arg]) {
300
+ return {
301
+ platformKey: arg,
302
+ builder: resolveBuilderForPlatform(arg, pkg),
303
+ };
304
+ }
305
+
306
+ return {
307
+ platformKey: resolvePlatformKey(arg, platforms) ?? arg,
308
+ builder: arg,
309
+ };
310
+ }
311
+
312
+ function escapeRegExp(value) {
313
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
314
+ }
@@ -1,42 +1,42 @@
1
- import fs from 'node:fs';
2
-
3
- /**
4
- * @param {string} builder
5
- * @param {{ name: string, version: string }} pkg
6
- */
7
- export function printBuildReport(builder, pkg) {
8
- const statusPath = './output/wrapfully-status.json';
9
- const legacyPath = `./output/${pkg.name}-${pkg.version}-${builder}.txt`;
10
-
11
- let status = null;
12
- if (fs.existsSync(statusPath)) {
13
- try {
14
- status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
15
- } catch {
16
- console.warn('Unable to read wrapfully-status.json');
17
- }
18
- }
19
-
20
- if (status) {
21
- for (const event of status.events) {
22
- const line = `[${event.step}] ${event.message}`;
23
- if (event.level === 'error') {
24
- console.error(line);
25
- } else if (event.level === 'warn') {
26
- console.warn(line);
27
- } else {
28
- console.log(line);
29
- }
30
- }
31
-
32
- if (!status.ok) {
33
- console.error('Wrapfully build finished with errors.');
34
- process.exit(1);
35
- }
36
- return;
37
- }
38
-
39
- if (fs.existsSync(legacyPath)) {
40
- console.log(fs.readFileSync(legacyPath, 'utf8'));
41
- }
42
- }
1
+ import fs from 'node:fs';
2
+
3
+ /**
4
+ * @param {string} builder
5
+ * @param {{ name: string, version: string }} pkg
6
+ */
7
+ export function printBuildReport(builder, pkg) {
8
+ const statusPath = './output/wrapfully-status.json';
9
+ const legacyPath = `./output/${pkg.name}-${pkg.version}-${builder}.txt`;
10
+
11
+ let status = null;
12
+ if (fs.existsSync(statusPath)) {
13
+ try {
14
+ status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
15
+ } catch {
16
+ console.warn('Unable to read wrapfully-status.json');
17
+ }
18
+ }
19
+
20
+ if (status) {
21
+ for (const event of status.events) {
22
+ const line = `[${event.step}] ${event.message}`;
23
+ if (event.level === 'error') {
24
+ console.error(line);
25
+ } else if (event.level === 'warn') {
26
+ console.warn(line);
27
+ } else {
28
+ console.log(line);
29
+ }
30
+ }
31
+
32
+ if (!status.ok) {
33
+ console.error('Wrapfully build finished with errors.');
34
+ process.exit(1);
35
+ }
36
+ return;
37
+ }
38
+
39
+ if (fs.existsSync(legacyPath)) {
40
+ console.log(fs.readFileSync(legacyPath, 'utf8'));
41
+ }
42
+ }
@@ -1,38 +1,38 @@
1
- /* global window */
2
-
3
- /**
4
- * @typedef {{ id: string, email: string }} AuthUser
5
- * @typedef {(result: { authenticated: boolean, user?: AuthUser | null }) => void} AuthCallback
6
- * @typedef {(err?: { error?: string }) => void} ReadyCallback
7
- *
8
- * @typedef {Object} AuthPlugin
9
- * @property {string} name
10
- * @property {(done: ReadyCallback) => void} whenReady
11
- * @property {(callback: AuthCallback) => void} login
12
- * @property {(callback: AuthCallback) => void} silentLogin
13
- * @property {(callback: () => void) => void} logout
14
- * @property {() => AuthUser | null} getUser
15
- * @property {() => boolean} isAuthenticated
16
- */
17
-
18
- (function registerAuthHelpers(ns) {
19
- const helpers = {
20
- configValue(key, fallback) {
21
- if (!ns.has('config')) {
22
- return fallback;
23
- }
24
- const config = ns.get('config');
25
- if (config && config[key] != null) {
26
- return config[key];
27
- }
28
- return fallback;
29
- },
30
-
31
- getStorage() {
32
- return ns.has('storage') ? ns.get('storage') : null;
33
- },
34
- };
35
-
36
- ns.auth = ns.auth || {};
37
- ns.auth.helpers = helpers;
38
- }(window.adaptfully));
1
+ /* global window */
2
+
3
+ /**
4
+ * @typedef {{ id: string, email: string }} AuthUser
5
+ * @typedef {(result: { authenticated: boolean, user?: AuthUser | null }) => void} AuthCallback
6
+ * @typedef {(err?: { error?: string }) => void} ReadyCallback
7
+ *
8
+ * @typedef {Object} AuthPlugin
9
+ * @property {string} name
10
+ * @property {(done: ReadyCallback) => void} whenReady
11
+ * @property {(callback: AuthCallback) => void} login
12
+ * @property {(callback: AuthCallback) => void} silentLogin
13
+ * @property {(callback: () => void) => void} logout
14
+ * @property {() => AuthUser | null} getUser
15
+ * @property {() => boolean} isAuthenticated
16
+ */
17
+
18
+ (function registerAuthHelpers(ns) {
19
+ const helpers = {
20
+ configValue(key, fallback) {
21
+ if (!ns.has('config')) {
22
+ return fallback;
23
+ }
24
+ const config = ns.get('config');
25
+ if (config && config[key] != null) {
26
+ return config[key];
27
+ }
28
+ return fallback;
29
+ },
30
+
31
+ getStorage() {
32
+ return ns.has('storage') ? ns.get('storage') : null;
33
+ },
34
+ };
35
+
36
+ ns.auth = ns.auth || {};
37
+ ns.auth.helpers = helpers;
38
+ }(window.adaptfully));
@@ -1,78 +1,78 @@
1
- /* global fetch, window */
2
-
3
- /**
4
- * Local development auth — test values for dev builds outside the Wrapfully flow.
5
- *
6
- * adaptfully.register('auth', adaptfully.auth.Dev);
7
- */
8
- (function registerDevAuth(ns) {
9
- const { getStorage } = ns.auth.helpers;
10
-
11
- const apiBase = () => {
12
- if (ns.has('config')) {
13
- const config = ns.get('config');
14
- if (config && typeof config.apiBase === 'string') {
15
- return config.apiBase;
16
- }
17
- }
18
- if (typeof window.__ADAPTFULLY_API__ === 'string') {
19
- return window.__ADAPTFULLY_API__;
20
- }
21
- return '';
22
- };
23
-
24
- const apiGet = (path) => fetch(`${apiBase()}${path}`, { credentials: 'include' })
25
- .then((response) => response.json());
26
-
27
- class DevAuthPlugin {
28
- constructor() {
29
- this.user = { id: 'dev-local-user', email: 'dev@local' };
30
- this.authenticated = true;
31
- }
32
-
33
- whenReady(done) {
34
- done();
35
- }
36
-
37
- login(callback) {
38
- apiGet('/get-user')
39
- .then((user) => {
40
- if (user?.id) {
41
- this.user = { id: user.id, email: user.email || 'dev@local' };
42
- this.authenticated = true;
43
- }
44
- callback({ authenticated: this.authenticated, user: this.getUser() });
45
- })
46
- .catch(() => {
47
- this.user = { id: 'dev-local-user', email: 'dev@local' };
48
- this.authenticated = true;
49
- callback({ authenticated: true, user: this.getUser() });
50
- });
51
- }
52
-
53
- silentLogin(callback) {
54
- callback({ authenticated: this.authenticated, user: this.getUser() });
55
- }
56
-
57
- logout(callback) {
58
- const storage = getStorage();
59
- this.authenticated = false;
60
- this.user = null;
61
- storage?.remove('lastLoggedIn');
62
- callback();
63
- }
64
-
65
- getUser() {
66
- if (!this.authenticated || !this.user) {
67
- return null;
68
- }
69
- return { id: this.user.id, email: this.user.email };
70
- }
71
-
72
- isAuthenticated() {
73
- return !!this.authenticated;
74
- }
75
- }
76
-
77
- ns.auth.Dev = () => new DevAuthPlugin();
78
- }(window.adaptfully));
1
+ /* global fetch, window */
2
+
3
+ /**
4
+ * Local development auth — test values for dev builds outside the Wrapfully flow.
5
+ *
6
+ * adaptfully.register('auth', adaptfully.auth.Dev);
7
+ */
8
+ (function registerDevAuth(ns) {
9
+ const { getStorage } = ns.auth.helpers;
10
+
11
+ const apiBase = () => {
12
+ if (ns.has('config')) {
13
+ const config = ns.get('config');
14
+ if (config && typeof config.apiBase === 'string') {
15
+ return config.apiBase;
16
+ }
17
+ }
18
+ if (typeof window.__ADAPTFULLY_API__ === 'string') {
19
+ return window.__ADAPTFULLY_API__;
20
+ }
21
+ return '';
22
+ };
23
+
24
+ const apiGet = (path) => fetch(`${apiBase()}${path}`, { credentials: 'include' })
25
+ .then((response) => response.json());
26
+
27
+ class DevAuthPlugin {
28
+ constructor() {
29
+ this.user = { id: 'dev-local-user', email: 'dev@local' };
30
+ this.authenticated = true;
31
+ }
32
+
33
+ whenReady(done) {
34
+ done();
35
+ }
36
+
37
+ login(callback) {
38
+ apiGet('/get-user')
39
+ .then((user) => {
40
+ if (user?.id) {
41
+ this.user = { id: user.id, email: user.email || 'dev@local' };
42
+ this.authenticated = true;
43
+ }
44
+ callback({ authenticated: this.authenticated, user: this.getUser() });
45
+ })
46
+ .catch(() => {
47
+ this.user = { id: 'dev-local-user', email: 'dev@local' };
48
+ this.authenticated = true;
49
+ callback({ authenticated: true, user: this.getUser() });
50
+ });
51
+ }
52
+
53
+ silentLogin(callback) {
54
+ callback({ authenticated: this.authenticated, user: this.getUser() });
55
+ }
56
+
57
+ logout(callback) {
58
+ const storage = getStorage();
59
+ this.authenticated = false;
60
+ this.user = null;
61
+ storage?.remove('lastLoggedIn');
62
+ callback();
63
+ }
64
+
65
+ getUser() {
66
+ if (!this.authenticated || !this.user) {
67
+ return null;
68
+ }
69
+ return { id: this.user.id, email: this.user.email };
70
+ }
71
+
72
+ isAuthenticated() {
73
+ return !!this.authenticated;
74
+ }
75
+ }
76
+
77
+ ns.auth.Dev = () => new DevAuthPlugin();
78
+ }(window.adaptfully));