@revopush/code-push-cli 0.0.7 → 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.
@@ -247,7 +247,13 @@ function deploymentRemove(commandName: string, yargs: yargs.Argv): void {
247
247
  yargs
248
248
  .usage(USAGE_PREFIX + " deployment " + commandName + " <appName> <deploymentName>")
249
249
  .demand(/*count*/ 2, /*max*/ 2) // Require exactly two non-option arguments
250
- .example("deployment " + commandName + " MyApp MyDeployment", 'Removes deployment "MyDeployment" from app "MyApp"');
250
+ .example("deployment " + commandName + " MyApp MyDeployment", 'Removes deployment "MyDeployment" from app "MyApp"')
251
+ .option("force", {
252
+ default: false,
253
+ demand: false,
254
+ description: "Bypass confirmation when removing deployments",
255
+ type: "boolean",
256
+ });
251
257
 
252
258
  addCommonConfiguration(yargs);
253
259
  }
@@ -732,6 +738,13 @@ yargs
732
738
  demand: false,
733
739
  description: "Path to the gradle file which specifies the binary version you want to target this release at (android only).",
734
740
  })
741
+ .option("initial", {
742
+ alias: "i",
743
+ default: false,
744
+ demand: false,
745
+ description: "Specifies whether release is initial (base) for given targetBinaryVersion.",
746
+ type: "boolean",
747
+ })
735
748
  .option("mandatory", {
736
749
  alias: "m",
737
750
  default: false,
@@ -844,10 +857,233 @@ yargs
844
857
  alias: "eo",
845
858
  default: [],
846
859
  demand: false,
860
+ description: "Option that gets passed to react-native bundler. Can be specified multiple times.",
861
+ type: "array",
862
+ })
863
+ .check((argv: any, aliases: { [aliases: string]: string }): any => {
864
+ return checkValidReleaseOptions(argv);
865
+ });
866
+
867
+ addCommonConfiguration(yargs);
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,
847
1044
  description:
848
- "Option that gets passed to react-native bundler. Can be specified multiple times.",
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.",
849
1053
  type: "array",
850
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
+ })
851
1087
  .check((argv: any, aliases: { [aliases: string]: string }): any => {
852
1088
  return checkValidReleaseOptions(argv);
853
1089
  });
@@ -1107,6 +1343,7 @@ export function createCommand(): cli.ICommand {
1107
1343
 
1108
1344
  deploymentRemoveCommand.appName = arg2;
1109
1345
  deploymentRemoveCommand.deploymentName = arg3;
1346
+ deploymentRemoveCommand.isForce = argv["force"] as any;
1110
1347
  }
1111
1348
  break;
1112
1349
 
@@ -1240,6 +1477,7 @@ export function createCommand(): cli.ICommand {
1240
1477
  releaseReactCommand.entryFile = argv["entryFile"] as any;
1241
1478
  releaseReactCommand.gradleFile = argv["gradleFile"] as any;
1242
1479
  releaseReactCommand.mandatory = argv["mandatory"] as any;
1480
+ releaseReactCommand.initial = argv["initial"] as any;
1243
1481
  releaseReactCommand.noDuplicateReleaseError = argv["noDuplicateReleaseError"] as any;
1244
1482
  releaseReactCommand.plistFile = argv["plistFile"] as any;
1245
1483
  releaseReactCommand.plistFilePrefix = argv["plistFilePrefix"] as any;
@@ -1257,6 +1495,61 @@ export function createCommand(): cli.ICommand {
1257
1495
  }
1258
1496
  break;
1259
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
+
1260
1553
  case "rollback":
1261
1554
  if (arg1 && arg2) {
1262
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
+ }
@@ -26,7 +26,9 @@ import {
26
26
  PackageInfo,
27
27
  ServerAccessKey,
28
28
  Session,
29
+ BaseRelease,
29
30
  } from "./types";
31
+ import { ReactNativePackageInfo } from "./types/rest-definitions";
30
32
 
31
33
  const packageJson = require("../../package.json");
32
34
 
@@ -273,6 +275,12 @@ class AccountManager {
273
275
  return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then((res: JsonResponse) => res.body.deployment);
274
276
  }
275
277
 
278
+ public getBaseRelease(appName: string, deploymentName: string, appVerison: string): Promise<BaseRelease> {
279
+ return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/basebundle?appVersion=${appVerison}`])).then(
280
+ (res: JsonResponse) => res.body.basebundle
281
+ );
282
+ }
283
+
276
284
  public renameDeployment(appName: string, oldDeploymentName: string, newDeploymentName: string): Promise<void> {
277
285
  return this.patch(
278
286
  urlEncode([`/apps/${appName}/deployments/${oldDeploymentName}`]),
@@ -300,12 +308,10 @@ class AccountManager {
300
308
  appName: string,
301
309
  deploymentName: string,
302
310
  filePath: string,
303
- targetBinaryVersion: string,
304
311
  updateMetadata: PackageInfo,
305
312
  uploadProgressCallback?: (progress: number) => void
306
313
  ): Promise<void> {
307
314
  return Promise<void>((resolve, reject, notify) => {
308
- updateMetadata.appVersion = targetBinaryVersion;
309
315
  const request: superagent.Request<any> = superagent.post(
310
316
  this._serverUrl + urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`])
