@makefully/adaptfully 3.16.1 → 4.1.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 +20 -0
- package/README.md +25 -21
- package/docs/credentials/README.md +50 -0
- package/docs/credentials/android.md +124 -0
- package/docs/credentials/ios.md +182 -0
- package/docs/credentials/steam.md +99 -0
- package/lib/node/android-keystore.js +243 -0
- package/lib/node/apple-signing.js +392 -0
- package/lib/node/index.js +20 -1
- package/lib/node/packagers/base.js +3 -3
- package/lib/node/packagers/index.js +0 -3
- package/lib/node/pipeline.js +11 -3
- package/lib/node/publish-credentials.js +47 -1
- package/lib/node/registrations.js +5 -2
- package/package.json +3 -2
- package/lib/node/packagers/cordova.js +0 -44
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
defaultDeploymentCredentialPath,
|
|
6
|
+
ensureDeploymentManifest,
|
|
7
|
+
ensurePublishCredentialsGitignore,
|
|
8
|
+
parsePublishCredentialArgv,
|
|
9
|
+
promptConfirm,
|
|
10
|
+
promptRequired,
|
|
11
|
+
} from './publish-credentials.js';
|
|
12
|
+
|
|
13
|
+
const DEBUG_ALIAS = 'androiddebugkey';
|
|
14
|
+
const DEBUG_PASSWORD = 'android';
|
|
15
|
+
const DEBUG_DNAME = 'CN=Android Debug,O=Android,C=US';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} [projectRoot]
|
|
19
|
+
* @param {string} [deploymentKey='android']
|
|
20
|
+
*/
|
|
21
|
+
export function defaultAndroidDeploymentDir(projectRoot = '.', deploymentKey = 'android') {
|
|
22
|
+
return path.dirname(defaultDeploymentCredentialPath(projectRoot, deploymentKey, 'build.json'));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {{
|
|
27
|
+
* debugOnly?: boolean,
|
|
28
|
+
* debug?: { keystore: string, storePassword: string, alias: string, password: string },
|
|
29
|
+
* release?: { keystore: string, storePassword: string, alias: string, password: string },
|
|
30
|
+
* }} options
|
|
31
|
+
*/
|
|
32
|
+
export function buildAndroidBuildJson(options = {}) {
|
|
33
|
+
/** @type {Record<string, unknown>} */
|
|
34
|
+
const android = {
|
|
35
|
+
debug: {
|
|
36
|
+
keystore: options.debug?.keystore ?? './android/debug.keystore',
|
|
37
|
+
packageType: 'apk',
|
|
38
|
+
storePassword: options.debug?.storePassword ?? DEBUG_PASSWORD,
|
|
39
|
+
alias: options.debug?.alias ?? DEBUG_ALIAS,
|
|
40
|
+
password: options.debug?.password ?? DEBUG_PASSWORD,
|
|
41
|
+
keystoreType: '',
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (!options.debugOnly && options.release) {
|
|
46
|
+
android.release = {
|
|
47
|
+
keystore: options.release.keystore,
|
|
48
|
+
packageType: 'bundle',
|
|
49
|
+
storePassword: options.release.storePassword,
|
|
50
|
+
alias: options.release.alias,
|
|
51
|
+
password: options.release.password,
|
|
52
|
+
keystoreType: '',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { android };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {string[]} args
|
|
61
|
+
* @param {{ command?: string }} [options]
|
|
62
|
+
* @returns {Promise<void>}
|
|
63
|
+
*/
|
|
64
|
+
export function runKeytool(args, options = {}) {
|
|
65
|
+
const command = options.command ?? 'keytool';
|
|
66
|
+
|
|
67
|
+
return new Promise((resolve, reject) => {
|
|
68
|
+
const child = spawn(command, args, {
|
|
69
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
70
|
+
shell: process.platform === 'win32',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
let stderr = '';
|
|
74
|
+
child.stderr?.on('data', (chunk) => {
|
|
75
|
+
stderr += String(chunk);
|
|
76
|
+
});
|
|
77
|
+
child.on('error', (err) => {
|
|
78
|
+
reject(new Error(
|
|
79
|
+
`Unable to run "${command}". Install a JDK and ensure keytool is on PATH. (${err.message})`,
|
|
80
|
+
));
|
|
81
|
+
});
|
|
82
|
+
child.on('close', (code) => {
|
|
83
|
+
if (code === 0) {
|
|
84
|
+
resolve();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
reject(new Error(`keytool failed (exit ${code}): ${stderr.trim() || 'no output'}`));
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @param {{
|
|
94
|
+
* keystorePath: string,
|
|
95
|
+
* alias: string,
|
|
96
|
+
* storePassword: string,
|
|
97
|
+
* keyPassword: string,
|
|
98
|
+
* dname: string,
|
|
99
|
+
* validityDays?: number,
|
|
100
|
+
* runKeytoolFn?: typeof runKeytool,
|
|
101
|
+
* }} options
|
|
102
|
+
*/
|
|
103
|
+
export async function generateKeystore(options) {
|
|
104
|
+
const run = options.runKeytoolFn ?? runKeytool;
|
|
105
|
+
await fsp.mkdir(path.dirname(options.keystorePath), { recursive: true });
|
|
106
|
+
await run([
|
|
107
|
+
'-genkeypair',
|
|
108
|
+
'-v',
|
|
109
|
+
'-keystore', options.keystorePath,
|
|
110
|
+
'-alias', options.alias,
|
|
111
|
+
'-keyalg', 'RSA',
|
|
112
|
+
'-keysize', '2048',
|
|
113
|
+
'-validity', String(options.validityDays ?? 10000),
|
|
114
|
+
'-storepass', options.storePassword,
|
|
115
|
+
'-keypass', options.keyPassword,
|
|
116
|
+
'-dname', options.dname,
|
|
117
|
+
]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {{
|
|
122
|
+
* deployment?: string,
|
|
123
|
+
* projectRoot?: string,
|
|
124
|
+
* debugOnly?: boolean,
|
|
125
|
+
* yes?: boolean,
|
|
126
|
+
* alias?: string,
|
|
127
|
+
* storePassword?: string,
|
|
128
|
+
* keyPassword?: string,
|
|
129
|
+
* cn?: string,
|
|
130
|
+
* log?: (message: string) => void,
|
|
131
|
+
* runKeytoolFn?: typeof runKeytool,
|
|
132
|
+
* }} [options]
|
|
133
|
+
*/
|
|
134
|
+
export async function runAndroidKeystore(options = {}) {
|
|
135
|
+
const log = options.log ?? console.log;
|
|
136
|
+
const projectRoot = options.projectRoot ?? '.';
|
|
137
|
+
const deploymentKey = options.deployment ?? 'android';
|
|
138
|
+
const deploymentDir = defaultAndroidDeploymentDir(projectRoot, deploymentKey);
|
|
139
|
+
const keystoreDir = path.join(deploymentDir, 'android');
|
|
140
|
+
const buildJsonPath = path.join(deploymentDir, 'build.json');
|
|
141
|
+
const debugOnly = options.debugOnly === true;
|
|
142
|
+
|
|
143
|
+
let buildJsonExists = false;
|
|
144
|
+
try {
|
|
145
|
+
await fsp.access(buildJsonPath);
|
|
146
|
+
buildJsonExists = true;
|
|
147
|
+
} catch {
|
|
148
|
+
buildJsonExists = false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (buildJsonExists) {
|
|
152
|
+
const overwrite = options.yes === true
|
|
153
|
+
|| await promptConfirm(`Overwrite existing ${buildJsonPath}?`, false);
|
|
154
|
+
if (!overwrite) {
|
|
155
|
+
throw new Error(`Refusing to overwrite ${buildJsonPath}. Pass --yes to overwrite.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const debugKeystorePath = path.join(keystoreDir, 'debug.keystore');
|
|
160
|
+
log(`adaptfully: generating debug keystore → ${debugKeystorePath}`);
|
|
161
|
+
await generateKeystore({
|
|
162
|
+
keystorePath: debugKeystorePath,
|
|
163
|
+
alias: DEBUG_ALIAS,
|
|
164
|
+
storePassword: DEBUG_PASSWORD,
|
|
165
|
+
keyPassword: DEBUG_PASSWORD,
|
|
166
|
+
dname: DEBUG_DNAME,
|
|
167
|
+
runKeytoolFn: options.runKeytoolFn,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
/** @type {{ keystore: string, storePassword: string, alias: string, password: string } | undefined} */
|
|
171
|
+
let release;
|
|
172
|
+
|
|
173
|
+
if (!debugOnly) {
|
|
174
|
+
const alias = options.alias
|
|
175
|
+
?? await promptRequired('Release keystore alias: ');
|
|
176
|
+
const storePassword = options.storePassword
|
|
177
|
+
?? await promptRequired('Release store password: ');
|
|
178
|
+
const keyPassword = options.keyPassword
|
|
179
|
+
?? options.storePassword
|
|
180
|
+
?? await promptRequired('Release key password (often same as store): ');
|
|
181
|
+
const cn = options.cn
|
|
182
|
+
?? await promptRequired('Certificate CN (e.g. Your Studio): ');
|
|
183
|
+
|
|
184
|
+
const releaseKeystorePath = path.join(keystoreDir, 'release.keystore');
|
|
185
|
+
log(`adaptfully: generating release keystore → ${releaseKeystorePath}`);
|
|
186
|
+
await generateKeystore({
|
|
187
|
+
keystorePath: releaseKeystorePath,
|
|
188
|
+
alias,
|
|
189
|
+
storePassword,
|
|
190
|
+
keyPassword,
|
|
191
|
+
dname: `CN=${cn},O=${cn},C=US`,
|
|
192
|
+
runKeytoolFn: options.runKeytoolFn,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
release = {
|
|
196
|
+
keystore: './android/release.keystore',
|
|
197
|
+
storePassword,
|
|
198
|
+
alias,
|
|
199
|
+
password: keyPassword,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const buildJson = buildAndroidBuildJson({
|
|
204
|
+
debugOnly,
|
|
205
|
+
debug: {
|
|
206
|
+
keystore: './android/debug.keystore',
|
|
207
|
+
storePassword: DEBUG_PASSWORD,
|
|
208
|
+
alias: DEBUG_ALIAS,
|
|
209
|
+
password: DEBUG_PASSWORD,
|
|
210
|
+
},
|
|
211
|
+
release,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await fsp.mkdir(deploymentDir, { recursive: true });
|
|
215
|
+
await fsp.writeFile(buildJsonPath, `${JSON.stringify(buildJson, null, 4)}\n`);
|
|
216
|
+
await ensureDeploymentManifest(deploymentDir, 'google', log);
|
|
217
|
+
await ensurePublishCredentialsGitignore(projectRoot, {
|
|
218
|
+
extraPaths: [buildJsonPath, keystoreDir],
|
|
219
|
+
log,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
log(`adaptfully: wrote ${buildJsonPath}`);
|
|
223
|
+
if (debugOnly) {
|
|
224
|
+
log('adaptfully: debug-only keystore ready. For Play uploads, re-run without --debug-only, then use google-publish.');
|
|
225
|
+
} else {
|
|
226
|
+
log('adaptfully: next — create a Play Console service account JSON, then: adaptfully google-publish --from <sa.json>');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
deploymentKey,
|
|
231
|
+
deploymentDir,
|
|
232
|
+
buildJsonPath,
|
|
233
|
+
buildJson,
|
|
234
|
+
debugOnly,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {string[]} [argv]
|
|
240
|
+
*/
|
|
241
|
+
export async function androidKeystoreFromCli(argv = process.argv) {
|
|
242
|
+
return runAndroidKeystore(parsePublishCredentialArgv(argv));
|
|
243
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fsp from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {
|
|
5
|
+
defaultDeploymentCredentialPath,
|
|
6
|
+
ensureDeploymentManifest,
|
|
7
|
+
ensurePublishCredentialsGitignore,
|
|
8
|
+
parsePublishCredentialArgv,
|
|
9
|
+
promptConfirm,
|
|
10
|
+
promptRequired,
|
|
11
|
+
resolveUserPath,
|
|
12
|
+
} from './publish-credentials.js';
|
|
13
|
+
|
|
14
|
+
/** @typedef {'development' | 'distribution'} AppleSigningKind */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} [projectRoot]
|
|
18
|
+
* @param {string} [deploymentKey='ios']
|
|
19
|
+
*/
|
|
20
|
+
export function defaultAppleSigningDir(projectRoot = '.', deploymentKey = 'ios') {
|
|
21
|
+
return path.join(
|
|
22
|
+
path.dirname(defaultDeploymentCredentialPath(projectRoot, deploymentKey, 'apple.json')),
|
|
23
|
+
'apple',
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} kind
|
|
29
|
+
* @returns {AppleSigningKind}
|
|
30
|
+
*/
|
|
31
|
+
export function normalizeAppleSigningKind(kind) {
|
|
32
|
+
const value = String(kind ?? '').trim().toLowerCase();
|
|
33
|
+
if (value === 'development' || value === 'dev' || value === 'debug') {
|
|
34
|
+
return 'development';
|
|
35
|
+
}
|
|
36
|
+
if (value === 'distribution' || value === 'dist' || value === 'release' || value === 'app-store') {
|
|
37
|
+
return 'distribution';
|
|
38
|
+
}
|
|
39
|
+
throw new Error(
|
|
40
|
+
`Unknown Apple signing kind "${kind}". Use "development" or "distribution".`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {AppleSigningKind} kind
|
|
46
|
+
*/
|
|
47
|
+
export function appleSigningFilenames(kind) {
|
|
48
|
+
if (kind === 'development') {
|
|
49
|
+
return {
|
|
50
|
+
p12: 'development.p12',
|
|
51
|
+
provision: 'development.mobileprovision',
|
|
52
|
+
certLabel: 'Apple Development',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
p12: 'distribution.p12',
|
|
57
|
+
provision: 'app-store.mobileprovision',
|
|
58
|
+
certLabel: 'Apple Distribution',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Merge or create build.json with apple.p12Password.
|
|
64
|
+
* @param {object | null | undefined} existing
|
|
65
|
+
* @param {string} p12Password
|
|
66
|
+
*/
|
|
67
|
+
export function mergeAppleP12PasswordBuildJson(existing, p12Password) {
|
|
68
|
+
/** @type {Record<string, unknown>} */
|
|
69
|
+
const next = existing && typeof existing === 'object' && !Array.isArray(existing)
|
|
70
|
+
? { ...existing }
|
|
71
|
+
: {};
|
|
72
|
+
const apple = next.apple && typeof next.apple === 'object' && !Array.isArray(next.apple)
|
|
73
|
+
? { .../** @type {Record<string, unknown>} */ (next.apple) }
|
|
74
|
+
: {};
|
|
75
|
+
apple.p12Password = p12Password;
|
|
76
|
+
next.apple = apple;
|
|
77
|
+
return next;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {string[]} args
|
|
82
|
+
* @param {{ command?: string }} [options]
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
export function runOpenssl(args, options = {}) {
|
|
86
|
+
const command = options.command ?? 'openssl';
|
|
87
|
+
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const child = spawn(command, args, {
|
|
90
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
91
|
+
shell: process.platform === 'win32',
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
let stderr = '';
|
|
95
|
+
child.stderr?.on('data', (chunk) => {
|
|
96
|
+
stderr += String(chunk);
|
|
97
|
+
});
|
|
98
|
+
child.on('error', (err) => {
|
|
99
|
+
reject(new Error(
|
|
100
|
+
`Unable to run "${command}". Install OpenSSL and ensure it is on PATH. (${err.message})`,
|
|
101
|
+
));
|
|
102
|
+
});
|
|
103
|
+
child.on('close', (code) => {
|
|
104
|
+
if (code === 0) {
|
|
105
|
+
resolve();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
reject(new Error(`openssl failed (exit ${code}): ${stderr.trim() || 'no output'}`));
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {{
|
|
115
|
+
* workdir: string,
|
|
116
|
+
* cn: string,
|
|
117
|
+
* email?: string,
|
|
118
|
+
* runOpensslFn?: typeof runOpenssl,
|
|
119
|
+
* }} options
|
|
120
|
+
*/
|
|
121
|
+
export async function generateAppleCsr(options) {
|
|
122
|
+
const run = options.runOpensslFn ?? runOpenssl;
|
|
123
|
+
await fsp.mkdir(options.workdir, { recursive: true });
|
|
124
|
+
|
|
125
|
+
const keyPath = path.join(options.workdir, 'private.key');
|
|
126
|
+
const csrPath = path.join(options.workdir, 'CertificateSigningRequest.certSigningRequest');
|
|
127
|
+
const subject = options.email
|
|
128
|
+
? `/emailAddress=${options.email}/CN=${options.cn}/C=US`
|
|
129
|
+
: `/CN=${options.cn}/C=US`;
|
|
130
|
+
|
|
131
|
+
await run(['genrsa', '-out', keyPath, '2048']);
|
|
132
|
+
await run([
|
|
133
|
+
'req',
|
|
134
|
+
'-new',
|
|
135
|
+
'-key', keyPath,
|
|
136
|
+
'-out', csrPath,
|
|
137
|
+
'-subj', subject,
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
return { keyPath, csrPath };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* @param {{
|
|
145
|
+
* cerPath: string,
|
|
146
|
+
* keyPath: string,
|
|
147
|
+
* p12Path: string,
|
|
148
|
+
* p12Password: string,
|
|
149
|
+
* runOpensslFn?: typeof runOpenssl,
|
|
150
|
+
* }} options
|
|
151
|
+
*/
|
|
152
|
+
export async function exportAppleP12(options) {
|
|
153
|
+
const run = options.runOpensslFn ?? runOpenssl;
|
|
154
|
+
await fsp.mkdir(path.dirname(options.p12Path), { recursive: true });
|
|
155
|
+
|
|
156
|
+
const pemPath = `${options.cerPath}.pem`;
|
|
157
|
+
await run(['x509', '-in', options.cerPath, '-inform', 'DER', '-out', pemPath, '-outform', 'PEM']);
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
await run([
|
|
161
|
+
'pkcs12',
|
|
162
|
+
'-export',
|
|
163
|
+
'-inkey', options.keyPath,
|
|
164
|
+
'-in', pemPath,
|
|
165
|
+
'-out', options.p12Path,
|
|
166
|
+
'-passout', `pass:${options.p12Password}`,
|
|
167
|
+
]);
|
|
168
|
+
} catch (derErr) {
|
|
169
|
+
// Some downloads are already PEM
|
|
170
|
+
try {
|
|
171
|
+
await run([
|
|
172
|
+
'pkcs12',
|
|
173
|
+
'-export',
|
|
174
|
+
'-inkey', options.keyPath,
|
|
175
|
+
'-in', options.cerPath,
|
|
176
|
+
'-out', options.p12Path,
|
|
177
|
+
'-passout', `pass:${options.p12Password}`,
|
|
178
|
+
]);
|
|
179
|
+
} catch {
|
|
180
|
+
throw derErr;
|
|
181
|
+
}
|
|
182
|
+
} finally {
|
|
183
|
+
try {
|
|
184
|
+
await fsp.unlink(pemPath);
|
|
185
|
+
} catch {
|
|
186
|
+
// ignore
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @param {(message: string) => void} log
|
|
193
|
+
* @param {AppleSigningKind} kind
|
|
194
|
+
* @param {string} csrPath
|
|
195
|
+
*/
|
|
196
|
+
function printPortalChecklist(log, kind, csrPath) {
|
|
197
|
+
const names = appleSigningFilenames(kind);
|
|
198
|
+
log('');
|
|
199
|
+
log('adaptfully: Apple Developer portal steps (browser):');
|
|
200
|
+
log(` 1. Open https://developer.apple.com/account/resources/certificates/list`);
|
|
201
|
+
log(` 2. Create a new "${names.certLabel}" certificate`);
|
|
202
|
+
log(` 3. Upload CSR: ${csrPath}`);
|
|
203
|
+
log(' 4. Download the .cer file');
|
|
204
|
+
log(' 5. Ensure your App ID exists; enable Sign in with Apple if you use Capgo Apple auth');
|
|
205
|
+
if (kind === 'development') {
|
|
206
|
+
log(' 6. Register device UDIDs, then create a Development provisioning profile');
|
|
207
|
+
} else {
|
|
208
|
+
log(' 6. Create an App Store Connect provisioning profile for the App ID');
|
|
209
|
+
}
|
|
210
|
+
log(' 7. Download the .mobileprovision file');
|
|
211
|
+
log('');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {{
|
|
216
|
+
* deployment?: string,
|
|
217
|
+
* projectRoot?: string,
|
|
218
|
+
* workdir?: string,
|
|
219
|
+
* csrOnly?: boolean,
|
|
220
|
+
* fromCer?: string,
|
|
221
|
+
* kind?: string,
|
|
222
|
+
* provision?: string,
|
|
223
|
+
* p12Password?: string,
|
|
224
|
+
* cn?: string,
|
|
225
|
+
* email?: string,
|
|
226
|
+
* yes?: boolean,
|
|
227
|
+
* log?: (message: string) => void,
|
|
228
|
+
* runOpensslFn?: typeof runOpenssl,
|
|
229
|
+
* }} [options]
|
|
230
|
+
*/
|
|
231
|
+
export async function runAppleSigning(options = {}) {
|
|
232
|
+
const log = options.log ?? console.log;
|
|
233
|
+
const projectRoot = options.projectRoot ?? '.';
|
|
234
|
+
const deploymentKey = options.deployment ?? 'ios';
|
|
235
|
+
const appleDir = defaultAppleSigningDir(projectRoot, deploymentKey);
|
|
236
|
+
const deploymentDir = path.dirname(appleDir);
|
|
237
|
+
const workdir = path.resolve(
|
|
238
|
+
options.workdir
|
|
239
|
+
?? path.join(appleDir, '.work'),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
let kind = options.kind
|
|
243
|
+
? normalizeAppleSigningKind(options.kind)
|
|
244
|
+
: null;
|
|
245
|
+
|
|
246
|
+
if (!kind && (options.fromCer || options.provision || !options.csrOnly)) {
|
|
247
|
+
kind = normalizeAppleSigningKind(
|
|
248
|
+
await promptRequired('Signing kind (development|distribution): ', options.kind),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (!kind) {
|
|
253
|
+
kind = 'development';
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const names = appleSigningFilenames(kind);
|
|
257
|
+
const keyPath = path.join(workdir, 'private.key');
|
|
258
|
+
const csrPath = path.join(workdir, 'CertificateSigningRequest.certSigningRequest');
|
|
259
|
+
|
|
260
|
+
let createdCsr = false;
|
|
261
|
+
try {
|
|
262
|
+
await fsp.access(keyPath);
|
|
263
|
+
await fsp.access(csrPath);
|
|
264
|
+
} catch {
|
|
265
|
+
const cn = options.cn ?? await promptRequired('Common name for CSR (e.g. Your Name): ');
|
|
266
|
+
const email = options.email;
|
|
267
|
+
log(`adaptfully: generating CSR in ${workdir}`);
|
|
268
|
+
await generateAppleCsr({
|
|
269
|
+
workdir,
|
|
270
|
+
cn,
|
|
271
|
+
email,
|
|
272
|
+
runOpensslFn: options.runOpensslFn,
|
|
273
|
+
});
|
|
274
|
+
createdCsr = true;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
printPortalChecklist(log, kind, csrPath);
|
|
278
|
+
|
|
279
|
+
if (options.csrOnly) {
|
|
280
|
+
await ensurePublishCredentialsGitignore(projectRoot, {
|
|
281
|
+
extraPaths: [appleDir],
|
|
282
|
+
log,
|
|
283
|
+
});
|
|
284
|
+
log('adaptfully: --csr-only set; stop here, then re-run with --from-cer after downloading the .cer');
|
|
285
|
+
return {
|
|
286
|
+
deploymentKey,
|
|
287
|
+
appleDir,
|
|
288
|
+
workdir,
|
|
289
|
+
csrPath,
|
|
290
|
+
keyPath,
|
|
291
|
+
kind,
|
|
292
|
+
csrOnly: true,
|
|
293
|
+
createdCsr,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const cerPath = resolveUserPath(
|
|
298
|
+
await promptRequired('Path to downloaded Apple .cer: ', options.fromCer),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const p12Password = options.p12Password != null
|
|
302
|
+
? String(options.p12Password)
|
|
303
|
+
: await promptRequired('Passphrase for the .p12 (remember this for build.json apple.p12Password): ');
|
|
304
|
+
|
|
305
|
+
const p12Path = path.join(appleDir, names.p12);
|
|
306
|
+
log(`adaptfully: exporting ${p12Path}`);
|
|
307
|
+
await exportAppleP12({
|
|
308
|
+
cerPath,
|
|
309
|
+
keyPath,
|
|
310
|
+
p12Path,
|
|
311
|
+
p12Password,
|
|
312
|
+
runOpensslFn: options.runOpensslFn,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
const buildJsonPath = path.join(deploymentDir, 'build.json');
|
|
316
|
+
let existingBuild = null;
|
|
317
|
+
try {
|
|
318
|
+
existingBuild = JSON.parse(await fsp.readFile(buildJsonPath, 'utf8'));
|
|
319
|
+
} catch {
|
|
320
|
+
existingBuild = null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const writePassword = options.yes === true
|
|
324
|
+
|| p12Password === ''
|
|
325
|
+
|| await promptConfirm(
|
|
326
|
+
`Write apple.p12Password into ${buildJsonPath}?`,
|
|
327
|
+
true,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
if (writePassword) {
|
|
331
|
+
const merged = mergeAppleP12PasswordBuildJson(existingBuild, p12Password);
|
|
332
|
+
await fsp.mkdir(deploymentDir, { recursive: true });
|
|
333
|
+
await fsp.writeFile(buildJsonPath, `${JSON.stringify(merged, null, 4)}\n`);
|
|
334
|
+
log(`adaptfully: wrote ${buildJsonPath}`);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
let provisionPath = options.provision;
|
|
338
|
+
if (!provisionPath) {
|
|
339
|
+
if (options.yes === true) {
|
|
340
|
+
log(`adaptfully: --yes set without --provision; skipping provisioning profile copy`);
|
|
341
|
+
} else {
|
|
342
|
+
const rlAnswer = await promptRequired(
|
|
343
|
+
`Path to downloaded ${names.provision} (type skip to finish later): `,
|
|
344
|
+
);
|
|
345
|
+
if (rlAnswer.toLowerCase() !== 'skip') {
|
|
346
|
+
provisionPath = rlAnswer;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (provisionPath) {
|
|
352
|
+
const dest = path.join(appleDir, names.provision);
|
|
353
|
+
await fsp.mkdir(appleDir, { recursive: true });
|
|
354
|
+
await fsp.copyFile(resolveUserPath(provisionPath), dest);
|
|
355
|
+
log(`adaptfully: copied provisioning profile → ${dest}`);
|
|
356
|
+
} else {
|
|
357
|
+
log(`adaptfully: skipped provisioning profile — place ${names.provision} under ${appleDir} when ready`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
await ensureDeploymentManifest(deploymentDir, 'apple', log);
|
|
361
|
+
await ensurePublishCredentialsGitignore(projectRoot, {
|
|
362
|
+
extraPaths: [appleDir, buildJsonPath],
|
|
363
|
+
log,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
log(`adaptfully: signing files under ${appleDir}`);
|
|
367
|
+
if (kind === 'distribution') {
|
|
368
|
+
log('adaptfully: next for App Store upload — adaptfully apple-publish');
|
|
369
|
+
} else {
|
|
370
|
+
log('adaptfully: development signing ready for ios-dev device builds');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return {
|
|
374
|
+
deploymentKey,
|
|
375
|
+
appleDir,
|
|
376
|
+
workdir,
|
|
377
|
+
csrPath,
|
|
378
|
+
keyPath,
|
|
379
|
+
p12Path,
|
|
380
|
+
kind,
|
|
381
|
+
csrOnly: false,
|
|
382
|
+
createdCsr,
|
|
383
|
+
provisionPath: provisionPath ? resolveUserPath(provisionPath) : null,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* @param {string[]} [argv]
|
|
389
|
+
*/
|
|
390
|
+
export async function appleSigningFromCli(argv = process.argv) {
|
|
391
|
+
return runAppleSigning(parsePublishCredentialArgv(argv));
|
|
392
|
+
}
|
package/lib/node/index.js
CHANGED
|
@@ -23,7 +23,6 @@ export { prebuildPlatform, prebuildOutputDir, resolveHtmlInjections } from './pr
|
|
|
23
23
|
export { getPackageRoot, getRuntimeDir, resolveRuntimeScript } from './paths.js';
|
|
24
24
|
export {
|
|
25
25
|
CapacitorPackager,
|
|
26
|
-
CordovaPackager,
|
|
27
26
|
ElectronPackager,
|
|
28
27
|
Packager,
|
|
29
28
|
VALID_PACKAGERS,
|
|
@@ -63,6 +62,7 @@ export {
|
|
|
63
62
|
ensurePublishCredentialsGitignore,
|
|
64
63
|
normalizeUserPath,
|
|
65
64
|
parsePublishCredentialArgv,
|
|
65
|
+
promptConfirm,
|
|
66
66
|
promptRequired,
|
|
67
67
|
PUBLISH_CREDENTIAL_GITIGNORE_PATTERNS,
|
|
68
68
|
resolveUserPath,
|
|
@@ -91,3 +91,22 @@ export {
|
|
|
91
91
|
runApplePublish,
|
|
92
92
|
validateApplePublishJson,
|
|
93
93
|
} from './apple-publish.js';
|
|
94
|
+
export {
|
|
95
|
+
androidKeystoreFromCli,
|
|
96
|
+
buildAndroidBuildJson,
|
|
97
|
+
defaultAndroidDeploymentDir,
|
|
98
|
+
generateKeystore,
|
|
99
|
+
runAndroidKeystore,
|
|
100
|
+
runKeytool,
|
|
101
|
+
} from './android-keystore.js';
|
|
102
|
+
export {
|
|
103
|
+
appleSigningFilenames,
|
|
104
|
+
appleSigningFromCli,
|
|
105
|
+
defaultAppleSigningDir,
|
|
106
|
+
exportAppleP12,
|
|
107
|
+
generateAppleCsr,
|
|
108
|
+
mergeAppleP12PasswordBuildJson,
|
|
109
|
+
normalizeAppleSigningKind,
|
|
110
|
+
runAppleSigning,
|
|
111
|
+
runOpenssl,
|
|
112
|
+
} from './apple-signing.js';
|
|
@@ -4,10 +4,10 @@ import { STANDARD_PLUGINS } from '../registrations.js';
|
|
|
4
4
|
import { resolvePlatformConfig } from '../platform-config.js';
|
|
5
5
|
|
|
6
6
|
/** @typedef {import('../registrations.js').RegistrationMap} RegistrationMap */
|
|
7
|
-
/** @typedef {'web' | 'electron' | '
|
|
7
|
+
/** @typedef {'web' | 'electron' | 'capacitor'} PackagerName */
|
|
8
8
|
|
|
9
9
|
/** @type {PackagerName[]} */
|
|
10
|
-
export const VALID_PACKAGERS = ['web', 'electron', '
|
|
10
|
+
export const VALID_PACKAGERS = ['web', 'electron', 'capacitor'];
|
|
11
11
|
|
|
12
12
|
const PACKAGER_MARKER = '<!-- adaptfully-packager -->';
|
|
13
13
|
const PACKAGER_END_MARKER = '<!-- /adaptfully-packager -->';
|
|
@@ -16,7 +16,7 @@ const HEAD_PACKAGER_END_MARKER = '<!-- /adaptfully-packager-head -->';
|
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* @typedef {Object} PackagerOptions
|
|
19
|
-
* @property {string[]} [platforms] Platform keys this packager targets
|
|
19
|
+
* @property {string[]} [platforms] Platform keys this packager targets
|
|
20
20
|
* @property {string} [platformKey] Active platform for a single prebuild run
|
|
21
21
|
* @property {(message: string) => void} [log]
|
|
22
22
|
*/
|