piral-cli 1.5.0-beta.6732 → 1.5.0-beta.6744

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.
@@ -1,10 +1,12 @@
1
- import { join } from 'path';
1
+ import { basename, dirname, join } from 'path';
2
2
  import { Agent } from 'https';
3
3
  import { Stream } from 'stream';
4
4
  import { tmpdir } from 'os';
5
5
  import { createWriteStream } from 'fs';
6
6
  import { log } from './log';
7
+ import { config } from './config';
7
8
  import { standardHeaders } from './info';
9
+ import { checkExists, readBinary } from './io';
8
10
  import { getTokenInteractively } from './interactive';
9
11
  import { axios, FormData } from '../external';
10
12
  import { PublishScheme } from '../types';
@@ -37,6 +39,67 @@ function streamToFile(source: Stream, target: string) {
37
39
  });
38
40
  }
39
41
 
42
+ export function getAxiosOptions(url: string) {
43
+ const auth = config.auth?.[url];
44
+
45
+ switch (auth?.mode) {
46
+ case 'header':
47
+ return {
48
+ headers: {
49
+ [auth.key]: auth.value,
50
+ },
51
+ };
52
+ case 'http':
53
+ return {
54
+ auth: {
55
+ username: auth.username,
56
+ password: auth.password,
57
+ },
58
+ };
59
+ default:
60
+ return {};
61
+ }
62
+ }
63
+
64
+ export async function getCertificate(cert = config.cert): Promise<Buffer> {
65
+ log('generalDebug_0003', 'Checking if certificate exists.');
66
+
67
+ if (await checkExists(cert)) {
68
+ const dir = dirname(cert);
69
+ const file = basename(cert);
70
+ log('generalDebug_0003', `Reading certificate file "${file}" from "${dir}".`);
71
+ return await readBinary(dir, file);
72
+ }
73
+
74
+ return undefined;
75
+ }
76
+
77
+ export function getAuthorizationHeaders(scheme: PublishScheme, key: string) {
78
+ if (key) {
79
+ switch (scheme) {
80
+ case 'basic':
81
+ return {
82
+ authorization: `Basic ${key}`,
83
+ };
84
+ case 'bearer':
85
+ return {
86
+ authorization: `Bearer ${key}`,
87
+ };
88
+ case 'digest':
89
+ return {
90
+ authorization: `Digest ${key}`,
91
+ };
92
+ case 'none':
93
+ default:
94
+ return {
95
+ authorization: key,
96
+ };
97
+ }
98
+ }
99
+
100
+ return {};
101
+ }
102
+
40
103
  export function downloadFile(target: string, ca?: Buffer): Promise<Array<string>> {
41
104
  const httpsAgent = ca ? new Agent({ ca }) : undefined;
42
105
  return axios.default
@@ -57,24 +120,9 @@ export function downloadFile(target: string, ca?: Buffer): Promise<Array<string>
57
120
  });
58
121
  }
59
122
 
60
- export interface PostFormResult {
61
- status: number;
62
- success: boolean;
63
- response?: object;
64
- }
65
-
66
123
  export type FormDataObj = Record<string, string | number | boolean | [Buffer, string]>;
67
124
 
