@makefully/adaptfully 3.8.0 → 3.8.2

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,18 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## 3.8.2 — 2026-07-14
6
+
7
+ ### Fixed
8
+
9
+ - **Release/deploy zips** resolve `assets/meta` and deployment credential dirs to absolute POSIX paths before `archiver.directory()`, and log which deployments were packaged (or skipped). Prevents empty `meta/deployments` payloads that made Wrapfully skip Steam.
10
+
11
+ ## 3.8.1 — 2026-07-14
12
+
13
+ ### Fixed
14
+
15
+ - **Extract mode** clears stale `output/artifacts/`, `wrapfully-build.json`, and `wrapfully-status.json` before unpacking a Wrapfully response so nested `artifacts/artifacts` leftovers cannot accumulate across runs.
16
+
5
17
  ## 3.8.0 — 2026-07-10
6
18
 
7
19
  ### Changed
@@ -2,57 +2,98 @@ import archiver from 'archiver';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
 
5
- const META_DIR = 'assets/meta';
6
- const DEPLOYMENTS_DIRNAME = 'deployments';
7
- const PUBLISH_DIRNAME = 'publish';
5
+ const
6
+ META_DIR = 'assets/meta',
7
+ DEPLOYMENTS_DIRNAME = 'deployments',
8
+ PUBLISH_DIRNAME = 'publish';
9
+
10
+ /**
11
+ * @param {string} dirPath
12
+ * @returns {string}
13
+ */
14
+ function toPosixDir(dirPath) {
15
+ return `${path.resolve(dirPath).replace(/\\/g, '/').replace(/\/?$/, '')}/`;
16
+ }
17
+
18
+ /**
19
+ * @param {import('archiver').Archiver} zip
20
+ * @param {string} sourceDir Absolute or relative directory to pack
21
+ * @param {string} zipPrefix Zip entry prefix without trailing slash
22
+ */
23
+ function appendDirectory(zip, sourceDir, zipPrefix) {
24
+ const
25
+ source = toPosixDir(sourceDir),
26
+ prefix = `${zipPrefix.replace(/\\/g, '/').replace(/\/?$/, '')}/`;
27
+ zip.directory(source, prefix);
28
+ }
8
29
 
9
30
  /**
10
31
  * @param {import('archiver').Archiver} zip
11
32
  * @param {string} [publishDir] Single deployment folder
12
33
  * @param {string[]} [deploymentDirs] Multiple deployment folders for release
34
+ * @param {{ log?: (message: string) => void }} [options]
13
35
  */