311
317
  );
@@ -368,6 +374,62 @@ class AccountManager {
368
374
  });
369
375
  }
370
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
+
371
433
  public patchRelease(appName: string, deploymentName: string, label: string, updateMetadata: PackageInfo): Promise<void> {
372
434
  updateMetadata.label = label;
373
435
  const requestBody: string = JSON.stringify({ packageInfo: updateMetadata });
@@ -569,7 +631,7 @@ class AccountManager {
569
631
 
570
632
  request.set("Accept", `application/vnd.code-push.v${AccountManager.API_VERSION}+json`);
571
633
  request.set("Authorization", `Bearer ${this._accessKey}`);
572
- request.set("X-CodePush-SDK-Version", packageJson.version);
634
+ request.set("X-CodePush-SDK-Version", packageJson.version); // TODO get version differently without require("../../package.json");
573
635
  }
574
636
  }
575
637
 
@@ -3,10 +3,11 @@ 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 { 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";
10
+ import { log, sdk } from "./command-executor";
10
11
 
11
12
  const g2js = require("gradle-to-js/lib/parser");
12
13
 
@@ -47,13 +48,37 @@ export async function getBundleSourceMapOutput(command: cli.IReleaseReactCommand
47
48
  return bundleSourceMapOutput;
48
49
  }
49
50
 
51
+ export async function takeHermesBaseBytecode(
52
+ command: cli.IReleaseReactCommand,
53
+ baseReleaseTmpFolder: string,
54
+ outputFolder: string,
55
+ bundleName: string
56
+ ): Promise<string | null> {
57
+ const { bundleBlobUrl } = await sdk.getBaseRelease(command.appName, command.deploymentName, command.appStoreVersion);
58
+ if (!bundleBlobUrl) {
59
+ return null;
60
+ }
61
+
62
+ const baseReleaseArchive = await downloadBlob(bundleBlobUrl, baseReleaseTmpFolder);
63
+ await extractIPA(baseReleaseArchive, baseReleaseTmpFolder);
64
+ const baseReleaseBundle = path.join(baseReleaseTmpFolder, path.basename(outputFolder), bundleName);
65
+
66
+ if (!fs.existsSync(baseReleaseBundle)) {
67
+ log(chalk.cyan("\nNo base release available...\n"));
68
+ return null;
69
+ }
70
+
71
+ return baseReleaseBundle;
72
+ }
73
+
50
74
  export async function runHermesEmitBinaryCommand(
51
75
  command: cli.IReleaseReactCommand,
52
76
  bundleName: string,
53
77
  outputFolder: string,
54
78
  sourcemapOutputFolder: string,
55
79
  extraHermesFlags: string[],
56
- gradleFile: string
80
+ gradleFile: string,
81
+ baseBytecode?: string
57
82
  ): Promise<void> {
58
83
  const hermesArgs: string[] = [];
59
84
  const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS;
@@ -68,6 +93,8 @@ export async function runHermesEmitBinaryCommand(
68
93
  "-out",
69
94
  path.join(outputFolder, bundleName + ".hbc"),
70
95
  path.join(outputFolder, bundleName),
96
+ "-w",
97
+ "-max-diagnostic-width=80",
71
98
  ...extraHermesFlags,
72
99
  ]);
73
100
 
@@ -75,6 +102,10 @@ export async function runHermesEmitBinaryCommand(
75
102
  hermesArgs.push("-output-source-map");
76
103
  }
77
104
 
105
+ if (baseBytecode) {
106
+ hermesArgs.push("-base-bytecode", baseBytecode);
107
+ }
108
+
78
109
  console.log(chalk.cyan("Converting JS bundle to byte code via Hermes, running command:\n"));
79
110
  const hermesCommand = await getHermesCommand(gradleFile);
80
111
  const hermesProcess = childProcess.spawn(hermesCommand, hermesArgs);
@@ -416,7 +447,7 @@ function getComposeSourceMapsPath(): string {
416
447
  }
417
448
 
418
449
  function getNodeModulesPath(reactNativePath: string): string {
419
- const nodeModulesPath = path.dirname(reactNativePath)
450
+ const nodeModulesPath = path.dirname(reactNativePath);
420
451
  if (directoryExistsSync(nodeModulesPath)) {
421
452
  return nodeModulesPath;
422
453
  }
@@ -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 {
@@ -132,6 +134,7 @@ export interface IDeploymentListCommand extends ICommand {
132
134
  export interface IDeploymentRemoveCommand extends ICommand {
133
135
  appName: string;
134
136
  deploymentName: string;
137
+ isForce?: boolean;
135
138
  }
136
139
 
137
140
  export interface IDeploymentRenameCommand extends ICommand {
@@ -156,6 +159,7 @@ export interface IPackageInfo {
156
159
  disabled?: boolean;
157
160
  mandatory?: boolean;
158
161
  rollout?: number;
162
+ initial?: boolean;
159
163
  }
160
164
 
161
165
  export interface IPatchCommand extends ICommand, IPackageInfo {
@@ -190,6 +194,7 @@ export interface IReleaseCommand extends IReleaseBaseCommand {
190
194
  }
191
195
 
192
196
  export interface IReleaseReactCommand extends IReleaseBaseCommand {
197
+ package?: string;
193
198
  bundleName?: string;
194
199
  development?: boolean;
195
200
  entryFile?: string;
@@ -209,6 +214,14 @@ export interface IReleaseReactCommand extends IReleaseBaseCommand {
209
214
  extraBundlerOptions?: string[];
210
215
  }
211
216
 
217
+ export interface IReleaseNativeCommand extends IReleaseBaseCommand {
218
+ platform: string;
219
+ targetBinary: string;
220
+ targetBinaryVersion?: string;
221
+ outputDir?: string;
222
+ bundleName?: string;
223
+ }
224
+
212
225
  export interface IRollbackCommand extends ICommand {
213
226
  appName: string;
214
227
  deploymentName: string;
@@ -49,11 +49,18 @@ export interface PackageInfo {
49
49
  description?: string;
50
50
  isDisabled?: boolean;
51
51
  isMandatory?: boolean;
52
+ isInitial?: boolean;
52
53
  /*generated*/ label?: string;
53
54
  /*generated*/ packageHash?: string;
54
55
  rollout?: number;
55
56
  }
56
57
 
58
+ /*inout*/
59
+ export interface ReactNativePackageInfo extends PackageInfo {
60
+ bundleName?: string;
61
+ outputDir?: string;
62
+ }
63
+
57
64
  /*out*/
58
65
  export interface UpdateCheckResponse extends PackageInfo {
59
66
  target_binary_range?: string;
@@ -126,6 +133,11 @@ export interface Deployment {
126
133
  /*generated*/ package?: Package;
127
134
  }
128
135
 
136
+ /*inout*/
137
+ export interface BaseRelease {
138
+ bundleBlobUrl?: string;
139
+ }
140
+
129
141
  /*out*/
130
142
  export interface BlobInfo {
131
143
  size: number;
package/script/types.ts CHANGED
@@ -13,6 +13,7 @@ export {
13
13
  PackageInfo,
14
14
  AccessKey as ServerAccessKey,
15
15
  UpdateMetrics,
16
+ BaseRelease,
16
17
  } from "../script/types/rest-definitions";
17
18
 
18
19
  export interface CodePushError {
@@ -2,6 +2,10 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as rimraf from "rimraf";
4
4
  import * as temp from "temp";
5
+ import * as unzipper from "unzipper";
6
+ import * as AdmZip from "adm-zip";
7
+
8
+ import superagent = require("superagent");
5
9
 
6
10
  export function isBinaryOrZip(path: string): boolean {
7
11
  return path.search(/\.zip$/i) !== -1 || path.search(/\.apk$/i) !== -1 || path.search(/\.ipa$/i) !== -1;
@@ -17,7 +21,7 @@ export function fileExists(file: string): boolean {
17
21
  } catch (e) {
18
22
  return false;
19
23
  }
20
- };
24
+ }
21
25
 
22
26
  export function copyFileToTmpDir(filePath: string): string {
23
27
  if (!isDirectory(filePath)) {
@@ -44,3 +48,34 @@ export function normalizePath(filePath: string): string {
44
48
  //replace all backslashes coming from cli running on windows machines by slashes
45
49
  return filePath.replace(/\\/g, "/");
46
50
  }
51
+
52
+ export async function downloadBlob(url: string, folder: string, filename: string = "blob.zip"): Promise<string> {
53
+ const destination = path.join(folder, filename);
54
+ const writeStream = fs.createWriteStream(destination);
55
+
56
+ return new Promise((resolve, reject) => {
57
+ writeStream.on("finish", () => resolve(destination));
58
+ writeStream.on("error", reject);
59
+
60
+ superagent
61
+ .get(url)
62
+ .ok((res) => res.status < 400)
63
+ .on("error", (err) => {
64
+ writeStream.destroy();
65
+ reject(err);
66
+ })
67
+ .pipe(writeStream);
68
+ });
69
+ }
70
+
71
+ export async function extractIPA(zipPath: string, extractTo: string) {
72
+ const extractStream = unzipper.Extract({ path: extractTo });
73
+ await new Promise<void>((resolve, reject) => {
74
+ fs.createReadStream(zipPath).pipe(extractStream).on("close", resolve).on("error", reject);
75
+ });
76
+ }
77
+
78
+ export async function extractAPK(zipPath: string, extractTo: string) {
79
+ const zip = new AdmZip(zipPath);
80
+ zip.extractAllTo(extractTo, true);
81
+ }