68
- export function postForm(
69
- target: string,
70
- scheme: PublishScheme,
71
- key: string,
72
- formData: FormDataObj,
73
- customHeaders: Record<string, string> = {},
74
- ca?: Buffer,
75
- interactive = false,
76
- ): Promise<PostFormResult> {
77
- const httpsAgent = ca ? new Agent({ ca }) : undefined;
125
+ export function createAxiosForm(formData: FormDataObj) {
78
126
  const form = new FormData();
79
127
 
80
128
  Object.keys(formData).forEach((key) => {
@@ -89,105 +137,126 @@ export function postForm(
89
137
  }
90
138
  });
91
139
 
140
+ return form;
141
+ }
142
+
143
+ export function handleAxiosError(
144
+ error: any,
145
+ interactive: boolean,
146
+ httpsAgent: Agent,
147
+ refetch: (mode: PublishScheme, key: string) => Promise<any>,
148
+ onfail?: (status: number, statusText: string, response: string) => any,
149
+ ) {
150
+ if (!onfail) {
151
+ onfail = () => {
152
+ throw error;
153
+ };
154
+ }
155
+
156
+ if (error.response) {
157
+ // The request was made and the server responded with a status code
158
+ // that falls out of the range of 2xx
159
+ const { data, statusText, status } = error.response;
160
+
161
+ if (interactive && 'interactiveAuth' in data) {
162
+ const { interactiveAuth } = data;
163
+
164
+ if (typeof interactiveAuth === 'string') {
165
+ log(
166
+ 'generalDebug_0003',
167
+ `Received status "${status}" from HTTP - trying interactive log in to "${interactiveAuth}".`,
168
+ );
169
+
170
+ return getTokenInteractively(interactiveAuth, httpsAgent).then(({ mode, token }) => refetch(mode, token));
171
+ }
172
+ }
173
+
174
+ const message = getMessage(data) || '';
175
+ return onfail(status, statusText, message);
176
+ } else if (error.isAxiosError) {
177
+ // axios initiated error: try to parse message from error object
178
+ let errorMessage: string = error.errno || 'Unknown Axios Error';
179
+
180
+ if (typeof error.toJSON === 'function') {
181
+ const errorObj: { message?: string } = error.toJSON();
182
+ errorMessage = errorObj?.message ?? errorMessage;
183
+ }
184
+
185
+ return onfail(500, undefined, errorMessage);
186
+ } else if (error.request) {
187
+ // The request was made but no response was received
188
+ // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
189
+ // http.ClientRequest in node.js
190
+ return onfail(500, undefined, error.request);
191
+ } else {
192
+ // Something happened in setting up the request that triggered an Error
193
+ return onfail(500, undefined, error.message);
194
+ }
195
+ }
196
+
197
+ export interface PostFormResult {
198
+ status: number;
199
+ success: boolean;
200
+ response?: object;
201
+ }
202
+
203
+ export async function postForm(
204
+ target: string,
205
+ scheme: PublishScheme,
206
+ key: string,
207
+ formData: FormDataObj,
208
+ customHeaders: Record<string, string> = {},
209
+ ca?: Buffer,
210
+ interactive = false,
211
+ ): Promise<PostFormResult> {
212
+ const httpsAgent = ca ? new Agent({ ca }) : undefined;
213
+ const form = createAxiosForm(formData);
214
+
92
215
  const headers: Record<string, string> = {
93
216
  ...form.getHeaders(),
94
217
  ...standardHeaders,
95
218
  ...customHeaders,
219
+ ...getAuthorizationHeaders(scheme, key),
96
220
  };
97
221
 
98
- if (key) {
99
- switch (scheme) {
100
- case 'basic':
101
- headers.authorization = `Basic ${key}`;
102
- break;
103
- case 'bearer':
104
- headers.authorization = `Bearer ${key}`;
105
- break;
106
- case 'digest':
107
- headers.authorization = `Digest ${key}`;
108
- break;
109
- case 'none':
110
- default:
111
- headers.authorization = key;
112
- break;
113
- }
114
- }
115
-
116
- return axios.default
117
- .post(target, form, {
222
+ try {
223
+ const res = await axios.default.post(target, form, {
118
224
  headers,
119
225
  httpsAgent,
120
226
  maxContentLength: Infinity,
121
227
  maxBodyLength: Infinity,
122
- })
123
- .then(
124
- (res) => {
125
- return {
126
- status: res.status,
127
- success: true,
128
- response: res.data,
129
- };
130
- },
131
- (error) => {
132
- if (error.response) {
133
- // The request was made and the server responded with a status code
134
- // that falls out of the range of 2xx
135
- const { data, statusText, status } = error.response;
136
-
137
- if (interactive && 'interactiveAuth' in data) {
138
- const { interactiveAuth } = data;
139
-
140
- if (typeof interactiveAuth === 'string') {
141
- log(
142
- 'generalDebug_0003',
143
- `Received status "${status}" from HTTP - trying interactive log in to "${interactiveAuth}".`,
144
- );
145
-
146
- return getTokenInteractively(interactiveAuth, httpsAgent).then(({ mode, token }) =>
147
- postForm(target, mode, token, formData, customHeaders, ca, false),
148
- );
149
- }
150
- }
151
-
152
- const message = getMessage(data) || '';
153
- log('unsuccessfulHttpPost_0066', statusText, status, message);
154
- return {
155
- status,
156
- success: false,
157
- response: message,
158
- };
159
- } else if (error.isAxiosError) {
160
- // axios initiated error: try to parse message from error object
161
- let errorMessage: string = error.errno || 'Unknown Axios Error';
162
-
163
- if (typeof error.toJSON === 'function') {
164
- const errorObj: { message?: string } = error.toJSON();
165
- errorMessage = errorObj?.message ?? errorMessage;
166
- }
228
+ });
167
229
 
168
- log('failedHttpPost_0065', errorMessage);
230
+ return {
231
+ status: res.status,
232
+ success: true,
233
+ response: res.data,
234
+ };
235
+ } catch (error) {
236
+ return await handleAxiosError(
237
+ error,
238
+ interactive,
239
+ httpsAgent,
240
+ (mode, token) => postForm(target, mode, token, formData, customHeaders, ca, false),
241
+ (status, statusText, response) => {
242
+ if (status === 500) {
243
+ log('failedHttpPost_0065', response);
169
244
  return {
170
245
  status: 500,
171
246
  success: false,
172
- response: errorMessage,
247
+ response: undefined,
173
248
  };
174
- } else if (error.request) {
175
- // The request was made but no response was received
176
- // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
177
- // http.ClientRequest in node.js
178
- log('failedHttpPost_0065', error.request);
179
249
  } else {
180
- // Something happened in setting up the request that triggered an Error
181
- log('failedHttpPost_0065', error.message);
250
+ log('unsuccessfulHttpPost_0066', statusText, status, response);
251
+ return {
252
+ status,
253
+ success: false,
254
+ response,
255
+ };
182
256
  }
183
-
184
- return {
185
- status: 500,
186
- success: false,
187
- response: undefined,
188
- };
189
257
  },
190
258
  );
259
+ }
191
260
  }
192
261
 
193
262
  export function postFile(
@@ -69,10 +69,10 @@ export function getTokenInteractively(url: string, httpsAgent: Agent): TokenResu
69
69
 
70
70
  try {
71
71
  while (true) {
72
- const { data, status } = await axios.default.get(callbackUrl);
72
+ const { data, status } = await axios.default.get(callbackUrl, { httpsAgent, headers: standardHeaders });
73
73
 
74
74
  if (status === 202) {
75
- await new Promise(resolve => setTimeout(resolve, 5000));
75
+ await new Promise((resolve) => setTimeout(resolve, 5000));
76
76
  continue;
77
77
  }
78
78
 
@@ -1,7 +1,9 @@
1
+ import { Agent } from 'https';
1
2
  import { join, relative, resolve } from 'path';
2
3
  import { createPiralStubIndexIfNotExists } from './template';
3
- import { config } from './config';
4
+ import { getAuthorizationHeaders, getAxiosOptions, getCertificate, handleAxiosError } from './http';
4
5
  import { packageJson } from './constants';
6
+ import { updateConfig } from './config';
5
7
  import { ForceOverwrite } from './enums';
6
8
  import { createDirectory, readJson, writeBinary } from './io';
7
9
  import { writeJson } from './io';
@@ -9,35 +11,43 @@ import { progress, log } from './log';
9
11
  import { axios } from '../external';
10
12
  import { EmulatorWebsiteManifestFiles, EmulatorWebsiteManifest } from '../types';
11
13
 
12
- function requestManifest(url: string) {
13
- const auth = config.auth?.[url];
14
+ async function requestManifest(url: string, httpsAgent?: Agent) {
15
+ const opts = getAxiosOptions(url);
14
16
 
15
- switch (auth?.mode) {
16
- case 'header':
17
- return axios.default.get(url, {
18
- headers: {
19
- [auth.key]: auth.value,
20
- },
21
- });
22
- case 'http':
23
- return axios.default.get(url, {
24
- auth: {
25
- username: auth.username,
26
- password: auth.password,
27
- },
28
- });
29
- default:
30
- return axios.default.get(url);
17
+ try {
18
+ return await axios.default.get(url, { ...opts, httpsAgent });
19
+ } catch (error) {
20
+ return await handleAxiosError(error, true, httpsAgent, async (mode, key) => {
21
+ const headers = getAuthorizationHeaders(mode, key);
22
+
23
+ if (headers.authorization) {
24
+ await updateConfig('auth', {
25
+ [url]: {
26
+ mode: 'header',
27
+ key: 'authorization',
28
+ value: headers.authorization,
29
+ },
30
+ });
31
+ }
32
+
33
+ return await axios.default.get(url, { headers, httpsAgent });
34
+ });
31
35
  }
32
36
  }
33
37
 
34
- async function downloadEmulatorFiles(manifestUrl: string, target: string, files: EmulatorWebsiteManifestFiles) {
38
+ async function downloadEmulatorFiles(
39
+ manifestUrl: string,
40
+ target: string,
41
+ files: EmulatorWebsiteManifestFiles,
42
+ httpsAgent?: Agent,
43
+ ) {
35
44
  const requiredFiles = [files.typings, files.main, files.app];
45
+ const opts = getAxiosOptions(manifestUrl);
36
46
 
37
47
  await Promise.all(
38
48
  requiredFiles.map(async (file) => {
39
49
  const url = new URL(file, manifestUrl);
40
- const res = await axios.default.get(url.href, { responseType: 'arraybuffer' });
50
+ const res = await axios.default.get(url.href, { ...opts, httpsAgent, responseType: 'arraybuffer' });
41
51
  const data: Buffer = res.data;
42
52
  await writeBinary(target, file, data);
43
53
  }),
@@ -49,6 +59,7 @@ async function createEmulatorFiles(
49
59
  appDir: string,
50
60
  manifestUrl: string,
51
61
  emulatorJson: EmulatorWebsiteManifest,
62
+ httpsAgent?: Agent,
52
63
  ) {
53
64
  const mainFile = 'index.js';
54
65
  const appDirName = relative(targetDir, appDir);
@@ -85,14 +96,16 @@ async function createEmulatorFiles(
85
96
  outFile: emulatorJson.files.main,
86
97
  });
87
98
 
88
- await downloadEmulatorFiles(manifestUrl, appDir, emulatorJson.files);
99
+ await downloadEmulatorFiles(manifestUrl, appDir, emulatorJson.files, httpsAgent);
89
100
  }
90
101
 
91
102
  export async function updateFromEmulatorWebsite(targetDir: string, manifestUrl: string) {
92
103
  progress(`Updating emulator from %s ...`, manifestUrl);
104
+ const ca = await getCertificate();
105
+ const httpsAgent = ca ? new Agent({ ca }) : undefined;
93
106
 
94
107
  try {
95
- const response = await requestManifest(manifestUrl);
108
+ const response = await requestManifest(manifestUrl, httpsAgent);
96
109
  const nextEmulator: EmulatorWebsiteManifest = response.data;
97
110
  const currentEmulator = await readJson(targetDir, packageJson);
98
111
 
@@ -101,7 +114,7 @@ export async function updateFromEmulatorWebsite(targetDir: string, manifestUrl:
101
114
  } else if (currentEmulator.piralCLI.timstamp !== nextEmulator.timestamp) {
102
115
  log('generalDebug_0003', `The timestamp on "${currentEmulator.name}" is different (${nextEmulator.timestamp}).`);
103
116
  const appDir = resolve(targetDir, 'app');
104
- await createEmulatorFiles(targetDir, appDir, manifestUrl, nextEmulator);
117
+ await createEmulatorFiles(targetDir, appDir, manifestUrl, nextEmulator, httpsAgent);
105
118
  } else {
106
119
  log('generalDebug_0003', `Nothing to update for "${currentEmulator.name}".`);
107
120
  }
@@ -113,11 +126,13 @@ export async function updateFromEmulatorWebsite(targetDir: string, manifestUrl:
113
126
 
114
127
  export async function scaffoldFromEmulatorWebsite(rootDir: string, manifestUrl: string) {
115
128
  progress(`Downloading emulator from %s ...`, manifestUrl);
116
- const response = await requestManifest(manifestUrl);
129
+ const ca = await getCertificate();
130
+ const httpsAgent = ca ? new Agent({ ca }) : undefined;
131
+ const response = await requestManifest(manifestUrl, httpsAgent);
117
132
  const emulatorJson: EmulatorWebsiteManifest = response.data;
118
133
  const targetDir = resolve(rootDir, 'node_modules', emulatorJson.name);
119
134
  const appDir = resolve(targetDir, 'app');
120
135
  await createDirectory(appDir);
121
- await createEmulatorFiles(targetDir, appDir, manifestUrl, emulatorJson);
136
+ await createEmulatorFiles(targetDir, appDir, manifestUrl, emulatorJson, httpsAgent);
122
137
  return { name: emulatorJson.name, path: targetDir };
123
138
  }
@@ -5,6 +5,7 @@ import { existsSync } from 'fs';
5
5
  import { readFile, stat, writeFile } from 'fs/promises';
6
6
  import { KrasInjector, KrasRequest, KrasInjectorConfig, KrasConfiguration, KrasResult } from 'kras';
7
7
  import { log } from '../common/log';
8
+ import { getAxiosOptions } from '../common/http';
8
9
  import { getPiletSpecMeta } from '../common/spec';
9
10
  import { config as commonConfig } from '../common/config';
10
11
  import { axios, mime, jju } from '../external';
@@ -146,7 +147,7 @@ export default class PiletInjector implements KrasInjector {
146
147
  if (existsSync(path)) {
147
148
  try {
148
149
  const packageJson = require(path);
149
-
150
+
150
151
  if (typeof packageJson.piralCLI.source === 'string') {
151
152
  this.proxyInfo = {
152
153
  source: packageJson.piralCLI.source,
@@ -343,28 +344,10 @@ export default class PiletInjector implements KrasInjector {
343
344
  }
344
345
 
345
346
  private download(path: string) {
346
- const url = new URL(path, this.proxyInfo.source);
347
- const auth = commonConfig.auth?.[this.proxyInfo.source];
348
-
349
- switch (auth?.mode) {
350
- case 'header':
351
- return axios.default.get(url.href, {
352
- responseType: 'arraybuffer',
353
- headers: {
354
- [auth.key]: auth.value,
355
- },
356
- });
357
- case 'http':
358
- return axios.default.get(url.href, {
359
- responseType: 'arraybuffer',
360
- auth: {
361
- username: auth.username,
362
- password: auth.password,
363
- },
364
- });
365
- default:
366
- return axios.default.get(url.href, { responseType: 'arraybuffer' });
367
- }
347
+ const manifestUrl = this.proxyInfo.source;
348
+ const url = new URL(path, manifestUrl);
349
+ const opts = getAxiosOptions(manifestUrl);
350
+ return axios.default.get(url.href, { ...opts, responseType: 'arraybuffer' });
368
351
  }
369
352
 
370
353
  private async shouldLoad(target: string, path: string) {