@makefully/adaptfully 3.10.0 → 3.11.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,17 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 3.11.0 — 2026-07-23
6
+
7
+ ### Added
8
+
9
+ - **Layered icon validation** — build/deploy/release zips always include `meta/icon-foreground.png` and `meta/icon-background.png`. When project icons are missing under `assets/meta/`, Adaptfully **warns** (does not fail) and ships packaged placeholder icons from `lib/assets/`.
10
+ - **`resolveMetaIcons` / `appendMetaIcons`** — exported helpers for resolving project vs placeholder icon paths.
11
+
12
+ ### Changed
13
+
14
+ - Legacy meta packaging skips re-adding icon filenames so placeholders and project icons are not duplicated in the zip.
15
+
5
16
  ## 3.10.0 — 2026-07-17
6
17
 
7
18
  ### Added
@@ -13,6 +24,7 @@ All notable changes to this project are documented in this file.
13
24
 
14
25
  - **Builder family defaults** — `android` / `ios` / `ios-sim` (and `-dev`) resolve to Wrapfully family **`capacitor`**. Explicit `packager: "cordova"` still selects Cordova for that platform. Composite `cordova` / `apple` routes are not used.
15
26
  - **`resolveBuildSpec`** includes `socialLogin: boolean`.
27
+ - **Single-target Wrapfully POSTs** go to the target route (`/android/build`, `/ios/release`, …) instead of the family route (`/capacitor/...`), so OS relay and existing target routes keep working.
16
28
 
17
29
  ## 3.9.0 — 2026-07-15
18
30
 
package/README.md CHANGED
@@ -363,7 +363,7 @@ mygame/
363
363
 
364
364
  The selected deployment's folder is shipped as `meta/publish/` in the zip, so per-platform credential file names (`build.json`, `sftp.json`, `steam.json`, `apple.json`, `google.json`, `ms.json`, `android/`, `ms/`) live inside `assets/meta/deployments/<key>/`.
365
365
 
366
- Icons (`icon-foreground.png`, `icon-background.png`) are required for mobile, desktop, and Steam builds.
366
+ Icons (`icon-foreground.png`, `icon-background.png`) are required for mobile, desktop, and Steam builds. If either file is missing from `assets/meta/`, Adaptfully **warns** and ships packaged placeholder icons for that build so Wrapfully can still run.
367
367
 
368
368
  ### Icons
369
369
 
@@ -378,6 +378,8 @@ The build server composites the foreground over the background, applies a bindin
378
378
 
379
379
  **Recommended format:** 1536×1536 pixel square PNGs for both files. Images with other dimensions are scaled to 1536×1536 automatically, but matching the target size produces the sharpest results.
380
380
 
381
+ Missing icons do not fail the Adaptfully client: check the `WARNING missing layered icon(s)` log line and replace the placeholders before a store release.
382
+
381
383
  ## Configuration
382
384
 
383
385
  Build settings are read from `package.json`. The client merges any `wrapfully.json` fields into `package.json`'s `config` object before sending.
Binary file
Binary file
@@ -1,9 +1,9 @@
1
1
  import archiver from 'archiver';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { appendMetaIcons, isMetaIconFilename, META_DIR } from './icons.js';
4
5
 
5
6
  const
6
- META_DIR = 'assets/meta',
7
7
  DEPLOYMENTS_DIRNAME = 'deployments',
8
8
  PUBLISH_DIRNAME = 'publish';
9
9
 
