@sitecore-jss/sitecore-jss-dev-tools 22.4.0-canary.14 → 22.4.0-canary.16

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.
@@ -17,17 +17,17 @@ exports.normalizeFingerprint = normalizeFingerprint;
17
17
  exports.doFingerprintsMatch = doFingerprintsMatch;
18
18
  exports.finishWatchJobStatusTask = finishWatchJobStatusTask;
19
19
  exports.logJobStatus = logJobStatus;
20
+ exports.watchJobStatus = watchJobStatus;
20
21
  exports.packageDeploy = packageDeploy;
21
- exports.extractProxy = extractProxy;
22
- exports.getHttpsTransport = getHttpsTransport;
22
+ exports.setProxy = setProxy;
23
+ exports.attachFormDataHandlers = attachFormDataHandlers;
23
24
  const chalk_1 = __importDefault(require("chalk"));
24
25
  const fs_1 = __importDefault(require("fs"));
25
26
  const https_1 = __importDefault(require("https"));
27
+ const http_1 = __importDefault(require("http"));
26
28
  const path_1 = __importDefault(require("path"));
27
29
  const form_data_1 = __importDefault(require("form-data"));
28
30
  const digest_1 = require("./digest");
29
- const sitecore_jss_1 = require("@sitecore-jss/sitecore-jss");
30
- const undici_1 = require("undici");
31
31
  // Node does not use system level trusted CAs. This causes issues because SIF likes to install
32
32
  // using a Windows trusted CA - so SSL connections to Sitecore will fail from Node.
33
33
  // If the options.acceptCertificate is passed, we disable normal SSL validation and use this function
