expo-release-it 0.0.6 → 0.0.8

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.
@@ -0,0 +1,81 @@
1
+ import { select } from '@inquirer/prompts';
2
+ import { prepareAndroid } from '../util/setup/prepareAndroid';
3
+ import { setup } from '../util/setup/setup';
4
+ import { prepareIos } from '../util/setup/prepareIos';
5
+ import { logger } from '../util/logger';
6
+ import { OptionHolder } from '../util/OptionHolder';
7
+ import { spinner, $ } from 'zx';
8
+ import { resolve, remove } from '../util/FileUtil';
9
+ import chalk from 'chalk';
10
+ import { calculateElapsed } from '../util/calculateElapsed';
11
+ import { isWin } from '../util/EnvUtil';
12
+ export async function build({ options }) {
13
+ Object.assign(OptionHolder.build, options);
14
+ const startTime = Date.now();
15
+ await setup();
16
+ await promptInputs();
17
+ logger.info('Build Started');
18
+ logger.info(`Version: ${OptionHolder.versionName}(${OptionHolder.versionCode})`);
19
+ if (OptionHolder.build.platform === 'ios') {
20
+ await buildIos();
21
+ logger.success(`iOS Build ${chalk.bold.inverse(calculateElapsed(startTime))}`);
22
+ }
23
+ else {
24
+ await buildAndroid();
25
+ logger.success(`Android Build ${chalk.bold.inverse(calculateElapsed(startTime))}`);
26
+ }
27
+ }
28
+ async function promptInputs() {
29
+ if (!OptionHolder.build.platform) {
30
+ OptionHolder.build.platform = await select({
31
+ message: 'Platform to release',
32
+ choices: [
33
+ { name: 'ios', value: 'ios', description: 'Release ios' },
34
+ { name: 'android', value: 'android', description: 'Release android' },
35
+ ],
36
+ });
37
+ }
38
+ if (OptionHolder.build.platform === 'ios') {
39
+ OptionHolder.build.pod = await select({
40
+ message: 'Install Cocoapods',
41
+ choices: [
42
+ { name: 'yes', value: true, description: 'Install pods before release' },
43
+ { name: 'no', value: false, description: 'Skip pods install' },
44
+ ],
45
+ });
46
+ }
47
+ }
48
+ async function buildIos() {
49
+ const iosDir = resolve(OptionHolder.rootDir, 'ios');
50
+ const $$ = $({
51
+ verbose: false,
52
+ cwd: iosDir,
53
+ env: {
54
+ MATCH_PASSWORD: OptionHolder.keyholderFileValueMap.ios_match_password,
55
+ },
56
+ });
57
+ await prepareIos();
58
+ await fastlane();
59
+ async function fastlane() {
60
+ await spinner('Bundler Install', () => $$ `bundle install`);
61
+ logger.success('Bundler Install');
62
+ if (OptionHolder.build.pod) {
63
+ await spinner('Pod Install', () => $$ `bundle exec pod install`);
64
+ logger.success('Pod Install');
65
+ }
66
+ remove(resolve(iosDir, '.xcode.env.local'));
67
+ await spinner('Fastlane', () => $$ `bundle exec fastlane build version_name:${OptionHolder.versionName} version_code:${OptionHolder.versionCode}`);
68
+ }
69
+ }
70
+ async function buildAndroid() {
71
+ const $$ = $({
72
+ verbose: false,
73
+ cwd: resolve(OptionHolder.rootDir, 'android'),
74
+ });
75
+ await prepareAndroid();
76
+ await fastlane();
77
+ async function fastlane() {
78
+ await spinner('Bundler Install', () => $$ `bundle install`);
79
+ await spinner('Bundle AAB', () => $$ `${isWin ? 'gradle.bat' : './gradlew'} app:bundleRelease`);
80
+ }
81
+ }
@@ -5,8 +5,9 @@ import { OptionHolder } from '../util/OptionHolder';
5
5
  import semver from 'semver';
