@makefully/adaptfully 4.2.2 → 5.0.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
+ ## 5.0.0 — 2026-09-11
6
+
7
+ ### Changed
8
+
9
+ - **Breaking:** Wrapfully extracts always land in `output/<platform>/` (including single-platform `build` / `release` / `deploy`). Sequential runs no longer overwrite siblings. Deploy still falls back to a legacy flat `output/` artifact if present.
10
+
11
+ ## 4.3.0 — 2026-09-11
12
+
13
+ ### Added
14
+
15
+ - **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.
16
+ - 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/`).
17
+
18
+ ### Changed
19
+
20
+ - Build report failures throw instead of calling `process.exit`, so sequential platform runs can continue and report a summary.
21
+
5
22
  ## 4.2.2 — 2026-09-10
6
23
 
7
24
  ### Added
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; each → 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**. Build/deploy/release extracts always go to `output/<platform>/` so sibling results are not overwritten; only that folder’s prior `artifacts/` / status files are cleared before each extract.
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.
@@ -279,7 +283,7 @@ import {
279
283
 
280
284
  ## Wrapfully deploy
281
285
 
282
- After prebuild, the build and deploy stages zip `output/<platform>-prebuild/` and POST it to a Wrapfully build server. Artifacts are saved to `./output/`.
286
+ After prebuild, the build and deploy stages zip `output/<platform>-prebuild/` and POST it to a Wrapfully build server. Artifacts are saved to `./output/<platform>/`.
283
287
 
284
288
  ## Quick start
285
289
 
@@ -318,7 +322,7 @@ npx adaptfully apple-publish [--deployment ios] [--category C] [--identity I] [-
318
322
  |----------|---------|-------------|
319
323
  | `platform` | — | Platform key from `config.platforms` (`web`, `steam`, etc.) |
320
324
  | `server` | see below | Wrapfully server base URL (`build` and `deploy` only) |
321
- | `mode` | `extract` | `extract` unpacks the response zip into `./output/`; any other value saves `./output/{name}-{version}-{builder}.zip` |
325
+ | `mode` | `extract` | `extract` unpacks the response zip into `./output/<platform>/`; any other value saves `./output/<platform>/{name}-{version}-{builder}.zip` |
322
326
 
323
327
  Examples:
324
328
 
@@ -398,7 +402,9 @@ mygame/
398
402
  │ └── index.html
399
403
  ├── output/
400
404
  │ ├── web-prebuild/ # after adaptfully prebuild web
401
- │ └── steam-prebuild/ # after adaptfully prebuild steam
405
+ │ ├── steam-prebuild/ # after adaptfully prebuild steam
406
+ │ ├── web/ # after adaptfully build/release web
407
+ │ └── steam/ # after adaptfully build/release steam
402
408
  └── assets/
403
409
  └── meta/ # shared files packaged as meta/ in the zip
404
410
  ├── icon-foreground.png
@@ -723,7 +729,7 @@ To sign the app, place your certificate at `assets/meta/deployments/<deployment>
723
729
 
724
730
  ## Response
725
731
 
726
- The server responds with a zip stream containing build artifacts (`.apk`, `.aab`, `.ipa`, `.app`, `.exe`, etc.) and optional status files. By default the client extracts this into `./output/`. Use a non-`extract` mode value to save the raw response zip instead.
732
+ The server responds with a zip stream containing build artifacts (`.apk`, `.aab`, `.ipa`, `.app`, `.exe`, etc.) and optional status files. By default the client extracts this into `./output/<platform>/`. Use a non-`extract` mode value to save the raw response zip instead.
727
733
 
728
734
  Every build also includes `wrapfully-status.json` with structured `success`, `warn`, and `error` events. The client prints these after extraction and exits with code 1 if any errors were reported, so build failures do not crash the server silently.
729
735
 
@@ -17,13 +17,30 @@ 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: always `output/<platformKey>/`
21
+ * so sequential single-platform and multi-platform runs share one layout.
22
+ * Without a platform key, falls back to `outputRoot`.
22
23
  *
23
- * @param {string} [outputRoot='output']
24
+ * @param {string} outputRoot
25
+ * @param {string} [platformKey]
26
+ * @returns {string}
24
27
  */