14
- function appendMeta(zip, publishDir, deploymentDirs = []) {
15
- if (!fs.existsSync(META_DIR)) {
36
+ function appendMeta(zip, publishDir, deploymentDirs = [], options = {}) {
37
+ const
38
+ log = options.log ?? (() => {}),
39
+ metaDir = path.resolve(META_DIR);
40
+
41
+ if (!fs.existsSync(metaDir)) {
42
+ log(`adaptfully: meta dir missing at ${metaDir}; skipping credentials`);
16
43
  return;
17
44
  }
18
45
 
19
- const deploymentsRoot = path.join(META_DIR, DEPLOYMENTS_DIRNAME);
46
+ const deploymentsRoot = path.join(metaDir, DEPLOYMENTS_DIRNAME);
20
47
 
21
48
  if (!fs.existsSync(deploymentsRoot)) {
22
- zip.directory(`${META_DIR}/`, 'meta');
49
+ appendDirectory(zip, metaDir, 'meta');
23
50
  return;
24
51
  }
25
52
 
26
- for (const entry of fs.readdirSync(META_DIR, { withFileTypes: true })) {
53
+ for (const entry of fs.readdirSync(metaDir, { withFileTypes: true })) {
27
54
  if (entry.name === DEPLOYMENTS_DIRNAME || entry.name === PUBLISH_DIRNAME) {
28
55
  continue;
29
56
  }
30
57
 
31
- const fullPath = path.join(META_DIR, entry.name);
58
+ const fullPath = path.join(metaDir, entry.name);
32
59
  if (entry.isDirectory()) {
33
- zip.directory(`${fullPath}/`, `meta/${entry.name}`);
60
+ appendDirectory(zip, fullPath, `meta/${entry.name}`);
34
61
  } else {
35
62
  zip.file(fullPath, { name: `meta/${entry.name}` });
36
63
  }
37
64
  }
38
65
 
39
66
  if (publishDir && fs.existsSync(publishDir)) {
40
- zip.directory(`${publishDir}/`, `meta/${PUBLISH_DIRNAME}`);
67
+ appendDirectory(zip, publishDir, `meta/${PUBLISH_DIRNAME}`);
68
+ log(`adaptfully: packaged meta/${PUBLISH_DIRNAME} from ${path.resolve(publishDir)}`);
41
69
  return;
42
70
  }
43
71
 
72
+ const packaged = [];
73
+
44
74
  for (const deploymentDir of deploymentDirs) {
45
- if (!fs.existsSync(deploymentDir)) {
75
+ const resolved = path.resolve(deploymentDir);
76
+ if (!fs.existsSync(resolved)) {
77
+ log(`adaptfully: skipping missing deployment dir ${resolved}`);
46
78
  continue;
47
79
  }
48
80
 
49
- const deploymentKey = path.basename(deploymentDir);
50
- zip.directory(`${deploymentDir}/`, `meta/${DEPLOYMENTS_DIRNAME}/${deploymentKey}`);
81
+ const deploymentKey = path.basename(resolved);
82
+ appendDirectory(zip, resolved, `meta/${DEPLOYMENTS_DIRNAME}/${deploymentKey}`);
83
+ packaged.push(deploymentKey);
84
+ }
85
+
86
+ if (packaged.length) {
87
+ log(`adaptfully: packaged deployments: ${packaged.join(', ')}`);
88
+ } else if (deploymentDirs.length) {
89
+ log('adaptfully: WARNING no deployment credential folders were packaged');
51
90
  }
52
91
  }
53
92
 
54
93
  function createZipStream(deployFolder, contents, options = {}) {
55
- const zip = archiver('zip', { zlib: { level: 0 } });
94
+ const
95
+ zip = archiver('zip', { zlib: { level: 0 } }),
96
+ log = options.log ?? console.log;
56
97
 
57
98
  zip.on('warning', (err) => {
58
99
  if (err.code === 'ENOENT') {
@@ -75,11 +116,11 @@ function createZipStream(deployFolder, contents, options = {}) {
75
116
  }, { prefix: 'deploy' });
76
117
  zip.append(options.indexHtml, { name: 'deploy/index.html' });
77
118
  } else {
78
- zip.directory(`${deployFolder}/`, 'deploy');
79
- zip.file(`${deployFolder}/index.html`, { name: 'deploy/index.html' });
119
+ appendDirectory(zip, deployFolder, 'deploy');
120
+ zip.file(path.resolve(deployFolder, 'index.html'), { name: 'deploy/index.html' });
80
121
  }
81
122
 
82
- appendMeta(zip, options.publishDir, options.deploymentDirs);
123
+ appendMeta(zip, options.publishDir, options.deploymentDirs, { log });
83
124
  zip.append(contents, { name: 'package.json' });
84
125
  zip.finalize();
85
126
 
@@ -91,7 +132,7 @@ function createZipStream(deployFolder, contents, options = {}) {
91
132
  *
92
133
  * @param {string} deployFolder
93
134
  * @param {string} contents
94
- * @param {{ indexHtml?: string, publishDir?: string, deploymentDirs?: string[] }} [options]
135
+ * @param {{ indexHtml?: string, publishDir?: string, deploymentDirs?: string[], log?: (message: string) => void }} [options]
95
136
  */
96
137
  export function createSourceArchive(deployFolder, contents, options = {}) {
97
138
  return createZipStream(deployFolder, contents, options);
@@ -110,12 +151,15 @@ export function createArchive(deployFolder, contents, options = {}) {
110
151
  * @param {string} artifactPath
111
152
  * @param {string} publishDir
112
153
  * @param {string} [contents]
154
+ * @param {{ log?: (message: string) => void }} [options]
113
155
  */
114
- export function createDeployArchive(artifactPath, publishDir, contents) {
115
- const zip = archiver('zip', { zlib: { level: 0 } });
156
+ export function createDeployArchive(artifactPath, publishDir, contents, options = {}) {
157
+ const
158
+ zip = archiver('zip', { zlib: { level: 0 } }),
159
+ log = options.log ?? console.log;
116
160
 
117
- zip.directory(`${artifactPath}/`, false);
118
- appendMeta(zip, publishDir);
161
+ zip.directory(toPosixDir(artifactPath), false);
162
+ appendMeta(zip, publishDir, [], { log });
119
163
 
120
164
  if (contents) {
121
165
  zip.append(contents, { name: 'package.json' });
@@ -131,7 +175,7 @@ export function createDeployArchive(artifactPath, publishDir, contents) {
131
175
  * @param {string} deployFolder
132
176
  * @param {string} contents
133
177
  * @param {string[]} deploymentDirs
134
- * @param {{ indexHtml?: string }} [options]
178
+ * @param {{ indexHtml?: string, log?: (message: string) => void }} [options]
135
179
  */
136
180
  export function createReleaseArchive(deployFolder, contents, deploymentDirs, options = {}) {
137
181
  return createZipStream(deployFolder, contents, {
@@ -1,7 +1,9 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
- const MANIFEST_NAME = 'wrapfully-build.json';
4
+ const
5
+ MANIFEST_NAME = 'wrapfully-build.json',
6
+ STATUS_NAME = 'wrapfully-status.json';
5
7
 
6
8
  /**
7
9
  * Directory where build responses are extracted (contains wrapfully-build.json).
@@ -14,6 +16,25 @@ export function buildOutputDir(pkg, outputRoot = 'output') {
14
16
  return path.resolve(outputFolder);
15
17
  }
16
18
 
19
+ /**
20
+ * Remove prior Wrapfully extract artifacts so zip responses do not stack
21
+ * nested `artifacts/` directories across runs.
22
+ *
23
+ * @param {string} [outputRoot='output']
24
+ */
25
+ export function clearStaleBuildExtract(outputRoot = 'output') {
26
+ const root = path.resolve(outputRoot);
27
+ const stalePaths = [
28
+ path.join(root, 'artifacts'),
29
+ path.join(root, MANIFEST_NAME),
30
+ path.join(root, STATUS_NAME),
31
+ ];
32
+
33
+ for (const stalePath of stalePaths) {
34
+ fs.rmSync(stalePath, { recursive: true, force: true });
35
+ }
36
+ }
37
+
17
38
  /**
18
39
  * Resolve the artifact directory from a prior `adaptfully build` (or compatible zip extract).
19
40
  *
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { pipeline } from 'node:stream/promises';
5
5
  import unzipper from 'unzip-stream';
6
6
  import { createDeployArchive, createReleaseArchive, createSourceArchive } from './archive.js';
7
+ import { clearStaleBuildExtract } from './artifacts.js';
7
8
  import { listHtmlFilesRecursive } from './fs-utils.js';
8
9
  import { printBuildReport } from './report.js';
9
10
 
@@ -47,9 +48,15 @@ function resolveWrapfullyUrl(server, stage, family, gameId, platformKey, deploym
47
48
  export async function send(gameId, contents, server, stage, family, deployFolder, pkg, mode = 'extract', options = {}) {
48
49
  const log = options.log ?? console.log;
49
50
  const platformKey = options.platformKey ?? family;
51
+ const outputRoot = pkg.config?.outputFolder || 'output';
52
+
53
+ if (mode === 'extract') {
54
+ clearStaleBuildExtract(outputRoot);
55
+ }
56
+
50
57
  const destination = mode === 'extract'
51
- ? unzipper.Extract({ path: './output/', concurrency: 1 })
52
- : fs.createWriteStream(`./output/${pkg.name}-${pkg.version}-${stage}-${family}.zip`);
58
+ ? unzipper.Extract({ path: `${outputRoot}/`, concurrency: 1 })
59
+ : fs.createWriteStream(`${outputRoot}/${pkg.name}-${pkg.version}-${stage}-${family}.zip`);
53
60
 
54
61
  let archiveStream;
55
62
 
@@ -60,11 +67,11 @@ export async function send(gameId, contents, server, stage, family, deployFolder
60
67
  if (!options.deploymentKey) {
61
68
  throw new Error('deploy stage requires options.deploymentKey');
62
69
  }
63
- archiveStream = createDeployArchive(options.artifactPath, options.publishDir, contents);
70
+ archiveStream = createDeployArchive(options.artifactPath, options.publishDir, contents, { log });
64
71
  } else if (stage === 'release') {
65
- archiveStream = createReleaseArchive(deployFolder, contents, options.deploymentDirs ?? []);
72
+ archiveStream = createReleaseArchive(deployFolder, contents, options.deploymentDirs ?? [], { log });
66
73
  } else {
67
- archiveStream = createSourceArchive(deployFolder, contents);
74
+ archiveStream = createSourceArchive(deployFolder, contents, { log });
68
75
  }
69
76
 
70
77
  const url = resolveWrapfullyUrl(server, stage, family, gameId, platformKey, options.deploymentKey);
package/lib/node/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { buildOutputDir, resolveBuildArtifactDir } from './artifacts.js';
1
+ export { buildOutputDir, clearStaleBuildExtract, resolveBuildArtifactDir } from './artifacts.js';
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';
@@ -1,3 +1,4 @@
1
+ import path from 'node:path';
1
2
  import { loadProjectConfig, resolveServerUrl } from './config.js';
2
3
  import { send } from './deploy.js';
3
4
  import { resolveBuildArtifactDir } from './artifacts.js';
@@ -73,6 +74,12 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
73
74
 
74
75
  log(`adaptfully: ${stage} ${platformKey} → ${buildSpec.family} (${buildSpec.targets.join(', ')})`);
75
76
 
77
+ log(
78
+ `adaptfully: release deployments → ${
79
+ deploymentDirs.map((dir) => path.resolve(dir)).join(', ') || '(none)'
80
+ }`,
81
+ );
82
+
76
83
  await send(
77
84
  gameId,
78
85
  contents,
@@ -85,7 +92,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
85
92
  {
86
93
  log,
87
94
  platformKey,
88
- deploymentDirs,
95
+ deploymentDirs: deploymentDirs.map((dir) => path.resolve(dir)),
89
96
  },
90
97
  );
91
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makefully/adaptfully",
3
- "version": "3.8.0",
3
+ "version": "3.8.2",
4
4
  "description": "Platform abstraction and Wrapfully deploy client for Makefully games",
5
5
  "type": "module",
6
6
  "main": "./lib/node/index.js",