expo-release-it 0.1.0 → 0.1.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.
Files changed (67) hide show
  1. package/build/command/build.js +156 -94
  2. package/build/command/build.js.map +1 -1
  3. package/build/command/bump.js +33 -27
  4. package/build/command/bump.js.map +1 -1
  5. package/build/command/init.js +161 -47
  6. package/build/command/init.js.map +1 -1
  7. package/build/command/pull.d.ts +5 -0
  8. package/build/command/pull.js +525 -0
  9. package/build/command/pull.js.map +1 -0
  10. package/build/command/submit.js +139 -84
  11. package/build/command/submit.js.map +1 -1
  12. package/build/command/upload.js +154 -104
  13. package/build/command/upload.js.map +1 -1
  14. package/build/index.js +414 -219
  15. package/build/index.js.map +1 -1
  16. package/build/util/FastlaneOption.js +9 -14
  17. package/build/util/FastlaneOption.js.map +1 -1
  18. package/build/util/FileUtil.d.ts +2 -1
  19. package/build/util/FileUtil.js +4 -0
  20. package/build/util/FileUtil.js.map +1 -1
  21. package/build/util/OptionHolder.d.ts +1 -3
  22. package/build/util/OptionHolder.js +7 -8
  23. package/build/util/OptionHolder.js.map +1 -1
  24. package/build/util/generateAppStoreConnectApiKeyFile.d.ts +3 -0
  25. package/build/util/{setup/promptCommonInputs.js → generateAppStoreConnectApiKeyFile.js} +36 -20
  26. package/build/util/generateAppStoreConnectApiKeyFile.js.map +1 -0
  27. package/build/util/injectTemplatePlaceHolders.js +8 -11
  28. package/build/util/injectTemplatePlaceHolders.js.map +1 -1
  29. package/build/util/input/InqueryInputs.d.ts +7 -1
  30. package/build/util/input/InqueryInputs.js +51 -13
  31. package/build/util/input/InqueryInputs.js.map +1 -1
  32. package/build/util/iterateAllFiles.js.map +1 -1
  33. package/build/util/logger.d.ts +1 -0
  34. package/build/util/logger.js +1 -0
  35. package/build/util/logger.js.map +1 -1
  36. package/build/util/{VersionUtil.js → setup/VersionUtil.js} +9 -9
  37. package/build/util/setup/VersionUtil.js.map +1 -0
  38. package/build/util/setup/execShellScript.d.ts +19 -0
  39. package/build/util/setup/execShellScript.js +19 -0
  40. package/build/util/setup/execShellScript.js.map +1 -0
  41. package/build/util/{hydrateKeyholderFileValues.js → setup/hydrateKeyholderFileValues.js} +14 -10
  42. package/build/util/setup/hydrateKeyholderFileValues.js.map +1 -0
  43. package/build/util/setup/prepareAndroid.js +64 -37
  44. package/build/util/setup/prepareAndroid.js.map +1 -1
  45. package/build/util/setup/prepareIos.js +63 -35
  46. package/build/util/setup/prepareIos.js.map +1 -1
  47. package/build/util/setup/setup.d.ts +2 -1
  48. package/build/util/setup/setup.js +30 -25
  49. package/build/util/setup/setup.js.map +1 -1
  50. package/build/util/spinner.d.ts +3 -0
  51. package/build/util/spinner.js +14 -0
  52. package/build/util/spinner.js.map +1 -0
  53. package/build/util/throwError.js +1 -0
  54. package/build/util/throwError.js.map +1 -1
  55. package/package.json +5 -5
  56. package/template/android/Gemfile +1 -1
  57. package/template/android/fastlane/Fastfile +2 -2
  58. package/template/ios/Gemfile +2 -1
  59. package/template/ios/fastlane/Appfile +0 -2
  60. package/template/ios/fastlane/Fastfile +20 -3
  61. package/build/util/VersionUtil.js.map +0 -1
  62. package/build/util/hydrateKeyholderFileValues.js.map +0 -1
  63. package/build/util/setup/promptCommonInputs.d.ts +0 -3
  64. package/build/util/setup/promptCommonInputs.js.map +0 -1
  65. package/template/ios/fastlane/Matchfile +0 -5
  66. /package/build/util/{VersionUtil.d.ts → setup/VersionUtil.d.ts} +0 -0
  67. /package/build/util/{hydrateKeyholderFileValues.d.ts → setup/hydrateKeyholderFileValues.d.ts} +0 -0
