@revopush/code-push-cli 0.0.8-rc.0 → 0.0.8-rc.1

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.
@@ -866,6 +866,230 @@ yargs
866
866
 
867
867
  addCommonConfiguration(yargs);
868
868
  })
869
+ .command("release-expo", "Release an Expo / React Native update to an app deployment", (yargs: yargs.Argv) => {
870
+ isValidCommand = true;
871
+
872
+ yargs
873
+ .usage(USAGE_PREFIX + " release-expo <appName> <platform> [options]")
874
+ .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments
875
+ .example(
876
+ "release-expo MyApp ios",
877
+ 'Releases the Expo-managed iOS project in the current working directory to the "MyApp" app\'s "Staging" deployment'
878
+ )
879
+ .example(
880
+ "release-expo MyApp android -d Production",
881
+ 'Releases the Expo-managed Android project in the current working directory to the "MyApp" app\'s "Production" deployment'
882
+ )
883
+ .option("bundleName", {
884
+ alias: "b",
885
+ default: null,
886
+ demand: false,
887
+ description:
888
+ 'Name of the generated JS bundle file. If unspecified, the standard bundle name will be used, depending on the specified platform: "main.jsbundle" (iOS), "index.android.bundle" (Android) or "index.windows.bundle" (Windows)',
889
+ type: "string",
890
+ })
891
+ .option("deploymentName", {
892
+ alias: "d",
893
+ default: "Staging",
894
+ demand: false,
895
+ description: "Deployment to release the update to",
896
+ type: "string",
897
+ })
898
+ .option("description", {
899
+ alias: "des",
900
+ default: null,
901
+ demand: false,
902
+ description: "Description of the changes made to the app with this release",
903
+ type: "string",
904
+ })
905
+ .option("development", {
906
+ alias: "dev",
907
+ default: false,
908
+ demand: false,
909
+ description: "Specifies whether to generate a dev or release build",
910
+ type: "boolean",
911
+ })
912
+ .option("disabled", {
913
+ alias: "x",
914
+ default: false,
915
+ demand: false,
916
+ description: "Specifies whether this release should be immediately downloadable",
917
+ type: "boolean",
918
+ })
919
+ .option("entryFile", {
920
+ alias: "e",
921
+ default: null,
922
+ demand: false,
923
+ description:
924
+ 'Path to the app\'s entry Javascript file. If omitted, "index.<platform>.js" and then "index.js" will be used (if they exist)',
925
+ type: "string",
926
+ })
927
+ .option("gradleFile", {
928
+ alias: "g",
929
+ default: null,
930
+ demand: false,
931
+ description: "Path to the gradle file which specifies the binary version you want to target this release at (android only).",
932
+ })
933
+ .option("initial", {
934
+ alias: "i",
935
+ default: false,
936
+ demand: false,
937
+ description: "Specifies whether release is initial (base) for given targetBinaryVersion.",
938
+ type: "boolean",
939
+ })
940
+ .option("mandatory", {
941
+ alias: "m",
942
+ default: false,
943
+ demand: false,
944
+ description: "Specifies whether this release should be considered mandatory",
945
+ type: "boolean",
946
+ })
947
+ .option("noDuplicateReleaseError", {
948
+ default: false,
949
+ demand: false,
950
+ description:
951
+ "When this flag is set, releasing a package that is identical to the latest release will produce a warning instead of an error",
952
+ type: "boolean",
953
+ })
954
+ .option("plistFile", {
955
+ alias: "p",
956
+ default: null,
957
+ demand: false,
958
+ description: "Path to the plist file which specifies the binary version you want to target this release at (iOS only).",
959
+ })
960
+ .option("plistFilePrefix", {
961
+ alias: "pre",
962
+ default: null,
963
+ demand: false,
964
+ description: "Prefix to append to the file name when attempting to find your app's Info.plist file (iOS only).",
965
+ })
966
+ .option("rollout", {
967
+ alias: "r",
968
+ default: "100%",
969
+ demand: false,
970
+ description: "Percentage of users this release should be immediately available to",
971
+ type: "string",
972
+ })
973
+ .option("sourcemapOutput", {
974
+ alias: "s",
975
+ default: null,
976
+ demand: false,
977
+ description:
978
+ "Path to where the sourcemap for the resulting bundle should be written. If omitted, a sourcemap will not be generated.",
979
+ type: "string",
980
+ })
981
+ .option("targetBinaryVersion", {
982
+ alias: "t",
983
+ default: null,
984
+ demand: false,
985
+ description:
986
+ 'Semver expression that specifies the binary app version(s) this release is targeting (e.g. 1.1.0, ~1.2.3). If omitted, the release will target the exact version specified in the "Info.plist" (iOS), "build.gradle" (Android) or "Package.appxmanifest" (Windows) files.',
987
+ type: "string",
988
+ })
989
+ .option("outputDir", {
990
+ alias: "o",
991
+ default: null,
992
+ demand: false,
993
+ description:
994
+ "Path to where the bundle and sourcemap should be written. If omitted, a bundle and sourcemap will not be written.",
995
+ type: "string",
996
+ })
997
+ .option("useHermes", {
998
+ alias: "h",
999
+ default: false,
1000
+ demand: false,
1001
+ description: "Enable hermes and bypass automatic checks",
1002
+ type: "boolean",
1003
+ })
1004
+ .option("podFile", {
1005
+ alias: "pod",
1006
+ default: null,
1007
+ demand: false,
1008
+ description: "Path to the cocopods config file (iOS only).",
1009
+ type: "string",
1010
+ })
1011
+ .option("extraHermesFlags", {
1012
+ alias: "hf",
1013
+ default: [],
1014
+ demand: false,
1015
+ description: "Flags that get passed to Hermes, JavaScript to bytecode compiler. Can be specified multiple times.",
1016
+ type: "array",
1017
+ })
1018
+ .option("privateKeyPath", {
1019
+ alias: "k",
1020
+ default: null,
1021
+ demand: false,
1022
+ description: "Path to private key used for code signing.",
1023
+ type: "string",
1024
+ })
1025
+ .option("xcodeProjectFile", {
1026
+ alias: "xp",
1027
+ default: null,
1028
+ demand: false,
1029
+ description: "Path to the Xcode project or project.pbxproj file",
1030
+ type: "string",
1031
+ })
1032
+ .option("xcodeTargetName", {
1033
+ alias: "xt",
1034
+ default: undefined,
1035
+ demand: false,
1036
+ description:
1037
+ "Name of target (PBXNativeTarget) which specifies the binary version you want to target this release at (iOS only)",
1038
+ type: "string",
1039
+ })
1040
+ .option("buildConfigurationName", {
1041
+ alias: "c",
1042
+ default: undefined,
1043
+ demand: false,
1044
+ description:
1045
+ "Name of build configuration which specifies the binary version you want to target this release at. For example, 'Debug' or 'Release' (iOS only)",
1046
+ type: "string",
1047
+ })
1048
+ .option("extraBundlerOption", {
1049
+ alias: "eo",
1050
+ default: [],
1051
+ demand: false,
1052
+ description: "Option that gets passed to react-native bundler. Can be specified multiple times.",
1053
+ type: "array",
1054
+ })
1055
+ .check((argv: any) => {
1056
+ return checkValidReleaseOptions(argv);
1057
+ });
1058
+
1059
+ addCommonConfiguration(yargs);
1060
+ })
1061
+ .command("release-native", "Release a binary update to an app deployment", (yargs: yargs.Argv) => {
1062
+ yargs
1063
+ .usage(USAGE_PREFIX + " release-native <appName> <platform> <targetBinary> [options]")
1064
+ .demand(/*count*/ 3, /*max*/ 3) // Require exactly three non-option arguments
1065
+ .example(
1066
+ "release-native MyApp ios ./app.ipa",
1067
+ 'Releases the React Native iOS project from "./app.ipa" to the "MyApp" app\'s "Staging" deployment'
1068
+ )
1069
+ .example(
1070
+ "release-native MyApp android ./app.apk -d Production",
1071
+ 'Releases the React Native Android project from "./app.apk" to the "MyApp" app\'s "Production" deployment'
1072
+ )
1073
+ .option("deploymentName", {
1074
+ alias: "d",
1075
+ default: "Staging",
1076
+ demand: false,
1077
+ description: "Deployment to release the update to",
1078
+ type: "string",
1079
+ })
1080
+ .option("targetBinaryVersion", {
1081
+ alias: "t",
1082
+ default: null,
1083
+ demand: false,
1084
+ description: "Semver expression that specifies the binary app version(s) this release is targeting (e.g. 1.1.0, ~1.2.3).",
1085
+ type: "string",
1086
+ })
1087
+ .check((argv: any, aliases: { [aliases: string]: string }): any => {
1088
+ return checkValidReleaseOptions(argv);
1089
+ });
1090
+
1091
+ addCommonConfiguration(yargs);
1092
+ })
869
1093
  .command("rollback", "Rollback the latest release for an app deployment", (yargs: yargs.Argv) => {
870
1094
  yargs
871
1095
  .usage(USAGE_PREFIX + " rollback <appName> <deploymentName> [options]")
@@ -1271,6 +1495,61 @@ export function createCommand(): cli.ICommand {
1271
1495
  }
1272
1496
  break;
1273
1497
 
1498
+ case "release-expo":
1499
+ if (arg1 && arg2) {
1500
+ cmd = { type: cli.CommandType.releaseExpo };
1501
+
1502
+ const releaseExpoCommand = <cli.IReleaseReactCommand>cmd;
1503
+
1504
+ releaseExpoCommand.appName = arg1;
1505
+ releaseExpoCommand.platform = arg2;
1506
+
1507
+ releaseExpoCommand.appStoreVersion = argv["targetBinaryVersion"] as any;
1508
+ releaseExpoCommand.bundleName = argv["bundleName"] as any;
1509
+ releaseExpoCommand.deploymentName = argv["deploymentName"] as any;
1510
+ releaseExpoCommand.disabled = argv["disabled"] as any;
1511
+ releaseExpoCommand.description = argv["description"] ? backslash(argv["description"]) : "";
1512
+ releaseExpoCommand.development = argv["development"] as any;
1513
+ releaseExpoCommand.entryFile = argv["entryFile"] as any;
1514
+ releaseExpoCommand.gradleFile = argv["gradleFile"] as any;
1515
+ releaseExpoCommand.mandatory = argv["mandatory"] as any;
1516
+ releaseExpoCommand.initial = argv["initial"] as any;
1517
+ releaseExpoCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any;
1518
+ releaseExpoCommand.plistFile = argv["plistFile"] as any;
1519
+ releaseExpoCommand.plistFilePrefix = argv["plistFilePrefix"] as any;
1520
+ releaseExpoCommand.rollout = getRolloutValue(argv["rollout"] as any);
1521
+ releaseExpoCommand.sourcemapOutput = argv["sourcemapOutput"] as any;
1522
+ releaseExpoCommand.outputDir = argv["outputDir"] as any;
1523
+ releaseExpoCommand.useHermes = argv["useHermes"] as any;
1524
+ releaseExpoCommand.extraHermesFlags = argv["extraHermesFlags"] as any;
1525
+ releaseExpoCommand.podFile = argv["podFile"] as any;
1526
+ releaseExpoCommand.privateKeyPath = argv["privateKeyPath"] as any;
1527
+ releaseExpoCommand.xcodeProjectFile = argv["xcodeProjectFile"] as any;
1528
+ releaseExpoCommand.xcodeTargetName = argv["xcodeTargetName"] as any;
1529
+ releaseExpoCommand.buildConfigurationName = argv["buildConfigurationName"] as any;
1530
+ releaseExpoCommand.extraBundlerOptions = argv["extraBundlerOption"] as any;
1531
+ }
1532
+ break;
1533
+
1534
+ case "release-native":
1535
+ if (arg1 && arg2 && arg3) {
1536
+ cmd = { type: cli.CommandType.releaseNative };
1537
+
1538
+ const releaseBinaryCommand = <cli.IReleaseNativeCommand>cmd;
1539
+
1540
+ releaseBinaryCommand.appName = arg1;
1541
+ releaseBinaryCommand.platform = arg2;
1542
+ releaseBinaryCommand.targetBinary = arg3;
1543
+ releaseBinaryCommand.deploymentName = argv["deploymentName"] as any;
1544
+ releaseBinaryCommand.appStoreVersion = argv["targetBinaryVersion"] as any;
1545
+ releaseBinaryCommand.initial = true;
1546
+ releaseBinaryCommand.disabled = true;
1547
+ releaseBinaryCommand.mandatory = false;
1548
+ // TODO add support for releaseBinaryCommand.bundleName
1549
+ // TODO add support for releaseBinaryCommand.outputDir
1550
+ }
1551
+ break;
1552
+
1274
1553
  case "rollback":
1275
1554
  if (arg1 && arg2) {
1276
1555
  cmd = { type: cli.CommandType.rollback };
@@ -0,0 +1,14 @@
1
+ import * as childProcess from "child_process";
2
+
3
+ export function getExpoCliPath(): string {
4
+ const result = childProcess.spawnSync("node", ["--print", "require.resolve('@expo/cli')"]);
5
+ const cliPath = result.stdout.toString().trim();
6
+
7
+ if (result.status === 0 && cliPath) {
8
+ return cliPath;
9
+ }
10
+
11
+ throw new Error(
12
+ 'Unable to resolve "@expo/cli". Please make sure it is installed in your project (e.g. "npm install --save-dev @expo/cli").'
13
+ );
14
+ }
@@ -28,6 +28,7 @@ import {
28
28
  Session,
29
29
  BaseRelease,
30
30
  } from "./types";
31
+ import { ReactNativePackageInfo } from "./types/rest-definitions";
31
32
 
32
33
  const packageJson = require("../../package.json");
33
34
 
@@ -373,6 +374,62 @@ class AccountManager {
373
374
  });
374
375
  }
375
376
 
377
+ public releaseNative(
378
+ appName: string,
379
+ deploymentName: string,
380
+ filePath: string,
381
+ updateMetadata: ReactNativePackageInfo,
382
+ uploadProgressCallback?: (progress: number) => void
383
+ ): Promise<void> {
384
+ return Promise<void>((resolve, reject, notify) => {
385
+ const request: superagent.Request<any> = superagent.post(
386
+ this._serverUrl + urlEncode([`/apps/${appName}/deployments/${deploymentName}/nativerelease`])
387
+ );
388
+
389
+ this.attachCredentials(request);
390
+
391
+ const file: any = fs.createReadStream(filePath);
392
+ request
393
+ .attach("package", file)
394
+ .field("packageInfo", JSON.stringify(updateMetadata))
395
+ .on("progress", (event: any) => {
396
+ if (uploadProgressCallback && event && event.total > 0) {
397
+ const currentProgress: number = (event.loaded / event.total) * 100;
398
+ uploadProgressCallback(currentProgress);
399
+ }
400
+ })
401
+ .end((err: any, res: superagent.Response) => {
402
+ fs.unlinkSync(filePath);
403
+
404
+ if (err) {
405
+ reject(this.getCodePushError(err, res));
406
+ return;
407
+ }
408
+
409
+ if (res.ok) {
410
+ resolve(<void>null);
411
+ } else {
412
+ let body;
413
+ try {
414
+ body = JSON.parse(res.text);
415
+ } catch (err) {}
416
+
417
+ if (body) {
418
+ reject(<CodePushError>{
419
+ message: body.message,
420
+ statusCode: res && res.status,
421
+ });
422
+ } else {
423
+ reject(<CodePushError>{
424
+ message: res.text,
425
+ statusCode: res && res.status,
426
+ });
427
+ }
428
+ }
429
+ });
430
+ });
431
+ }
432
+
376
433
  public patchRelease(appName: string, deploymentName: string, label: string, updateMetadata: PackageInfo): Promise<void> {
377
434
  updateMetadata.label = label;
378
435
  const requestBody: string = JSON.stringify({ packageInfo: updateMetadata });
@@ -3,7 +3,7 @@ import * as chalk from "chalk";
3
3
  import * as path from "path";
4
4
  import * as childProcess from "child_process";
5
5
  import { coerce, compare, valid } from "semver";
6
- import { downloadBlob, extract, fileDoesNotExistOrIsDirectory } from "./utils/file-utils";
6
+ import { downloadBlob, extractIPA, fileDoesNotExistOrIsDirectory } from "./utils/file-utils";
7
7
  import * as dotenv from "dotenv";
8
8
  import { DotenvParseOutput } from "dotenv";
9
9
  import * as cli from "../script/types/cli";
@@ -60,7 +60,7 @@ export async function takeHermesBaseBytecode(
60
60
  }
61
61
 
62
62
  const baseReleaseArchive = await downloadBlob(bundleBlobUrl, baseReleaseTmpFolder);
63
- await extract(baseReleaseArchive, baseReleaseTmpFolder);
63
+ await extractIPA(baseReleaseArchive, baseReleaseTmpFolder);
64
64
  const baseReleaseBundle = path.join(baseReleaseTmpFolder, path.basename(outputFolder), bundleName);
65
65
 
66
66
  if (!fs.existsSync(baseReleaseBundle)) {
@@ -93,6 +93,8 @@ export async function runHermesEmitBinaryCommand(
93
93
  "-out",
94
94
  path.join(outputFolder, bundleName + ".hbc"),
95
95
  path.join(outputFolder, bundleName),
96
+ "-w",
97
+ "-max-diagnostic-width=80",
96
98
  ...extraHermesFlags,
97
99
  ]);
98
100
 
@@ -36,6 +36,8 @@ export enum CommandType {
36
36
  sessionList,
37
37
  sessionRemove,
38
38
  whoami,
39
+ releaseExpo,
40
+ releaseNative,
39
41
  }
40
42
 
41
43
  export interface ICommand {
@@ -212,6 +214,14 @@ export interface IReleaseReactCommand extends IReleaseBaseCommand {
212
214
  extraBundlerOptions?: string[];
213
215
  }
214
216
 
217
+ export interface IReleaseNativeCommand extends IReleaseBaseCommand {
218
+ platform: string;
219
+ targetBinary: string;
220
+ targetBinaryVersion?: string;
221
+ outputDir?: string;
222
+ bundleName?: string;
223
+ }
224
+
215
225
  export interface IRollbackCommand extends ICommand {
216
226
  appName: string;
217
227
  deploymentName: string;
@@ -3,6 +3,7 @@ import * as path from "path";
3
3
  import * as rimraf from "rimraf";
4
4
  import * as temp from "temp";
5
5
  import * as unzipper from "unzipper";
6
+ import * as AdmZip from "adm-zip";
6
7
 
7
8
  import superagent = require("superagent");
8
9
 
@@ -67,9 +68,14 @@ export async function downloadBlob(url: string, folder: string, filename: string
67
68
  });
68
69
  }
69
70
 
70
- export async function extract(zipPath: string, extractTo: string) {
71
+ export async function extractIPA(zipPath: string, extractTo: string) {
71
72
  const extractStream = unzipper.Extract({ path: extractTo });
72
73
  await new Promise<void>((resolve, reject) => {
73
74
  fs.createReadStream(zipPath).pipe(extractStream).on("close", resolve).on("error", reject);
74
75
  });
75
76
  }
77
+
78
+ export async function extractAPK(zipPath: string, extractTo: string) {
79
+ const zip = new AdmZip(zipPath);
80
+ zip.extractAllTo(extractTo, true);
81
+ }