@makefully/adaptfully 4.2.1 → 4.3.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 CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 4.3.0 — 2026-09-11
6
+
7
+ ### Added
8
+
9
+ - **Sequential multi-platform stages** — `adaptfully release steam,android,ios` (or `--platforms steam,android,ios`) runs each platform in order. Failures are collected; the command exits non-zero if any platform failed.
10
+ - Multi-platform extracts land in `output/<platform>/` so sibling results are preserved. Only that folder’s prior `artifacts/` / wrapfully status files are cleared before each extract (single-platform still uses `output/`).
11
+
12
+ ### Changed
13
+
14
+ - Build report failures throw instead of calling `process.exit`, so sequential platform runs can continue and report a summary.
15
+
16
+ ## 4.2.2 — 2026-09-10
17
+
18
+ ### Added
19
+
20
+ - While a Wrapfully Yap chore is pending, poll `GET /yap/wrapfully/:choreId/logs` and print new live worker log lines.
21
+
5
22
  ## 4.2.1 — 2026-09-09
6
23
 
7
24
  ### Fixed
package/README.md CHANGED
@@ -30,6 +30,8 @@ adaptfully prebuild web # deploy/ → output/web-prebuild/
30
30
  adaptfully build steam # Yap /yap/wrapfully chore → artifact zip
31
31
  adaptfully deploy web --deployment web-prod # Yap deploy chore
32
32
  adaptfully release android --encrypt # optional envelope encryption
33
+ adaptfully release steam,android,ios # sequential platforms; extracts to output/<platform>/
34
+ adaptfully release --platforms steam,android # same, flag form
33
35
  adaptfully steam-publish # one-time: log in with steamcmd → assets/meta/deployments/steam/steam.json
34
36
  ```
35
37
 
@@ -45,6 +47,8 @@ adaptfully steam-publish # one-time: log in with steamcmd
45
47
  | `android-keystore` | One-time local setup: generate debug/release keystores and write `android` `build.json` (also updates `.gitignore`) |
46
48
  | `apple-signing` | One-time local setup: CSR → Apple `.cer` → `.p12` / provisioning profiles under `ios/apple/` (also updates `.gitignore`) |
47
49
 
50
+ Comma-separated platforms (or `--platforms`) run **sequentially**. Multi-platform extracts go to `output/<platform>/` so sibling results are not overwritten; only that folder’s prior `artifacts/` / status files are cleared before each extract. A single platform still extracts to `output/` for backward compatibility.
51
+
48
52
  **Server** means the Showfully Yap base (`SHOWFULLY_SERVER` / `wrapfully.json` `showfullyServer` or `server`), default `https://make.makefullystudios.com/`. Wrapfully workers claim chores with a separate service token.
49
53
 
50
54
  **Encryption (optional):** `--encrypt` or `"encrypt": true` wraps the chore to the Makefully Wrapfully fleet public key (override with `--encrypt-public-key`). Results are decrypted locally with an ephemeral `resultPriv` kept only for that chore.
@@ -17,13 +17,31 @@ export function buildOutputDir(pkg, outputRoot = 'output') {
17
17
  }
18
18
 
19
19
  /**
20
- * Remove prior Wrapfully extract artifacts so zip responses do not stack
21
- * nested `artifacts/` directories across runs.
20
+ * Where a platform's Wrapfully zip is extracted.
21
+ * Multi-platform runs use `output/<platformKey>/` so siblings are not overwritten.
22
+ * Single-platform runs keep `output/` for backward compatibility.
22
23
  *
23
- * @param {string} [outputRoot='output']
24
+ * @param {string} outputRoot
25
+ * @param {string} [platformKey]
26
+ * @param {{ multiPlatform?: boolean }} [options]
27
+ * @returns {string}
24
28
  */
