@revopush/code-push-cli 0.0.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.
- package/.eslintrc.json +17 -0
- package/.idea/cli.iml +9 -0
- package/.idea/inspectionProfiles/Project_Default.xml +6 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/prettier.xml +6 -0
- package/.idea/vcs.xml +6 -0
- package/README.md +774 -0
- package/bin/script/acquisition-sdk.js +178 -0
- package/bin/script/cli.js +23 -0
- package/bin/script/command-executor.js +1290 -0
- package/bin/script/command-parser.js +1097 -0
- package/bin/script/commands/debug.js +125 -0
- package/bin/script/hash-utils.js +203 -0
- package/bin/script/index.js +5 -0
- package/bin/script/management-sdk.js +419 -0
- package/bin/script/react-native-utils.js +249 -0
- package/bin/script/sign.js +69 -0
- package/bin/script/types/cli.js +40 -0
- package/bin/script/types/rest-definitions.js +19 -0
- package/bin/script/types.js +4 -0
- package/bin/script/utils/file-utils.js +50 -0
- package/bin/test/acquisition-rest-mock.js +108 -0
- package/bin/test/acquisition-sdk.js +188 -0
- package/bin/test/cli.js +1342 -0
- package/bin/test/hash-utils.js +149 -0
- package/bin/test/management-sdk.js +338 -0
- package/package.json +68 -0
- package/prettier.config.js +7 -0
- package/script/acquisition-sdk.ts +273 -0
- package/script/cli.ts +27 -0
- package/script/command-executor.ts +1610 -0
- package/script/command-parser.ts +1310 -0
- package/script/commands/debug.ts +148 -0
- package/script/hash-utils.ts +241 -0
- package/script/index.ts +5 -0
- package/script/management-sdk.ts +575 -0
- package/script/react-native-utils.ts +283 -0
- package/script/sign.ts +80 -0
- package/script/types/cli.ts +232 -0
- package/script/types/rest-definitions.ts +152 -0
- package/script/types.ts +35 -0
- package/script/utils/file-utils.ts +46 -0
- package/test/acquisition-rest-mock.ts +125 -0
- package/test/acquisition-sdk.ts +272 -0
- package/test/cli.ts +1692 -0
- package/test/hash-utils.ts +170 -0
- package/test/management-sdk.ts +438 -0
- package/test/resources/TestApp/android/app/build.gradle +56 -0
- package/test/resources/TestApp/iOS/TestApp/Info.plist +49 -0
- package/test/resources/TestApp/index.android.js +2 -0
- package/test/resources/TestApp/index.ios.js +2 -0
- package/test/resources/TestApp/index.windows.js +2 -0
- package/test/resources/TestApp/package.json +6 -0
- package/test/resources/TestApp/windows/TestApp/Package.appxmanifest +46 -0
- package/test/resources/ignoredMetadata.zip +0 -0
- package/test/resources/test.zip +0 -0
- package/test/superagent-mock-config.js +58 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
// Copyright (c) Microsoft Corporation.
|
|
2
|
+
// Licensed under the MIT License.
|
|
3
|
+
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as os from "os";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import Q = require("q");
|
|
8
|
+
import superagent = require("superagent");
|
|
9
|
+
import * as recursiveFs from "recursive-fs";
|
|
10
|
+
import * as yazl from "yazl";
|
|
11
|
+
import slash = require("slash");
|
|
12
|
+
|
|
13
|
+
import Promise = Q.Promise;
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
AccessKey,
|
|
17
|
+
AccessKeyRequest,
|
|
18
|
+
Account,
|
|
19
|
+
App,
|
|
20
|
+
CodePushError,
|
|
21
|
+
CollaboratorMap,
|
|
22
|
+
Deployment,
|
|
23
|
+
DeploymentMetrics,
|
|
24
|
+
Headers,
|
|
25
|
+
Package,
|
|
26
|
+
PackageInfo,
|
|
27
|
+
ServerAccessKey,
|
|
28
|
+
Session,
|
|
29
|
+
} from "./types";
|
|
30
|
+
|
|
31
|
+
const packageJson = require("../../package.json");
|
|
32
|
+
|
|
33
|
+
interface JsonResponse {
|
|
34
|
+
headers: Headers;
|
|
35
|
+
body?: any;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface PackageFile {
|
|
39
|
+
isTemporary: boolean;
|
|
40
|
+
path: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// A template string tag function that URL encodes the substituted values
|
|
44
|
+
function urlEncode(strings: string[], ...values: string[]): string {
|
|
45
|
+
let result = "";
|
|
46
|
+
for (let i = 0; i < strings.length; i++) {
|
|
47
|
+
result += strings[i];
|
|
48
|
+
if (i < values.length) {
|
|
49
|
+
result += encodeURIComponent(values[i]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class AccountManager {
|
|
57
|
+
public static AppPermission = {
|
|
58
|
+
OWNER: "Owner",
|
|
59
|
+
COLLABORATOR: "Collaborator",
|
|
60
|
+
};
|
|
61
|
+
public static SERVER_URL = "http://localhost:3000";
|
|
62
|
+
|
|
63
|
+
private static API_VERSION: number = 2;
|
|
64
|
+
|
|
65
|
+
public static ERROR_GATEWAY_TIMEOUT = 504; // Used if there is a network error
|
|
66
|
+
public static ERROR_INTERNAL_SERVER = 500;
|
|
67
|
+
public static ERROR_NOT_FOUND = 404;
|
|
68
|
+
public static ERROR_CONFLICT = 409; // Used if the resource already exists
|
|
69
|
+
public static ERROR_UNAUTHORIZED = 401;
|
|
70
|
+
|
|
71
|
+
private _accessKey: string;
|
|
72
|
+
private _serverUrl: string;
|
|
73
|
+
private _customHeaders: Headers;
|
|
74
|
+
|
|
75
|
+
constructor(accessKey: string, customHeaders?: Headers, serverUrl?: string) {
|
|
76
|
+
if (!accessKey) throw new Error("An access key must be specified.");
|
|
77
|
+
|
|
78
|
+
this._accessKey = accessKey;
|
|
79
|
+
this._customHeaders = customHeaders;
|
|
80
|
+
this._serverUrl = serverUrl || AccountManager.SERVER_URL;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public get accessKey(): string {
|
|
84
|
+
return this._accessKey;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public isAuthenticated(throwIfUnauthorized?: boolean): Promise<boolean> {
|
|
88
|
+
return Promise<any>((resolve, reject, notify) => {
|
|
89
|
+
const request: superagent.Request<any> = superagent.get(`${this._serverUrl}${urlEncode(["/authenticated"])}`);
|
|
90
|
+
this.attachCredentials(request);
|
|
91
|
+
|
|
92
|
+
request.end((err: any, res: superagent.Response) => {
|
|
93
|
+
const status: number = this.getErrorStatus(err, res);
|
|
94
|
+
if (err && status !== AccountManager.ERROR_UNAUTHORIZED) {
|
|
95
|
+
reject(this.getCodePushError(err, res));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const authenticated: boolean = status === 200;
|
|
100
|
+
|
|
101
|
+
if (!authenticated && throwIfUnauthorized) {
|
|
102
|
+
reject(this.getCodePushError(err, res));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
resolve(authenticated);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public addAccessKey(friendlyName: string, ttl?: number): Promise<AccessKey> {
|
|
112
|
+
if (!friendlyName) {
|
|
113
|
+
throw new Error("A name must be specified when adding an access key.");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const accessKeyRequest: AccessKeyRequest = {
|
|
117
|
+
createdBy: os.hostname(),
|
|
118
|
+
friendlyName,
|
|
119
|
+
ttl,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
return this.post(urlEncode(["/accessKeys/"]), JSON.stringify(accessKeyRequest), /*expectResponseBody=*/ true).then(
|
|
123
|
+
(response: JsonResponse) => {
|
|
124
|
+
return {
|
|
125
|
+
createdTime: response.body.accessKey.createdTime,
|
|
126
|
+
expires: response.body.accessKey.expires,
|
|
127
|
+
key: response.body.accessKey.name,
|
|
128
|
+
name: response.body.accessKey.friendlyName,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
public getAccessKey(accessKeyName: string): Promise<AccessKey> {
|
|
135
|
+
return this.get(urlEncode([`/accessKeys/${accessKeyName}`])).then((res: JsonResponse) => {
|
|
136
|
+
return {
|
|
137
|
+
createdTime: res.body.accessKey.createdTime,
|
|
138
|
+
expires: res.body.accessKey.expires,
|
|
139
|
+
name: res.body.accessKey.friendlyName,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
public getAccessKeys(): Promise<AccessKey[]> {
|
|
145
|
+
return this.get(urlEncode(["/accessKeys"])).then((res: JsonResponse) => {
|
|
146
|
+
const accessKeys: AccessKey[] = [];
|
|
147
|
+
|
|
148
|
+
res.body.accessKeys.forEach((serverAccessKey: ServerAccessKey) => {
|
|
149
|
+
!serverAccessKey.isSession &&
|
|
150
|
+
accessKeys.push({
|
|
151
|
+
createdTime: serverAccessKey.createdTime,
|
|
152
|
+
expires: serverAccessKey.expires,
|
|
153
|
+
name: serverAccessKey.friendlyName,
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return accessKeys;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public getSessions(): Promise<Session[]> {
|
|
162
|
+
return this.get(urlEncode(["/accessKeys"])).then((res: JsonResponse) => {
|
|
163
|
+
// A machine name might be associated with multiple session keys,
|
|
164
|
+
// but we should only return one per machine name.
|
|
165
|
+
const sessionMap: { [machineName: string]: Session } = {};
|
|
166
|
+
const now: number = new Date().getTime();
|
|
167
|
+
res.body.accessKeys.forEach((serverAccessKey: ServerAccessKey) => {
|
|
168
|
+
if (serverAccessKey.isSession && serverAccessKey.expires > now) {
|
|
169
|
+
sessionMap[serverAccessKey.createdBy] = {
|
|
170
|
+
loggedInTime: serverAccessKey.createdTime,
|
|
171
|
+
machineName: serverAccessKey.createdBy,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const sessions: Session[] = Object.keys(sessionMap).map((machineName: string) => sessionMap[machineName]);
|
|
177
|
+
|
|
178
|
+
return sessions;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
public patchAccessKey(oldName: string, newName?: string, ttl?: number): Promise<AccessKey> {
|
|
183
|
+
const accessKeyRequest: AccessKeyRequest = {
|
|
184
|
+
friendlyName: newName,
|
|
185
|
+
ttl,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
return this.patch(urlEncode([`/accessKeys/${oldName}`]), JSON.stringify(accessKeyRequest)).then((res: JsonResponse) => {
|
|
189
|
+
return {
|
|
190
|
+
createdTime: res.body.accessKey.createdTime,
|
|
191
|
+
expires: res.body.accessKey.expires,
|
|
192
|
+
name: res.body.accessKey.friendlyName,
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public removeAccessKey(name: string): Promise<void> {
|
|
198
|
+
return this.del(urlEncode([`/accessKeys/${name}`])).then(() => null);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public removeSession(machineName: string): Promise<void> {
|
|
202
|
+
return this.del(urlEncode([`/sessions/${machineName}`])).then(() => null);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Account
|
|
206
|
+
public getAccountInfo(): Promise<Account> {
|
|
207
|
+
return this.get(urlEncode(["/account"])).then((res: JsonResponse) => res.body.account);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Apps
|
|
211
|
+
public getApps(): Promise<App[]> {
|
|
212
|
+
return this.get(urlEncode(["/apps"])).then((res: JsonResponse) => res.body.apps);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
public getApp(appName: string): Promise<App> {
|
|
216
|
+
return this.get(urlEncode([`/apps/${appName}`])).then((res: JsonResponse) => res.body.app);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
public addApp(appName: string): Promise<App> {
|
|
220
|
+
const app: App = { name: appName };
|
|
221
|
+
return this.post(urlEncode(["/apps/"]), JSON.stringify(app), /*expectResponseBody=*/ false).then(() => app);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
public removeApp(appName: string): Promise<void> {
|
|
225
|
+
return this.del(urlEncode([`/apps/${appName}`])).then(() => null);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
public renameApp(oldAppName: string, newAppName: string): Promise<void> {
|
|
229
|
+
return this.patch(urlEncode([`/apps/${oldAppName}`]), JSON.stringify({ name: newAppName })).then(() => null);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
public transferApp(appName: string, email: string): Promise<void> {
|
|
233
|
+
return this.post(urlEncode([`/apps/${appName}/transfer/${email}`]), /*requestBody=*/ null, /*expectResponseBody=*/ false).then(
|
|
234
|
+
() => null
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Collaborators
|
|
239
|
+
public getCollaborators(appName: string): Promise<CollaboratorMap> {
|
|
240
|
+
return this.get(urlEncode([`/apps/${appName}/collaborators`])).then((res: JsonResponse) => res.body.collaborators);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public addCollaborator(appName: string, email: string): Promise<void> {
|
|
244
|
+
return this.post(
|
|
245
|
+
urlEncode([`/apps/${appName}/collaborators/${email}`]),
|
|
246
|
+
/*requestBody=*/ null,
|
|
247
|
+
/*expectResponseBody=*/ false
|
|
248
|
+
).then(() => null);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
public removeCollaborator(appName: string, email: string): Promise<void> {
|
|
252
|
+
return this.del(urlEncode([`/apps/${appName}/collaborators/${email}`])).then(() => null);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Deployments
|
|
256
|
+
public addDeployment(appName: string, deploymentName: string): Promise<Deployment> {
|
|
257
|
+
const deployment = <Deployment>{ name: deploymentName };
|
|
258
|
+
return this.post(urlEncode([`/apps/${appName}/deployments/`]), JSON.stringify(deployment), /*expectResponseBody=*/ true).then(
|
|
259
|
+
(res: JsonResponse) => res.body.deployment
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
public clearDeploymentHistory(appName: string, deploymentName: string): Promise<void> {
|
|
264
|
+
return this.del(urlEncode([`/apps/${appName}/deployments/${deploymentName}/history`])).then(() => null);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
public getDeployments(appName: string): Promise<Deployment[]> {
|
|
268
|
+
return this.get(urlEncode([`/apps/${appName}/deployments/`])).then((res: JsonResponse) => res.body.deployments);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
public getDeployment(appName: string, deploymentName: string): Promise<Deployment> {
|
|
272
|
+
return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then((res: JsonResponse) => res.body.deployment);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
public renameDeployment(appName: string, oldDeploymentName: string, newDeploymentName: string): Promise<void> {
|
|
276
|
+
return this.patch(
|
|
277
|
+
urlEncode([`/apps/${appName}/deployments/${oldDeploymentName}`]),
|
|
278
|
+
JSON.stringify({ name: newDeploymentName })
|
|
279
|
+
).then(() => null);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
public removeDeployment(appName: string, deploymentName: string): Promise<void> {
|
|
283
|
+
return this.del(urlEncode([`/apps/${appName}/deployments/${deploymentName}`])).then(() => null);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public getDeploymentMetrics(appName: string, deploymentName: string): Promise<DeploymentMetrics> {
|
|
287
|
+
return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/metrics`])).then(
|
|
288
|
+
(res: JsonResponse) => res.body.metrics
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
public getDeploymentHistory(appName: string, deploymentName: string): Promise<Package[]> {
|
|
293
|
+
return this.get(urlEncode([`/apps/${appName}/deployments/${deploymentName}/history`])).then(
|
|
294
|
+
(res: JsonResponse) => res.body.history
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
public release(
|
|
299
|
+
appName: string,
|
|
300
|
+
deploymentName: string,
|
|
301
|
+
filePath: string,
|
|
302
|
+
targetBinaryVersion: string,
|
|
303
|
+
updateMetadata: PackageInfo,
|
|
304
|
+
uploadProgressCallback?: (progress: number) => void
|
|
305
|
+
): Promise<void> {
|
|
306
|
+
return Promise<void>((resolve, reject, notify) => {
|
|
307
|
+
updateMetadata.appVersion = targetBinaryVersion;
|
|
308
|
+
const request: superagent.Request<any> = superagent.post(
|
|
309
|
+
this._serverUrl + urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`])
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
this.attachCredentials(request);
|
|
313
|
+
|
|
314
|
+
const getPackageFilePromise = Q.Promise((resolve, reject) => {
|
|
315
|
+
this.packageFileFromPath(filePath)
|
|
316
|
+
.then((result) => {
|
|
317
|
+
resolve(result);
|
|
318
|
+
})
|
|
319
|
+
.catch((error) => {
|
|
320
|
+
reject(error);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
getPackageFilePromise.then((packageFile: PackageFile) => {
|
|
325
|
+
const file: any = fs.createReadStream(packageFile.path);
|
|
326
|
+
request
|
|
327
|
+
.attach("package", file)
|
|
328
|
+
.field("packageInfo", JSON.stringify(updateMetadata))
|
|
329
|
+
.on("progress", (event: any) => {
|
|
330
|
+
if (uploadProgressCallback && event && event.total > 0) {
|
|
331
|
+
const currentProgress: number = (event.loaded / event.total) * 100;
|
|
332
|
+
uploadProgressCallback(currentProgress);
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
.end((err: any, res: superagent.Response) => {
|
|
336
|
+
if (packageFile.isTemporary) {
|
|
337
|
+
fs.unlinkSync(packageFile.path);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (err) {
|
|
341
|
+
reject(this.getCodePushError(err, res));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (res.ok) {
|
|
346
|
+
resolve(<void>null);
|
|
347
|
+
} else {
|
|
348
|
+
let body;
|
|
349
|
+
try {
|
|
350
|
+
body = JSON.parse(res.text);
|
|
351
|
+
} catch (err) {}
|
|
352
|
+
|
|
353
|
+
if (body) {
|
|
354
|
+
reject(<CodePushError>{
|
|
355
|
+
message: body.message,
|
|
356
|
+
statusCode: res && res.status,
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
reject(<CodePushError>{
|
|
360
|
+
message: res.text,
|
|
361
|
+
statusCode: res && res.status,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
public patchRelease(appName: string, deploymentName: string, label: string, updateMetadata: PackageInfo): Promise<void> {
|
|
371
|
+
updateMetadata.label = label;
|
|
372
|
+
const requestBody: string = JSON.stringify({ packageInfo: updateMetadata });
|
|
373
|
+
return this.patch(
|
|
374
|
+
urlEncode([`/apps/${appName}/deployments/${deploymentName}/release`]),
|
|
375
|
+
requestBody,
|
|
376
|
+
/*expectResponseBody=*/ false
|
|
377
|
+
).then(() => null);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
public promote(
|
|
381
|
+
appName: string,
|
|
382
|
+
sourceDeploymentName: string,
|
|
383
|
+
destinationDeploymentName: string,
|
|
384
|
+
updateMetadata: PackageInfo
|
|
385
|
+
): Promise<void> {
|
|
386
|
+
const requestBody: string = JSON.stringify({ packageInfo: updateMetadata });
|
|
387
|
+
return this.post(
|
|
388
|
+
urlEncode([`/apps/${appName}/deployments/${sourceDeploymentName}/promote/${destinationDeploymentName}`]),
|
|
389
|
+
requestBody,
|
|
390
|
+
/*expectResponseBody=*/ false
|
|
391
|
+
).then(() => null);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
public rollback(appName: string, deploymentName: string, targetRelease?: string): Promise<void> {
|
|
395
|
+
return this.post(
|
|
396
|
+
urlEncode([`/apps/${appName}/deployments/${deploymentName}/rollback/${targetRelease || ``}`]),
|
|
397
|
+
/*requestBody=*/ null,
|
|
398
|
+
/*expectResponseBody=*/ false
|
|
399
|
+
).then(() => null);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private packageFileFromPath(filePath: string) {
|
|
403
|
+
let getPackageFilePromise: Promise<PackageFile>;
|
|
404
|
+
if (fs.lstatSync(filePath).isDirectory()) {
|
|
405
|
+
getPackageFilePromise = Promise<PackageFile>((resolve: (file: PackageFile) => void, reject: (reason: Error) => void): void => {
|
|
406
|
+
const directoryPath: string = filePath;
|
|
407
|
+
|
|
408
|
+
recursiveFs.readdirr(directoryPath, (error?: any, directories?: string[], files?: string[]) => {
|
|
409
|
+
if (error) {
|
|
410
|
+
reject(error);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const baseDirectoryPath = path.dirname(directoryPath);
|
|
415
|
+
const fileName: string = this.generateRandomFilename(15) + ".zip";
|
|
416
|
+
const zipFile = new yazl.ZipFile();
|
|
417
|
+
const writeStream: fs.WriteStream = fs.createWriteStream(fileName);
|
|
418
|
+
|
|
419
|
+
zipFile.outputStream
|
|
420
|
+
.pipe(writeStream)
|
|
421
|
+
.on("error", (error: Error): void => {
|
|
422
|
+
reject(error);
|
|
423
|
+
})
|
|
424
|
+
.on("close", (): void => {
|
|
425
|
+
filePath = path.join(process.cwd(), fileName);
|
|
426
|
+
|
|
427
|
+
resolve({ isTemporary: true, path: filePath });
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
for (let i = 0; i < files.length; ++i) {
|
|
431
|
+
const file: string = files[i];
|
|
432
|
+
// yazl does not like backslash (\) in the metadata path.
|
|
433
|
+
const relativePath: string = slash(path.relative(baseDirectoryPath, file));
|
|
434
|
+
|
|
435
|
+
zipFile.addFile(file, relativePath);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
zipFile.end();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
} else {
|
|
442
|
+
getPackageFilePromise = Q({ isTemporary: false, path: filePath });
|
|
443
|
+
}
|
|
444
|
+
return getPackageFilePromise;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private generateRandomFilename(length: number): string {
|
|
448
|
+
let filename: string = "";
|
|
449
|
+
const validChar: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
450
|
+
|
|
451
|
+
for (let i = 0; i < length; i++) {
|
|
452
|
+
filename += validChar.charAt(Math.floor(Math.random() * validChar.length));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return filename;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private get(endpoint: string, expectResponseBody: boolean = true): Promise<JsonResponse> {
|
|
459
|
+
return this.makeApiRequest("get", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private post(
|
|
463
|
+
endpoint: string,
|
|
464
|
+
requestBody: string,
|
|
465
|
+
expectResponseBody: boolean,
|
|
466
|
+
contentType: string = "application/json;charset=UTF-8"
|
|
467
|
+
): Promise<JsonResponse> {
|
|
468
|
+
return this.makeApiRequest("post", endpoint, requestBody, expectResponseBody, contentType);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private patch(
|
|
472
|
+
endpoint: string,
|
|
473
|
+
requestBody: string,
|
|
474
|
+
expectResponseBody: boolean = false,
|
|
475
|
+
contentType: string = "application/json;charset=UTF-8"
|
|
476
|
+
): Promise<JsonResponse> {
|
|
477
|
+
return this.makeApiRequest("patch", endpoint, requestBody, expectResponseBody, contentType);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private del(endpoint: string, expectResponseBody: boolean = false): Promise<JsonResponse> {
|
|
481
|
+
return this.makeApiRequest("del", endpoint, /*requestBody=*/ null, expectResponseBody, /*contentType=*/ null);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
private makeApiRequest(
|
|
485
|
+
method: string,
|
|
486
|
+
endpoint: string,
|
|
487
|
+
requestBody: string,
|
|
488
|
+
expectResponseBody: boolean,
|
|
489
|
+
contentType: string
|
|
490
|
+
): Promise<JsonResponse> {
|
|
491
|
+
return Promise<JsonResponse>((resolve, reject, notify) => {
|
|
492
|
+
let request: superagent.Request<any> = (<any>superagent)[method](this._serverUrl + endpoint);
|
|
493
|
+
|
|
494
|
+
this.attachCredentials(request);
|
|
495
|
+
|
|
496
|
+
if (requestBody) {
|
|
497
|
+
if (contentType) {
|
|
498
|
+
request = request.set("Content-Type", contentType);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
request = request.send(requestBody);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
request.end((err: any, res: superagent.Response) => {
|
|
505
|
+
if (err) {
|
|
506
|
+
reject(this.getCodePushError(err, res));
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
let body;
|
|
510
|
+
try {
|
|
511
|
+
body = JSON.parse(res.text);
|
|
512
|
+
} catch (err) {}
|
|
513
|
+
|
|
514
|
+
if (res.ok) {
|
|
515
|
+
if (expectResponseBody && !body) {
|
|
516
|
+
reject(<CodePushError>{
|
|
517
|
+
message: `Could not parse response: ${res.text}`,
|
|
518
|
+
statusCode: AccountManager.ERROR_INTERNAL_SERVER,
|
|
519
|
+
});
|
|
520
|
+
} else {
|
|
521
|
+
resolve(<JsonResponse>{
|
|
522
|
+
headers: res.header,
|
|
523
|
+
body: body,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
if (body) {
|
|
528
|
+
reject(<CodePushError>{
|
|
529
|
+
message: body.message,
|
|
530
|
+
statusCode: this.getErrorStatus(err, res),
|
|
531
|
+
});
|
|
532
|
+
} else {
|
|
533
|
+
reject(<CodePushError>{
|
|
534
|
+
message: res.text,
|
|
535
|
+
statusCode: this.getErrorStatus(err, res),
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
private getCodePushError(error: any, response: superagent.Response): CodePushError {
|
|
544
|
+
if (error.syscall === "getaddrinfo") {
|
|
545
|
+
error.message = `Unable to connect to the CodePush server. Are you offline, or behind a firewall or proxy?\n(${error.message})`;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return {
|
|
549
|
+
message: this.getErrorMessage(error, response),
|
|
550
|
+
statusCode: this.getErrorStatus(error, response),
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
private getErrorStatus(error: any, response: superagent.Response): number {
|
|
555
|
+
return (error && error.status) || (response && response.status) || AccountManager.ERROR_GATEWAY_TIMEOUT;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
private getErrorMessage(error: Error, response: superagent.Response): string {
|
|
559
|
+
return response && response.text ? response.text : error.message;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
private attachCredentials(request: superagent.Request<any>): void {
|
|
563
|
+
if (this._customHeaders) {
|
|
564
|
+
for (const headerName in this._customHeaders) {
|
|
565
|
+
request.set(headerName, this._customHeaders[headerName]);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
request.set("Accept", `application/vnd.code-push.v${AccountManager.API_VERSION}+json`);
|
|
570
|
+
request.set("Authorization", `Bearer ${this._accessKey}`);
|
|
571
|
+
request.set("X-CodePush-SDK-Version", packageJson.version);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export = AccountManager;
|