25
- export function clearStaleBuildExtract(outputRoot = 'output') {
28
+ export function resolveExtractRoot(outputRoot, platformKey) {
26
29
  const root = path.resolve(outputRoot);
30
+ if (platformKey) {
31
+ return path.join(root, platformKey);
32
+ }
33
+ return root;
34
+ }
35
+
36
+ /**
37
+ * Remove prior extract artifacts in `extractRoot` only (artifacts/, status,
38
+ * manifest). Does not wipe sibling platform folders under output/.
39
+ *
40
+ * @param {string} [extractRoot='output']
41
+ */
42
+ export function clearStaleBuildExtract(extractRoot = 'output') {
43
+ const root = path.resolve(extractRoot);
27
44
  const stalePaths = [
28
45
  path.join(root, 'artifacts'),
29
46
  path.join(root, MANIFEST_NAME),
@@ -37,19 +54,37 @@ export function clearStaleBuildExtract(outputRoot = 'output') {
37
54
 
38
55
  /**
39
56
  * Resolve the artifact directory from a prior `adaptfully build` (or compatible zip extract).
57
+ * Prefers `output/<platform>/`; still accepts a legacy flat `output/` extract.
40
58
  *
41
59
  * @param {{ name: string, version: string, config?: { outputFolder?: string } }} pkg
42
60
  * @param {{ outputRoot?: string, platformKey?: string }} [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));
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
+ }
@@ -197,6 +197,7 @@ export async function submitWrapfullyChore ({
197
197
  * encrypt?: boolean,
198
198
  * encryptPublicKey?: string,
199
199
  * wrapfullyConfig?: object,
200
+ * extractRoot?: string,
200
201
  * }} [options]
201
202
  */
202
203
  export async function send (
@@ -213,6 +214,7 @@ export async function send (
213
214
  const log = options.log ?? console.log;
214
215
  const platformKey = options.platformKey ?? family;
215
216
  const outputRoot = pkg.config?.outputFolder || 'output';
217
+ const extractRoot = path.resolve(options.extractRoot || outputRoot);
216
218
  const accessToken = options.accessToken;
217
219
 
218
220
  if (!accessToken) {
@@ -220,7 +222,8 @@ export async function send (
220
222
  }
221
223
 
222
224
  if (mode === 'extract') {
223
- clearStaleBuildExtract(outputRoot);
225
+ fs.mkdirSync(extractRoot, { recursive: true });
226
+ clearStaleBuildExtract(extractRoot);
224
227
  }
225
228
 
226
229
  let archiveStream;
@@ -336,8 +339,8 @@ export async function send (
336
339
  }
337
340
 
338
341
  const destination = mode === 'extract'
339
- ? unzipper.Extract({path: `${outputRoot}/`, concurrency: 1})
340
- : 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`);
341
344
 
342
345
  try {
343
346
  await pipeline(Readable.from(resultBuffer), destination);
@@ -348,6 +351,6 @@ export async function send (
348
351
  }
349
352
 
350
353
  if (mode === 'extract') {
351
- printBuildReport(`${stage}-${family}`, pkg);
354
+ printBuildReport(`${stage}-${family}`, pkg, { extractRoot });
352
355
  }
353
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 }} 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);
58
66
 
59
67
  if (options.deployment) {
60
68
  if (!deployments.includes(options.deployment)) {
@@ -88,7 +96,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
88
96
  });
89
97
 
90
98
  if (stage === 'prebuild') {
91
- return { prebuiltDir, platformKey, buildSpec };
99
+ return { prebuiltDir, platformKey, buildSpec, extractRoot };
92
100
  }
93
101
 
94
102
  const gameId = `${pkg.name}-${pkg.version}`;
@@ -97,6 +105,15 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
97
105
  const sent = [];
98
106
 
99
107
  const wrapfullyRoute = resolveWrapfullyRoute(buildSpec);
108
+ const sendOptionsBase = {
109
+ log,
110
+ platformKey,
111
+ accessToken: options.accessToken,
112
+ encrypt: options.encrypt,
113
+ encryptPublicKey: options.encryptPublicKey,
114
+ wrapfullyConfig: options.wrapfullyConfig,
115
+ extractRoot,
116
+ };
100
117
 
101
118
  if (stage === 'release') {
102
119
  const deploymentDirs = deployments.map((key) => resolvePublishDir(key));
@@ -111,6 +128,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
111
128
  deploymentDirs.map((dir) => path.resolve(dir)).join(', ') || '(none)'
112
129
  }`,
113
130
  );
131
+ log(`adaptfully: extract → ${extractRoot}`);
114
132
 
115
133
  await send(
116
134
  gameId,
@@ -122,18 +140,13 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
122
140
  pkg,
123
141
  options.mode ?? 'extract',
124
142
  {
125
- log,
126
- platformKey,
143
+ ...sendOptionsBase,
127
144
  deploymentDirs: deploymentDirs.map((dir) => path.resolve(dir)),
128
- accessToken: options.accessToken,
129
- encrypt: options.encrypt,
130
- encryptPublicKey: options.encryptPublicKey,
131
- wrapfullyConfig: options.wrapfullyConfig,
132
145
  },
133
146
  );
134
147
 
135
148
  sent.push({ deploymentKey: 'release', stage, family: buildSpec.family });
136
- return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath };
149
+ return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath, extractRoot };
137
150
  }
138
151
 
139
152
  for (const deploymentKey of deployments) {
@@ -156,6 +169,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
156
169
  }`,
157
170
  );
158
171
  }
172
+ log(`adaptfully: extract → ${extractRoot}`);
159
173
 
160
174
  await send(
161
175
  gameId,
@@ -167,23 +181,58 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
167
181
  pkg,
168
182
  options.mode ?? 'extract',
169
183
  {
170
- log,
184
+ ...sendOptionsBase,
171
185
  publishDir,
172
186
  deploymentDirs,
173
- platformKey,
174
187
  deploymentKey: wrapStage === 'deploy' ? deploymentKey : undefined,
175
188
  artifactPath,
176
- accessToken: options.accessToken,
177
- encrypt: options.encrypt,
178
- encryptPublicKey: options.encryptPublicKey,
179
- wrapfullyConfig: options.wrapfullyConfig,
180
189
  },
181
190
  );
182
191
 
183
192
  sent.push({ deploymentKey, stage: wrapStage, family: buildSpec.family });
184
193
  }
185
194
 
186
- return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath };
195
+ return { prebuiltDir, platformKey, buildSpec, deployments: sent, artifactPath, extractRoot };
196
+ }
197
+
198
+ /**
199
+ * Run a stage for one or more platforms sequentially.
200
+ * Each platform extracts to `output/<platformKey>/`.
201
+ *
202
+ * @param {AdaptfullyStage} stage
203
+ * @param {string[]} platformKeys
204
+ * @param {Parameters<typeof runAdaptfullyStage>[2]} options
205
+ */
206
+ export async function runAdaptfullyStages(stage, platformKeys, options) {
207
+ const log = options.log ?? console.log;
208
+ /** @type {{ platformKey: string, ok: boolean, result?: object, error?: Error }[]} */
209
+ const results = [];
210
+
211
+ if (platformKeys.length > 1) {
212
+ log(`adaptfully: sequential ${stage} for ${platformKeys.join(', ')}`);
213
+ }
214
+
215
+ for (const platformKey of platformKeys) {
216
+ log(`adaptfully: ——— ${stage} ${platformKey} ———`);
217
+ try {
218
+ const result = await runAdaptfullyStage(stage, platformKey, options);
219
+ results.push({ platformKey, ok: true, result });
220
+ } catch (err) {
221
+ const error = err instanceof Error ? err : new Error(String(err));
222
+ console.error(`adaptfully: ${stage} ${platformKey} failed: ${error.message}`);
223
+ results.push({ platformKey, ok: false, error });
224
+ }
225
+ }
226
+
227
+ const failed = results.filter((entry) => !entry.ok);
228
+ if (failed.length) {
229
+ throw new Error(
230
+ `adaptfully: ${failed.length}/${results.length} platform(s) failed: `
231
+ + failed.map((entry) => entry.platformKey).join(', '),
232
+ );
233
+ }
234
+
235
+ return results;
187
236
  }
188
237
 
189
238
  /**
@@ -202,16 +251,23 @@ export async function adaptfullyFromCli(argv = process.argv) {
202
251
  }
203
252
 
204
253
  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';
254
+ const {
255
+ positionals,
256
+ deployment,
257
+ artifactPath,
258
+ encrypt,
259
+ encryptPublicKey,
260
+ platforms,
261
+ } = parseStageArgs(argv.slice(3));
262
+ const platformArg = platforms || positionals[0];
263
+ const cliServer = platforms ? positionals[0] : positionals[1];
264
+ const mode = platforms ? (positionals[1] ?? 'extract') : (positionals[2] ?? 'extract');
209
265
 
210
- if (!stage || !platformKey) {
266
+ if (!stage || !platformArg) {
211
267
  throw new Error(
212
268
  'Usage:\n'
213
- + ' adaptfully <prebuild|build|deploy|release> <platform> [showfullyServer] [mode]\n'
214
- + ' [--deployment <key>] [--artifact <path>] [--encrypt] [--encrypt-public-key <pem|path>]\n'
269
+ + ' adaptfully <prebuild|build|deploy|release> <platform[,platform...]> [showfullyServer] [mode]\n'
270
+ + ' [--platforms a,b,c] [--deployment <key>] [--artifact <path>] [--encrypt] [--encrypt-public-key <pem|path>]\n'
215
271
  + ' adaptfully steam-publish [--username U] [--password P] [--deployment steam] [--output path]\n'
216
272
  + ' adaptfully google-publish [--from service-account.json] [--deployment android] [--output path]\n'
217
273
  + ' adaptfully apple-publish [--deployment ios] [--category C] [--identity I] [--username U] [--password P]\n'
@@ -224,8 +280,11 @@ export async function adaptfullyFromCli(argv = process.argv) {
224
280
  + ' deploy Yap deploy chore with prior artifact + deployment credentials\n'
225
281
  + ' release Yap release chore (build + configured deployments)\n'
226
282
  + '\n'
283
+ + ' Multiple platforms (comma-separated or --platforms) run sequentially.\n'
284
+ + ' Extracts always go to output/<platform>/ so siblings are preserved.\n'
285
+ + '\n'
227
286
  + ' --deployment <key> Target a single named deployment.\n'
228
- + ' --artifact <path> Prior build artifact directory (deploy only; default: ./output/ after build).\n'
287
+ + ' --artifact <path> Prior build artifact directory (deploy only; default: ./output/<platform>/ after build).\n'
229
288
  + ' --encrypt Envelope-encrypt chore to Wrapfully (optional; default key embedded).\n'
230
289
  + ' --encrypt-public-key <pem|path> Override Wrapfully public key when encrypting.\n'
231
290
  + '\n'
@@ -242,13 +301,18 @@ export async function adaptfullyFromCli(argv = process.argv) {
242
301
  );
243
302
  }
244
303
 
304
+ const platformKeys = parsePlatformList(platformArg);
305
+ if (!platformKeys.length) {
306
+ throw new Error('At least one platform is required.');
307
+ }
308
+
245
309
  const { pkg, wrapfullyConfig } = await loadProjectConfig();
246
310
  const server = resolveServerUrl(wrapfullyConfig, cliServer);
247
311
  const accessToken = resolveAccessToken(wrapfullyConfig);
248
312
  const encryptFlag = resolveEncryptFlag(wrapfullyConfig, encrypt);
249
313
  const deployFolder = pkg.config?.deployFolder || 'deploy';
250
314
 
251
- return runAdaptfullyStage(/** @type {AdaptfullyStage} */ (stage), platformKey, {
315
+ return runAdaptfullyStages(/** @type {AdaptfullyStage} */ (stage), platformKeys, {
252
316
  pkg,
253
317
  deployFolder,
254
318
  server,
@@ -264,7 +328,7 @@ export async function adaptfullyFromCli(argv = process.argv) {
264
328
 
265
329
  /**
266
330
  * @param {string[]} args
267
- * @returns {{ positionals: string[], deployment?: string, artifactPath?: string, encrypt?: boolean, encryptPublicKey?: string }}
331
+ * @returns {{ positionals: string[], deployment?: string, artifactPath?: string, encrypt?: boolean, encryptPublicKey?: string, platforms?: string }}
268
332
  */
269
333
  export function parseStageArgs(args) {
270
334
  /** @type {string[]} */
@@ -273,6 +337,7 @@ export function parseStageArgs(args) {
273
337
  let artifactPath;
274
338
  let encrypt;
275
339
  let encryptPublicKey;
340
+ let platforms;
276
341
 
277
342
  for (let i = 0; i < args.length; i++) {
278
343
  const arg = args[i];
@@ -281,6 +346,10 @@ export function parseStageArgs(args) {
281
346
  deployment = args[++i];
282
347
  } else if (arg.startsWith('--deployment=')) {
283
348
  deployment = arg.slice('--deployment='.length);
349
+ } else if (arg === '--platforms' && args[i + 1]) {
350
+ platforms = args[++i];
351
+ } else if (arg.startsWith('--platforms=')) {
352
+ platforms = arg.slice('--platforms='.length);
284
353
  } else if (arg === '--artifact' && args[i + 1]) {
285
354
  artifactPath = args[++i];
286
355
  } else if (arg.startsWith('--artifact=')) {
@@ -296,5 +365,5 @@ export function parseStageArgs(args) {
296
365
  }
297
366
  }
298
367
 
299
- return { positionals, deployment, artifactPath, encrypt, encryptPublicKey };
368
+ return { positionals, deployment, artifactPath, encrypt, encryptPublicKey, platforms };
300
369
  }
@@ -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.2",
3
+ "version": "5.0.0",
4
4
  "description": "Platform abstraction and Wrapfully deploy client for Makefully games",
5
5
  "type": "module",
6
6
  "main": "./lib/node/index.js",