25
- export function clearStaleBuildExtract(outputRoot = 'output') {
29
+ export function resolveExtractRoot(outputRoot, platformKey, options = {}) {
26
30
  const root = path.resolve(outputRoot);
31
+ if (options.multiPlatform && platformKey) {
32
+ return path.join(root, platformKey);
33
+ }
34
+ return root;
35
+ }
36
+
37
+ /**
38
+ * Remove prior extract artifacts in `extractRoot` only (artifacts/, status,
39
+ * manifest). Does not wipe sibling platform folders under output/.
40
+ *
41
+ * @param {string} [extractRoot='output']
42
+ */
43
+ export function clearStaleBuildExtract(extractRoot = 'output') {
44
+ const root = path.resolve(extractRoot);
27
45
  const stalePaths = [
28
46
  path.join(root, 'artifacts'),
29
47
  path.join(root, MANIFEST_NAME),
@@ -39,17 +57,34 @@ export function clearStaleBuildExtract(outputRoot = 'output') {
39
57
  * Resolve the artifact directory from a prior `adaptfully build` (or compatible zip extract).
40
58
  *
41
59
  * @param {{ name: string, version: string, config?: { outputFolder?: string } }} pkg
42
- * @param {{ outputRoot?: string, platformKey?: string }} [options]
60
+ * @param {{ outputRoot?: string, platformKey?: string, multiPlatform?: boolean }} [options]
43
61
  * @returns {string}
44
62
  */
45
63
  export function resolveBuildArtifactDir(pkg, options = {}) {
46
- const artifactDir = buildOutputDir(pkg, options.outputRoot);
47
- const manifestPath = path.join(artifactDir, MANIFEST_NAME);
64
+ const outputRoot = buildOutputDir(pkg, options.outputRoot);
65
+ const candidates = [];
48
66
 
49
- if (!fs.existsSync(manifestPath)) {
67
+ if (options.platformKey) {
68
+ candidates.push(resolveExtractRoot(outputRoot, options.platformKey, { multiPlatform: true }));
69
+ }
70
+ candidates.push(outputRoot);
71
+
72
+ let artifactDir = null;
73
+ let manifestPath = null;
74
+
75
+ for (const candidate of candidates) {
76
+ const candidateManifest = path.join(candidate, MANIFEST_NAME);
77
+ if (fs.existsSync(candidateManifest)) {
78
+ artifactDir = candidate;
79
+ manifestPath = candidateManifest;
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (!artifactDir || !manifestPath) {
50
85
  throw new Error(
51
86
  'No prior build artifact found in '
52
- + `${artifactDir}. Run \`adaptfully build <platform>\` first or pass --artifact <path>.`,
87
+ + `${candidates.join(' or ')}. Run \`adaptfully build <platform>\` first or pass --artifact <path>.`,
53
88
  );
54
89
  }
55
90
 
@@ -77,3 +112,14 @@ export function resolveBuildArtifactDir(pkg, options = {}) {
77
112
 
78
113
  return artifactDir;
79
114
  }
115
+
116
+ /**
117
+ * @param {string} value
118
+ * @returns {string[]}
119
+ */
120
+ export function parsePlatformList(value) {
121
+ return String(value ?? '')
122
+ .split(',')
123
+ .map((entry) => entry.trim())
124
+ .filter(Boolean);
125
+ }
@@ -110,6 +110,7 @@ export async function submitWrapfullyChore ({
110
110
  log(`adaptfully: chore ${choreId} submitted; waiting…`);
111
111
 
112
112
  let lastStatus = '';
113
+ let logAfter = 0;
113
114
 
114
115
  for (;;) {
115
116
  const statusUrl = `${base}yap/wrapfully/${encodeURIComponent(choreId)}/status`;
@@ -125,6 +126,28 @@ export async function submitWrapfullyChore ({
125
126
  throw new Error(status.errors?.[0] || `Chore ${choreId} failed`);
126
127
  }
127
128
 
129
+ if (status?.state === 'pending') {
130
+ try {
131
+ const logsUrl = `${base}yap/wrapfully/${encodeURIComponent(choreId)}/logs?after=${logAfter}`;
132
+ const {data: logs} = await axios.get(logsUrl, {
133
+ headers: {Authorization: `Bearer ${accessToken}`},
134
+ validateStatus: () => true,
135
+ });
136
+
137
+ if (Array.isArray(logs?.lines) && logs.lines.length) {
138
+ for (const line of logs.lines) {
139
+ const text = line?.message || JSON.stringify(line);
140
+
141
+ log(`adaptfully: ${text}`);
142
+ }
143
+ logAfter = logs.after ?? logAfter;
144
+ lastStatus = '';
145
+ }
146
+ } catch (e) {
147
+ // live logs are best-effort
148
+ }
149
+ }
150
+
128
151
  const message = String(status?.status || status?.state || 'waiting');
129
152
 
130
153
  if (message !== lastStatus) {
@@ -174,6 +197,7 @@ export async function submitWrapfullyChore ({
174
197
  * encrypt?: boolean,
175
198
  * encryptPublicKey?: string,
176
199
  * wrapfullyConfig?: object,
200
+ * extractRoot?: string,
177
201
  * }} [options]
178
202
  */
179
203
  export async function send (
@@ -190,6 +214,7 @@ export async function send (
190
214
  const log = options.log ?? console.log;
191
215
  const platformKey = options.platformKey ?? family;
192
216
  const outputRoot = pkg.config?.outputFolder || 'output';
217
+ const extractRoot = path.resolve(options.extractRoot || outputRoot);
193
218
  const accessToken = options.accessToken;
194
219
 
195
220
  if (!accessToken) {
@@ -197,7 +222,8 @@ export async function send (
197
222
  }
198
223
 
199
224
  if (mode === 'extract') {
200
- clearStaleBuildExtract(outputRoot);
225
+ fs.mkdirSync(extractRoot, { recursive: true });
226
+ clearStaleBuildExtract(extractRoot);
201
227
  }
202
228
 
203
229
  let archiveStream;
@@ -313,8 +339,8 @@ export async function send (
313
339
  }
314
340
 
315
341
  const destination = mode === 'extract'
316
- ? unzipper.Extract({path: `${outputRoot}/`, concurrency: 1})
317
- : fs.createWriteStream(`${outputRoot}/${pkg.name}-${pkg.version}-${stage}-${family}.zip`);
342
+ ? unzipper.Extract({path: `${extractRoot}/`, concurrency: 1})
343
+ : fs.createWriteStream(`${extractRoot}/${pkg.name}-${pkg.version}-${stage}-${family}.zip`);
318
344
 
319
345
  try {
320
346
  await pipeline(Readable.from(resultBuffer), destination);
@@ -325,6 +351,6 @@ export async function send (
325
351
  }
326
352
 
327
353
  if (mode === 'extract') {
328
- printBuildReport(`${stage}-${family}`, pkg);
354
+ printBuildReport(`${stage}-${family}`, pkg, { extractRoot });
329
355
  }
330
356
  }
package/lib/node/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { buildOutputDir, clearStaleBuildExtract, resolveBuildArtifactDir } from './artifacts.js';
1
+ export { buildOutputDir, clearStaleBuildExtract, parsePlatformList, resolveBuildArtifactDir, resolveExtractRoot } from './artifacts.js';
2
2
  export { createArchive, createDeployArchive, createReleaseArchive, createSourceArchive } from './archive.js';
3
3
  export { loadProjectConfig, resolveServerUrl } from './config.js';
4
4
  export {
@@ -18,7 +18,7 @@ export {
18
18
  resolveMetaIcons,
19
19
  resolvePlaceholderIconPath,
20
20
  } from './icons.js';
21
- export { adaptfullyFromCli, parseStageArgs, resolveWrapfullyRoute, runAdaptfullyStage } from './pipeline.js';
21
+ export { adaptfullyFromCli, parseStageArgs, resolveWrapfullyRoute, runAdaptfullyStage, runAdaptfullyStages } from './pipeline.js';
22
22
  export { prebuildPlatform, prebuildOutputDir, resolveHtmlInjections } from './prebuild.js';
23
23
  export { getPackageRoot, getRuntimeDir, resolveRuntimeScript } from './paths.js';
24
24
  export {
@@ -1,7 +1,12 @@
1
1
  import path from 'node:path';
2
2
  import { loadProjectConfig, resolveServerUrl, resolveAccessToken, resolveEncryptFlag } from './config.js';
3
3
  import { send } from './deploy.js';
4
- import { resolveBuildArtifactDir } from './artifacts.js';
4
+ import {
5
+ buildOutputDir,
6
+ parsePlatformList,
7
+ resolveBuildArtifactDir,
8
+ resolveExtractRoot,
9
+ } from './artifacts.js';
5
10
  import { prebuildPlatform } from './prebuild.js';
6
11
  import {
7
12
  resolveBuildCredentialDirs,
@@ -44,7 +49,7 @@ export function resolveWrapfullyRoute(buildSpec) {
44
49
  /**
45
50
  * @param {AdaptfullyStage} stage
46
51
  * @param {string} platformKey
47
- * @param {{ pkg: object, deployFolder: string, server?: string, mode?: string, deployment?: string, artifactPath?: string, log?: (message: string) => void, outputRoot?: string, accessToken?: string, encrypt?: boolean, encryptPublicKey?: string, wrapfullyConfig?: object }} options
52
+ * @param {{ pkg: object, deployFolder: string, server?: string, mode?: string, deployment?: string, artifactPath?: string, log?: (message: string) => void, outputRoot?: string, accessToken?: string, encrypt?: boolean, encryptPublicKey?: string, wrapfullyConfig?: object, extractRoot?: string, multiPlatform?: boolean }} options
48
53
  */
49
54
  export async function runAdaptfullyStage(stage, platformKey, options) {
50
55
  if (!VALID_STAGES.has(stage)) {
@@ -55,6 +60,9 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
55
60
  const { pkg, deployFolder } = options;
56
61
  const buildSpec = resolveBuildSpec(platformKey, pkg);
57
62
  let deployments = resolveDeploymentsForPlatform(platformKey, pkg);
63
+ const outputRoot = buildOutputDir(pkg, options.outputRoot);
64
+ const extractRoot = options.extractRoot
65
+ ?? resolveExtractRoot(outputRoot, platformKey, { multiPlatform: options.multiPlatform === true });
58
66
 
59
67
  if (options.deployment) {
60
68
  if (!deployments.includes(options.deployment)) {
@@ -77,6 +85,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
77
85
  artifactPath = resolveBuildArtifactDir(pkg, {
78
86
  outputRoot: options.outputRoot,
79
87
  platformKey,
88
+ multiPlatform: options.multiPlatform,
80
89
  });
81
90
  }
82
91
 
@@ -88,7 +97,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
88
97
  });
89
98
 
90
99
  if (stage === 'prebuild') {
91
- return { prebuiltDir, platformKey, buildSpec };
100
+ return { prebuiltDir, platformKey, buildSpec, extractRoot };
92
101
  }
93
102
 
94
103
  const gameId = `${pkg.name}-${pkg.version}`;
@@ -97,6 +106,15 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
97
106
  const sent = [];
98
107
 
99
108
  const wrapfullyRoute = resolveWrapfullyRoute(buildSpec);
109
+ const sendOptionsBase = {
110
+ log,
111
+ platformKey,
112
+ accessToken: options.accessToken,
113
+ encrypt: options.encrypt,
114
+ encryptPublicKey: options.encryptPublicKey,
115
+ wrapfullyConfig: options.wrapfullyConfig,
116
+ extractRoot,
117
+ };
100
118
 
101
119
  if (stage === 'release') {
102
120
  const deploymentDirs = deployments.map((key) => resolvePublishDir(key));
@@ -111,6 +129,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
111
129
  deploymentDirs.map((dir) => path.resolve(dir)).join(', ') || '(none)'
112
130
  }`,
113
131
  );
132
+ log(`adaptfully: extract → ${extractRoot}`);
114
133
 
115
134
  await send(
116
135
  gameId,
@@ -122,18 +141,13 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
122
141
  pkg,
123
142
  options.mode ?? 'extract',
124
143
  {
125
- log,
126
- platformKey,
144
+ ...sendOptionsBase,
127
145
  deploymentDirs: deploymentDirs.map((dir) => path.resolve(dir)),
128
- accessToken: options.accessToken,
129
- encrypt: options.encrypt,
130
- encryptPublicKey: options.encryptPublicKey,
131
- wrapfullyConfig: options.wrapfullyConfig,
132
146
  },
133
147
  );
134
148
 
135
149
  sent.push({ deploymentKey: 'release', stage, family: buildSpec.family });
136
- return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath };
150
+ return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath, extractRoot };
137
151
  }
138
152
 
139
153
  for (const deploymentKey of deployments) {
@@ -156,6 +170,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
156
170
  }`,
157
171
  );
158
172
  }
173
+ log(`adaptfully: extract → ${extractRoot}`);
159
174
 
160
175
  await send(
161
176
  gameId,
@@ -167,23 +182,62 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
167
182
  pkg,
168
183
  options.mode ?? 'extract',
169
184
  {
170
- log,
185
+ ...sendOptionsBase,
171
186
  publishDir,
172
187
  deploymentDirs,
173
- platformKey,
174
188
  deploymentKey: wrapStage === 'deploy' ? deploymentKey : undefined,
175
189
  artifactPath,
176
- accessToken: options.accessToken,
177
- encrypt: options.encrypt,
178
- encryptPublicKey: options.encryptPublicKey,
179
- wrapfullyConfig: options.wrapfullyConfig,
180
190
  },
181
191
  );
182
192
 
183
193
  sent.push({ deploymentKey, stage: wrapStage, family: buildSpec.family });
184
194
  }
185
195
 
186
- return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath };
196
+ return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath, extractRoot };
197
+ }
198
+
199
+ /**
200
+ * Run a stage for one or more platforms sequentially.
201
+ * Multi-platform extracts land in `output/<platformKey>/` so outputs do not overwrite.
202
+ *
203
+ * @param {AdaptfullyStage} stage
204
+ * @param {string[]} platformKeys
205
+ * @param {Parameters<typeof runAdaptfullyStage>[2]} options
206
+ */
207
+ export async function runAdaptfullyStages(stage, platformKeys, options) {
208
+ const log = options.log ?? console.log;
209
+ const multiPlatform = platformKeys.length > 1;
210
+ /** @type {{ platformKey: string, ok: boolean, result?: object, error?: Error }[]} */
211
+ const results = [];
212
+
213
+ if (multiPlatform) {
214
+ log(`adaptfully: sequential ${stage} for ${platformKeys.join(', ')}`);
215
+ }
216
+
217
+ for (const platformKey of platformKeys) {
218
+ log(`adaptfully: ——— ${stage} ${platformKey} ———`);
219
+ try {
220
+ const result = await runAdaptfullyStage(stage, platformKey, {
221
+ ...options,
222
+ multiPlatform,
223
+ });
224
+ results.push({ platformKey, ok: true, result });
225
+ } catch (err) {
226
+ const error = err instanceof Error ? err : new Error(String(err));
227
+ console.error(`adaptfully: ${stage} ${platformKey} failed: ${error.message}`);
228
+ results.push({ platformKey, ok: false, error });
229
+ }
230
+ }
231
+
232
+ const failed = results.filter((entry) => !entry.ok);
233
+ if (failed.length) {
234
+ throw new Error(
235
+ `adaptfully: ${failed.length}/${results.length} platform(s) failed: `
236
+ + failed.map((entry) => entry.platformKey).join(', '),
237
+ );
238
+ }
239
+
240
+ return results;
187
241
  }
188
242
 
189
243
  /**
@@ -202,16 +256,23 @@ export async function adaptfullyFromCli(argv = process.argv) {
202
256
  }
203
257
 
204
258
  const stage = command;
205
- const { positionals, deployment, artifactPath, encrypt, encryptPublicKey } = parseStageArgs(argv.slice(3));
206
- const platformKey = positionals[0];
207
- const cliServer = positionals[1];
208
- const mode = positionals[2] ?? 'extract';
259
+ const {
260
+ positionals,
261
+ deployment,
262
+ artifactPath,
263
+ encrypt,
264
+ encryptPublicKey,
265
+ platforms,
266
+ } = parseStageArgs(argv.slice(3));
267
+ const platformArg = platforms || positionals[0];
268
+ const cliServer = platforms ? positionals[0] : positionals[1];
269
+ const mode = platforms ? (positionals[1] ?? 'extract') : (positionals[2] ?? 'extract');
209
270
 
210
- if (!stage || !platformKey) {
271
+ if (!stage || !platformArg) {
211
272
  throw new Error(
212
273
  'Usage:\n'
213
- + ' adaptfully <prebuild|build|deploy|release> <platform> [showfullyServer] [mode]\n'
214
- + ' [--deployment <key>] [--artifact <path>] [--encrypt] [--encrypt-public-key <pem|path>]\n'
274
+ + ' adaptfully <prebuild|build|deploy|release> <platform[,platform...]> [showfullyServer] [mode]\n'
275
+ + ' [--platforms a,b,c] [--deployment <key>] [--artifact <path>] [--encrypt] [--encrypt-public-key <pem|path>]\n'
215
276
  + ' adaptfully steam-publish [--username U] [--password P] [--deployment steam] [--output path]\n'
216
277
  + ' adaptfully google-publish [--from service-account.json] [--deployment android] [--output path]\n'
217
278
  + ' adaptfully apple-publish [--deployment ios] [--category C] [--identity I] [--username U] [--password P]\n'
@@ -224,6 +285,9 @@ export async function adaptfullyFromCli(argv = process.argv) {
224
285
  + ' deploy Yap deploy chore with prior artifact + deployment credentials\n'
225
286
  + ' release Yap release chore (build + configured deployments)\n'
226
287
  + '\n'
288
+ + ' Multiple platforms (comma-separated or --platforms) run sequentially.\n'
289
+ + ' Multi-platform extracts go to output/<platform>/ so siblings are preserved.\n'
290
+ + '\n'
227
291
  + ' --deployment <key> Target a single named deployment.\n'
228
292
  + ' --artifact <path> Prior build artifact directory (deploy only; default: ./output/ after build).\n'
229
293
  + ' --encrypt Envelope-encrypt chore to Wrapfully (optional; default key embedded).\n'
@@ -242,13 +306,18 @@ export async function adaptfullyFromCli(argv = process.argv) {
242
306
  );
243
307
  }
244
308
 
309
+ const platformKeys = parsePlatformList(platformArg);
310
+ if (!platformKeys.length) {
311
+ throw new Error('At least one platform is required.');
312
+ }
313
+
245
314
  const { pkg, wrapfullyConfig } = await loadProjectConfig();
246
315
  const server = resolveServerUrl(wrapfullyConfig, cliServer);
247
316
  const accessToken = resolveAccessToken(wrapfullyConfig);
248
317
  const encryptFlag = resolveEncryptFlag(wrapfullyConfig, encrypt);
249
318
  const deployFolder = pkg.config?.deployFolder || 'deploy';
250
319
 
251
- return runAdaptfullyStage(/** @type {AdaptfullyStage} */ (stage), platformKey, {
320
+ return runAdaptfullyStages(/** @type {AdaptfullyStage} */ (stage), platformKeys, {
252
321
  pkg,
253
322
  deployFolder,
254
323
  server,
@@ -264,7 +333,7 @@ export async function adaptfullyFromCli(argv = process.argv) {
264
333
 
265
334
  /**
266
335
  * @param {string[]} args
267
- * @returns {{ positionals: string[], deployment?: string, artifactPath?: string, encrypt?: boolean, encryptPublicKey?: string }}
336
+ * @returns {{ positionals: string[], deployment?: string, artifactPath?: string, encrypt?: boolean, encryptPublicKey?: string, platforms?: string }}
268
337
  */
269
338
  export function parseStageArgs(args) {
270
339
  /** @type {string[]} */
@@ -273,6 +342,7 @@ export function parseStageArgs(args) {
273
342
  let artifactPath;
274
343
  let encrypt;
275
344
  let encryptPublicKey;
345
+ let platforms;
276
346
 
277
347
  for (let i = 0; i < args.length; i++) {
278
348
  const arg = args[i];
@@ -281,6 +351,10 @@ export function parseStageArgs(args) {
281
351
  deployment = args[++i];
282
352
  } else if (arg.startsWith('--deployment=')) {
283
353
  deployment = arg.slice('--deployment='.length);
354
+ } else if (arg === '--platforms' && args[i + 1]) {
355
+ platforms = args[++i];
356
+ } else if (arg.startsWith('--platforms=')) {
357
+ platforms = arg.slice('--platforms='.length);
284
358
  } else if (arg === '--artifact' && args[i + 1]) {
285
359
  artifactPath = args[++i];
286
360
  } else if (arg.startsWith('--artifact=')) {
@@ -296,5 +370,5 @@ export function parseStageArgs(args) {
296
370
  }
297
371
  }
298
372
 
299
- return { positionals, deployment, artifactPath, encrypt, encryptPublicKey };
373
+ return { positionals, deployment, artifactPath, encrypt, encryptPublicKey, platforms };
300
374
  }
@@ -1,12 +1,15 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
2
3
 
3
4
  /**
4
5
  * @param {string} builder
5
6
  * @param {{ name: string, version: string }} pkg
7
+ * @param {{ extractRoot?: string }} [options]
6
8
  */
7
- export function printBuildReport(builder, pkg) {
8
- const statusPath = './output/wrapfully-status.json';
9
- const legacyPath = `./output/${pkg.name}-${pkg.version}-${builder}.txt`;
9
+ export function printBuildReport(builder, pkg, options = {}) {
10
+ const extractRoot = path.resolve(options.extractRoot || './output');
11
+ const statusPath = path.join(extractRoot, 'wrapfully-status.json');
12
+ const legacyPath = path.join(extractRoot, `${pkg.name}-${pkg.version}-${builder}.txt`);
10
13
 
11
14
  let status = null;
12
15
  if (fs.existsSync(statusPath)) {
@@ -30,8 +33,7 @@ export function printBuildReport(builder, pkg) {
30
33
  }
31
34
 
32
35
  if (!status.ok) {
33
- console.error('Wrapfully build finished with errors.');
34
- process.exit(1);
36
+ throw new Error('Wrapfully build finished with errors.');
35
37
  }
36
38
  return;
37
39
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makefully/adaptfully",
3
- "version": "4.2.1",
3
+ "version": "4.3.0",
4
4
  "description": "Platform abstraction and Wrapfully deploy client for Makefully games",
5
5
  "type": "module",
6
6
  "main": "./lib/node/index.js",