@@ -38,6 +38,9 @@ function appendMeta(zip, publishDir, deploymentDirs = [], options = {}) {
38
38
  log = options.log ?? (() => {}),
39
39
  metaDir = path.resolve(META_DIR);
40
40
 
41
+ // Always ship layered icons (project files or Adaptfully placeholders).
42
+ appendMetaIcons(zip, { metaDir, log });
43
+
41
44
  if (!fs.existsSync(metaDir)) {
42
45
  log(`adaptfully: meta dir missing at ${metaDir}; skipping credentials`);
43
46
  return;
@@ -46,7 +49,17 @@ function appendMeta(zip, publishDir, deploymentDirs = [], options = {}) {
46
49
  const deploymentsRoot = path.join(metaDir, DEPLOYMENTS_DIRNAME);
47
50
 
48
51
  if (!fs.existsSync(deploymentsRoot)) {
49
- appendDirectory(zip, metaDir, 'meta');
52
+ for (const entry of fs.readdirSync(metaDir, { withFileTypes: true })) {
53
+ if (isMetaIconFilename(entry.name)) {
54
+ continue;
55
+ }
56
+ const fullPath = path.join(metaDir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ appendDirectory(zip, fullPath, `meta/${entry.name}`);
59
+ } else {
60
+ zip.file(fullPath, { name: `meta/${entry.name}` });
61
+ }
62
+ }
50
63
  return;
51
64
  }
52
65
 
@@ -54,6 +67,9 @@ function appendMeta(zip, publishDir, deploymentDirs = [], options = {}) {
54
67
  if (entry.name === DEPLOYMENTS_DIRNAME || entry.name === PUBLISH_DIRNAME) {
55
68
  continue;
56
69
  }
70
+ if (isMetaIconFilename(entry.name)) {
71
+ continue;
72
+ }
57
73
 
58
74
  const fullPath = path.join(metaDir, entry.name);
59
75
  if (entry.isDirectory()) {
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getPackageRoot } from './paths.js';
4
+
5
+ export const META_DIR = 'assets/meta';
6
+ export const ICON_FOREGROUND = 'icon-foreground.png';
7
+ export const ICON_BACKGROUND = 'icon-background.png';
8
+
9
+ const ICON_FILES = [ICON_FOREGROUND, ICON_BACKGROUND];
10
+
11
+ /**
12
+ * Absolute path to a packaged Adaptfully placeholder icon.
13
+ * @param {string} filename
14
+ */
15
+ export function resolvePlaceholderIconPath(filename) {
16
+ return path.join(getPackageRoot(), 'lib', 'assets', filename);
17
+ }
18
+
19
+ /**
20
+ * @param {string} [metaDir=META_DIR]
21
+ */
22
+ export function resolveMetaIconPaths(metaDir = META_DIR) {
23
+ const root = path.resolve(metaDir);
24
+ return {
25
+ metaDir: root,
26
+ foreground: path.join(root, ICON_FOREGROUND),
27
+ background: path.join(root, ICON_BACKGROUND),
28
+ };
29
+ }
30
+
31
+ /**
32
+ * Resolve layered icon paths for Wrapfully packaging. Warns (does not throw) when
33
+ * project icons are missing and falls back to Adaptfully placeholders.
34
+ *
35
+ * @param {{ metaDir?: string, log?: (message: string) => void }} [options]
36
+ * @returns {{
37
+ * metaDir: string,
38
+ * foreground: string,
39
+ * background: string,
40
+ * missing: string[],
41
+ * usedPlaceholders: boolean,
42
+ * }}
43
+ */
44
+ export function resolveMetaIcons(options = {}) {
45
+ const log = options.log ?? console.log;
46
+ const { metaDir, foreground, background } = resolveMetaIconPaths(options.metaDir);
47
+ /** @type {string[]} */
48
+ const missing = [];
49
+ let resolvedForeground = foreground;
50
+ let resolvedBackground = background;
51
+
52
+ if (!fs.existsSync(foreground)) {
53
+ missing.push(ICON_FOREGROUND);
54
+ resolvedForeground = resolvePlaceholderIconPath(ICON_FOREGROUND);
55
+ }
56
+ if (!fs.existsSync(background)) {
57
+ missing.push(ICON_BACKGROUND);
58
+ resolvedBackground = resolvePlaceholderIconPath(ICON_BACKGROUND);
59
+ }
60
+
61
+ if (missing.length > 0) {
62
+ log(
63
+ `adaptfully: WARNING missing layered icon(s) in ${metaDir}: ${missing.join(', ')}. `
64
+ + 'Using Adaptfully placeholder icons for this build.',
65
+ );
66
+ }
67
+
68
+ return {
69
+ metaDir,
70
+ foreground: resolvedForeground,
71
+ background: resolvedBackground,
72
+ missing,
73
+ usedPlaceholders: missing.length > 0,
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Append layered icons to a zip under meta/. Uses project icons when present;
79
+ * otherwise warns and ships Adaptfully placeholders.
80
+ *
81
+ * @param {import('archiver').Archiver} zip
82
+ * @param {{ metaDir?: string, log?: (message: string) => void }} [options]
83
+ * @returns {ReturnType<typeof resolveMetaIcons>}
84
+ */
85
+ export function appendMetaIcons(zip, options = {}) {
86
+ const resolved = resolveMetaIcons(options);
87
+ zip.file(resolved.foreground, { name: `meta/${ICON_FOREGROUND}` });
88
+ zip.file(resolved.background, { name: `meta/${ICON_BACKGROUND}` });
89
+ return resolved;
90
+ }
91
+
92
+ /**
93
+ * @param {string} name
94
+ */
95
+ export function isMetaIconFilename(name) {
96
+ return ICON_FILES.includes(name);
97
+ }
package/lib/node/index.js CHANGED
@@ -2,7 +2,17 @@ export { buildOutputDir, clearStaleBuildExtract, resolveBuildArtifactDir } from
2
2
  export { createArchive, createDeployArchive, createReleaseArchive, createSourceArchive } from './archive.js';
3
3
  export { loadProjectConfig, resolveServerUrl } from './config.js';
4
4
  export { send } from './deploy.js';
5
- export { adaptfullyFromCli, parseStageArgs, runAdaptfullyStage } from './pipeline.js';
5
+ export {
6
+ appendMetaIcons,
7
+ ICON_BACKGROUND,
8
+ ICON_FOREGROUND,
9
+ isMetaIconFilename,
10
+ META_DIR,
11
+ resolveMetaIconPaths,
12
+ resolveMetaIcons,
13
+ resolvePlaceholderIconPath,
14
+ } from './icons.js';
15
+ export { adaptfullyFromCli, parseStageArgs, resolveWrapfullyRoute, runAdaptfullyStage } from './pipeline.js';
6
16
  export { prebuildPlatform, prebuildOutputDir, resolveHtmlInjections } from './prebuild.js';
7
17
  export { getPackageRoot, getRuntimeDir, resolveRuntimeScript } from './paths.js';
8
18
  export {
@@ -14,6 +14,21 @@ import { steamAuthFromCli } from './steam-auth.js';
14
14
 
15
15
  const VALID_STAGES = new Set(['prebuild', 'build', 'deploy', 'release']);
16
16
 
17
+ /**
18
+ * Wrapfully URL path segment for build/release.
19
+ * Single-target platforms post to the target route (`android`, `ios`, …) so OS relay
20
+ * and long-standing target routes work; multi-target platforms post to the family
21
+ * (`electron`, …).
22
+ *
23
+ * @param {{ family: string, targets: string[] }} buildSpec
24
+ */
25
+ export function resolveWrapfullyRoute(buildSpec) {
26
+ if (buildSpec.targets.length === 1) {
27
+ return buildSpec.targets[0];
28
+ }
29
+ return buildSpec.family;
30
+ }
31
+
17
32
  /**
18
33
  * @param {AdaptfullyStage} stage
19
34
  * @param {string} platformKey
@@ -69,10 +84,15 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
69
84
  /** @type {{ deploymentKey: string, stage: string, family: string }[]} */
70
85
  const sent = [];
71
86
 
87
+ const wrapfullyRoute = resolveWrapfullyRoute(buildSpec);
88
+
72
89
  if (stage === 'release') {
73
90
  const deploymentDirs = deployments.map((key) => resolvePublishDir(key));
74
91
 
75
- log(`adaptfully: ${stage} ${platformKey} → ${buildSpec.family} (${buildSpec.targets.join(', ')})`);
92
+ log(
93
+ `adaptfully: ${stage} ${platformKey} → ${buildSpec.family} `
94
+ + `(${buildSpec.targets.join(', ')}) via /${wrapfullyRoute}/release`,
95
+ );
76
96
 
77
97
  log(
78
98
  `adaptfully: release deployments → ${
@@ -85,7 +105,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
85
105
  contents,
86
106
  options.server,
87
107
  'release',
88
- buildSpec.family,
108
+ wrapfullyRoute,
89
109
  prebuiltDir,
90
110
  pkg,
91
111
  options.mode ?? 'extract',
@@ -107,7 +127,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
107
127
  log(
108
128
  `adaptfully: ${stage} ${platformKey} → `
109
129
  + `${deploymentKey === 'zip' ? 'artifact zip' : `deployment "${deploymentKey}"`} `
110
- + `via Wrapfully ${buildSpec.family}`,
130
+ + `via Wrapfully /${wrapfullyRoute}/${wrapStage} (${buildSpec.family})`,
111
131
  );
112
132
 
113
133
  await send(
@@ -115,7 +135,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
115
135
  contents,
116
136
  options.server,
117
137
  wrapStage,
118
- buildSpec.family,
138
+ wrapfullyRoute,
119
139
  prebuiltDir,
120
140
  pkg,
121
141
  options.mode ?? 'extract',
@@ -158,9 +178,9 @@ export async function adaptfullyFromCli(argv = process.argv) {
158
178
  + '\n'
159
179
  + 'Stages:\n'
160
180
  + ' prebuild Copy deploy/ and apply platform registrations → output/<platform>-prebuild/\n'
161
- + ' build prebuild + POST /{family}/build (zip artifact only)\n'
181
+ + ' build prebuild + POST /{target|family}/build (zip artifact only)\n'
162
182
  + ' deploy POST /deploy/{key} with prior artifact + deployment credentials\n'
163
- + ' release prebuild + POST /{family}/release (build + configured deployments)\n'
183
+ + ' release prebuild + POST /{target|family}/release (build + configured deployments)\n'
164
184
  + '\n'
165
185
  + ' --deployment <key> Target a single named deployment.\n'
166
186
  + ' --artifact <path> Prior build artifact directory (deploy only; default: ./output/ after build).\n'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makefully/adaptfully",
3
- "version": "3.10.0",
3
+ "version": "3.11.0",
4
4
  "description": "Platform abstraction and Wrapfully deploy client for Makefully games",
5
5
  "type": "module",
6
6
  "main": "./lib/node/index.js",