@revopush/code-push-cli 0.0.5 → 0.0.8-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/script/command-executor.js +116 -74
- package/bin/script/command-parser.js +16 -1
- package/bin/script/management-sdk.js +5 -3
- package/bin/script/react-native-utils.js +207 -16
- package/bin/script/utils/file-utils.js +27 -2
- package/bin/test/management-sdk.js +7 -0
- package/package.json +5 -2
- package/script/command-executor.ts +163 -104
- package/script/command-parser.ts +17 -3
- package/script/management-sdk.ts +8 -3
- package/script/react-native-utils.ts +236 -21
- package/script/types/cli.ts +3 -0
- package/script/types/rest-definitions.ts +12 -0
- package/script/types.ts +1 -0
- package/script/utils/file-utils.ts +30 -1
- package/test/management-sdk.ts +9 -0
- package/test.json +5963 -0
- package/test2.json +5844 -0
|
@@ -3,7 +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, extract, fileDoesNotExistOrIsDirectory } from "./utils/file-utils";
|
|
7
|
+
import * as dotenv from "dotenv";
|
|
8
|
+
import { DotenvParseOutput } from "dotenv";
|
|
9
|
+
import * as cli from "../script/types/cli";
|
|
10
|
+
import { log, sdk } from "./command-executor";
|
|
7
11
|
|
|
8
12
|
const g2js = require("gradle-to-js/lib/parser");
|
|
9
13
|
|
|
@@ -11,12 +15,70 @@ export function isValidVersion(version: string): boolean {
|
|
|
11
15
|
return !!valid(version) || /^\d+\.\d+$/.test(version);
|
|
12
16
|
}
|
|
13
17
|
|
|
18
|
+
export async function getBundleSourceMapOutput(command: cli.IReleaseReactCommand, bundleName: string, sourcemapOutputFolder: string) {
|
|
19
|
+
let bundleSourceMapOutput: string | undefined;
|
|
20
|
+
switch (command.platform) {
|
|
21
|
+
case "android": {
|
|
22
|
+
// see BundleHermesCTask -> resolvePackagerSourceMapFile
|
|
23
|
+
// for Hermes targeted bundles there are 2 source maps: "packager" (metro) and "compiler" (Hermes)
|
|
24
|
+
// Metro bundles use <bundleAssetName>.packager.map notation
|
|
25
|
+
const isHermes = await isHermesEnabled(command, command.platform);
|
|
26
|
+
if (isHermes) {
|
|
27
|
+
bundleSourceMapOutput = path.join(sourcemapOutputFolder, bundleName + ".packager.map");
|
|
28
|
+
} else {
|
|
29
|
+
bundleSourceMapOutput = path.join(sourcemapOutputFolder, bundleName + ".map");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
case "ios": {
|
|
35
|
+
// see react-native-xcode.sh
|
|
36
|
+
// to match js bundle generated by Xcode and by Revopush cli we must respect SOURCEMAP_FILE value
|
|
37
|
+
// because it appears as //# sourceMappingURL value in a js bundle
|
|
38
|
+
const xcodeDotEnvValue = getXcodeDotEnvValue("SOURCEMAP_FILE");
|
|
39
|
+
const sourceMapFilename = xcodeDotEnvValue ? path.basename(xcodeDotEnvValue) : bundleName + ".map";
|
|
40
|
+
|
|
41
|
+
bundleSourceMapOutput = path.join(sourcemapOutputFolder, sourceMapFilename);
|
|
42
|
+
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
default:
|
|
46
|
+
throw new Error('Platform must be either "android", "ios" or "windows".');
|
|
47
|
+
}
|
|
48
|
+
return bundleSourceMapOutput;
|
|
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 extract(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
|
+
|
|
14
74
|
export async function runHermesEmitBinaryCommand(
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
75
|
+
command: cli.IReleaseReactCommand,
|
|
76
|
+
bundleName: string,
|
|
77
|
+
outputFolder: string,
|
|
78
|
+
sourcemapOutputFolder: string,
|
|
79
|
+
extraHermesFlags: string[],
|
|
80
|
+
gradleFile: string,
|
|
81
|
+
baseBytecode?: string
|
|
20
82
|
): Promise<void> {
|
|
21
83
|
const hermesArgs: string[] = [];
|
|
22
84
|
const envNodeArgs: string = process.env.CODE_PUSH_NODE_ARGS;
|
|
@@ -27,16 +89,21 @@ export async function runHermesEmitBinaryCommand(
|
|
|
27
89
|
|
|
28
90
|
Array.prototype.push.apply(hermesArgs, [
|
|
29
91
|
"-emit-binary",
|
|
92
|
+
"-O",
|
|
30
93
|
"-out",
|
|
31
94
|
path.join(outputFolder, bundleName + ".hbc"),
|
|
32
95
|
path.join(outputFolder, bundleName),
|
|
33
96
|
...extraHermesFlags,
|
|
34
97
|
]);
|
|
35
98
|
|
|
36
|
-
if (
|
|
99
|
+
if (sourcemapOutputFolder) {
|
|
37
100
|
hermesArgs.push("-output-source-map");
|
|
38
101
|
}
|
|
39
102
|
|
|
103
|
+
if (baseBytecode) {
|
|
104
|
+
hermesArgs.push("-base-bytecode", baseBytecode);
|
|
105
|
+
}
|
|
106
|
+
|
|
40
107
|
console.log(chalk.cyan("Converting JS bundle to byte code via Hermes, running command:\n"));
|
|
41
108
|
const hermesCommand = await getHermesCommand(gradleFile);
|
|
42
109
|
const hermesProcess = childProcess.spawn(hermesCommand, hermesArgs);
|
|
@@ -72,8 +139,8 @@ export async function runHermesEmitBinaryCommand(
|
|
|
72
139
|
});
|
|
73
140
|
});
|
|
74
141
|
});
|
|
75
|
-
}).then(() => {
|
|
76
|
-
if (!
|
|
142
|
+
}).then(async () => {
|
|
143
|
+
if (!sourcemapOutputFolder) {
|
|
77
144
|
// skip source map compose if source map is not enabled
|
|
78
145
|
return;
|
|
79
146
|
}
|
|
@@ -88,8 +155,33 @@ export async function runHermesEmitBinaryCommand(
|
|
|
88
155
|
throw new Error(`sourcemap file ${jsCompilerSourceMapFile} is not found`);
|
|
89
156
|
}
|
|
90
157
|
|
|
158
|
+
const platformSourceMapOutput = await getBundleSourceMapOutput(command, bundleName, sourcemapOutputFolder);
|
|
91
159
|
return new Promise((resolve, reject) => {
|
|
92
|
-
|
|
160
|
+
let bundleSourceMapOutput = sourcemapOutputFolder;
|
|
161
|
+
let combinedSourceMapOutput = sourcemapOutputFolder;
|
|
162
|
+
|
|
163
|
+
if (!sourcemapOutputFolder.endsWith(".map")) {
|
|
164
|
+
bundleSourceMapOutput = platformSourceMapOutput;
|
|
165
|
+
switch (command.platform) {
|
|
166
|
+
case "android": {
|
|
167
|
+
combinedSourceMapOutput = path.join(sourcemapOutputFolder, bundleName + ".map");
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
case "ios": {
|
|
171
|
+
combinedSourceMapOutput = bundleSourceMapOutput;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
default:
|
|
175
|
+
throw new Error('Platform must be either "android", "ios" or "windows".');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const composeSourceMapsArgs = [
|
|
179
|
+
composeSourceMapsPath,
|
|
180
|
+
bundleSourceMapOutput,
|
|
181
|
+
jsCompilerSourceMapFile,
|
|
182
|
+
"-o",
|
|
183
|
+
combinedSourceMapOutput,
|
|
184
|
+
];
|
|
93
185
|
|
|
94
186
|
// https://github.com/facebook/react-native/blob/master/react.gradle#L211
|
|
95
187
|
// https://github.com/facebook/react-native/blob/master/scripts/react-native-xcode.sh#L178
|
|
@@ -124,6 +216,40 @@ export async function runHermesEmitBinaryCommand(
|
|
|
124
216
|
});
|
|
125
217
|
}
|
|
126
218
|
|
|
219
|
+
export function getXcodeDotEnvValue(key: string): string | undefined {
|
|
220
|
+
const xcodeEnvs = loadEnvAsMap([path.join("ios", ".xcode.env.local"), path.join("ios", ".xcode.env.local")]);
|
|
221
|
+
return xcodeEnvs.get(key);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function getMinifyParams(command: cli.IReleaseReactCommand) {
|
|
225
|
+
const isHermes = await isHermesEnabled(command);
|
|
226
|
+
|
|
227
|
+
switch (command.platform) {
|
|
228
|
+
case "android": {
|
|
229
|
+
// android always explicitly pass --minify true/false
|
|
230
|
+
// TaskConfiguration it.minifyEnabled.set(!isHermesEnabledInThisVariant)
|
|
231
|
+
return ["--minify", !isHermes];
|
|
232
|
+
}
|
|
233
|
+
case "ios": {
|
|
234
|
+
//if [[ $USE_HERMES != false && $DEV == false ]]; then
|
|
235
|
+
// EXTRA_ARGS+=("--minify" "false")
|
|
236
|
+
// fi
|
|
237
|
+
// ios does pass --minify false only if Hermes enables and does pass anything otherwise
|
|
238
|
+
return isHermes ? ["--minify", false] : [];
|
|
239
|
+
}
|
|
240
|
+
default:
|
|
241
|
+
throw new Error('Platform must be either "android", "ios" or "windows".');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export async function isHermesEnabled(command: cli.IReleaseReactCommand, platform: string = command.platform.toLowerCase()) {
|
|
246
|
+
// Check if we have to run hermes to compile JS to Byte Code if Hermes is enabled in Podfile and we're releasing an iOS build
|
|
247
|
+
const isAndroidHermesEnabled = await getAndroidHermesEnabled(command.gradleFile);
|
|
248
|
+
const isIOSHermesEnabled = getiOSHermesEnabled(command.podFile);
|
|
249
|
+
|
|
250
|
+
return command.useHermes || (platform === "android" && isAndroidHermesEnabled) || (platform === "ios" && isIOSHermesEnabled);
|
|
251
|
+
}
|
|
252
|
+
|
|
127
253
|
function parseBuildGradleFile(gradleFile: string) {
|
|
128
254
|
let buildGradlePath: string = path.join("android", "app");
|
|
129
255
|
if (gradleFile) {
|
|
@@ -142,6 +268,47 @@ function parseBuildGradleFile(gradleFile: string) {
|
|
|
142
268
|
});
|
|
143
269
|
}
|
|
144
270
|
|
|
271
|
+
function parseGradlePropertiesFile(gradleFile: string): Record<string, string> {
|
|
272
|
+
let gradlePropsPath: string = path.join("android", "gradle.properties");
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
if (gradleFile) {
|
|
276
|
+
const base = gradleFile;
|
|
277
|
+
const stat = fs.lstatSync(base);
|
|
278
|
+
|
|
279
|
+
if (stat.isDirectory()) {
|
|
280
|
+
if (path.basename(base) === "app") {
|
|
281
|
+
gradlePropsPath = path.join(base, "..", "gradle.properties");
|
|
282
|
+
} else {
|
|
283
|
+
gradlePropsPath = path.join(base, "gradle.properties");
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
gradlePropsPath = path.join(path.dirname(base), "..", "gradle.properties");
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch {}
|
|
290
|
+
|
|
291
|
+
gradlePropsPath = path.normalize(gradlePropsPath);
|
|
292
|
+
|
|
293
|
+
if (fileDoesNotExistOrIsDirectory(gradlePropsPath)) {
|
|
294
|
+
throw new Error(`Unable to find gradle.properties file "${gradlePropsPath}".`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const text = fs.readFileSync(gradlePropsPath, "utf8");
|
|
298
|
+
const props: Record<string, string> = {};
|
|
299
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
300
|
+
const line = rawLine.trim();
|
|
301
|
+
if (!line || line.startsWith("#")) continue;
|
|
302
|
+
const m = line.match(/^([^=\s]+)\s*=\s*(.*)$/);
|
|
303
|
+
if (m) {
|
|
304
|
+
const key = m[1].trim();
|
|
305
|
+
const val = m[2].trim();
|
|
306
|
+
props[key] = val;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return props;
|
|
310
|
+
}
|
|
311
|
+
|
|
145
312
|
async function getHermesCommandFromGradle(gradleFile: string): Promise<string> {
|
|
146
313
|
const buildGradle: any = await parseBuildGradleFile(gradleFile);
|
|
147
314
|
const hermesCommandProperty: any = Array.from(buildGradle["project.ext.react"] || []).find((prop: string) =>
|
|
@@ -154,13 +321,28 @@ async function getHermesCommandFromGradle(gradleFile: string): Promise<string> {
|
|
|
154
321
|
}
|
|
155
322
|
}
|
|
156
323
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
324
|
+
async function getAndroidHermesEnabled(gradleFile: string): Promise<boolean> {
|
|
325
|
+
try {
|
|
326
|
+
const props = parseGradlePropertiesFile(gradleFile);
|
|
327
|
+
if (typeof props.hermesEnabled !== "undefined") {
|
|
328
|
+
const v = String(props.hermesEnabled).trim().toLowerCase();
|
|
329
|
+
if (v === "true") return true;
|
|
330
|
+
if (v === "false") return false;
|
|
331
|
+
}
|
|
332
|
+
} catch {}
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
const buildGradle: any = await parseBuildGradleFile(gradleFile);
|
|
336
|
+
const lines: string[] = Array.from(buildGradle["project.ext.react"] || []);
|
|
337
|
+
if (lines.some((l) => /\benableHermes\s*:\s*true\b/.test(l))) return true;
|
|
338
|
+
if (lines.some((l) => /\benableHermes\s*:\s*false\b/.test(l))) return false;
|
|
339
|
+
} catch {}
|
|
340
|
+
|
|
341
|
+
const rnVersion = coerce(getReactNativeVersion())?.version;
|
|
342
|
+
return rnVersion && compare(rnVersion, "0.70.0") >= 0;
|
|
161
343
|
}
|
|
162
344
|
|
|
163
|
-
|
|
345
|
+
function getiOSHermesEnabled(podFile: string): boolean {
|
|
164
346
|
let podPath = path.join("ios", "Podfile");
|
|
165
347
|
if (podFile) {
|
|
166
348
|
podPath = podFile;
|
|
@@ -171,12 +353,33 @@ export function getiOSHermesEnabled(podFile: string): boolean {
|
|
|
171
353
|
|
|
172
354
|
try {
|
|
173
355
|
const podFileContents = fs.readFileSync(podPath).toString();
|
|
174
|
-
|
|
356
|
+
|
|
357
|
+
const hasTrue = /([^#\n]*:?hermes_enabled(\s+|\n+)?(=>|:)(\s+|\n+)?true)/.test(podFileContents);
|
|
358
|
+
if (hasTrue) return true;
|
|
359
|
+
|
|
360
|
+
const hasFalse = /([^#\n]*:?hermes_enabled(\s+|\n+)?(=>|:)(\s+|\n+)?false)/.test(podFileContents);
|
|
361
|
+
if (hasFalse) return false;
|
|
362
|
+
|
|
363
|
+
const rnVersion = coerce(getReactNativeVersion())?.version;
|
|
364
|
+
return rnVersion && compare(rnVersion, "0.70.0") >= 0;
|
|
175
365
|
} catch (error) {
|
|
176
366
|
throw error;
|
|
177
367
|
}
|
|
178
368
|
}
|
|
179
369
|
|
|
370
|
+
function loadEnvAsMap(envPaths = []): Map<string, string | undefined> {
|
|
371
|
+
const merged: DotenvParseOutput = {};
|
|
372
|
+
|
|
373
|
+
for (const envPath of envPaths) {
|
|
374
|
+
if (fs.existsSync(envPath)) {
|
|
375
|
+
Object.assign(merged, dotenv.parse(fs.readFileSync(envPath))); // later files override earlier ones
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// fallback to process.env for anything missing
|
|
380
|
+
return new Map([...Object.entries(process.env), ...Object.entries(merged)]);
|
|
381
|
+
}
|
|
382
|
+
|
|
180
383
|
function getHermesOSBin(): string {
|
|
181
384
|
switch (process.platform) {
|
|
182
385
|
case "win32":
|
|
@@ -211,7 +414,8 @@ async function getHermesCommand(gradleFile: string): Promise<string> {
|
|
|
211
414
|
}
|
|
212
415
|
};
|
|
213
416
|
// Hermes is bundled with react-native since 0.69
|
|
214
|
-
const
|
|
417
|
+
const reactNativePath = getReactNativePackagePath();
|
|
418
|
+
const bundledHermesEngine = path.join(reactNativePath, "sdks", "hermesc", getHermesOSBin(), getHermesOSExe());
|
|
215
419
|
if (fileExists(bundledHermesEngine)) {
|
|
216
420
|
return bundledHermesEngine;
|
|
217
421
|
}
|
|
@@ -220,12 +424,14 @@ async function getHermesCommand(gradleFile: string): Promise<string> {
|
|
|
220
424
|
if (gradleHermesCommand) {
|
|
221
425
|
return path.join("android", "app", gradleHermesCommand.replace("%OS-BIN%", getHermesOSBin()));
|
|
222
426
|
} else {
|
|
427
|
+
const nodeModulesPath = getNodeModulesPath(reactNativePath);
|
|
428
|
+
|
|
223
429
|
// assume if hermes-engine exists it should be used instead of hermesvm
|
|
224
|
-
const hermesEngine = path.join(
|
|
430
|
+
const hermesEngine = path.join(nodeModulesPath, "hermes-engine", getHermesOSBin(), getHermesOSExe());
|
|
225
431
|
if (fileExists(hermesEngine)) {
|
|
226
432
|
return hermesEngine;
|
|
227
433
|
}
|
|
228
|
-
return path.join(
|
|
434
|
+
return path.join(nodeModulesPath, "hermesvm", getHermesOSBin(), "hermes");
|
|
229
435
|
}
|
|
230
436
|
}
|
|
231
437
|
|
|
@@ -238,7 +444,16 @@ function getComposeSourceMapsPath(): string {
|
|
|
238
444
|
return null;
|
|
239
445
|
}
|
|
240
446
|
|
|
241
|
-
function
|
|
447
|
+
function getNodeModulesPath(reactNativePath: string): string {
|
|
448
|
+
const nodeModulesPath = path.dirname(reactNativePath);
|
|
449
|
+
if (directoryExistsSync(nodeModulesPath)) {
|
|
450
|
+
return nodeModulesPath;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return path.join("node_modules");
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function getReactNativePackagePath(): string {
|
|
242
457
|
const result = childProcess.spawnSync("node", ["--print", "require.resolve('react-native/package.json')"]);
|
|
243
458
|
const packagePath = path.dirname(result.stdout.toString());
|
|
244
459
|
if (result.status === 0 && directoryExistsSync(packagePath)) {
|
|
@@ -280,4 +495,4 @@ export function getReactNativeVersion(): string {
|
|
|
280
495
|
(projectPackageJson.dependencies && projectPackageJson.dependencies["react-native"]) ||
|
|
281
496
|
(projectPackageJson.devDependencies && projectPackageJson.devDependencies["react-native"])
|
|
282
497
|
);
|
|
283
|
-
}
|
|
498
|
+
}
|
package/script/types/cli.ts
CHANGED
|
@@ -132,6 +132,7 @@ export interface IDeploymentListCommand extends ICommand {
|
|
|
132
132
|
export interface IDeploymentRemoveCommand extends ICommand {
|
|
133
133
|
appName: string;
|
|
134
134
|
deploymentName: string;
|
|
135
|
+
isForce?: boolean;
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
export interface IDeploymentRenameCommand extends ICommand {
|
|
@@ -156,6 +157,7 @@ export interface IPackageInfo {
|
|
|
156
157
|
disabled?: boolean;
|
|
157
158
|
mandatory?: boolean;
|
|
158
159
|
rollout?: number;
|
|
160
|
+
initial?: boolean;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
export interface IPatchCommand extends ICommand, IPackageInfo {
|
|
@@ -190,6 +192,7 @@ export interface IReleaseCommand extends IReleaseBaseCommand {
|
|
|
190
192
|
}
|
|
191
193
|
|
|
192
194
|
export interface IReleaseReactCommand extends IReleaseBaseCommand {
|
|
195
|
+
package?: string;
|
|
193
196
|
bundleName?: string;
|
|
194
197
|
development?: boolean;
|
|
195
198
|
entryFile?: 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
|
@@ -2,6 +2,9 @@ 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
|
+
|
|
7
|
+
import superagent = require("superagent");
|
|
5
8
|
|
|
6
9
|
export function isBinaryOrZip(path: string): boolean {
|
|
7
10
|
return path.search(/\.zip$/i) !== -1 || path.search(/\.apk$/i) !== -1 || path.search(/\.ipa$/i) !== -1;
|
|
@@ -17,7 +20,7 @@ export function fileExists(file: string): boolean {
|
|
|
17
20
|
} catch (e) {
|
|
18
21
|
return false;
|
|
19
22
|
}
|
|
20
|
-
}
|
|
23
|
+
}
|
|
21
24
|
|
|
22
25
|
export function copyFileToTmpDir(filePath: string): string {
|
|
23
26
|
if (!isDirectory(filePath)) {
|
|
@@ -44,3 +47,29 @@ export function normalizePath(filePath: string): string {
|
|
|
44
47
|
//replace all backslashes coming from cli running on windows machines by slashes
|
|
45
48
|
return filePath.replace(/\\/g, "/");
|
|
46
49
|
}
|
|
50
|
+
|
|
51
|
+
export async function downloadBlob(url: string, folder: string, filename: string = "blob.zip"): Promise<string> {
|
|
52
|
+
const destination = path.join(folder, filename);
|
|
53
|
+
const writeStream = fs.createWriteStream(destination);
|
|
54
|
+
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
writeStream.on("finish", () => resolve(destination));
|
|
57
|
+
writeStream.on("error", reject);
|
|
58
|
+
|
|
59
|
+
superagent
|
|
60
|
+
.get(url)
|
|
61
|
+
.ok((res) => res.status < 400)
|
|
62
|
+
.on("error", (err) => {
|
|
63
|
+
writeStream.destroy();
|
|
64
|
+
reject(err);
|
|
65
|
+
})
|
|
66
|
+
.pipe(writeStream);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function extract(zipPath: string, extractTo: string) {
|
|
71
|
+
const extractStream = unzipper.Extract({ path: extractTo });
|
|
72
|
+
await new Promise<void>((resolve, reject) => {
|
|
73
|
+
fs.createReadStream(zipPath).pipe(extractStream).on("close", resolve).on("error", reject);
|
|
74
|
+
});
|
|
75
|
+
}
|
package/test/management-sdk.ts
CHANGED
|
@@ -196,6 +196,15 @@ describe("Management SDK", () => {
|
|
|
196
196
|
}, rejectHandler);
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
it("getBaseBundle handles JSON response", (done: Mocha.Done) => {
|
|
200
|
+
mockReturn(JSON.stringify({ basebundle: { bundleBlobUrl: "https://test.test/release.zip" } }), 200, {});
|
|
201
|
+
|
|
202
|
+
manager.getBaseRelease("appName", "deploymentName", "1.2.3").done((obj: any) => {
|
|
203
|
+
assert.ok(obj);
|
|
204
|
+
done();
|
|
205
|
+
}, rejectHandler);
|
|
206
|
+
});
|
|
207
|
+
|
|
199
208
|
it("getDeployments handles JSON response", (done: Mocha.Done) => {
|
|
200
209
|
mockReturn(JSON.stringify({ deployments: [] }), 200, {});
|
|
201
210
|
|