6
6
  import { isDev } from '../util/EnvUtil';
7
7
  import { $ } from 'zx';
8
- import { setup } from '../util/setup';
9
- export async function bump({}) {
8
+ import { setup } from '../util/setup/setup';
9
+ export async function bump({ options }) {
10
+ Object.assign(OptionHolder.bump, options);
10
11
  await preCheck();
11
12
  await setup();
12
13
  logger.info(`current version: ${OptionHolder.versionName}(${OptionHolder.versionCode})`);
@@ -14,6 +15,7 @@ export async function bump({}) {
14
15
  const nextVersionCode = Number(OptionHolder.versionCode) + 1 + '';
15
16
  logger.info(`next version: ${nextVersionName}(${nextVersionCode})`);
16
17
  await injectBinaryVersions({ versionName: nextVersionName, versionCode: nextVersionCode });
18
+ logger.success('Binary versions have been bumped in expo config file');
17
19
  }
18
20
  async function preCheck() {
19
21
  if (isDev)
@@ -5,10 +5,11 @@ import { input } from '@inquirer/prompts';
5
5
  import { iterateAllFilesInGeneratedTemplate } from '../util/iterateAllFilesInGeneratedTemplate';
6
6
  import { isDev } from '../util/EnvUtil';
7
7
  import { parseBinaryVersions } from '../util/VersionUtil';
8
- import { promptCommonInputs } from '../util/promptCommonInputs';
8
+ import { promptCommonInputs } from '../util/setup/promptCommonInputs';
9
9
  import { exist, remove, copy, read, write, join, writeJson } from '../util/FileUtil';
10
10
  import { path } from 'zx';
11
- export async function init() {
11
+ export async function init({ options }) {
12
+ Object.assign(OptionHolder.init, options);
12
13
  logger.info(`${chalk.inverse('expo-local-build')}`);
13
14
  await promptCommonInputs();
14
15
  await parseBinaryVersions();
@@ -1,23 +1,21 @@
1
- import { setup } from '../util/setup';
1
+ import { setup } from '../util/setup/setup';
2
2
  import { select } from '@inquirer/prompts';
3
3
  import { logger } from '../util/logger';
4
4
  import { OptionHolder } from '../util/OptionHolder';
5
5
  import chalk from 'chalk';
6
6
  import { calculateElapsed } from '../util/calculateElapsed';
7
7
  import { spinner, $ } from 'zx';
8
- import { prepareAndroid } from '../util/prepareAndroid';
8
+ import { prepareAndroid } from '../util/setup/prepareAndroid';
9
9
  import { remove, resolve } from '../util/FileUtil';
10
- import { prepareIos } from '../util/prepareIos';
11
- const releaseOptions = {
12
- platform: 'ios',
13
- };
14
- export async function submit() {
10
+ import { prepareIos } from '../util/setup/prepareIos';
11
+ export async function submit({ options }) {
12
+ Object.assign(OptionHolder.submit, options);
15
13
  const startTime = Date.now();
16
14
  await setup();
17
15
  await promptInputs();
18
16
  logger.info('Submit Started');
19
17
  logger.info(`Version: ${OptionHolder.versionName}(${OptionHolder.versionCode})`);
20
- if (releaseOptions.platform === 'ios') {
18
+ if (OptionHolder.submit.platform === 'ios') {
21
19
  await submitIos();
22
20
  logger.success(`Appstore Review Submitted ${chalk.bold.inverse(calculateElapsed(startTime))}`);
23
21
  }
@@ -27,13 +25,15 @@ export async function submit() {
27
25
  }
28
26
  }
29
27
  async function promptInputs() {
30
- releaseOptions.platform = await select({
31
- message: 'Platform to release',
32
- choices: [
33
- { name: 'ios', value: 'ios', description: 'Release ios' },
34
- { name: 'android', value: 'android', description: 'Release android' },
35
- ],
36
- });
28
+ if (!OptionHolder.submit.platform) {
29
+ OptionHolder.submit.platform = await select({
30
+ message: 'Platform to submit',
31
+ choices: [
32
+ { name: 'ios', value: 'ios', description: 'Release ios' },
33
+ { name: 'android', value: 'android', description: 'Release android' },
34
+ ],
35
+ });
36
+ }
37
37
  }
38
38
  async function submitAndroid() {
39
39
  const $$ = $({
@@ -0,0 +1,67 @@
1
+ import { select } from '@inquirer/prompts';
2
+ import { prepareAndroid } from '../util/setup/prepareAndroid';
3
+ import { setup } from '../util/setup/setup';
4
+ import { prepareIos } from '../util/setup/prepareIos';
5
+ import { logger } from '../util/logger';
6
+ import { OptionHolder } from '../util/OptionHolder';
7
+ import { spinner, $ } from 'zx';
8
+ import { resolve } from '../util/FileUtil';
9
+ import chalk from 'chalk';
10
+ import { calculateElapsed } from '../util/calculateElapsed';
11
+ export async function upload({ options }) {
12
+ Object.assign(OptionHolder.upload, options);
13
+ const startTime = Date.now();
14
+ await setup();
15
+ await promptInputs();
16
+ logger.info('Build & Upload Started');
17
+ logger.info(`Version: ${OptionHolder.versionName}(${OptionHolder.versionCode})`);
18
+ if (OptionHolder.upload.platform === 'ios') {
19
+ await uploadIos();
20
+ logger.success(`iOS Upload ${chalk.bold.inverse(calculateElapsed(startTime))}`);
21
+ }
22
+ else {
23
+ await uploadAndroid();
24
+ logger.success(`Android Upload ${chalk.bold.inverse(calculateElapsed(startTime))}`);
25
+ }
26
+ }
27
+ async function promptInputs() {
28
+ if (!OptionHolder.upload.platform) {
29
+ OptionHolder.upload.platform = await select({
30
+ message: 'Platform to release',
31
+ choices: [
32
+ { name: 'ios', value: 'ios', description: 'Release ios' },
33
+ { name: 'android', value: 'android', description: 'Release android' },
34
+ ],
35
+ });
36
+ }
37
+ }
38
+ async function uploadIos() {
39
+ const iosDir = resolve(OptionHolder.rootDir, 'ios');
40
+ const $$ = $({
41
+ verbose: false,
42
+ cwd: iosDir,
43
+ env: {
44
+ MATCH_PASSWORD: OptionHolder.keyholderFileValueMap.ios_match_password,
45
+ },
46
+ });
47
+ await prepareIos();
48
+ await fastlane();
49
+ async function fastlane() {
50
+ await spinner('Bundler Install', () => $$ `bundle install`);
51
+ logger.success('Bundler Install');
52
+ await spinner('Fastlane', () => $$ `bundle exec fastlane upload version_name:${OptionHolder.versionName} version_code:${OptionHolder.versionCode}`);
53
+ }
54
+ }
55
+ async function uploadAndroid() {
56
+ const $$ = $({
57
+ verbose: false,
58
+ cwd: resolve(OptionHolder.rootDir, 'android'),
59
+ });
60
+ await prepareAndroid();
61
+ await fastlane();
62
+ async function fastlane() {
63
+ await spinner('Bundler Install', () => $$ `bundle install`);
64
+ logger.success('Bundler Install');
65
+ await spinner('Fastlane Supply', () => $$ `bundle exec fastlane deploy version_name:${OptionHolder.versionName} version_code:${OptionHolder.versionCode}`);
66
+ }
67
+ }
package/build/index.js CHANGED
@@ -1,21 +1,25 @@
1
- import { Command } from 'commander';
1
+ import { Command, Option } from 'commander';
2
2
  import { init } from './command/init';
3
3
  import { OptionHolder } from './util/OptionHolder';
4
4
  import { logger } from './util/logger';
5
5
  import { bump } from './command/bump';
6
- import { release } from './command/release';
6
+ import { upload } from './command/upload';
7
7
  import { submit } from './command/submit';
8
+ const platformOption = new Option('-p --platform <platform>', 'Platform').choices([
9
+ 'android',
10
+ 'ios',
11
+ ]);
8
12
  const program = new Command();
9
13
  program
10
- .name('expo-local-cicd')
11
- .description('generate CLI script to Build & Submit local Expo project')
14
+ .name('expo-release-it')
15
+ .description('Build & Upload & Submit locally for Expo project')
12
16
  .version(OptionHolder.cli.version);
13
17
  program
14
18
  .command('init')
15
19
  .description('Initialize CLI utilities')
16
- .action(async () => {
20
+ .action(async (options) => {
17
21
  try {
18
- await init();
22
+ await init({ options });
19
23
  }
20
24
  catch (e) {
21
25
  logger.error(e);
@@ -31,16 +35,24 @@ program
31
35
  await bump({ options });
32
36
  });
33
37
  program
34
- .command('release')
35
- .description('Build binary & upload to internal test track(android) and testflight(ios)')
36
- .option('-p --platform <platform>', 'Platform', 'ios')
37
- .action(async () => {
38
- await release();
38
+ .command('build')
39
+ .description('Build binary')
40
+ .addOption(platformOption)
41
+ .action(async (options) => {
42
+ await upload({ options });
43
+ });
44
+ program
45
+ .command('upload')
46
+ .description('Upload artifact to play console internal test track(android) and testflight(ios)')
47
+ .addOption(platformOption)
48
+ .action(async (options) => {
49
+ await upload({ options });
39
50
  });
40
51
  program
41
52
  .command('submit')
42
53
  .description('Submit Google Play Console & App Store Connect Review with latest testing track')
43
- .action(async () => {
44
- await submit();
54
+ .addOption(platformOption)
55
+ .action(async (options) => {
56
+ await submit({ options });
45
57
  });
46
58
  program.parse();
@@ -1 +1,2 @@
1
1
  export const isDev = process.env.NODE_ENV === 'development';
2
+ export const isWin = process.platform.startsWith('win');
@@ -1,12 +1,21 @@
1
1
  import pkgJson from '../../package.json';
2
2
  import { fileURLToPath } from 'url';
3
3
  import { resolve } from './FileUtil';
4
+ /**
5
+ * CLI Comprehensive options
6
+ * Each values can be empty by command and timing.
7
+ */
4
8
  export const OptionHolder = {
5
9
  cli: {
6
10
  version: pkgJson.version,
7
11
  rootDir: resolve(fileURLToPath(import.meta.url), '../../..'),
8
12
  templateDir: resolve(fileURLToPath(import.meta.url), '../../../template'),
9
13
  },
14
+ init: {},
15
+ bump: {},
16
+ build: {},
17
+ upload: {},
18
+ submit: {},
10
19
  templateValuePlaceholderMap: {},
11
20
  keyholderFileValueMap: {},
12
21
  templateDirNames: ['fastlane-android', 'fastlane-ios', 'key'],
@@ -0,0 +1,34 @@
1
+ import { spinner, $, path } from 'zx';
2
+ import { resolve, remove, iterateDir, copy, read, write } from '../FileUtil';
3
+ import { OptionHolder } from '../OptionHolder';
4
+ import { logger } from '../logger';
5
+ export async function prepareAndroid() {
6
+ const androidDir = resolve(OptionHolder.rootDir, 'android');
7
+ const fastlaneSrcDir = resolve(OptionHolder.outDir, 'fastlane-android');
8
+ const fastlaneDestDir = resolve(OptionHolder.rootDir, 'android', 'fastlane');
9
+ await spinner('Prebuild Android', () => $ `cd ${OptionHolder.rootDir} && expo prebuild -p android --no-install`);
10
+ remove(fastlaneDestDir);
11
+ await iterateDir(fastlaneSrcDir, async (file) => {
12
+ const filePath = resolve(fastlaneSrcDir, file);
13
+ remove(resolve(androidDir, file));
14
+ copy(filePath, resolve(androidDir, file));
15
+ });
16
+ logger.success('Inject Android Fastlane files');
17
+ await replaceAndroidSigningConfig();
18
+ async function replaceAndroidSigningConfig() {
19
+ const buildGradlePath = resolve(androidDir, 'app', 'build.gradle');
20
+ const { android_keystore_store_password, android_keystore_key_alias, android_keystore_key_password, } = OptionHolder.keyholderFileValueMap;
21
+ const content = read(buildGradlePath).replace(/signingConfigs.*?{.*?debug.*?{.*?}.*?}/s, `
22
+ signingConfigs {
23
+ debug {
24
+ storeFile file('${path.relative(buildGradlePath, resolve(OptionHolder.outDir, 'key', 'android_release_keystore.jks'))}')
25
+ storePassword '${android_keystore_store_password}'
26
+ keyAlias '${android_keystore_key_alias}'
27
+ keyPassword '${android_keystore_key_password}'
28
+ }
29
+ }
30
+ `);
31
+ write(buildGradlePath, content);
32
+ logger.success('Replace Android Signing Config');
33
+ }
34
+ }
@@ -0,0 +1,17 @@
1
+ import { $, spinner } from 'zx';
2
+ import { remove, resolve, iterateDir, copy } from '../FileUtil';
3
+ import { OptionHolder } from '../OptionHolder';
4
+ import { logger } from '../logger';
5
+ export async function prepareIos() {
6
+ const iosDir = resolve(OptionHolder.rootDir, 'ios');
7
+ const fastlaneSrcDir = resolve(OptionHolder.outDir, 'fastlane-ios');
8
+ const fastlaneDestDir = resolve(OptionHolder.rootDir, 'ios', 'fastlane');
9
+ await spinner('Prebuild iOS', () => $ `cd ${OptionHolder.rootDir} && expo prebuild -p ios --no-install`);
10
+ remove(fastlaneDestDir);
11
+ await iterateDir(fastlaneSrcDir, async (file) => {
12
+ const filePath = resolve(fastlaneSrcDir, file);
13
+ remove(resolve(iosDir, file));
14
+ copy(filePath, resolve(iosDir, file));
15
+ });
16
+ logger.success('Inject iOS Fastlane files');
17
+ }
@@ -0,0 +1,22 @@
1
+ import { OptionHolder } from '../OptionHolder';
2
+ import { input } from '@inquirer/prompts';
3
+ import { isDev } from '../EnvUtil';
4
+ import { exist, resolve } from '../FileUtil';
5
+ export async function promptCommonInputs() {
6
+ OptionHolder.rootDir = isDev
7
+ ? resolve('example')
8
+ : resolve(await input({
9
+ message: 'react native project root directory path',
10
+ default: isDev ? 'example' : '.',
11
+ validate: (value) => {
12
+ const p = resolve(value, 'package.json');
13
+ if (!exist(p)) {
14
+ return "package.json hasn\'t been detected. provider valid project root.";
15
+ }
16
+ return true;
17
+ },
18
+ }));
19
+ OptionHolder.outDir = resolve(OptionHolder.rootDir, 'expo-local-build');
20
+ OptionHolder.keyDir = resolve(OptionHolder.outDir, 'key');
21
+ OptionHolder.keyholderFilePath = resolve(OptionHolder.outDir, 'keyholder.json');
22
+ }
@@ -0,0 +1,8 @@
1
+ import { promptCommonInputs } from './promptCommonInputs';
2
+ import { hydrateKeyholderFileValues } from '../hydrateKeyholderFileValues';
3
+ import { parseBinaryVersions } from '../VersionUtil';
4
+ export async function setup() {
5
+ await promptCommonInputs();
6
+ await hydrateKeyholderFileValues();
7
+ await parseBinaryVersions();
8
+ }
package/package.json CHANGED
@@ -2,14 +2,15 @@
2
2
  "name": "expo-release-it",
3
3
  "module": "src/index.ts",
4
4
  "type": "module",
5
- "version": "0.0.6",
5
+ "version": "0.0.8",
6
6
  "license": "MIT",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "dev": "bun run build && NODE_ENV=development bun build/index.js",
10
- "release": "release-it",
10
+ "release": "yarn build && release-it",
11
11
  "android": "expo run:android",
12
- "ios": "expo run:ios"
12
+ "ios": "expo run:ios",
13
+ "t": "tsc --noEmit -p ."
13
14
  },
14
15
  "dependencies": {
15
16
  "@inquirer/prompts": "^7.4.1",
@@ -4,7 +4,7 @@ platform :android do
4
4
  before_all do
5
5
  ENV['SUPPLY_UPLOAD_MAX_RETRIES']='3'
6
6
  end
7
- lane :deploy do |options|
7
+ lane :upload do |options|
8
8
  supply(
9
9
  root_url: "https://androidpublisher.googleapis.com/",
10
10
  package_name: "{{android_package_name}}",
@@ -19,7 +19,7 @@ platform :android do
19
19
  skip_upload_screenshots: true
20
20
  )
21
21
  end
22
- lane :submit_playstore_review do |options|
22
+ lane :submit do |options|
23
23
  supply(
24
24
  root_url: "https://androidpublisher.googleapis.com/",
25
25
  package_name: "{{android_package_name}}",
@@ -10,7 +10,7 @@ platform :ios do
10
10
  Actions.lane_context[:API_KEY] = api_key
11
11
  end
12
12
 
13
- lane :release do |options|
13
+ lane :build do |options|
14
14
  common
15
15
  setup_ci
16
16
  match(
@@ -49,19 +49,45 @@ platform :ios do
49
49
  }
50
50
  }
51
51
  )
52
-
53
- upload_to_testflight(
54
- api_key: lane_context[:API_KEY],
55
- app_version: options[:version_name],
56
- app_identifier: "{{ios_app_identifier}}",
57
- app_platform: "ios",
58
- ipa: "output/output.ipa",
59
- skip_submission: true,
60
- skip_waiting_for_build_processing: true,
61
- )
62
52
  end
63
53
 
64
- lane :submit_appstore_review do |options|
54
+ lane :upload do |options|
55
+ common
56
+ setup_ci
57
+ match(
58
+ type: "appstore",
59
+ app_identifier: "{{ios_app_identifier}}",
60
+ verbose: false,
61
+ team_id: "{{ios_developer_team_id}}"
62
+ )
63
+
64
+ update_code_signing_settings(
65
+ use_automatic_signing: false,
66
+ path: "{{ios_xcode_project_target}}.xcodeproj",
67
+ team_id: ENV["sigh_{{ios_app_identifier}}_appstore_team-id"],
68
+ code_sign_identity: "Apple Distribution"
69
+ )
70
+
71
+ update_project_provisioning(
72
+ xcodeproj: "{{ios_xcode_project_target}}.xcodeproj",
73
+ profile: ENV["sigh_{{ios_app_identifier}}_appstore_profile-path"],
74
+ target_filter: "{{ios_xcode_project_target}}",
75
+ build_configuration: "Release",
76
+ code_signing_identity: "Apple Distribution"
77
+ )
78
+
79
+ upload_to_testflight(
80
+ api_key: lane_context[:API_KEY],
81
+ app_version: options[:version_name],
82
+ app_identifier: "{{ios_app_identifier}}",
83
+ app_platform: "ios",
84
+ ipa: "output/output.ipa",
85
+ skip_submission: true,
86
+ skip_waiting_for_build_processing: true,
87
+ )
88
+ end
89
+
90
+ lane :submit do |options|
65
91
  common
66
92
  upload_to_app_store(
67
93
  api_key: lane_context[:API_KEY],