package/build/index.js CHANGED
@@ -6,11 +6,11 @@ var package_default = {
6
6
  name: "expo-release-it",
7
7
  module: "src/index.ts",
8
8
  type: "module",
9
- version: "0.0.17",
9
+ version: "0.1.1",
10
10
  license: "MIT",
11
11
  scripts: {
12
12
  build: "tsup",
13
- dev: "bun run build && NODE_ENV=development node bin/expo-release-it.mjs",
13
+ dev: "yarn run build && NODE_ENV=development node bin/expo-release-it.mjs",
14
14
  release: "yarn build && release-it",
15
15
  android: "expo run:android",
16
16
  ios: "expo run:ios",
@@ -21,15 +21,15 @@ var package_default = {
21
21
  "@mj-studio/js-util": "^1.1.20",
22
22
  chalk: "^5.4.1",
23
23
  commander: "^13.1.0",
24
+ execa: "^9.5.2",
24
25
  "fs-extra": "^11.3.0",
26
+ ora: "^8.2.0",
25
27
  react: "18.3.1",
26
28
  "react-native": "0.76.9",
27
29
  semver: "^7.7.1",
28
- url: "^0.11.4",
29
- zx: "^8.5.3"
30
+ url: "^0.11.4"
30
31
  },
31
32
  devDependencies: {
32
- "@types/bun": "latest",
33
33
  "@types/fs-extra": "^11.0.4",
34
34
  "@types/semver": "^7.7.0",
35
35
  prettier: "3.5.3",
@@ -87,6 +87,9 @@ function write(p, content) {
87
87
  function copy(src, dest, options) {
88
88
  fs.copySync(src, dest, options);
89
89
  }
90
+ function move(src, dest, options) {
91
+ fs.moveSync(src, dest, options);
92
+ }
90
93
  function writeJson(path3, json) {
91
94
  return write(path3, JSON.stringify(json, null, 2));
92
95
  }
@@ -120,20 +123,20 @@ var OptionHolder = {
120
123
  input: {},
121
124
  keyholderMap: {},
122
125
  templateDirNames: ["key"],
123
- iosBundleIdentifier: "",
124
- androidPackageName: "",
125
126
  projectDir: "",
126
127
  outputOfInitDir: "",
127
128
  keyDir: "",
128
129
  keyholderFilePath: "",
129
130
  versionName: "",
130
- versionCode: ""
131
+ versionCode: "",
132
+ tempDir: ""
131
133
  };
132
134
 
133
135
  // src/util/logger.ts
134
136
  import chalk from "chalk";
135
137
  var logger = {
136
138
  success: (...msg) => console.log(chalk.green(`\u2714 ${msg.join(", ")}`)),
139
+ done: (...msg) => console.log(chalk.green(`\u2714 ${msg.join(", ")} - Done`)),
137
140
  error: (...msg) => console.log(chalk.red(`\u2716 ${msg.join(", ")}`)),
138
141
  warn: (...msg) => console.log(chalk.yellow(`\u26A0 ${msg.join(", ")}`)),
139
142
  info: (...msg) => console.log(chalk.cyan(`\u2139 ${msg.join(", ")}`)),
@@ -143,12 +146,105 @@ ${msg.join(", ")}`)),
143
146
  };
144
147
 
145
148
  // src/command/init.ts
146
- import chalk2 from "chalk";
149
+ import chalk3 from "chalk";
147
150
  import { input } from "@inquirer/prompts";
148
151
 
149
- // src/util/EnvUtil.ts
150
- var isDev = process.env.NODE_ENV === "development";
151
- var isWin = process.platform.startsWith("win");
152
+ // src/util/input/InqueryInputs.ts
153
+ import { select } from "@inquirer/prompts";
154
+ import { is } from "@mj-studio/js-util";
155
+ import chalk2 from "chalk";
156
+ var InqueryInputs = {
157
+ platform: async () => {
158
+ if (!OptionHolder.input.platform) {
159
+ OptionHolder.input.platform = await select({
160
+ message: constructInquirerFormattedMessage({
161
+ name: "Platform",
162
+ explanation: `Platform to run action`
163
+ }),
164
+ choices: [
165
+ { name: "ios", value: "ios", description: "Release ios" },
166
+ { name: "android", value: "android", description: "Release android" }
167
+ ]
168
+ });
169
+ }
170
+ },
171
+ podinstall: async () => {
172
+ if (OptionHolder.input.platform === "ios") {
173
+ OptionHolder.input.pod = await select({
174
+ message: constructInquirerFormattedMessage({
175
+ name: "Install CocoaPods?"
176
+ }),
177
+ choices: [
178
+ { name: "yes", value: true, description: "Install pods before release" },
179
+ { name: "no", value: false, description: "Skip pods install" }
180
+ ]
181
+ });
182
+ }
183
+ },
184
+ androidBuildOutput: async () => {
185
+ if (OptionHolder.input.platform === "android") {
186
+ if (!OptionHolder.input.androidOutput) {
187
+ OptionHolder.input.androidOutput = await select({
188
+ message: constructInquirerFormattedMessage({
189
+ name: "Android output artifact type"
190
+ }),
191
+ choices: [
192
+ { name: "aab", value: "aab", description: "Build as App bundle" },
193
+ { name: "apk", value: "apk", description: "Build as APK" }
194
+ ]
195
+ });
196
+ }
197
+ }
198
+ },
199
+ uploadMetadata: async () => {
200
+ if (!is.boolean(OptionHolder.input.uploadMetadata)) {
201
+ OptionHolder.input.uploadMetadata = await select({
202
+ message: constructInquirerFormattedMessage({
203
+ name: "Upload Metadata(screenshot, title, description, ...)"
204
+ }),
205
+ choices: [
206
+ { name: "yes", value: true, description: "Upload" },
207
+ { name: "no", value: false, description: "Don't Upload" }
208
+ ]
209
+ });
210
+ }
211
+ }
212
+ };
213
+ function constructInquirerFormattedMessage({
214
+ example,
215
+ explanation,
216
+ referUrl,
217
+ name
218
+ }) {
219
+ let ret = `${chalk2.bold(name)}`;
220
+ if (explanation) {
221
+ ret += `
222
+
223
+ ${chalk2.italic.blue(explanation)}`;
224
+ }
225
+ if (example) {
226
+ ret += `
227
+ ${chalk2.italic(`${chalk2.greenBright("example :")} ${example}`)}`;
228
+ }
229
+ if (referUrl) {
230
+ ret += `
231
+ ${chalk2.italic(`${chalk2.greenBright("references :")} ${chalk2.underline.blue(referUrl)}`)}`;
232
+ }
233
+ if (explanation) {
234
+ ret += "\n";
235
+ if (example || referUrl) {
236
+ ret += "\n";
237
+ }
238
+ }
239
+ return ret;
240
+ }
241
+
242
+ // src/util/setup/hydrateKeyholderFileValues.ts
243
+ async function hydrateKeyholderFileValues() {
244
+ if (exist(OptionHolder.keyholderFilePath)) {
245
+ Object.assign(OptionHolder.keyholderMap, readJsonSlow(OptionHolder.keyholderFilePath));
246
+ }
247
+ }
152
248
 
153
249
  // src/util/throwError.ts
154
250
  function throwError(message, e) {
@@ -160,7 +256,7 @@ function throwError(message, e) {
160
256
  process.exit(1);
161
257
  }
162
258
 
163
- // src/util/VersionUtil.ts
259
+ // src/util/setup/VersionUtil.ts
164
260
  import semver from "semver";
165
261
  async function parseBinaryVersions() {
166
262
  const files = readdir(OptionHolder.projectDir);
@@ -257,73 +353,151 @@ async function injectBinaryVersions({
257
353
  }
258
354
  }
259
355
 
260
- // src/util/setup/promptCommonInputs.ts
261
- async function promptCommonInputs() {
356
+ // src/util/EnvUtil.ts
357
+ var isDev = process.env.NODE_ENV === "development";
358
+ var isWin = process.platform.startsWith("win");
359
+
360
+ // src/util/setup/setup.ts
361
+ async function setup() {
362
+ await setupBasicOptions();
363
+ await hydrateKeyholderFileValues();
364
+ await parseBinaryVersions();
365
+ }
366
+ async function setupBasicOptions() {
262
367
  OptionHolder.projectDir = isDev ? resolve2("example") : resolve2(".");
263
368
  OptionHolder.outputOfInitDir = resolve2(OptionHolder.projectDir, "expo-release-it");
264
369
  OptionHolder.keyDir = resolve2(OptionHolder.outputOfInitDir, "key");
265
370
  OptionHolder.keyholderFilePath = resolve2(OptionHolder.outputOfInitDir, "keyholder.json");
371
+ OptionHolder.tempDir = resolve2(OptionHolder.outputOfInitDir, ".temp");
266
372
  }
267
373
 
268
374
  // src/command/init.ts
269
375
  async function init({ options }) {
270
376
  Object.assign(OptionHolder.input, options);
271
- logger.info(`${chalk2.inverse("expo-release-it")} Initialization Get Started`);
272
- await promptCommonInputs();
273
- await parseBinaryVersions();
377
+ logger.info(`${chalk3.inverse("expo-release-it")} Initialization Get Started`);
378
+ await setup();
274
379
  await promptInputs();
275
380
  await copyTemplates();
276
- logger.info(`${chalk2.inverse("expo-release-it")} Initialization Done`);
381
+ logger.success(
382
+ `${chalk3.inverse("expo-release-it")} Initialization Done. Check out generated 'expo-release-it' directory.`
383
+ );
277
384
  }
278
385
  async function promptInputs() {
279
- OptionHolder.iosBundleIdentifier = OptionHolder.keyholderMap.ios_app_identifier = await input({
280
- message: "IOS Bundle Identifier",
386
+ OptionHolder.keyholderMap.ios_app_identifier = await input({
387
+ message: constructInquirerFormattedMessage({
388
+ name: "iOS Bundle Identifier",
389
+ explanation: `The bundle identifier for your iOS standalone app. You make it up, but it needs to be unique on the App Store.
390
+ This means 'CFBundleIdentifier' in info.plist and 'ios.bundleIdentifier' in expo app config`,
391
+ example: "com.companyname.appname",
392
+ referUrl: "https://docs.expo.dev/versions/latest/config/app/#bundleidentifier"
393
+ }),
281
394
  required: true,
282
- default: isDev ? "ios_app_identifier" : void 0
283
- });
284
- OptionHolder.keyholderMap.ios_app_store_connect_team_id = await input({
285
- message: "IOS App Store Connect Team Id",
286
- default: isDev ? "ios_app_store_connect_team_id" : void 0
395
+ default: OptionHolder.keyholderMap.ios_app_identifier
287
396
  });
288
397
  OptionHolder.keyholderMap.ios_developer_team_id = await input({
289
- message: "IOS Developer Portal Team Id",
290
- default: isDev ? "ios_developer_team_id" : void 0
398
+ message: constructInquirerFormattedMessage({
399
+ name: "IOS Developer Team Id",
400
+ explanation: `A unique alphanumeric string that identifies your Apple Developer Team`,
401
+ example: "8A51ABC4H7",
402
+ referUrl: "https://developer.apple.com/account"
403
+ }),
404
+ required: true,
405
+ default: OptionHolder.keyholderMap.ios_developer_team_id
291
406
  });
292
407
  OptionHolder.keyholderMap.ios_match_git_url = await input({
293
- message: "IOS Match Key Storage Github URL",
294
- default: isDev ? "ios_match_git_url" : void 0
408
+ message: constructInquirerFormattedMessage({
409
+ name: "iOS Fastlane Match Github URL",
410
+ explanation: `The URL of the private Git repository where code signing certificates and profiles are stored for Fastlane Match.`,
411
+ example: "https://github.com/yourname/your-certificates-repo.git",
412
+ referUrl: "https://docs.fastlane.tools/actions/match/"
413
+ }),
414
+ required: true,
415
+ default: OptionHolder.keyholderMap.ios_match_git_url
295
416
  });
296
417
  OptionHolder.keyholderMap.ios_match_password = await input({
297
- message: "IOS Match Password",
298
- default: isDev ? "ios_match_password" : void 0
418
+ message: constructInquirerFormattedMessage({
419
+ name: "iOS Fastlane Match Passphrase",
420
+ explanation: `When running match for the first time on a new machine, it will ask you for the passphrase for the Git repository. This is an additional layer of security: each of the files will be encrypted using openssl. Make sure to remember the password, as you'll need it when you run match on a different machine.`,
421
+ example: "yourpassphrase",
422
+ referUrl: "https://docs.fastlane.tools/actions/match/"
423
+ }),
424
+ required: true,
425
+ default: OptionHolder.keyholderMap.ios_match_password
299
426
  });
300
427
  OptionHolder.keyholderMap.ios_app_store_connect_api_key_id = await input({
301
- message: "IOS App Store Connect Api Key Id",
302
- default: isDev ? "ios_app_store_connect_api_key_id" : void 0
428
+ message: constructInquirerFormattedMessage({
429
+ name: "iOS App Store Connect API Key ID",
430
+ explanation: `ID of App Store Connect API key, used for authentication with Apple\u2019s API.
431
+ App Store Connect API Key can be issued in ${chalk3.underline.blue("https://appstoreconnect.apple.com/access/integrations/api")}`,
432
+ example: "1A2BC3DE4F",
433
+ referUrl: "https://appstoreconnect.apple.com/access/integrations/api"
434
+ }),
435
+ required: true,
436
+ default: OptionHolder.keyholderMap.ios_app_store_connect_api_key_id
303
437
  });
304
438
  OptionHolder.keyholderMap.ios_app_store_connect_api_key_issuer_id = await input({
305
- message: "IOS App Store Connect Api Key Issuer Id",
306
- default: isDev ? "ios_app_store_connect_api_key_issuer_id" : void 0
439
+ message: constructInquirerFormattedMessage({
440
+ name: "iOS App Store Connect API Key Issuer ID",
441
+ explanation: `Identifies the issuer who created the authentication token.
442
+ App Store Connect API Key can be issued in ${chalk3.underline.blue("https://appstoreconnect.apple.com/access/integrations/api")}`,
443
+ example: "abcd1234-5678-90ef-ghij-1234567890kl",
444
+ referUrl: "https://appstoreconnect.apple.com/access/integrations/api"
445
+ }),
446
+ required: true,
447
+ default: OptionHolder.keyholderMap.ios_app_store_connect_api_key_issuer_id
307
448
  });
308
449
  OptionHolder.keyholderMap.ios_xcode_project_target = await input({
309
- message: "IOS Xcode Target Name",
310
- default: "app"
450
+ message: constructInquirerFormattedMessage({
451
+ name: "iOS App Target Name",
452
+ explanation: `A target in Xcode defines a product to build, including app, extension, or framework, with its own settings and build instructions.
453
+ Usually, this is equivalent to {{name}}.xcodeproj in your ios directory`,
454
+ example: "app"
455
+ }),
456
+ required: true,
457
+ default: OptionHolder.keyholderMap.ios_xcode_project_target || "app"
311
458
  });
312
- OptionHolder.androidPackageName = OptionHolder.keyholderMap.android_package_name = await input({
313
- message: "Android Package Name",
314
- default: isDev ? "android_package_name" : void 0
459
+ OptionHolder.keyholderMap.android_package_name = await input({
460
+ message: constructInquirerFormattedMessage({
461
+ name: "Android Package Name",
462
+ explanation: `The package name for your Android standalone app. You make it up, but it needs to be unique on the Play Store.`,
463
+ example: "com.companyname.appname",
464
+ referUrl: "https://docs.expo.dev/versions/latest/config/app/#package"
465
+ }),
466
+ required: true,
467
+ default: OptionHolder.keyholderMap.android_package_name
315
468
  });
316
469
  OptionHolder.keyholderMap.android_keystore_store_password = await input({
317
- message: "Android Keystore Store Password",
318
- default: isDev ? "android_keystore_store_password" : void 0
470
+ message: constructInquirerFormattedMessage({
471
+ name: "Android Keystore Store Password",
472
+ explanation: `Storage password used in android release keystore
473
+ You can issue your android release keystore following this guide: ${chalk3.underline.blue("https://developer.android.com/studio/publish/app-signing.html#generate-key")}
474
+ Or, you can do same thing with CLI: ${chalk3.underline.blue("https://gist.github.com/henriquemenezes/70feb8fff20a19a65346e48786bedb8f")}`,
475
+ referUrl: "https://developer.android.com/studio/publish/app-signing.html#generate-key"
476
+ }),
477
+ required: true,
478
+ default: OptionHolder.keyholderMap.android_keystore_store_password
319
479
  });
320
480
  OptionHolder.keyholderMap.android_keystore_key_alias = await input({
321
- message: "Android Keystore Key Alias",
322
- default: isDev ? "android_keystore_key_alias" : void 0
481
+ message: constructInquirerFormattedMessage({
482
+ name: "Android Keystore Key Alias",
483
+ explanation: `Key alias used in android release keystore
484
+ You can issue your android release keystore following this guide: ${chalk3.underline.blue("https://developer.android.com/studio/publish/app-signing.html#generate-key")}
485
+ Or, you can do same thing with CLI: ${chalk3.underline.blue("https://gist.github.com/henriquemenezes/70feb8fff20a19a65346e48786bedb8f")}`,
486
+ referUrl: "https://developer.android.com/studio/publish/app-signing.html#generate-key"
487
+ }),
488
+ required: true,
489
+ default: OptionHolder.keyholderMap.android_keystore_key_alias
323
490
  });
324
491
  OptionHolder.keyholderMap.android_keystore_key_password = await input({
325
- message: "Android Keystore Key Password",
326
- default: isDev ? "android_keystore_key_password" : void 0
492
+ message: constructInquirerFormattedMessage({
493
+ name: "Android Keystore Key Password",
494
+ explanation: `Key password used in android release keystore
495
+ You can issue your android release keystore following this guide: ${chalk3.underline.blue("https://developer.android.com/studio/publish/app-signing.html#generate-key")}
496
+ Or, you can do same thing with CLI: ${chalk3.underline.blue("https://gist.github.com/henriquemenezes/70feb8fff20a19a65346e48786bedb8f")}`,
497
+ referUrl: "https://developer.android.com/studio/publish/app-signing.html#generate-key"
498
+ }),
499
+ required: true,
500
+ default: OptionHolder.keyholderMap.android_keystore_key_password
327
501
  });
328
502
  writeJson(OptionHolder.keyholderFilePath, OptionHolder.keyholderMap);
329
503
  }
@@ -340,19 +514,21 @@ async function copyTemplates() {
340
514
 
341
515
  // src/command/bump.ts
342
516
  import semver2 from "semver";
343
- import { $ } from "zx";
344
-
345
- // src/util/hydrateKeyholderFileValues.ts
346
- async function hydrateKeyholderFileValues() {
347
- Object.assign(OptionHolder.keyholderMap, readJsonSlow(OptionHolder.keyholderFilePath));
348
- }
349
517
 
350
- // src/util/setup/setup.ts
351
- async function setup() {
352
- await promptCommonInputs();
353
- await hydrateKeyholderFileValues();
354
- await parseBinaryVersions();
355
- }
518
+ // src/util/setup/execShellScript.ts
519
+ import { execa } from "execa";
520
+ var exe = execa({
521
+ stdin: "pipe",
522
+ stdout: "inherit",
523
+ stderr: "inherit",
524
+ shell: false
525
+ });
526
+ var yesShell = (command, args = [], options) => {
527
+ const subProcess = exe(command, args, { ...options, stdin: "pipe" });
528
+ subProcess.stdin?.write("y\n");
529
+ subProcess.stdin?.end();
530
+ return subProcess;
531
+ };
356
532
 
357
533
  // src/command/bump.ts
358
534
  async function bump({ options }) {
@@ -367,11 +543,10 @@ async function bump({ options }) {
367
543
  logger.success("Binary versions have been bumped in expo config file");
368
544
  }
369
545
  async function preCheck() {
370
- if (isDev) return;
371
546
  try {
372
547
  let hasChange = false;
373
548
  try {
374
- await $`git diff --quiet HEAD`;
549
+ await exe("git", ["diff", "--quiet", "HEAD"]);
375
550
  } catch (_) {
376
551
  hasChange = true;
377
552
  }
@@ -383,9 +558,6 @@ async function preCheck() {
383
558
  }
384
559
  }
385
560
 
386
- // src/util/setup/prepareAndroid.ts
387
- import { spinner, $ as $2 } from "zx";
388
-
389
561
  // src/util/iterateAllFiles.ts
390
562
  async function iterateAllFiles(root, callback) {
391
563
  await go(root);
@@ -402,7 +574,7 @@ async function iterateAllFiles(root, callback) {
402
574
  }
403
575
 
404
576
  // src/util/injectTemplatePlaceHolders.ts
405
- import { path as path2 } from "zx";
577
+ import * as path2 from "node:path";
406
578
  async function injectTemplatePlaceHolders(root) {
407
579
  await iterateAllFiles(root, async (filePath) => {
408
580
  const dirPath = path2.dirname(filePath);
@@ -418,25 +590,40 @@ async function injectTemplatePlaceHolders(root) {
418
590
  });
419
591
  }
420
592
 
593
+ // src/util/spinner.ts
594
+ import { oraPromise } from "ora";
595
+ import chalk4 from "chalk";
596
+ async function spinner(text, promise) {
597
+ return oraPromise(promise, {
598
+ text,
599
+ successText: chalk4.green(`${text} - Done`),
600
+ failText: chalk4.red(`${text} - Failed`)
601
+ });
602
+ }
603
+
421
604
  // src/util/setup/prepareAndroid.ts
422
605
  async function prepareAndroid() {
423
606
  const srcDir = resolve2(OptionHolder.cli.templateDir, "android");
424
607
  const destDir = resolve2(OptionHolder.projectDir, "android");
608
+ await yesShell("expo", ["prebuild", "-p", "android", "--no-install"], {
609
+ cwd: OptionHolder.projectDir
610
+ });
611
+ logger.success("Expo Prebuild - Done");
425
612
  await spinner(
426
- "Prebuild Android",
427
- () => $2`cd ${OptionHolder.projectDir} && expo prebuild -p android --no-install`
613
+ "Inject Templates",
614
+ iterateDir(srcDir, async (file) => {
615
+ const filePath = resolve2(srcDir, file);
616
+ remove(resolve2(destDir, file));
617
+ copy(filePath, resolve2(destDir, file));
618
+ })
428
619
  );
429
- logger.success("expo prebuild");
430
- await iterateDir(srcDir, async (file) => {
431
- const filePath = resolve2(srcDir, file);
432
- remove(resolve2(destDir, file));
433
- copy(filePath, resolve2(destDir, file));
434
- });
435
- logger.success("Inject Android templates");
436
- await replaceAndroidSigningConfig(destDir);
437
- logger.success("Overwrite Android Release Signing Config");
438
- await injectTemplatePlaceHolders(resolve2(destDir, "fastlane"));
439
- logger.success("Hydrate template placeholders");
620
+ await spinner("Overwrite Android Release Signing Config", replaceAndroidSigningConfig(destDir));
621
+ await spinner(
622
+ "Hydrate template placeholders",
623
+ injectTemplatePlaceHolders(resolve2(destDir, "fastlane"))
624
+ );
625
+ await exe({ cwd: destDir })`bundle install`;
626
+ logger.success("Bunder Install - Done");
440
627
  }
441
628
  async function replaceAndroidSigningConfig(destDir) {
442
629
  const buildGradleDirPath = resolve2(destDir, "app");
@@ -463,28 +650,31 @@ async function replaceAndroidSigningConfig(destDir) {
463
650
  }
464
651
 
465
652
  // src/util/setup/prepareIos.ts
466
- import { $ as $3, spinner as spinner2 } from "zx";
467
653
  async function prepareIos() {
468
654
  const srcDir = resolve2(OptionHolder.cli.templateDir, "ios");
469
655
  const destDir = resolve2(OptionHolder.projectDir, "ios");
470
- await spinner2(
471
- "Prebuild iOS",
472
- () => $3`cd ${OptionHolder.projectDir} && expo prebuild -p ios --no-install`
473
- );
474
- logger.success("expo prebuild");
475
- await iterateDir(srcDir, async (file) => {
476
- const filePath = resolve2(srcDir, file);
477
- remove(resolve2(destDir, file));
478
- copy(filePath, resolve2(destDir, file));
656
+ await yesShell("expo", ["prebuild", "-p", "ios", "--no-install"], {
657
+ cwd: OptionHolder.projectDir
479
658
  });
480
- logger.success("Inject iOS templates");
481
- await injectTemplatePlaceHolders(resolve2(destDir, "fastlane"));
482
- logger.success("Hydrate template placeholders");
659
+ logger.success("Expo Prebuild - Done");
660
+ await spinner(
661
+ "Inject templates",
662
+ iterateDir(srcDir, async (file) => {
663
+ const filePath = resolve2(srcDir, file);
664
+ remove(resolve2(destDir, file));
665
+ copy(filePath, resolve2(destDir, file));
666
+ })
667
+ );
668
+ await spinner(
669
+ "Hydrate template placeholders",
670
+ injectTemplatePlaceHolders(resolve2(destDir, "fastlane"))
671
+ );
672
+ await exe({ cwd: destDir })`bundle install`;
673
+ logger.success("Bunder Install - Done");
483
674
  }
484
675
 
485
676
  // src/command/upload.ts
486
- import { spinner as spinner3, $ as $4 } from "zx";
487
- import chalk3 from "chalk";
677
+ import chalk5 from "chalk";
488
678
 
489
679
  // src/util/calculateElapsed.ts
490
680
  import { padZero } from "@mj-studio/js-util";
@@ -495,64 +685,12 @@ function calculateElapsed(t) {
495
685
  return `${minutes}:${padZero(seconds)}`;
496
686
  }
497
687
 
498
- // src/util/input/InqueryInputs.ts
499
- import { select } from "@inquirer/prompts";
500
- import { is } from "@mj-studio/js-util";
501
- var InqueryInputs = {
502
- platform: async () => {
503
- if (!OptionHolder.input.platform) {
504
- OptionHolder.input.platform = await select({
505
- message: "Platform to release",
506
- choices: [
507
- { name: "ios", value: "ios", description: "Release ios" },
508
- { name: "android", value: "android", description: "Release android" }
509
- ]
510
- });
511
- }
512
- },
513
- podinstall: async () => {
514
- if (OptionHolder.input.platform === "ios") {
515
- OptionHolder.input.pod = await select({
516
- message: "Install Cocoapods",
517
- choices: [
518
- { name: "yes", value: true, description: "Install pods before release" },
519
- { name: "no", value: false, description: "Skip pods install" }
520
- ]
521
- });
522
- }
523
- },
524
- androidBuildOutput: async () => {
525
- if (OptionHolder.input.platform === "android") {
526
- if (!OptionHolder.input.androidOutput) {
527
- OptionHolder.input.androidOutput = await select({
528
- message: "Android Output",
529
- choices: [
530
- { name: "aab", value: "aab", description: "Build as App bundle" },
531
- { name: "apk", value: "apk", description: "Build as APK" }
532
- ]
533
- });
534
- }
535
- }
536
- },
537
- uploadMetadata: async () => {
538
- if (!is.boolean(OptionHolder.input.uploadMetadata)) {
539
- OptionHolder.input.uploadMetadata = await select({
540
- message: "Upload Metadata(screenshot, title, description, ...)",
541
- choices: [
542
- { name: "yes", value: true, description: "Upload" },
543
- { name: "no", value: false, description: "Don't Upload" }
544
- ]
545
- });
546
- }
547
- }
548
- };
549
-
550
688
  // src/util/FastlaneOption.ts
551
689
  function getAndroidFastlaneOptions() {
552
690
  return [
691
+ ...Object.entries(OptionHolder.keyholderMap).map(([k, v]) => `${k}:${v}`),
553
692
  `version_name:${OptionHolder.versionName}`,
554
693
  `version_code:${OptionHolder.versionCode}`,
555
- `package_name:${OptionHolder.androidPackageName}`,
556
694
  `upload_metadata:${OptionHolder.input.uploadMetadata}`,
557
695
  `aab_path:${resolve2(OptionHolder.outputOfInitDir, "output", "android", "aab", "app-release.aab")}`,
558
696
  `apk_path:${resolve2(OptionHolder.outputOfInitDir, "output", "android", "apk", "app-release.apk")}`,
@@ -562,13 +700,9 @@ function getAndroidFastlaneOptions() {
562
700
  }
563
701
  function getIosFastlaneOptions() {
564
702
  return [
703
+ ...Object.entries(OptionHolder.keyholderMap).map(([k, v]) => `${k}:${v}`),
565
704
  `version_name:${OptionHolder.versionName}`,
566
705
  `version_code:${OptionHolder.versionCode}`,
567
- `ios_app_identifier:${OptionHolder.iosBundleIdentifier}`,
568
- `ios_xcode_project_target:${OptionHolder.keyholderMap.ios_xcode_project_target}`,
569
- `ios_developer_team_id:${OptionHolder.keyholderMap.ios_developer_team_id}`,
570
- `ios_app_store_connect_api_key_id:${OptionHolder.keyholderMap.ios_app_store_connect_api_key_id}`,
571
- `ios_app_store_connect_api_key_issuer_id:${OptionHolder.keyholderMap.ios_app_store_connect_api_key_issuer_id}`,
572
706
  `api_key_filepath:${resolve2(OptionHolder.keyDir, "ios_testflight_upload_api_key.p8")}`,
573
707
  `upload_metadata:${OptionHolder.input.uploadMetadata}`,
574
708
  `ipa_path:${resolve2(OptionHolder.outputOfInitDir, "output", "ios", "app.ipa")}`,
@@ -587,10 +721,10 @@ async function upload({ options }) {
587
721
  logger.info(`Version: ${OptionHolder.versionName}(${OptionHolder.versionCode})`);
588
722
  if (OptionHolder.input.platform === "ios") {
589
723
  await uploadIos();
590
- logger.success(`iOS Upload ${chalk3.bold.inverse(calculateElapsed(startTime))}`);
724
+ logger.success(`iOS Upload ${chalk5.bold.inverse(calculateElapsed(startTime))}`);
591
725
  } else {
592
726
  await uploadAndroid();
593
- logger.success(`Android Upload ${chalk3.bold.inverse(calculateElapsed(startTime))}`);
727
+ logger.success(`Android Upload ${chalk5.bold.inverse(calculateElapsed(startTime))}`);
594
728
  }
595
729
  }
596
730
  async function promptInputs2() {
@@ -605,45 +739,29 @@ async function promptInputs2() {
605
739
  }
606
740
  async function uploadIos() {
607
741
  const iosDir = resolve2(OptionHolder.projectDir, "ios");
608
- const $$ = $4({
609
- verbose: false,
610
- cwd: iosDir,
611
- env: {
612
- MATCH_PASSWORD: OptionHolder.keyholderMap.ios_match_password
613
- }
614
- });
615
742
  await prepareIos();
616
743
  await fastlane();
617
744
  async function fastlane() {
618
- await spinner3("Bundler Install", () => $$`bundle install`);
619
- logger.success("Bundler Install");
620
- await spinner3("Fastlane", () => $$`bundle exec fastlane upload ${getIosFastlaneOptions()}`);
745
+ await exe({
746
+ cwd: iosDir,
747
+ env: {
748
+ MATCH_PASSWORD: OptionHolder.keyholderMap.ios_match_password
749
+ }
750
+ })`bundle exec fastlane upload ${getIosFastlaneOptions()}`;
621
751
  }
622
752
  }
623
753
  async function uploadAndroid() {
624
- const $$ = $4({
625
- verbose: false,
626
- cwd: resolve2(OptionHolder.projectDir, "android")
627
- });
628
754
  await prepareAndroid();
629
755
  await fastlane();
630
756
  async function fastlane() {
631
- await spinner3("Bundler Install", () => $$`bundle install`);
632
- logger.success("Bundler Install");
633
- try {
634
- await spinner3(
635
- "Fastlane",
636
- () => $$`bundle exec fastlane upload ${getAndroidFastlaneOptions()}`
637
- );
638
- } catch (e) {
639
- throwError("Fastlane failed");
640
- }
757
+ await exe({
758
+ cwd: resolve2(OptionHolder.projectDir, "android")
759
+ })`bundle exec fastlane upload ${getAndroidFastlaneOptions()}`;
641
760
  }
642
761
  }
643
762
 
644
763
  // src/command/submit.ts
645
- import chalk4 from "chalk";
646
- import { spinner as spinner4, $ as $5 } from "zx";
764
+ import chalk6 from "chalk";
647
765
  async function submit({ options }) {
648
766
  Object.assign(OptionHolder.input, options);
649
767
  const startTime = Date.now();
@@ -653,10 +771,10 @@ async function submit({ options }) {
653
771
  logger.info(`Version: ${OptionHolder.versionName}(${OptionHolder.versionCode})`);
654
772
  if (OptionHolder.input.platform === "ios") {
655
773
  await submitIos();
656
- logger.success(`Appstore Review Submitted ${chalk4.bold.inverse(calculateElapsed(startTime))}`);
774
+ logger.success(`Appstore Review Submitted ${chalk6.bold.inverse(calculateElapsed(startTime))}`);
657
775
  } else {
658
776
  await submitAndroid();
659
- logger.success(`Playstore Review Submitted ${chalk4.bold.inverse(calculateElapsed(startTime))}`);
777
+ logger.success(`Playstore Review Submitted ${chalk6.bold.inverse(calculateElapsed(startTime))}`);
660
778
  }
661
779
  }
662
780
  async function promptInputs3() {
@@ -664,37 +782,28 @@ async function promptInputs3() {
664
782
  await InqueryInputs.uploadMetadata();
665
783
  }
666
784
  async function submitAndroid() {
667
- const $$ = $5({
668
- verbose: false,
669
- cwd: resolve2(OptionHolder.projectDir, "android")
670
- });
671
785
  await prepareAndroid();
672
786
  await fastlane();
673
787
  async function fastlane() {
674
- await spinner4("Bundler Install", () => $$`bundle install`);
675
- logger.success("Bundler Install");
676
- await spinner4("Fastlane", () => $$`bundle exec fastlane sumbit ${getIosFastlaneOptions()}`);
788
+ await exe({
789
+ cwd: resolve2(OptionHolder.projectDir, "android")
790
+ })`bundle exec fastlane sumbit ${getIosFastlaneOptions()}`;
677
791
  }
678
792
  }
679
793
  async function submitIos() {
680
794
  const iosDir = resolve2(OptionHolder.projectDir, "ios");
681
- const $$ = $5({
682
- verbose: false,
683
- cwd: iosDir
684
- });
685
795
  await prepareIos();
686
796
  await fastlane();
687
797
  async function fastlane() {
688
- await spinner4("Bundler Install", () => $$`bundle install`);
689
- logger.success("Bundler Install");
690
798
  remove(resolve2(iosDir, ".xcode.env.local"));
691
- await spinner4("Fastlane", () => $$`bundle exec fastlane submit ${getAndroidFastlaneOptions()}`);
799
+ await exe({
800
+ cwd: iosDir
801
+ })`bundle exec fastlane submit ${getAndroidFastlaneOptions()}`;
692
802
  }
693
803
  }
694
804
 
695
805
  // src/command/build.ts
696
- import { spinner as spinner5, $ as $6 } from "zx";
697
- import chalk5 from "chalk";
806
+ import chalk7 from "chalk";
698
807
  async function build({ options }) {
699
808
  Object.assign(OptionHolder.input, options);
700
809
  const startTime = Date.now();
@@ -704,10 +813,10 @@ async function build({ options }) {
704
813
  logger.info(`Version: ${OptionHolder.versionName}(${OptionHolder.versionCode})`);
705
814
  if (OptionHolder.input.platform === "ios") {
706
815
  await buildIos();
707
- logger.success(`iOS Build ${chalk5.bold.inverse(calculateElapsed(startTime))}`);
816
+ logger.success(`iOS Build ${chalk7.bold.inverse(calculateElapsed(startTime))}`);
708
817
  } else {
709
818
  await buildAndroid();
710
- logger.success(`Android Build ${chalk5.bold.inverse(calculateElapsed(startTime))}`);
819
+ logger.success(`Android Build ${chalk7.bold.inverse(calculateElapsed(startTime))}`);
711
820
  }
712
821
  }
713
822
  async function promptInputs4() {
@@ -717,8 +826,7 @@ async function promptInputs4() {
717
826
  }
718
827
  async function buildIos() {
719
828
  const iosDir = resolve2(OptionHolder.projectDir, "ios");
720
- const $$ = $6({
721
- verbose: false,
829
+ const exeEnv = exe({
722
830
  cwd: iosDir,
723
831
  env: {
724
832
  MATCH_PASSWORD: OptionHolder.keyholderMap.ios_match_password
@@ -727,40 +835,38 @@ async function buildIos() {
727
835
  await prepareIos();
728
836
  await fastlane();
729
837
  async function fastlane() {
730
- await spinner5("Bundler Install", () => $$`bundle install`);
731
- logger.success("Bundler Install");
732
838
  if (OptionHolder.input.pod) {
733
- await spinner5("Pod Install", () => $$`bundle exec pod install`);
734
- logger.success("Pod Install");
839
+ await exeEnv("bundle", ["exec", "pod", "install"]);
840
+ logger.done("Pod Install");
735
841
  }
736
842
  remove(resolve2(iosDir, ".xcode.env.local"));
737
- await spinner5("Fastlane", () => $$`bundle exec fastlane build ${getIosFastlaneOptions()}`);
843
+ await exeEnv("bundle", ["exec", "fastlane", "build", ...getIosFastlaneOptions()]);
844
+ logger.success(
845
+ `artifact generated: ${relativePath(OptionHolder.projectDir, resolve2(OptionHolder.outputOfInitDir, "output", "ios", "app.ipa"))}`
846
+ );
738
847
  }
739
848
  }
740
849
  async function buildAndroid() {
741
850
  const androidDir = resolve2(OptionHolder.projectDir, "android");
742
- const $$ = $6({
743
- verbose: false,
851
+ const exeEnv = exe({
744
852
  cwd: androidDir
745
853
  });
746
854
  await prepareAndroid();
747
855
  await fastlane();
748
856
  async function fastlane() {
749
- await spinner5("Bundler Install", () => $$`bundle install`);
750
- logger.success("Bundler Install");
751
857
  let buildOutputDir;
752
858
  let outputDir;
753
859
  if (OptionHolder.input.androidOutput === "aab") {
754
- await spinner5(
860
+ await spinner(
755
861
  "Bundle AAB",
756
- () => $$`${isWin ? "gradle.bat" : "./gradlew"} app:bundleRelease`
862
+ exeEnv(isWin ? "gradle.bat" : "./gradlew", ["app:bundleRelease"])
757
863
  );
758
864
  buildOutputDir = resolve2(androidDir, "app", "build", "outputs", "bundle", "release");
759
865
  outputDir = resolve2(OptionHolder.outputOfInitDir, "output", "android", "aab");
760
866
  } else {
761
- await spinner5(
867
+ await spinner(
762
868
  "Bundle APK",
763
- () => $$`${isWin ? "gradle.bat" : "./gradlew"} app:assembleRelease`
869
+ exeEnv(isWin ? "gradle.bat" : "./gradlew", ["app:assembleRelease"])
764
870
  );
765
871
  buildOutputDir = resolve2(androidDir, "app", "build", "outputs", "apk", "release");
766
872
  outputDir = resolve2(OptionHolder.outputOfInitDir, "output", "android", "apk");
@@ -771,6 +877,72 @@ async function buildAndroid() {
771
877
  }
772
878
  }
773
879
 
880
+ // src/util/generateAppStoreConnectApiKeyFile.ts
881
+ function generateAppStoreConnectApiKeyFile() {
882
+ const json = {
883
+ key: read(resolve2(OptionHolder.keyDir, "ios_testflight_upload_api_key.p8")),
884
+ key_id: OptionHolder.keyholderMap.ios_app_store_connect_api_key_id,
885
+ issuer_id: OptionHolder.keyholderMap.ios_app_store_connect_api_key_issuer_id
886
+ };
887
+ const file = join2(OptionHolder.tempDir, "app_store_connect_api.json");
888
+ writeJson(file, json);
889
+ return file;
890
+ }
891
+
892
+ // src/command/pull.ts
893
+ async function pull({ options }) {
894
+ Object.assign(OptionHolder.input, options);
895
+ await setup();
896
+ await promptInputs5();
897
+ if (OptionHolder.input.platform === "ios") {
898
+ await pullIosMetadata();
899
+ } else {
900
+ await pullAndroidMetadata();
901
+ }
902
+ }
903
+ async function promptInputs5() {
904
+ await InqueryInputs.platform();
905
+ }
906
+ async function pullIosMetadata() {
907
+ await prepareIos();
908
+ const iosDir = join2(OptionHolder.projectDir, "ios");
909
+ const exeEnv = exe({
910
+ cwd: iosDir,
911
+ env: {
912
+ MATCH_PASSWORD: OptionHolder.keyholderMap.ios_match_password
913
+ }
914
+ });
915
+ await exeEnv("bundle", [
916
+ "exec",
917
+ "fastlane",
918
+ "deliver",
919
+ "download_metadata",
920
+ "-f",
921
+ "--api_key_path",
922
+ generateAppStoreConnectApiKeyFile()
923
+ ]);
924
+ await exeEnv("bundle", [
925
+ "exec",
926
+ "fastlane",
927
+ "deliver",
928
+ "download_screenshots",
929
+ "-f",
930
+ "--api_key_path",
931
+ generateAppStoreConnectApiKeyFile()
932
+ ]);
933
+ const metadataSrcDir = join2(iosDir, "fastlane/metadata");
934
+ const screenshotsSrcDir = join2(iosDir, "fastlane/screenshots");
935
+ const metadataDestDir = join2(OptionHolder.outputOfInitDir, "metadata/ios/metadata");
936
+ const screenshotsDestDir = join2(OptionHolder.outputOfInitDir, "metadata/ios/screenshots");
937
+ move(metadataSrcDir, metadataDestDir);
938
+ move(screenshotsSrcDir, screenshotsDestDir);
939
+ }
940
+ async function pullAndroidMetadata() {
941
+ await prepareAndroid();
942
+ const androidDir = join2(OptionHolder.projectDir, "android");
943
+ await exe("bundle", ["exec", "fastlane", "supply", "init"], { cwd: androidDir });
944
+ }
945
+
774
946
  // src/index.ts
775
947
  var platformOption = new Option("-p --platform <platform>", "Platform").choices([
776
948
  "android",
@@ -787,20 +959,43 @@ var run = () => {
787
959
  try {
788
960
  await init({ options });
789
961
  } catch (e) {
790
- logger.error(e);
962
+ throwError("Command Failed");
791
963
  }
792
964
  });
793
965
  program.command("bump").description("Bump binary release patch version with modifying app.json").action(async (options) => {
794
- await bump({ options });
966
+ try {
967
+ await bump({ options });
968
+ } catch (e) {
969
+ throwError("Command Failed");
970
+ }
795
971
  });
796
972
  program.command("build").description("Build binary").addOption(platformOption).addOption(androidBuildOutputOption).action(async (options) => {
797
- await build({ options });
973
+ try {
974
+ await build({ options });
975
+ } catch (e) {
976
+ throwError("Command Failed", e);
977
+ }
798
978
  });
799
979
  program.command("upload").description("Upload artifact to play console internal test track(android) and testflight(ios)").addOption(platformOption).addOption(androidBuildOutputOption).option("-m --uploadMetadata", "Upload store metadatas").action(async (options) => {
800
- await upload({ options });
980
+ try {
981
+ await upload({ options });
982
+ } catch (e) {
983
+ throwError("Command Failed");
984
+ }
801
985
  });
802
986
  program.command("submit").description("Submit Google Play Console & App Store Connect Review with latest testing track").addOption(platformOption).action(async (options) => {
803
- await submit({ options });
987
+ try {
988
+ await submit({ options });
989
+ } catch (e) {
990
+ throwError("Command Failed");
991
+ }
992
+ });
993
+ program.command("pull").description("Sync registered metadatas from stores").addOption(platformOption).action(async (options) => {
994
+ try {
995
+ await pull({ options });
996
+ } catch (e) {
997
+ throwError("Command Failed");
998
+ }
804
999
  });
805
1000
  program.parse();
806
1001
  };