@@ -139,79 +139,102 @@ function watchJobStatus(options, taskName) {
139
139
  const warnings = [];
140
140
  const factors = [options.appName, taskName, `${options.importServiceUrl}/status`];
141
141
  const mac = (0, digest_1.hmac)(factors, options.secret);
142
- const requestBaseOptions = {
142
+ const url = new URL(`${options.importServiceUrl}/status?appName=${options.appName}&jobName=${taskName}&after=${logOffset}`);
143
+ const isHttps = options.importServiceUrl.startsWith('https');
144
+ const client = isHttps ? https_1.default : http_1.default;
145
+ const reqOptions = {
143
146
  headers: {
144
147
  'User-Agent': 'Sitecore/JSS-Import',
145
148
  'Cache-Control': 'no-cache',
146
149
  'X-JSS-Auth': mac,
147
150
  },
148
- dispatcher: new undici_1.ProxyAgent({
149
- uri: options.proxy || '',
150
- maxRedirections: 0,
151
- connect: {
151
+ method: 'GET',
152
+ hostname: url.hostname,
153
+ protocol: url.protocol,
154
+ path: url.pathname + url.search,
155
+ agent: isHttps
156
+ ? new https_1.default.Agent({
157
+ // we turn off normal CA cert validation when we are whitelisting a single cert thumbprint
152
158
  rejectUnauthorized: options.acceptCertificate ? false : true,
159
+ // needed to allow whitelisting a cert thumbprint if a connection is reused
153
160
  maxCachedSessions: options.acceptCertificate ? 0 : undefined,
154
- },
155
- }),
161
+ })
162
+ : undefined,
156
163
  };
157
164
  if (options.debugSecurity) {
158
165
  console.log(`Deployment status security factors: ${factors}`);
159
166
  console.log(`Deployment status HMAC: ${mac}`);
160
167
  }
161
- return new Promise((resolve, reject) => {
162
- /**
163
- * Send job status request
164
- */
165
- function sendJobStatusRequest() {
166
- new sitecore_jss_1.NativeDataFetcher()
167
- .get(`${options.importServiceUrl}/status?appName=${options.appName}&jobName=${taskName}&after=${logOffset}`, requestBaseOptions)
168
- .then((response) => {
169
- const body = response.data;
170
- try {
171
- const { state, messages } = body.data;
172
- messages.forEach((entry) => {
173
- logOffset++;
174
- const entryBits = /^(\[([A-Z]+)\] )?(.+)/.exec(entry);
175
- let entryLevel = 'INFO';
176
- let message = entry;
177
- if (entryBits && entryBits[2]) {
178
- entryLevel = entryBits[2];
179
- // 3 = '[] ' in say [INFO] My log message
180
- // we're not using the capture group as the message might be multi-line
181
- message = entry.substring(entryLevel.length + 3);
182
- }
183
- if (message.startsWith('[JSS] - ')) {
184
- message = message.substring(8);
185
- }
186
- logJobStatus({ message, entryLevel, warnings, errors });
187
- });
188
- if (state === 'Finished') {
189
- finishWatchJobStatusTask({ warnings, errors, resolve, reject });
190
- return;
191
- }
192
- setTimeout(sendJobStatusRequest, 1000);
193
- }
194
- catch (error) {
195
- console.error(chalk_1.default.red(`Unexpected error processing reply from import status service: ${error}`));
196
- console.error(chalk_1.default.red(`Response: ${body}`));
197
- console.error(chalk_1.default.red('Consult the Sitecore logs for details.'));
198
- reject(error);
199
- }
200
- })
201
- .catch((error) => {
168
+ if (options.proxy) {
169
+ setProxy(reqOptions, options.proxy, options.importServiceUrl);
170
+ }
171
+ const req = client.request(reqOptions);
172
+ if (isHttps) {
173
+ applyCertPinning(req, options);
174
+ }
175
+ let responseData = '';
176
+ req.on('response', (res) => {
177
+ if (res.statusCode !== 200) {
178
+ let errorData = '';
179
+ res.on('data', (chunk) => {
180
+ errorData += chunk;
181
+ });
182
+ res.on('end', () => {
202
183
  console.error(chalk_1.default.red('Unexpected response from import status service. The import task is probably still running; check the Sitecore logs for details.'));
203
- if (error.response) {
204
- console.error(chalk_1.default.red(`Status message: ${error.response.statusText}`));
205
- console.error(chalk_1.default.red(`Status: ${error.response.status}`));
206
- }
207
- else {
208
- console.error(chalk_1.default.red(error.message));
209
- }
210
- reject();
184
+ console.error(chalk_1.default.red(`Status message: ${res.statusMessage}`));
185
+ console.error(chalk_1.default.red(`Status: ${res.statusCode}`));
186
+ process.exit(1);
211
187
  });
188
+ return;
212
189
  }
213
- setTimeout(sendJobStatusRequest, 1000);
190
+ res.on('error', (err) => {
191
+ console.error(chalk_1.default.red('Unexpected response from import status service. The import task is probably still running; check the Sitecore logs for details.'));
192
+ console.error(chalk_1.default.red(`Status message: ${err.message}`));
193
+ console.error(chalk_1.default.red(`Status: ${res.statusCode}`));
194
+ process.exit(1);
195
+ });
196
+ res.on('data', (chunk) => {
197
+ responseData += chunk;
198
+ });
199
+ res.on('end', () => {
200
+ try {
201
+ const body = JSON.parse(responseData);
202
+ const { state, messages } = body;
203
+ messages.forEach((entry) => {
204
+ logOffset++;
205
+ const entryBits = /^(\[([A-Z]+)\] )?(.+)/.exec(entry);
206
+ let entryLevel = 'INFO';
207
+ let message = entry;
208
+ if (entryBits && entryBits[2]) {
209
+ entryLevel = entryBits[2];
210
+ // 3 = '[] ' in say [INFO] My log message
211
+ // we're not using the capture group as the message might be multi-line
212
+ message = entry.substring(entryLevel.length + 3);
213
+ }
214
+ if (message.startsWith('[JSS] - ')) {
215
+ message = message.substring(8);
216
+ }
217
+ logJobStatus({ message, entryLevel, warnings, errors });
218
+ });
219
+ if (state === 'Finished') {
220
+ finishWatchJobStatusTask({ warnings, errors, resolve: () => { }, reject: () => { } });
221
+ return;
222
+ }
223
+ setTimeout(() => watchJobStatus(options, taskName), 1000);
224
+ }
225
+ catch (error) {
226
+ console.error(chalk_1.default.red(`Unexpected error processing reply from import status service: ${error}`));
227
+ console.error(chalk_1.default.red(`Response: ${responseData}`));
228
+ console.error(chalk_1.default.red('Consult the Sitecore logs for details.'));
229
+ process.exit(1);
230
+ }
231
+ });
214
232
  });
233
+ req.on('error', (err) => {
234
+ console.error(chalk_1.default.red(`Request error: ${err.message}`));
235
+ process.exit(1);
236
+ });
237
+ req.end();
215
238
  });
216
239
  }
217
240
  /**
@@ -225,7 +248,7 @@ function packageDeploy(options) {
225
248
  if (options.secret.length < 32) {
226
249
  throw new Error('Deployment secret was too short. Use a RANDOM (not words or phrases) secret at least 32 characters long.');
227
250
  }
228
- let packageFile = null;
251
+ let packageFile = '';
229
252
  fs_1.default.readdirSync(options.packagePath).forEach((file) => {
230
253
  if (file.startsWith(options.appName) && file.endsWith('.manifest.zip')) {
231
254
  packageFile = path_1.default.join(options.packagePath, file);
@@ -242,58 +265,83 @@ function packageDeploy(options) {
242
265
  console.log(`Deployment HMAC: ${(0, digest_1.hmac)(factors, options.secret)}`);
243
266
  }
244
267
  const formData = new form_data_1.default();
245
- formData.append('path', fs_1.default.createReadStream(packageFile));
268
+ const fileStream = fs_1.default.createReadStream(packageFile);
269
+ const url = new URL(options.importServiceUrl);
246
270
  formData.append('appName', options.appName);
247
- const requestBaseOptions = {
271
+ formData.append('path', fileStream);
272
+ const isHttps = options.importServiceUrl.startsWith('https');
273
+ const client = isHttps ? https_1.default : http_1.default;
274
+ const reqOptions = {
248
275
  headers: Object.assign({ 'User-Agent': 'Sitecore/JSS-Import', 'Cache-Control': 'no-cache', 'X-JSS-Auth': (0, digest_1.hmac)(factors, options.secret) }, formData.getHeaders()),
249
- dispatcher: new undici_1.ProxyAgent({
250
- uri: options.proxy ? options.proxy : '',
251
- maxRedirections: 0,
252
- connect: {
276
+ method: 'POST',
277
+ hostname: url.hostname,
278
+ protocol: url.protocol,
279
+ path: url.pathname,
280
+ agent: isHttps
281
+ ? new https_1.default.Agent({
253
282
  // we turn off normal CA cert validation when we are whitelisting a single cert thumbprint
254
283
  rejectUnauthorized: options.acceptCertificate ? false : true,
255
284
  // needed to allow whitelisting a cert thumbprint if a connection is reused
256
285
  maxCachedSessions: options.acceptCertificate ? 0 : undefined,
257
- },
258
- }),
286
+ })
287
+ : undefined,
259
288
  };
260
- console.log(`Sending package ${packageFile} to ${options.importServiceUrl}...`);
261
- return new Promise((resolve, reject) => {
262
- new sitecore_jss_1.NativeDataFetcher()
263
- .post(options.importServiceUrl, formData, requestBaseOptions)
264
- .then((response) => {
265
- const body = response.data;
266
- console.log(chalk_1.default.green(`Sitecore has accepted import task ${body}`));
267
- resolve(body);
268
- })
269
- .catch((error) => {
270
- console.error(chalk_1.default.red('Unexpected response from import service:'));
271
- if (error.response) {
272
- console.error(chalk_1.default.red(`Status message: ${error.response.statusText}`));
273
- console.error(chalk_1.default.red(`Status: ${error.response.status}`));
274
- }
275
- else {
276
- console.error(chalk_1.default.red(error.message));
277
- }
278
- reject();
289
+ if (options.proxy) {
290
+ setProxy(reqOptions, options.proxy, options.importServiceUrl);
291
+ }
292
+ const req = client.request(reqOptions);
293
+ if (https_1.default) {
294
+ applyCertPinning(req, options);
295
+ }
296
+ attachFormDataHandlers(req, formData);
297
+ formData.pipe(req);
298
+ req.on('response', (res) => {
299
+ if (res.statusCode !== 200) {
300
+ let errorData = '';
301
+ res.on('data', (chunk) => {
302
+ errorData += chunk;
303
+ });
304
+ res.on('end', () => {
305
+ console.error(chalk_1.default.red(`Error while uploading package: ${res.statusCode} ${res.statusMessage}`));
306
+ console.error(chalk_1.default.red(errorData));
307
+ process.exit(1);
308
+ });
309
+ return;
310
+ }
311
+ let responseData = '';
312
+ res.on('error', (err) => {
313
+ console.error(chalk_1.default.red(`Response error when uploading package: ${err.message}`));
314
+ process.exit(1);
315
+ });
316
+ res.on('data', (chunk) => {
317
+ responseData += chunk;
279
318
  });
280
- }).then((taskName) => watchJobStatus(options, taskName));
319
+ res.on('end', () => {
320
+ const taskName = responseData;
321
+ console.log('we are in packageDeploy', responseData);
322
+ console.log(chalk_1.default.green(`Package uploaded. Import task name: ${taskName}`));
323
+ watchJobStatus(options, taskName);
324
+ });
325
+ });
326
+ req.on('error', (err) => {
327
+ console.error(chalk_1.default.red(`Error while uploading package: ${err.message}`));
328
+ process.exit(1);
329
+ });
281
330
  });
282
331
  }
283
332
  /**
284
333
  * Creates valid proxy object
285
- * @param {string} [proxy] proxy url
334
+ * @param {RequestOptions} reqOptions
335
+ * @param {string} proxy proxy url
336
+ * @param {string} targetUrl target url
286
337
  */
287
- function extractProxy(proxy) {
288
- if (!proxy)
289
- return undefined;
338
+ function setProxy(reqOptions, proxy, targetUrl) {
290
339
  try {
291
340
  const proxyUrl = new URL(proxy);
292
- return {
293
- protocol: proxyUrl.protocol.slice(0, -1),
294
- host: proxyUrl.hostname,
295
- port: +proxyUrl.port,
296
- };
341
+ reqOptions.hostname = proxyUrl.hostname;
342
+ reqOptions.port = proxyUrl.port || (proxyUrl.protocol === 'https:' ? '443' : '80');
343
+ reqOptions.protocol = proxyUrl.protocol;
344
+ reqOptions.path = targetUrl;
297
345
  }
298
346
  catch (error) {
299
347
  console.error(chalk_1.default.red(`Invalid proxy url provided ${proxy}`));
@@ -301,13 +349,24 @@ function extractProxy(proxy) {
301
349
  }
302
350
  }
303
351
  /**
304
- * Provides way to customize request adapter
305
- * @param {PackageDeployOptions} options
352
+ * Attach form data handlers to handle errors and close events
353
+ * @param {ClientRequest} req request object
354
+ * @param {FormData} formData FormData object
306
355
  */
307
- function getHttpsTransport(options) {
308
- return Object.assign(Object.assign({}, https_1.default), { request: (reqOptions, callback) => {
309
- const req = https_1.default.request(Object.assign({}, reqOptions), callback);
310
- applyCertPinning(req, options);
311
- return req;
312
- } });
356
+ function attachFormDataHandlers(req, formData) {
357
+ let ended = false;
358
+ let errored = false;
359
+ formData.on('end', () => {
360
+ ended = true;
361
+ });
362
+ formData.once('error', (err) => {
363
+ errored = true;
364
+ console.log('Error when uploading package:', err);
365
+ req.destroy(err);
366
+ });
367
+ formData.on('close', () => {
368
+ if (!ended && !errored) {
369
+ new Error('Request stream has been aborted');
370
+ }
371
+ });
313
372
  }
@@ -10,11 +10,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  import chalk from 'chalk';
11
11
  import fs from 'fs';
12
12
  import https from 'https';
13
+ import http from 'http';
13
14
  import path from 'path';
14
15
  import FormData from 'form-data';
15
16
  import { digest, hmac } from './digest';
16
- import { NativeDataFetcher } from '@sitecore-jss/sitecore-jss';
17
- import { ProxyAgent } from 'undici';
18
17
  // Node does not use system level trusted CAs. This causes issues because SIF likes to install
19
18
  // using a Windows trusted CA - so SSL connections to Sitecore will fail from Node.
20
19
  // If the options.acceptCertificate is passed, we disable normal SSL validation and use this function
@@ -119,86 +118,109 @@ export function logJobStatus({ message, entryLevel, warnings, errors, }) {
119
118
  * @param {PackageDeployOptions} options
120
119
  * @param {string} taskName
121
120
  */
122
- function watchJobStatus(options, taskName) {
121
+ export function watchJobStatus(options, taskName) {
123
122
  return __awaiter(this, void 0, void 0, function* () {
124
123
  let logOffset = 0;
125
124
  const errors = [];
126
125
  const warnings = [];
127
126
  const factors = [options.appName, taskName, `${options.importServiceUrl}/status`];
128
127
  const mac = hmac(factors, options.secret);
129
- const requestBaseOptions = {
128
+ const url = new URL(`${options.importServiceUrl}/status?appName=${options.appName}&jobName=${taskName}&after=${logOffset}`);
129
+ const isHttps = options.importServiceUrl.startsWith('https');
130
+ const client = isHttps ? https : http;
131
+ const reqOptions = {
130
132
  headers: {
131
133
  'User-Agent': 'Sitecore/JSS-Import',
132
134
  'Cache-Control': 'no-cache',
133
135
  'X-JSS-Auth': mac,
134
136
  },
135
- dispatcher: new ProxyAgent({
136
- uri: options.proxy || '',
137
- maxRedirections: 0,
138
- connect: {
137
+ method: 'GET',
138
+ hostname: url.hostname,
139
+ protocol: url.protocol,
140
+ path: url.pathname + url.search,
141
+ agent: isHttps
142
+ ? new https.Agent({
143
+ // we turn off normal CA cert validation when we are whitelisting a single cert thumbprint
139
144
  rejectUnauthorized: options.acceptCertificate ? false : true,
145
+ // needed to allow whitelisting a cert thumbprint if a connection is reused
140
146
  maxCachedSessions: options.acceptCertificate ? 0 : undefined,
141
- },
142
- }),
147
+ })
148
+ : undefined,
143
149
  };
144
150
  if (options.debugSecurity) {
145
151
  console.log(`Deployment status security factors: ${factors}`);
146
152
  console.log(`Deployment status HMAC: ${mac}`);
147
153
  }
148
- return new Promise((resolve, reject) => {
149
- /**
150
- * Send job status request
151
- */
152
- function sendJobStatusRequest() {
153
- new NativeDataFetcher()
154
- .get(`${options.importServiceUrl}/status?appName=${options.appName}&jobName=${taskName}&after=${logOffset}`, requestBaseOptions)
155
- .then((response) => {
156
- const body = response.data;
157
- try {
158
- const { state, messages } = body.data;
159
- messages.forEach((entry) => {
160
- logOffset++;
161
- const entryBits = /^(\[([A-Z]+)\] )?(.+)/.exec(entry);
162
- let entryLevel = 'INFO';
163
- let message = entry;
164
- if (entryBits && entryBits[2]) {
165
- entryLevel = entryBits[2];
166
- // 3 = '[] ' in say [INFO] My log message
167
- // we're not using the capture group as the message might be multi-line
168
- message = entry.substring(entryLevel.length + 3);
169
- }
170
- if (message.startsWith('[JSS] - ')) {
171
- message = message.substring(8);
172
- }
173
- logJobStatus({ message, entryLevel, warnings, errors });
174
- });
175
- if (state === 'Finished') {
176
- finishWatchJobStatusTask({ warnings, errors, resolve, reject });
177
- return;
178
- }
179
- setTimeout(sendJobStatusRequest, 1000);
180
- }
181
- catch (error) {
182
- console.error(chalk.red(`Unexpected error processing reply from import status service: ${error}`));
183
- console.error(chalk.red(`Response: ${body}`));
184
- console.error(chalk.red('Consult the Sitecore logs for details.'));
185
- reject(error);
186
- }
187
- })
188
- .catch((error) => {
154
+ if (options.proxy) {
155
+ setProxy(reqOptions, options.proxy, options.importServiceUrl);
156
+ }
157
+ const req = client.request(reqOptions);
158
+ if (isHttps) {
159
+ applyCertPinning(req, options);
160
+ }
161
+ let responseData = '';
162
+ req.on('response', (res) => {
163
+ if (res.statusCode !== 200) {
164
+ let errorData = '';
165
+ res.on('data', (chunk) => {
166
+ errorData += chunk;
167
+ });
168
+ res.on('end', () => {
189
169
  console.error(chalk.red('Unexpected response from import status service. The import task is probably still running; check the Sitecore logs for details.'));
190
- if (error.response) {
191
- console.error(chalk.red(`Status message: ${error.response.statusText}`));
192
- console.error(chalk.red(`Status: ${error.response.status}`));
193
- }
194
- else {
195
- console.error(chalk.red(error.message));
196
- }
197
- reject();
170
+ console.error(chalk.red(`Status message: ${res.statusMessage}`));
171
+ console.error(chalk.red(`Status: ${res.statusCode}`));
172
+ process.exit(1);
198
173
  });
174
+ return;
199
175
  }
200
- setTimeout(sendJobStatusRequest, 1000);
176
+ res.on('error', (err) => {
177
+ console.error(chalk.red('Unexpected response from import status service. The import task is probably still running; check the Sitecore logs for details.'));
178
+ console.error(chalk.red(`Status message: ${err.message}`));
179
+ console.error(chalk.red(`Status: ${res.statusCode}`));
180
+ process.exit(1);
181
+ });
182
+ res.on('data', (chunk) => {
183
+ responseData += chunk;
184
+ });
185
+ res.on('end', () => {
186
+ try {
187
+ const body = JSON.parse(responseData);
188
+ const { state, messages } = body;
189
+ messages.forEach((entry) => {
190
+ logOffset++;
191
+ const entryBits = /^(\[([A-Z]+)\] )?(.+)/.exec(entry);
192
+ let entryLevel = 'INFO';
193
+ let message = entry;
194
+ if (entryBits && entryBits[2]) {
195
+ entryLevel = entryBits[2];
196
+ // 3 = '[] ' in say [INFO] My log message
197
+ // we're not using the capture group as the message might be multi-line
198
+ message = entry.substring(entryLevel.length + 3);
199
+ }
200
+ if (message.startsWith('[JSS] - ')) {
201
+ message = message.substring(8);
202
+ }
203
+ logJobStatus({ message, entryLevel, warnings, errors });
204
+ });
205
+ if (state === 'Finished') {
206
+ finishWatchJobStatusTask({ warnings, errors, resolve: () => { }, reject: () => { } });
207
+ return;
208
+ }
209
+ setTimeout(() => watchJobStatus(options, taskName), 1000);
210
+ }
211
+ catch (error) {
212
+ console.error(chalk.red(`Unexpected error processing reply from import status service: ${error}`));
213
+ console.error(chalk.red(`Response: ${responseData}`));
214
+ console.error(chalk.red('Consult the Sitecore logs for details.'));
215
+ process.exit(1);
216
+ }
217
+ });
201
218
  });
219
+ req.on('error', (err) => {
220
+ console.error(chalk.red(`Request error: ${err.message}`));
221
+ process.exit(1);
222
+ });
223
+ req.end();
202
224
  });
203
225
  }
204
226
  /**
@@ -212,7 +234,7 @@ export function packageDeploy(options) {
212
234
  if (options.secret.length < 32) {
213
235
  throw new Error('Deployment secret was too short. Use a RANDOM (not words or phrases) secret at least 32 characters long.');
214
236
  }
215
- let packageFile = null;
237
+ let packageFile = '';
216
238
  fs.readdirSync(options.packagePath).forEach((file) => {
217
239
  if (file.startsWith(options.appName) && file.endsWith('.manifest.zip')) {
218
240
  packageFile = path.join(options.packagePath, file);
@@ -229,58 +251,83 @@ export function packageDeploy(options) {
229
251
  console.log(`Deployment HMAC: ${hmac(factors, options.secret)}`);
230
252
  }
231
253
  const formData = new FormData();
232
- formData.append('path', fs.createReadStream(packageFile));
254
+ const fileStream = fs.createReadStream(packageFile);
255
+ const url = new URL(options.importServiceUrl);
233
256
  formData.append('appName', options.appName);
234
- const requestBaseOptions = {
257
+ formData.append('path', fileStream);
258
+ const isHttps = options.importServiceUrl.startsWith('https');
259
+ const client = isHttps ? https : http;
260
+ const reqOptions = {
235
261
  headers: Object.assign({ 'User-Agent': 'Sitecore/JSS-Import', 'Cache-Control': 'no-cache', 'X-JSS-Auth': hmac(factors, options.secret) }, formData.getHeaders()),
236
- dispatcher: new ProxyAgent({
237
- uri: options.proxy ? options.proxy : '',
238
- maxRedirections: 0,
239
- connect: {
262
+ method: 'POST',
263
+ hostname: url.hostname,
264
+ protocol: url.protocol,
265
+ path: url.pathname,
266
+ agent: isHttps
267
+ ? new https.Agent({
240
268
  // we turn off normal CA cert validation when we are whitelisting a single cert thumbprint
241
269
  rejectUnauthorized: options.acceptCertificate ? false : true,
242
270
  // needed to allow whitelisting a cert thumbprint if a connection is reused
243
271
  maxCachedSessions: options.acceptCertificate ? 0 : undefined,
244
- },
245
- }),
272
+ })
273
+ : undefined,
246
274
  };
247
- console.log(`Sending package ${packageFile} to ${options.importServiceUrl}...`);
248
- return new Promise((resolve, reject) => {
249
- new NativeDataFetcher()
250
- .post(options.importServiceUrl, formData, requestBaseOptions)
251
- .then((response) => {
252
- const body = response.data;
253
- console.log(chalk.green(`Sitecore has accepted import task ${body}`));
254
- resolve(body);
255
- })
256
- .catch((error) => {
257
- console.error(chalk.red('Unexpected response from import service:'));
258
- if (error.response) {
259
- console.error(chalk.red(`Status message: ${error.response.statusText}`));
260
- console.error(chalk.red(`Status: ${error.response.status}`));
261
- }
262
- else {
263
- console.error(chalk.red(error.message));
264
- }
265
- reject();
275
+ if (options.proxy) {
276
+ setProxy(reqOptions, options.proxy, options.importServiceUrl);
277
+ }
278
+ const req = client.request(reqOptions);
279
+ if (https) {
280
+ applyCertPinning(req, options);
281
+ }
282
+ attachFormDataHandlers(req, formData);
283
+ formData.pipe(req);
284
+ req.on('response', (res) => {
285
+ if (res.statusCode !== 200) {
286
+ let errorData = '';
287
+ res.on('data', (chunk) => {
288
+ errorData += chunk;
289
+ });
290
+ res.on('end', () => {
291
+ console.error(chalk.red(`Error while uploading package: ${res.statusCode} ${res.statusMessage}`));
292
+ console.error(chalk.red(errorData));
293
+ process.exit(1);
294
+ });
295
+ return;
296
+ }
297
+ let responseData = '';
298
+ res.on('error', (err) => {
299
+ console.error(chalk.red(`Response error when uploading package: ${err.message}`));
300
+ process.exit(1);
301
+ });
302
+ res.on('data', (chunk) => {
303
+ responseData += chunk;
266
304
  });
267
- }).then((taskName) => watchJobStatus(options, taskName));
305
+ res.on('end', () => {
306
+ const taskName = responseData;
307
+ console.log('we are in packageDeploy', responseData);
308
+ console.log(chalk.green(`Package uploaded. Import task name: ${taskName}`));
309
+ watchJobStatus(options, taskName);
310
+ });
311
+ });
312
+ req.on('error', (err) => {
313
+ console.error(chalk.red(`Error while uploading package: ${err.message}`));
314
+ process.exit(1);
315
+ });
268
316
  });
269
317
  }
270
318
  /**
271
319
  * Creates valid proxy object
272
- * @param {string} [proxy] proxy url
320
+ * @param {RequestOptions} reqOptions
321
+ * @param {string} proxy proxy url
322
+ * @param {string} targetUrl target url
273
323
  */
274
- export function extractProxy(proxy) {
275
- if (!proxy)
276
- return undefined;
324
+ export function setProxy(reqOptions, proxy, targetUrl) {
277
325
  try {
278
326
  const proxyUrl = new URL(proxy);
279
- return {
280
- protocol: proxyUrl.protocol.slice(0, -1),
281
- host: proxyUrl.hostname,
282
- port: +proxyUrl.port,
283
- };
327
+ reqOptions.hostname = proxyUrl.hostname;
328
+ reqOptions.port = proxyUrl.port || (proxyUrl.protocol === 'https:' ? '443' : '80');
329
+ reqOptions.protocol = proxyUrl.protocol;
330
+ reqOptions.path = targetUrl;
284
331
  }
285
332
  catch (error) {
286
333
  console.error(chalk.red(`Invalid proxy url provided ${proxy}`));
@@ -288,13 +335,24 @@ export function extractProxy(proxy) {
288
335
  }
289
336
  }
290
337
  /**
291
- * Provides way to customize request adapter
292
- * @param {PackageDeployOptions} options
338
+ * Attach form data handlers to handle errors and close events
339
+ * @param {ClientRequest} req request object
340
+ * @param {FormData} formData FormData object
293
341
  */
294
- export function getHttpsTransport(options) {
295
- return Object.assign(Object.assign({}, https), { request: (reqOptions, callback) => {
296
- const req = https.request(Object.assign({}, reqOptions), callback);
297
- applyCertPinning(req, options);
298
- return req;
299
- } });
342
+ export function attachFormDataHandlers(req, formData) {
343
+ let ended = false;
344
+ let errored = false;
345
+ formData.on('end', () => {
346
+ ended = true;
347
+ });
348
+ formData.once('error', (err) => {
349
+ errored = true;
350
+ console.log('Error when uploading package:', err);
351
+ req.destroy(err);
352
+ });
353
+ formData.on('close', () => {
354
+ if (!ended && !errored) {
355
+ new Error('Request stream has been aborted');
356
+ }
357
+ });
300
358
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sitecore-jss/sitecore-jss-dev-tools",
3
- "version": "22.4.0-canary.14",
3
+ "version": "22.4.0-canary.16",
4
4
  "description": "Utilities to assist in the development and deployment of Sitecore JSS apps.",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@babel/parser": "^7.24.0",
36
- "@sitecore-jss/sitecore-jss": "^22.4.0-canary.14",
36
+ "@sitecore-jss/sitecore-jss": "^22.4.0-canary.16",
37
37
  "chalk": "^4.1.2",
38
38
  "chokidar": "^3.6.0",
39
39
  "del": "^6.0.0",
@@ -50,7 +50,6 @@
50
50
  "recast": "^0.23.5",
51
51
  "resolve": "^1.22.1",
52
52
  "ts-node": "^10.9.1",
53
- "undici": "^7.2.1",
54
53
  "url-join": "^4.0.1",
55
54
  "uuid": "^9.0.0",
56
55
  "yargs": "^17.6.2"
@@ -86,7 +85,7 @@
86
85
  "typescript": "~5.6.3"
87
86
  },
88
87
  "types": "types/index.d.ts",
89
- "gitHead": "d07212049058fdd49b82f92715f88699eee350e8",
88
+ "gitHead": "61be81c2be989d2735a841f1792e32a4467ffe24",
90
89
  "files": [
91
90
  "dist",
92
91
  "types",
@@ -1,5 +1,6 @@
1
- import https from 'https';
2
- import { ClientRequest, IncomingMessage } from 'http';
1
+ import { RequestOptions } from 'https';
2
+ import FormData from 'form-data';
3
+ import { ClientRequest } from 'http';
3
4
  export interface PackageDeployOptions {
4
5
  packagePath: string;
5
6
  appName: string;
@@ -51,28 +52,23 @@ export declare function logJobStatus({ message, entryLevel, warnings, errors, }:
51
52
  }): void;
52
53
  /**
53
54
  * @param {PackageDeployOptions} options
55
+ * @param {string} taskName
54
56
  */
55
- export declare function packageDeploy(options: PackageDeployOptions): Promise<unknown>;
57
+ export declare function watchJobStatus(options: PackageDeployOptions, taskName: string): Promise<void>;
58
+ /**
59
+ * @param {PackageDeployOptions} options
60
+ */
61
+ export declare function packageDeploy(options: PackageDeployOptions): Promise<void>;
56
62
  /**
57
63
  * Creates valid proxy object
58
- * @param {string} [proxy] proxy url
64
+ * @param {RequestOptions} reqOptions
65
+ * @param {string} proxy proxy url
66
+ * @param {string} targetUrl target url
59
67
  */
60
- export declare function extractProxy(proxy?: string): {
61
- protocol: string;
62
- host: string;
63
- port: number;
64
- } | undefined;
68
+ export declare function setProxy(reqOptions: RequestOptions, proxy: string, targetUrl: string): void;
65
69
  /**
66
- * Provides way to customize request adapter
67
- * @param {PackageDeployOptions} options
70
+ * Attach form data handlers to handle errors and close events
71
+ * @param {ClientRequest} req request object
72
+ * @param {FormData} formData FormData object
68
73
  */
69
- export declare function getHttpsTransport(options: PackageDeployOptions): {
70
- request: (reqOptions: https.RequestOptions, callback: (res: IncomingMessage) => void) => ClientRequest;
71
- createServer<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof import("http").ServerResponse = typeof import("http").ServerResponse>(requestListener?: import("http").RequestListener<Request, Response>): https.Server<Request, Response>;
72
- createServer<Request extends typeof IncomingMessage = typeof IncomingMessage, Response extends typeof import("http").ServerResponse = typeof import("http").ServerResponse>(options: https.ServerOptions<Request, Response>, requestListener?: import("http").RequestListener<Request, Response>): https.Server<Request, Response>;
73
- get(options: https.RequestOptions | string | import("url").URL, callback?: (res: IncomingMessage) => void): ClientRequest;
74
- get(url: string | import("url").URL, options: https.RequestOptions, callback?: (res: IncomingMessage) => void): ClientRequest;
75
- Agent: typeof https.Agent;
76
- Server: typeof https.Server;
77
- globalAgent: https.Agent;
78
- };
74
+ export declare function attachFormDataHandlers(req: ClientRequest, formData: FormData): void;