@makefully/adaptfully 4.1.0 → 4.2.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 +12 -0
- package/README.md +16 -11
- package/lib/node/choreCrypto.js +197 -0
- package/lib/node/config.js +45 -4
- package/lib/node/deploy.js +239 -42
- package/lib/node/pipeline.js +40 -11
- package/lib/node/wrapfullyDefaultPublicKey.js +17 -0
- package/package.json +1 -1
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
|
+
## 4.2.0 — 2026-09-08
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Build / deploy / release** submit **Showfully Yap** chores (`POST /yap/wrapfully`) instead of direct Wrapfully HTTP. Requires a Showfully PAT (`SHOWFULLY_PAT` / `wrapfully.json` `accessToken`).
|
|
10
|
+
- Server URL defaults to Showfully (`https://make.makefullystudios.com/`); `wrapfully.json` may set `showfullyServer` or `server`.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Optional `--encrypt` / `"encrypt": true` chore envelope encryption (default Wrapfully fleet public key; override with `--encrypt-public-key`).
|
|
15
|
+
- `wrapfully.json` routing fields appended to Yap zips (`stage`, `route`, `platformKey`, `gameId`, `deploymentKey`).
|
|
16
|
+
|
|
5
17
|
## 4.1.0 — 2026-09-04
|
|
6
18
|
|
|
7
19
|
### Added
|
package/README.md
CHANGED
|
@@ -21,29 +21,34 @@ Games register platform services before load and retrieve them in-game. Adaptful
|
|
|
21
21
|
### Pipeline stages
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
# wrapfully.json (gitignored) or env — Showfully PAT required for build/deploy/release:
|
|
25
|
+
# { "accessToken": "sfpat_…", "server": "https://make.makefullystudios.com/" }
|
|
26
|
+
# optional: "encrypt": true
|
|
27
|
+
|
|
28
|
+
export SHOWFULLY_PAT=sfpat_…
|
|
29
|
+
adaptfully prebuild web # deploy/ → output/web-prebuild/ (local only)
|
|
30
|
+
adaptfully build steam # Yap /yap/wrapfully chore → artifact zip
|
|
31
|
+
adaptfully deploy web --deployment web-prod # Yap deploy chore
|
|
32
|
+
adaptfully release android --encrypt # optional envelope encryption
|
|
29
33
|
adaptfully steam-publish # one-time: log in with steamcmd → assets/meta/deployments/steam/steam.json
|
|
30
|
-
adaptfully google-publish --from ./sa.json # one-time: import Play service account → deployments/android/google.json
|
|
31
|
-
adaptfully apple-publish # one-time: write App Store Connect creds → deployments/ios/apple.json
|
|
32
|
-
adaptfully android-keystore # one-time: generate keystores + android build.json
|
|
33
|
-
adaptfully apple-signing # one-time: CSR / .p12 / provisioning files under ios/apple/
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
| Stage | What it does |
|
|
37
37
|
|-------|----------------|
|
|
38
38
|
| `prebuild` | Copy `deploy/` to `output/<platform>-prebuild/` and inject registrations into `config.htmlInjections` |
|
|
39
|
-
| `build` | Prebuild, then
|
|
40
|
-
| `deploy` |
|
|
39
|
+
| `build` | Prebuild, then submit a **Showfully Yap** `wrapfully` chore (PAT required); poll until the artifact zip returns |
|
|
40
|
+
| `deploy` | Submit a Yap deploy chore with the prior build artifact + deployment credentials |
|
|
41
|
+
| `release` | Yap release chore (build + configured deployments on Wrapfully) |
|
|
41
42
|
| `steam-publish` | One-time local setup: install steamcmd, interactive login, write the `steam` deployment's `steam.json` (also updates `.gitignore`) |
|
|
42
43
|
| `google-publish` | One-time local setup: import a Play Console service-account JSON into the `android` deployment folder (also updates `.gitignore`) |
|
|
43
44
|
| `apple-publish` | One-time local setup: write App Store Connect credentials into the `ios` deployment folder (also updates `.gitignore`) |
|
|
44
45
|
| `android-keystore` | One-time local setup: generate debug/release keystores and write `android` `build.json` (also updates `.gitignore`) |
|
|
45
46
|
| `apple-signing` | One-time local setup: CSR → Apple `.cer` → `.p12` / provisioning profiles under `ios/apple/` (also updates `.gitignore`) |
|
|
46
47
|
|
|
48
|
+
**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
|
+
|
|
50
|
+
**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.
|
|
51
|
+
|
|
47
52
|
First-time walkthroughs (manual steps + helper skip markers): [docs/credentials/](docs/credentials/README.md).
|
|
48
53
|
|
|
49
54
|
`wrapfully-deploy` is a compatibility alias for `adaptfully deploy` when invoked with a Wrapfully builder name (`steam`, `win`, `android`, etc.).
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {Readable, PassThrough} from 'node:stream';
|
|
6
|
+
import {pipeline} from 'node:stream/promises';
|
|
7
|
+
import archiver from 'archiver';
|
|
8
|
+
import unzipper from 'unzip-stream';
|
|
9
|
+
import {
|
|
10
|
+
WRAPFULLY_DEFAULT_KID,
|
|
11
|
+
WRAPFULLY_DEFAULT_PUBLIC_KEY,
|
|
12
|
+
} from './wrapfullyDefaultPublicKey.js';
|
|
13
|
+
|
|
14
|
+
const
|
|
15
|
+
ENVELOPE_DATA = 'wrapfully.enc',
|
|
16
|
+
ENVELOPE_KEY = 'wrapfully.key.enc',
|
|
17
|
+
ENVELOPE_META = 'wrapfully-crypto.json',
|
|
18
|
+
RESULT_DATA = 'result.enc',
|
|
19
|
+
RESULT_KEY = 'result.key.enc',
|
|
20
|
+
ALG = 'RSA-OAEP-SHA256+AES-256-GCM';
|
|
21
|
+
|
|
22
|
+
function aesEncrypt (plaintext) {
|
|
23
|
+
const key = crypto.randomBytes(32);
|
|
24
|
+
const iv = crypto.randomBytes(12);
|
|
25
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
|
|
26
|
+
const enc = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
27
|
+
const tag = cipher.getAuthTag();
|
|
28
|
+
|
|
29
|
+
return {key, ciphertext: Buffer.concat([iv, tag, enc])};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function aesDecrypt (key, blob) {
|
|
33
|
+
const iv = blob.subarray(0, 12);
|
|
34
|
+
const tag = blob.subarray(12, 28);
|
|
35
|
+
const data = blob.subarray(28);
|
|
36
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
|
|
37
|
+
|
|
38
|
+
decipher.setAuthTag(tag);
|
|
39
|
+
|
|
40
|
+
return Buffer.concat([decipher.update(data), decipher.final()]);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function wrapKey (aesKey, publicKeyPem) {
|
|
44
|
+
return crypto.publicEncrypt(
|
|
45
|
+
{
|
|
46
|
+
key: publicKeyPem,
|
|
47
|
+
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
48
|
+
oaepHash: 'sha256',
|
|
49
|
+
},
|
|
50
|
+
aesKey,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function unwrapKey (wrapped, privateKeyPem) {
|
|
55
|
+
return crypto.privateDecrypt(
|
|
56
|
+
{
|
|
57
|
+
key: privateKeyPem,
|
|
58
|
+
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
59
|
+
oaepHash: 'sha256',
|
|
60
|
+
},
|
|
61
|
+
wrapped,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function generateResultKeyPair () {
|
|
66
|
+
const {publicKey, privateKey} = crypto.generateKeyPairSync('rsa', {
|
|
67
|
+
modulusLength: 2048,
|
|
68
|
+
publicKeyEncoding: {type: 'spki', format: 'pem'},
|
|
69
|
+
privateKeyEncoding: {type: 'pkcs8', format: 'pem'},
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return {publicKey, privateKey};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function resolveEncryptPublicKey (wrapfullyConfig = {}, cliPublicKey) {
|
|
76
|
+
if (cliPublicKey) {
|
|
77
|
+
if (cliPublicKey.includes('BEGIN PUBLIC KEY')) {
|
|
78
|
+
return {pem: cliPublicKey.trim(), kid: wrapfullyConfig.encryptKid || 'override'};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
pem: fs.readFileSync(cliPublicKey, 'utf8').trim(),
|
|
82
|
+
kid: wrapfullyConfig.encryptKid || 'override',
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (wrapfullyConfig.encryptPublicKey) {
|
|
86
|
+
const value = wrapfullyConfig.encryptPublicKey;
|
|
87
|
+
|
|
88
|
+
if (String(value).includes('BEGIN PUBLIC KEY')) {
|
|
89
|
+
return {pem: String(value).trim(), kid: wrapfullyConfig.encryptKid || 'override'};
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
pem: fs.readFileSync(value, 'utf8').trim(),
|
|
93
|
+
kid: wrapfullyConfig.encryptKid || 'override',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {pem: WRAPFULLY_DEFAULT_PUBLIC_KEY.trim(), kid: WRAPFULLY_DEFAULT_KID};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function bufferFromStream (stream) {
|
|
101
|
+
const chunks = [];
|
|
102
|
+
|
|
103
|
+
for await (const chunk of stream) {
|
|
104
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return Buffer.concat(chunks);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function zipToBuffer (appendFn) {
|
|
111
|
+
const archive = archiver('zip', {zlib: {level: 0}});
|
|
112
|
+
const pass = new PassThrough();
|
|
113
|
+
const chunks = [];
|
|
114
|
+
|
|
115
|
+
archive.pipe(pass);
|
|
116
|
+
pass.on('data', (c) => chunks.push(c));
|
|
117
|
+
|
|
118
|
+
const done = new Promise((resolve, reject) => {
|
|
119
|
+
pass.on('end', resolve);
|
|
120
|
+
pass.on('error', reject);
|
|
121
|
+
archive.on('error', reject);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await appendFn(archive);
|
|
125
|
+
await archive.finalize();
|
|
126
|
+
await done;
|
|
127
|
+
|
|
128
|
+
return Buffer.concat(chunks);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Wrap an inner zip stream/buffer in a Yap-safe envelope for Wrapfully workers.
|
|
133
|
+
*/
|
|
134
|
+
export async function createEnvelopeFromInnerZip (innerZip, {publicKeyPem, kid, resultKeyPair}) {
|
|
135
|
+
const innerBuf = Buffer.isBuffer(innerZip) ? innerZip : await bufferFromStream(innerZip);
|
|
136
|
+
const {key, ciphertext} = aesEncrypt(innerBuf);
|
|
137
|
+
const wrapped = wrapKey(key, publicKeyPem);
|
|
138
|
+
const meta = {
|
|
139
|
+
kid,
|
|
140
|
+
alg: ALG,
|
|
141
|
+
resultPub: resultKeyPair.publicKey,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const zip = await zipToBuffer(async (archive) => {
|
|
145
|
+
archive.append(ciphertext, {name: ENVELOPE_DATA});
|
|
146
|
+
archive.append(wrapped, {name: ENVELOPE_KEY});
|
|
147
|
+
archive.append(JSON.stringify(meta, null, 2), {name: ENVELOPE_META});
|
|
148
|
+
archive.append(resultKeyPair.publicKey, {name: 'resultPub.pem'});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
return {zip, resultPriv: resultKeyPair.privateKey, kid};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Decrypt a completed Yap result envelope into a plaintext zip Buffer.
|
|
156
|
+
*/
|
|
157
|
+
export async function decryptResultEnvelope (resultZipBuf, resultPrivPem) {
|
|
158
|
+
if (!resultPrivPem) {
|
|
159
|
+
throw new Error('Missing resultPriv for encrypted Wrapfully chore download.');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const tmp = path.join(os.tmpdir(), `adaptfully-result-${Date.now().toString(16)}`);
|
|
163
|
+
|
|
164
|
+
fs.mkdirSync(tmp, {recursive: true});
|
|
165
|
+
try {
|
|
166
|
+
await pipeline(Readable.from(resultZipBuf), unzipper.Extract({path: tmp}));
|
|
167
|
+
if (!fs.existsSync(path.join(tmp, RESULT_DATA))) {
|
|
168
|
+
// Not an envelope — return as-is
|
|
169
|
+
return resultZipBuf;
|
|
170
|
+
}
|
|
171
|
+
const ciphertext = fs.readFileSync(path.join(tmp, RESULT_DATA));
|
|
172
|
+
const wrapped = fs.readFileSync(path.join(tmp, RESULT_KEY));
|
|
173
|
+
const aesKey = unwrapKey(wrapped, resultPrivPem);
|
|
174
|
+
|
|
175
|
+
return aesDecrypt(aesKey, ciphertext);
|
|
176
|
+
} finally {
|
|
177
|
+
fs.rmSync(tmp, {recursive: true, force: true});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function isResultEnvelopeBuffer (buf) {
|
|
182
|
+
// Weak check — PK zip; decryptResultEnvelope falls back if members missing
|
|
183
|
+
return Buffer.isBuffer(buf) && buf.length > 4 && buf[0] === 0x50 && buf[1] === 0x4b;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export {
|
|
187
|
+
ENVELOPE_DATA,
|
|
188
|
+
ENVELOPE_KEY,
|
|
189
|
+
ENVELOPE_META,
|
|
190
|
+
RESULT_DATA,
|
|
191
|
+
RESULT_KEY,
|
|
192
|
+
ALG,
|
|
193
|
+
aesEncrypt,
|
|
194
|
+
aesDecrypt,
|
|
195
|
+
wrapKey,
|
|
196
|
+
unwrapKey,
|
|
197
|
+
};
|
package/lib/node/config.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const DEFAULT_SHOWFULLY_SERVER = process.env.SHOWFULLY_DEV
|
|
4
|
+
? 'http://localhost:9630/'
|
|
5
|
+
: 'https://make.makefullystudios.com/';
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* @param {string} [projectRoot='.']
|
|
@@ -29,14 +31,53 @@ export async function loadProjectConfig(projectRoot = '.') {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
|
-
*
|
|
34
|
+
* Showfully Yap base URL (not Wrapfully HTTP).
|
|
35
|
+
* @param {{ server?: string, showfullyServer?: string }} wrapfullyConfig
|
|
33
36
|
* @param {string} [cliServer]
|
|
34
37
|
*/
|
|
35
38
|
export function resolveServerUrl(wrapfullyConfig, cliServer) {
|
|
36
39
|
return (
|
|
37
40
|
cliServer
|
|
38
|
-
|| process.env.
|
|
41
|
+
|| process.env.SHOWFULLY_SERVER
|
|
42
|
+
|| wrapfullyConfig.showfullyServer
|
|
39
43
|
|| wrapfullyConfig.server
|
|
40
|
-
||
|
|
44
|
+
|| process.env.WRAPFULLY_SERVER
|
|
45
|
+
|| DEFAULT_SHOWFULLY_SERVER
|
|
41
46
|
).replace(/\/?$/, '/');
|
|
42
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Required PAT for Yap submit/poll/download.
|
|
51
|
+
* @param {{ accessToken?: string, showfullyPat?: string }} wrapfullyConfig
|
|
52
|
+
* @param {string} [cliToken]
|
|
53
|
+
*/
|
|
54
|
+
export function resolveAccessToken(wrapfullyConfig = {}, cliToken) {
|
|
55
|
+
const token = (
|
|
56
|
+
cliToken
|
|
57
|
+
|| process.env.SHOWFULLY_PAT
|
|
58
|
+
|| process.env.WRAPFULLY_ACCESS_TOKEN
|
|
59
|
+
|| wrapfullyConfig.accessToken
|
|
60
|
+
|| wrapfullyConfig.showfullyPat
|
|
61
|
+
|| ''
|
|
62
|
+
).trim();
|
|
63
|
+
|
|
64
|
+
if (!token) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'Showfully PAT required. Set SHOWFULLY_PAT or wrapfully.json "accessToken" '
|
|
67
|
+
+ '(Settings → API tokens on play.makefullystudios.com).',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return token;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {{ encrypt?: boolean }} wrapfullyConfig
|
|
76
|
+
* @param {boolean} [cliEncrypt]
|
|
77
|
+
*/
|
|
78
|
+
export function resolveEncryptFlag(wrapfullyConfig = {}, cliEncrypt) {
|
|
79
|
+
if (typeof cliEncrypt === 'boolean') {
|
|
80
|
+
return cliEncrypt;
|
|
81
|
+
}
|
|
82
|
+
return !!wrapfullyConfig.encrypt;
|
|
83
|
+
}
|
package/lib/node/deploy.js
CHANGED
|
@@ -1,37 +1,150 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import {Readable} from 'node:stream';
|
|
5
|
+
import {pipeline} from 'node:stream/promises';
|
|
5
6
|
import unzipper from 'unzip-stream';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import {createDeployArchive, createReleaseArchive, createSourceArchive} from './archive.js';
|
|
8
|
+
import {clearStaleBuildExtract} from './artifacts.js';
|
|
9
|
+
import {listHtmlFilesRecursive} from './fs-utils.js';
|
|
10
|
+
import {printBuildReport} from './report.js';
|
|
11
|
+
import {
|
|
12
|
+
createEnvelopeFromInnerZip,
|
|
13
|
+
decryptResultEnvelope,
|
|
14
|
+
generateResultKeyPair,
|
|
15
|
+
resolveEncryptPublicKey,
|
|
16
|
+
} from './choreCrypto.js';
|
|
17
|
+
|
|
18
|
+
const POLL_MS = 5000;
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
21
|
* @param {string} gameId
|
|
13
22
|
* @param {string} platformKey
|
|
14
23
|
*/
|
|
15
|
-
function buildInfoParam(gameId, platformKey) {
|
|
24
|
+
export function buildInfoParam (gameId, platformKey) {
|
|
16
25
|
return platformKey && platformKey !== gameId ? `${gameId}_${platformKey}` : gameId;
|
|
17
26
|
}
|
|
18
27
|
|
|
19
28
|
/**
|
|
20
|
-
*
|
|
21
|
-
* @param {'build' | 'deploy' | 'release'} stage
|
|
22
|
-
* @param {string} family
|
|
23
|
-
* @param {string} gameId
|
|
24
|
-
* @param {string} platformKey
|
|
25
|
-
* @param {string} [deploymentKey]
|
|
29
|
+
* Build wrapfully.json routing config appended to the Yap zip.
|
|
26
30
|
*/
|
|
27
|
-
function
|
|
28
|
-
|
|
31
|
+
export function buildWrapfullyJobConfig (stage, family, gameId, platformKey, deploymentKey) {
|
|
32
|
+
/** @type {Record<string, string>} */
|
|
33
|
+
const job = {
|
|
34
|
+
stage,
|
|
35
|
+
route: family,
|
|
36
|
+
platformKey,
|
|
37
|
+
gameId,
|
|
38
|
+
};
|
|
29
39
|
|
|
30
40
|
if (stage === 'deploy') {
|
|
31
|
-
|
|
41
|
+
if (!deploymentKey) {
|
|
42
|
+
throw new Error('deploy stage requires deploymentKey');
|
|
43
|
+
}
|
|
44
|
+
job.deploymentKey = deploymentKey;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return job;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function streamToBuffer (stream) {
|
|
51
|
+
const chunks = [];
|
|
52
|
+
|
|
53
|
+
for await (const chunk of stream) {
|
|
54
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return Buffer.concat(chunks);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function sleep (ms) {
|
|
61
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Submit zip to Showfully Yap, poll until complete, return result zip Buffer.
|
|
66
|
+
*/
|
|
67
|
+
export async function submitWrapfullyChore ({
|
|
68
|
+
server,
|
|
69
|
+
accessToken,
|
|
70
|
+
zipBuffer,
|
|
71
|
+
log = console.log,
|
|
72
|
+
}) {
|
|
73
|
+
const base = server.replace(/\/?$/, '/');
|
|
74
|
+
const submitUrl = `${base}yap/wrapfully`;
|
|
75
|
+
const headers = {
|
|
76
|
+
Authorization: `Bearer ${accessToken}`,
|
|
77
|
+
'Content-Type': 'application/zip',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
log(`adaptfully: POST ${submitUrl}`);
|
|
81
|
+
|
|
82
|
+
let submitJson;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const {data} = await axios.post(submitUrl, zipBuffer, {
|
|
86
|
+
maxRedirects: 0,
|
|
87
|
+
headers,
|
|
88
|
+
maxBodyLength: Infinity,
|
|
89
|
+
maxContentLength: Infinity,
|
|
90
|
+
validateStatus: () => true,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
submitJson = data;
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (/** @type {NodeJS.ErrnoException} */ (err).code === 'ECONNREFUSED') {
|
|
96
|
+
throw new Error(`Cannot connect to Showfully server "${server}"`);
|
|
97
|
+
}
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (submitJson?.errors?.length) {
|
|
102
|
+
throw new Error(submitJson.errors[0]);
|
|
103
|
+
}
|
|
104
|
+
if (!submitJson?.choreId) {
|
|
105
|
+
throw new Error('Showfully did not return a choreId');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const {choreId} = submitJson;
|
|
109
|
+
|
|
110
|
+
log(`adaptfully: chore ${choreId} submitted; waiting…`);
|
|
111
|
+
|
|
112
|
+
for (;;) {
|
|
113
|
+
const statusUrl = `${base}yap/wrapfully/${encodeURIComponent(choreId)}/status`;
|
|
114
|
+
const {data: status} = await axios.get(statusUrl, {
|
|
115
|
+
headers: {Authorization: `Bearer ${accessToken}`},
|
|
116
|
+
validateStatus: () => true,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (status?.state === 'complete') {
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (status?.state === 'error' || status?.errors?.length) {
|
|
123
|
+
throw new Error(status.errors?.[0] || `Chore ${choreId} failed`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
log(`adaptfully: ${status?.status || status?.state || 'waiting'}…`);
|
|
127
|
+
await sleep(POLL_MS);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
log(`adaptfully: downloading chore ${choreId}`);
|
|
131
|
+
const downloadUrl = `${base}yap/wrapfully/${encodeURIComponent(choreId)}`;
|
|
132
|
+
const {data, headers: resHeaders} = await axios.get(downloadUrl, {
|
|
133
|
+
headers: {Authorization: `Bearer ${accessToken}`},
|
|
134
|
+
responseType: 'arraybuffer',
|
|
135
|
+
maxContentLength: Infinity,
|
|
136
|
+
validateStatus: () => true,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const ctype = resHeaders['content-type'] || '';
|
|
140
|
+
|
|
141
|
+
if (ctype.includes('application/json')) {
|
|
142
|
+
const json = JSON.parse(Buffer.from(data).toString('utf8'));
|
|
143
|
+
|
|
144
|
+
throw new Error(json.errors?.[0] || 'Chore download returned JSON');
|
|
32
145
|
}
|
|
33
146
|
|
|
34
|
-
return
|
|
147
|
+
return Buffer.from(data);
|
|
35
148
|
}
|
|
36
149
|
|
|
37
150
|
/**
|
|
@@ -41,23 +154,45 @@ function resolveWrapfullyUrl(server, stage, family, gameId, platformKey, deploym
|
|
|
41
154
|
* @param {'build' | 'deploy' | 'release'} stage
|
|
42
155
|
* @param {string} family
|
|
43
156
|
* @param {string} deployFolder
|
|
44
|
-
* @param {{ name: string, version: string }} pkg
|
|
157
|
+
* @param {{ name: string, version: string, config?: object }} pkg
|
|
45
158
|
* @param {'extract' | string} mode
|
|
46
|
-
* @param {{
|
|
159
|
+
* @param {{
|
|
160
|
+
* log?: (message: string) => void,
|
|
161
|
+
* publishDir?: string,
|
|
162
|
+
* deploymentDirs?: string[],
|
|
163
|
+
* platformKey?: string,
|
|
164
|
+
* deploymentKey?: string,
|
|
165
|
+
* artifactPath?: string,
|
|
166
|
+
* accessToken?: string,
|
|
167
|
+
* encrypt?: boolean,
|
|
168
|
+
* encryptPublicKey?: string,
|
|
169
|
+
* wrapfullyConfig?: object,
|
|
170
|
+
* }} [options]
|
|
47
171
|
*/
|
|
48
|
-
export async function send(
|
|
172
|
+
export async function send (
|
|
173
|
+
gameId,
|
|
174
|
+
contents,
|
|
175
|
+
server,
|
|
176
|
+
stage,
|
|
177
|
+
family,
|
|
178
|
+
deployFolder,
|
|
179
|
+
pkg,
|
|
180
|
+
mode = 'extract',
|
|
181
|
+
options = {},
|
|
182
|
+
) {
|
|
49
183
|
const log = options.log ?? console.log;
|
|
50
184
|
const platformKey = options.platformKey ?? family;
|
|
51
185
|
const outputRoot = pkg.config?.outputFolder || 'output';
|
|
186
|
+
const accessToken = options.accessToken;
|
|
187
|
+
|
|
188
|
+
if (!accessToken) {
|
|
189
|
+
throw new Error('accessToken (Showfully PAT) is required for Yap wrapfully chores');
|
|
190
|
+
}
|
|
52
191
|
|
|
53
192
|
if (mode === 'extract') {
|
|
54
193
|
clearStaleBuildExtract(outputRoot);
|
|
55
194
|
}
|
|
56
195
|
|
|
57
|
-
const destination = mode === 'extract'
|
|
58
|
-
? unzipper.Extract({ path: `${outputRoot}/`, concurrency: 1 })
|
|
59
|
-
: fs.createWriteStream(`${outputRoot}/${pkg.name}-${pkg.version}-${stage}-${family}.zip`);
|
|
60
|
-
|
|
61
196
|
let archiveStream;
|
|
62
197
|
|
|
63
198
|
if (stage === 'deploy') {
|
|
@@ -67,9 +202,9 @@ export async function send(gameId, contents, server, stage, family, deployFolder
|
|
|
67
202
|
if (!options.deploymentKey) {
|
|
68
203
|
throw new Error('deploy stage requires options.deploymentKey');
|
|
69
204
|
}
|
|
70
|
-
archiveStream = createDeployArchive(options.artifactPath, options.publishDir, contents, {
|
|
205
|
+
archiveStream = createDeployArchive(options.artifactPath, options.publishDir, contents, {log});
|
|
71
206
|
} else if (stage === 'release') {
|
|
72
|
-
archiveStream = createReleaseArchive(deployFolder, contents, options.deploymentDirs ?? [], {
|
|
207
|
+
archiveStream = createReleaseArchive(deployFolder, contents, options.deploymentDirs ?? [], {log});
|
|
73
208
|
} else {
|
|
74
209
|
archiveStream = createSourceArchive(deployFolder, contents, {
|
|
75
210
|
log,
|
|
@@ -78,33 +213,95 @@ export async function send(gameId, contents, server, stage, family, deployFolder
|
|
|
78
213
|
});
|
|
79
214
|
}
|
|
80
215
|
|
|
81
|
-
const url = resolveWrapfullyUrl(server, stage, family, gameId, platformKey, options.deploymentKey);
|
|
82
|
-
log(`adaptfully: POST ${url}`);
|
|
83
|
-
|
|
84
216
|
if (stage !== 'deploy') {
|
|
85
217
|
const htmlFiles = listHtmlFilesRecursive(deployFolder);
|
|
218
|
+
|
|
86
219
|
log(`adaptfully: sending ${htmlFiles.length} HTML file(s) from ${path.resolve(deployFolder)}`);
|
|
87
220
|
}
|
|
88
221
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
222
|
+
// Append wrapfully.json routing into the archive by rebuilding into a buffer.
|
|
223
|
+
const job = buildWrapfullyJobConfig(stage, family, gameId, platformKey, options.deploymentKey);
|
|
224
|
+
const innerChunks = [];
|
|
93
225
|
|
|
94
|
-
archiveStream
|
|
95
|
-
|
|
96
|
-
|
|
226
|
+
// Re-pack: read archiveStream into zip, add wrapfully.json
|
|
227
|
+
// Archiver streams aren't easily mutable — collect then use yazl-free approach:
|
|
228
|
+
// pipe archive to buffer, unzip to tmp, add file, rezip is heavy.
|
|
229
|
+
// Simpler: createSourceArchive already finalized as stream — append via second archiver
|
|
230
|
+
// that copies entries is complex. Instead append wrapfully.json by concatenating
|
|
231
|
+
// a sidecar zip is wrong. Best: write wrapfully.json into deploy folder meta before
|
|
232
|
+
// archive... but that mutates disk.
|
|
233
|
+
//
|
|
234
|
+
// Practical approach matching Dutifully: build archive, buffer it, then use a new
|
|
235
|
+
// archiver that includes the buffer as... no that's nested.
|
|
236
|
+
//
|
|
237
|
+
// Use jszip-like: buffer the created archive by consuming stream, then use
|
|
238
|
+
// adm-zip equivalent — we have unzipper. Extract to tmp, write wrapfully.json, rezip.
|
|
239
|
+
|
|
240
|
+
const tmpRoot = path.join(outputRoot, `.adaptfully-yap-${Date.now().toString(16)}`);
|
|
97
241
|
|
|
242
|
+
fs.mkdirSync(tmpRoot, {recursive: true});
|
|
98
243
|
try {
|
|
99
|
-
await pipeline(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
|
|
244
|
+
await pipeline(archiveStream, unzipper.Extract({path: tmpRoot, concurrency: 1}));
|
|
245
|
+
fs.writeFileSync(path.join(tmpRoot, 'wrapfully.json'), JSON.stringify(job, null, 2));
|
|
246
|
+
|
|
247
|
+
const {default: archiver} = await import('archiver');
|
|
248
|
+
const {PassThrough} = await import('node:stream');
|
|
249
|
+
const archive = archiver('zip', {zlib: {level: 0}});
|
|
250
|
+
const pass = new PassThrough();
|
|
251
|
+
|
|
252
|
+
archive.pipe(pass);
|
|
253
|
+
pass.on('data', (c) => innerChunks.push(c));
|
|
254
|
+
|
|
255
|
+
const done = new Promise((resolve, reject) => {
|
|
256
|
+
pass.on('end', resolve);
|
|
257
|
+
pass.on('error', reject);
|
|
258
|
+
archive.on('error', reject);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
archive.directory(tmpRoot, false);
|
|
262
|
+
await archive.finalize();
|
|
263
|
+
await done;
|
|
264
|
+
} finally {
|
|
265
|
+
fs.rmSync(tmpRoot, {recursive: true, force: true});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let uploadBuffer = Buffer.concat(innerChunks);
|
|
269
|
+
let resultPriv = null;
|
|
270
|
+
|
|
271
|
+
if (options.encrypt) {
|
|
272
|
+
const {pem, kid} = resolveEncryptPublicKey(
|
|
273
|
+
options.wrapfullyConfig || {},
|
|
274
|
+
options.encryptPublicKey,
|
|
275
|
+
);
|
|
276
|
+
const resultKeyPair = generateResultKeyPair();
|
|
277
|
+
const envelope = await createEnvelopeFromInnerZip(uploadBuffer, {
|
|
278
|
+
publicKeyPem: pem,
|
|
279
|
+
kid,
|
|
280
|
+
resultKeyPair,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
uploadBuffer = envelope.zip;
|
|
284
|
+
resultPriv = envelope.resultPriv;
|
|
285
|
+
log(`adaptfully: encrypting chore with kid "${kid}"`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
let resultBuffer = await submitWrapfullyChore({
|
|
289
|
+
server,
|
|
290
|
+
accessToken,
|
|
291
|
+
zipBuffer: uploadBuffer,
|
|
292
|
+
log,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (resultPriv) {
|
|
296
|
+
resultBuffer = await decryptResultEnvelope(resultBuffer, resultPriv);
|
|
106
297
|
}
|
|
107
298
|
|
|
299
|
+
const destination = mode === 'extract'
|
|
300
|
+
? unzipper.Extract({path: `${outputRoot}/`, concurrency: 1})
|
|
301
|
+
: fs.createWriteStream(`${outputRoot}/${pkg.name}-${pkg.version}-${stage}-${family}.zip`);
|
|
302
|
+
|
|
303
|
+
await pipeline(Readable.from(resultBuffer), destination);
|
|
304
|
+
|
|
108
305
|
if (mode === 'extract') {
|
|
109
306
|
printBuildReport(`${stage}-${family}`, pkg);
|
|
110
307
|
}
|
package/lib/node/pipeline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { loadProjectConfig, resolveServerUrl } from './config.js';
|
|
2
|
+
import { loadProjectConfig, resolveServerUrl, resolveAccessToken, resolveEncryptFlag } from './config.js';
|
|
3
3
|
import { send } from './deploy.js';
|
|
4
4
|
import { resolveBuildArtifactDir } from './artifacts.js';
|
|
5
5
|
import { prebuildPlatform } from './prebuild.js';
|
|
@@ -44,7 +44,7 @@ export function resolveWrapfullyRoute(buildSpec) {
|
|
|
44
44
|
/**
|
|
45
45
|
* @param {AdaptfullyStage} stage
|
|
46
46
|
* @param {string} platformKey
|
|
47
|
-
* @param {{ pkg: object, deployFolder: string, server?: string, mode?: string, deployment?: string, artifactPath?: string, log?: (message: string) => void, outputRoot?: string }} options
|
|
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
|
|
48
48
|
*/
|
|
49
49
|
export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
50
50
|
if (!VALID_STAGES.has(stage)) {
|
|
@@ -103,7 +103,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
103
103
|
|
|
104
104
|
log(
|
|
105
105
|
`adaptfully: ${stage} ${platformKey} → ${buildSpec.family} `
|
|
106
|
-
+ `(${buildSpec.targets.join(', ')}) via
|
|
106
|
+
+ `(${buildSpec.targets.join(', ')}) via Yap /yap/wrapfully (route ${wrapfullyRoute}/release)`,
|
|
107
107
|
);
|
|
108
108
|
|
|
109
109
|
log(
|
|
@@ -125,6 +125,10 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
125
125
|
log,
|
|
126
126
|
platformKey,
|
|
127
127
|
deploymentDirs: deploymentDirs.map((dir) => path.resolve(dir)),
|
|
128
|
+
accessToken: options.accessToken,
|
|
129
|
+
encrypt: options.encrypt,
|
|
130
|
+
encryptPublicKey: options.encryptPublicKey,
|
|
131
|
+
wrapfullyConfig: options.wrapfullyConfig,
|
|
128
132
|
},
|
|
129
133
|
);
|
|
130
134
|
|
|
@@ -142,7 +146,7 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
142
146
|
log(
|
|
143
147
|
`adaptfully: ${stage} ${platformKey} → `
|
|
144
148
|
+ `${deploymentKey === 'zip' ? 'artifact zip' : `deployment "${deploymentKey}"`} `
|
|
145
|
-
+ `via
|
|
149
|
+
+ `via Yap /yap/wrapfully (route ${wrapfullyRoute}/${wrapStage}, ${buildSpec.family})`,
|
|
146
150
|
);
|
|
147
151
|
|
|
148
152
|
if (deploymentDirs.length) {
|
|
@@ -169,6 +173,10 @@ export async function runAdaptfullyStage(stage, platformKey, options) {
|
|
|
169
173
|
platformKey,
|
|
170
174
|
deploymentKey: wrapStage === 'deploy' ? deploymentKey : undefined,
|
|
171
175
|
artifactPath,
|
|
176
|
+
accessToken: options.accessToken,
|
|
177
|
+
encrypt: options.encrypt,
|
|
178
|
+
encryptPublicKey: options.encryptPublicKey,
|
|
179
|
+
wrapfullyConfig: options.wrapfullyConfig,
|
|
172
180
|
},
|
|
173
181
|
);
|
|
174
182
|
|
|
@@ -194,7 +202,7 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
194
202
|
}
|
|
195
203
|
|
|
196
204
|
const stage = command;
|
|
197
|
-
const { positionals, deployment, artifactPath } = parseStageArgs(argv.slice(3));
|
|
205
|
+
const { positionals, deployment, artifactPath, encrypt, encryptPublicKey } = parseStageArgs(argv.slice(3));
|
|
198
206
|
const platformKey = positionals[0];
|
|
199
207
|
const cliServer = positionals[1];
|
|
200
208
|
const mode = positionals[2] ?? 'extract';
|
|
@@ -202,7 +210,8 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
202
210
|
if (!stage || !platformKey) {
|
|
203
211
|
throw new Error(
|
|
204
212
|
'Usage:\n'
|
|
205
|
-
+ ' adaptfully <prebuild|build|deploy|release> <platform> [
|
|
213
|
+
+ ' adaptfully <prebuild|build|deploy|release> <platform> [showfullyServer] [mode]\n'
|
|
214
|
+
+ ' [--deployment <key>] [--artifact <path>] [--encrypt] [--encrypt-public-key <pem|path>]\n'
|
|
206
215
|
+ ' adaptfully steam-publish [--username U] [--password P] [--deployment steam] [--output path]\n'
|
|
207
216
|
+ ' adaptfully google-publish [--from service-account.json] [--deployment android] [--output path]\n'
|
|
208
217
|
+ ' adaptfully apple-publish [--deployment ios] [--category C] [--identity I] [--username U] [--password P]\n'
|
|
@@ -211,12 +220,18 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
211
220
|
+ '\n'
|
|
212
221
|
+ 'Stages:\n'
|
|
213
222
|
+ ' prebuild Copy deploy/ and apply platform registrations → output/<platform>-prebuild/\n'
|
|
214
|
-
+ ' build prebuild + POST /
|
|
215
|
-
+ ' deploy
|
|
216
|
-
+ ' release
|
|
223
|
+
+ ' build prebuild + POST /yap/wrapfully (zip artifact only; Showfully PAT required)\n'
|
|
224
|
+
+ ' deploy Yap deploy chore with prior artifact + deployment credentials\n'
|
|
225
|
+
+ ' release Yap release chore (build + configured deployments)\n'
|
|
217
226
|
+ '\n'
|
|
218
227
|
+ ' --deployment <key> Target a single named deployment.\n'
|
|
219
228
|
+ ' --artifact <path> Prior build artifact directory (deploy only; default: ./output/ after build).\n'
|
|
229
|
+
+ ' --encrypt Envelope-encrypt chore to Wrapfully (optional; default key embedded).\n'
|
|
230
|
+
+ ' --encrypt-public-key <pem|path> Override Wrapfully public key when encrypting.\n'
|
|
231
|
+
+ '\n'
|
|
232
|
+
+ 'Auth / server (wrapfully.json or env):\n'
|
|
233
|
+
+ ' SHOWFULLY_PAT / accessToken required PAT from play.makefullystudios.com Settings\n'
|
|
234
|
+
+ ' SHOWFULLY_SERVER / server Showfully Yap base (default https://make.makefullystudios.com/)\n'
|
|
220
235
|
+ '\n'
|
|
221
236
|
+ 'Publish credential helpers (one-time local setup):\n'
|
|
222
237
|
+ ' steam-publish Install steamcmd if needed, log in, write steam deployment steam.json\n'
|
|
@@ -229,6 +244,8 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
229
244
|
|
|
230
245
|
const { pkg, wrapfullyConfig } = await loadProjectConfig();
|
|
231
246
|
const server = resolveServerUrl(wrapfullyConfig, cliServer);
|
|
247
|
+
const accessToken = resolveAccessToken(wrapfullyConfig);
|
|
248
|
+
const encryptFlag = resolveEncryptFlag(wrapfullyConfig, encrypt);
|
|
232
249
|
const deployFolder = pkg.config?.deployFolder || 'deploy';
|
|
233
250
|
|
|
234
251
|
return runAdaptfullyStage(/** @type {AdaptfullyStage} */ (stage), platformKey, {
|
|
@@ -238,18 +255,24 @@ export async function adaptfullyFromCli(argv = process.argv) {
|
|
|
238
255
|
mode,
|
|
239
256
|
deployment,
|
|
240
257
|
artifactPath,
|
|
258
|
+
accessToken,
|
|
259
|
+
encrypt: encryptFlag,
|
|
260
|
+
encryptPublicKey,
|
|
261
|
+
wrapfullyConfig,
|
|
241
262
|
});
|
|
242
263
|
}
|
|
243
264
|
|
|
244
265
|
/**
|
|
245
266
|
* @param {string[]} args
|
|
246
|
-
* @returns {{ positionals: string[], deployment?: string, artifactPath?: string }}
|
|
267
|
+
* @returns {{ positionals: string[], deployment?: string, artifactPath?: string, encrypt?: boolean, encryptPublicKey?: string }}
|
|
247
268
|
*/
|
|
248
269
|
export function parseStageArgs(args) {
|
|
249
270
|
/** @type {string[]} */
|
|
250
271
|
const positionals = [];
|
|
251
272
|
let deployment;
|
|
252
273
|
let artifactPath;
|
|
274
|
+
let encrypt;
|
|
275
|
+
let encryptPublicKey;
|
|
253
276
|
|
|
254
277
|
for (let i = 0; i < args.length; i++) {
|
|
255
278
|
const arg = args[i];
|
|
@@ -262,10 +285,16 @@ export function parseStageArgs(args) {
|
|
|
262
285
|
artifactPath = args[++i];
|
|
263
286
|
} else if (arg.startsWith('--artifact=')) {
|
|
264
287
|
artifactPath = arg.slice('--artifact='.length);
|
|
288
|
+
} else if (arg === '--encrypt') {
|
|
289
|
+
encrypt = true;
|
|
290
|
+
} else if (arg === '--encrypt-public-key' && args[i + 1]) {
|
|
291
|
+
encryptPublicKey = args[++i];
|
|
292
|
+
} else if (arg.startsWith('--encrypt-public-key=')) {
|
|
293
|
+
encryptPublicKey = arg.slice('--encrypt-public-key='.length);
|
|
265
294
|
} else {
|
|
266
295
|
positionals.push(arg);
|
|
267
296
|
}
|
|
268
297
|
}
|
|
269
298
|
|
|
270
|
-
return { positionals, deployment, artifactPath };
|
|
299
|
+
return { positionals, deployment, artifactPath, encrypt, encryptPublicKey };
|
|
271
300
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default Makefully Wrapfully fleet public key (kid: makefully-wrapfully-1).
|
|
3
|
+
* Private key lives only on Wrapfully workers (WRAPFULLY_PRIVATE_KEY / cred.json).
|
|
4
|
+
* Safe to embed in clients — used only to wrap chore AES keys when encrypt: true.
|
|
5
|
+
*/
|
|
6
|
+
export const WRAPFULLY_DEFAULT_KID = 'makefully-wrapfully-1';
|
|
7
|
+
|
|
8
|
+
export const WRAPFULLY_DEFAULT_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
|
9
|
+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArbQGAXiX+XtnsdS0yUPj
|
|
10
|
+
dgM2Vt8lwDXPtLxzjYD45fr/NEOct0+ECsvr3jyIB/cVCjINpJJyKGEBvZk8woFj
|
|
11
|
+
FgyodSWjJrc1UsomeOz6IcOArWu6GnkFo0jn8R0FN8LOhEl1xjqT7VPzF4jd7/P5
|
|
12
|
+
4WBNeB+wjkY/XBmk5wsNLvGaREp8G4fFfMgDNPpTUy7RB8ezolTiWZH0KgiXZtM8
|
|
13
|
+
yUXk5YBPxTwI28wCB6Om5RGmwozWc66k29qZQRv5d4GRMSM18lMKh3gkjptZRhRD
|
|
14
|
+
D48bKDQwYPX8CWcpl/Q9qm2L0wtikjQWro1NsAreqa3r91wlrsYZ82stYIyKBYhW
|
|
15
|
+
LwIDAQAB
|
|
16
|
+
-----END PUBLIC KEY-----
|
|
17
|
+
`;
|