@bigcommerce/stencil-cli 3.8.3 → 3.9.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/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ### 3.9.1 (2022-01-25)
2
+
3
+ - fix: strf-9612 Fix stencil pull when there is 1 channel ([859](https://github.com/bigcommerce/stencil-cli/pull/859))
4
+
5
+ ### 3.9.0 (2022-01-21)
6
+
7
+ - fix: return channel id as an iterable when a store only has a single storefront ([849](https://github.com/bigcommerce/stencil-cli/pull/849))
8
+ - feat: warn npm users if npm is above 7 version ([846](https://github.com/bigcommerce/stencil-cli/pull/846))
9
+
10
+ ### 3.8.5 (2022-01-18)
11
+
12
+ - fix(storefront): strf-9594 loosed frontmatter refex ([841](https://github.com/bigcommerce/stencil-cli/pull/841))
13
+
14
+ ### 3.8.4 (2022-01-13)
15
+
16
+ - fix: `stencil init` command including `apiHost` option is now recognized ([830](https://github.com/bigcommerce/stencil-cli/pull/830))
17
+ - feat: `stencil push` command allows to push a theme to multiple channels ([825](https://github.com/bigcommerce/stencil-cli/pull/825))
18
+
1
19
  ### 3.8.3 (2022-01-12)
2
20
 
3
21
  - fix: add activate sass engine name logic ([837](https://github.com/bigcommerce/stencil-cli/pull/837))
package/README.md CHANGED
@@ -56,7 +56,9 @@ This is useful for tracking your changes in your Theme, and is the tool we use t
56
56
 
57
57
  Run `stencil push` to bundle the local theme and upload it to your store, so it will be available in My Themes.
58
58
  To push the theme and also activate it, use `stencil push -a`. To automatically delete the oldest theme if you are at
59
- your theme limit, use `stencil push -d`. These can be used together, as `stencil push -a -d`.
59
+ your theme limit, use `stencil push -d`. These can be used together, as `stencil push -a -d`. You can apply the theme to
60
+ multiple storefronts, just specify ids of desired storefronts/channels after `-c` option `stencil push -a -c 123 456 789`.
61
+ If you want to apply theme to all available storefronts, just use `-allc` option: `stencil push -a -allc`.
60
62
 
61
63
  Run `stencil pull` to sync changes to your theme configuration from your live store. For example, if Page Builder has
62
64
  been used to change certain theme settings, this will update those settings in config.json in your theme files so you
@@ -11,6 +11,7 @@ program
11
11
  .option('-u, --url [url]', 'Store URL')
12
12
  .option('-t, --token [token]', 'Access Token')
13
13
  .option('-p, --port [port]', 'Port')
14
+ .option('-h, --apiHost [host]', 'API Host')
14
15
  .parse(process.argv);
15
16
 
16
17
  checkNodeVersion();
@@ -22,5 +23,6 @@ new StencilInit()
22
23
  normalStoreUrl: cliOptions.url,
23
24
  accessToken: cliOptions.token,
24
25
  port: cliOptions.port,
26
+ apiHost: cliOptions.apiHost,
25
27
  })
26
28
  .catch(printCliResultErrorAndExit);
@@ -15,10 +15,10 @@ program
15
15
  .option('-a, --activate [variationname]', 'specify the variation of the theme to activate')
16
16
  .option('-d, --delete', 'delete oldest private theme if upload limit reached')
17
17
  .option(
18
- '-c, --channel_id [channelId]',
19
- 'specify the channel ID of the storefront to push the theme to',
20
- parseInt,
18
+ '-c, --channel_ids <channelIds...>',
19
+ 'specify the channel IDs of the storefront to push the theme to',
21
20
  )
21
+ .option('-allc, --all_channels', 'push a theme to all available channels')
22
22
  .parse(process.argv);
23
23
 
24
24
  checkNodeVersion();
@@ -26,11 +26,12 @@ checkNodeVersion();
26
26
  const cliOptions = program.opts();
27
27
  const options = {
28
28
  apiHost: cliOptions.host,
29
- channelId: cliOptions.channel_id,
29
+ channelIds: cliOptions.channel_ids,
30
30
  bundleZipPath: cliOptions.file,
31
31
  activate: cliOptions.activate,
32
32
  saveBundleName: cliOptions.save,
33
33
  deleteOldest: cliOptions.delete,
34
+ allChannels: cliOptions.all_channels,
34
35
  };
35
36
  stencilPush(options, (err, result) => {
36
37
  if (err) {
package/constants.js CHANGED
@@ -15,17 +15,11 @@ const DEFAULT_CUSTOM_LAYOUTS_CONFIG = {
15
15
 
16
16
  /// ///////////////////////////////////////// Other //////////////////////////////////////// ///
17
17
 
18
- const DEV_API_HOST = 'https://api.service.bcdev';
19
- const INTG_API_HOST = 'https://api.integration.zone';
20
- const STG_API_HOST = 'https://api.staging.zone';
21
18
  const API_HOST = 'https://api.bigcommerce.com';
22
19
 
23
20
  module.exports = {
24
21
  PACKAGE_INFO,
25
22
  THEME_PATH,
26
23
  DEFAULT_CUSTOM_LAYOUTS_CONFIG,
27
- DEV_API_HOST,
28
- INTG_API_HOST,
29
- STG_API_HOST,
30
24
  API_HOST,
31
25
  };
@@ -4,13 +4,7 @@ const inquirerModule = require('inquirer');
4
4
 
5
5
  const serverConfigModule = require('../server/config');
6
6
  const StencilConfigManager = require('./StencilConfigManager');
7
- const {
8
- DEFAULT_CUSTOM_LAYOUTS_CONFIG,
9
- API_HOST,
10
- INTG_API_HOST,
11
- STG_API_HOST,
12
- DEV_API_HOST,
13
- } = require('../constants');
7
+ const { DEFAULT_CUSTOM_LAYOUTS_CONFIG, API_HOST } = require('../constants');
14
8
 
15
9
  class StencilInit {
16
10
  /**
@@ -71,20 +65,21 @@ class StencilInit {
71
65
  }
72
66
 
73
67
  /**
74
- * @param {{port: (number), normalStoreUrl: (string), accessToken: (string)}} stencilConfig
75
- * @returns {{port: (number), normalStoreUrl: (string), accessToken: (string)}}
68
+ * @param {{port: (number), normalStoreUrl: (string), accessToken: (string), apiHost: (string)}} stencilConfig
69
+ * @returns {{port: (number), normalStoreUrl: (string), accessToken: (string), apiHost: (string)}}
76
70
  */
77
71
  getDefaultAnswers(stencilConfig) {
78
72
  return {
79
73
  normalStoreUrl: stencilConfig.normalStoreUrl,
80
74
  accessToken: stencilConfig.accessToken,
81
75
  port: stencilConfig.port || this._serverConfig.get('/server/port'),
76
+ apiHost: API_HOST,
82
77
  };
83
78
  }
84
79
 
85
80
  /**
86
- * @param {{port: (number), normalStoreUrl: (string), accessToken: (string)}} defaultAnswers
87
- * @param {{port: (number), normalStoreUrl: (string), accessToken: (string)}} cliOptions
81
+ * @param {{port: (number), normalStoreUrl: (string), accessToken: (string), apiHost: (string)}} defaultAnswers
82
+ * @param {{port: (number), normalStoreUrl: (string), accessToken: (string), apiHost: (string)}} cliOptions
88
83
  * @returns {{object[]}}
89
84
  */
90
85
  getQuestions(defaultAnswers, cliOptions) {
@@ -110,6 +105,15 @@ class StencilInit {
110
105
  });
111
106
  }
112
107
 
108
+ if (!cliOptions.apiHost) {
109
+ prompts.push({
110
+ type: 'input',
111
+ name: 'apiHost',
112
+ message: 'What API Host would you like to use?',
113
+ default: defaultAnswers.apiHost,
114
+ });
115
+ }
116
+
113
117
  if (!cliOptions.port) {
114
118
  prompts.push({
115
119
  type: 'input',
@@ -139,27 +143,12 @@ class StencilInit {
139
143
  return questions.length ? this._inquirer.prompt(questions) : {};
140
144
  }
141
145
 
142
- apiHostFromStoreUrl(storeUrl) {
143
- let host = null;
144
- if (storeUrl !== undefined) {
145
- if (storeUrl.includes('service.bcdev')) {
146
- host = DEV_API_HOST;
147
- }
148
- if (storeUrl.includes('my-integration.zone')) {
149
- host = INTG_API_HOST;
150
- }
151
- if (storeUrl.includes('my-staging.zone')) {
152
- host = STG_API_HOST;
153
- }
154
- }
155
-
156
- if (host === null) {
157
- host = API_HOST;
158
- }
146
+ updateApiHost(stencilConfig, cliOptions) {
147
+ const host = cliOptions.apiHost || API_HOST;
159
148
 
160
149
  console.log('Set API host to: ' + host);
161
150
 
162
- return host;
151
+ return { ...stencilConfig, apiHost: host };
163
152
  }
164
153
 
165
154
  /**
@@ -169,13 +158,10 @@ class StencilInit {
169
158
  * @returns {object}
170
159
  */
171
160
  applyAnswers(stencilConfig, answers, cliOptions) {
172
- const storeUrl =
173
- answers.normalStoreUrl || cliOptions.normalStoreUrl || stencilConfig.normalStoreUrl;
174
-
161
+ const config = this.updateApiHost(stencilConfig, cliOptions);
175
162
  return {
176
163
  customLayouts: DEFAULT_CUSTOM_LAYOUTS_CONFIG,
177
- apiHost: this.apiHostFromStoreUrl(storeUrl),
178
- ...stencilConfig,
164
+ ...config,
179
165
  ...cliOptions,
180
166
  ...answers,
181
167
  };
@@ -18,7 +18,7 @@ function stencilPush(options = {}, callback) {
18
18
  utils.pollForJobCompletion((data) => ({ themeId: data.theme_id })),
19
19
  utils.promptUserWhetherToApplyTheme,
20
20
  utils.getChannels,
21
- utils.promptUserForChannel,
21
+ utils.promptUserForChannels,
22
22
  utils.getVariations,
23
23
  utils.promptUserForVariation,
24
24
  utils.requestToApplyVariationWithRetrys(),
@@ -275,13 +275,14 @@ utils.getChannels = async (options) => {
275
275
  const {
276
276
  config: { accessToken },
277
277
  channelId,
278
+ channelIds,
278
279
  storeHash,
279
280
  applyTheme,
280
281
  } = options;
281
282
 
282
283
  const apiHost = options.apiHost || options.config.apiHost;
283
284
 
284
- if (!applyTheme || channelId) {
285
+ if (!applyTheme || channelIds || channelId) {
285
286
  return options;
286
287
  }
287
288
 
@@ -338,6 +339,43 @@ utils.getVariations = async (options) => {
338
339
  return { ...options, variations };
339
340
  };
340
341
 
342
+ utils.promptUserForChannels = async (options) => {
343
+ const { applyTheme, channelIds, channels, allChannels } = options;
344
+
345
+ if (!applyTheme || channelIds) {
346
+ return options;
347
+ }
348
+
349
+ if (allChannels) {
350
+ const allIds = channels.map((chanel) => chanel.channel_id);
351
+ return { ...options, channelIds: allIds };
352
+ }
353
+
354
+ const selectedChannelIds = await utils.promptUserToSelectChannels(channels);
355
+ return { ...options, channelIds: selectedChannelIds };
356
+ };
357
+
358
+ utils.promptUserToSelectChannels = async (channels) => {
359
+ if (channels.length < 2) {
360
+ return [channels[0].channel_id];
361
+ }
362
+
363
+ const questions = [
364
+ {
365
+ type: 'checkbox',
366
+ name: 'channelIds',
367
+ message: 'Which channel(s) would you like to use?',
368
+ choices: channels.map((channel) => ({
369
+ name: channel.url,
370
+ value: channel.channel_id,
371
+ })),
372
+ },
373
+ ];
374
+
375
+ const answer = await Inquirer.prompt(questions);
376
+ return answer.channelIds;
377
+ };
378
+
341
379
  utils.promptUserForChannel = async (options) => {
342
380
  const { applyTheme, channelId, channels } = options;
343
381
 
@@ -414,18 +452,18 @@ utils.requestToApplyVariation = async (options) => {
414
452
  config: { accessToken },
415
453
  storeHash,
416
454
  variationId,
417
- channelId,
455
+ channelIds,
418
456
  } = options;
419
457
 
420
458
  const apiHost = options.apiHost || options.config.apiHost;
421
459
 
422
460
  if (options.applyTheme) {
423
461
  await themeApiClient.activateThemeByVariationId({
424
- accessToken,
462
+ variationId,
463
+ channelIds,
425
464
  apiHost,
426
465
  storeHash,
427
- variationId,
428
- channelId,
466
+ accessToken,
429
467
  });
430
468
  }
431
469
 
@@ -65,33 +65,36 @@ async function checkCliVersion({ storeUrl, currentCliVersion = PACKAGE_INFO.vers
65
65
  /**
66
66
  * @param {object} options
67
67
  * @param {string} options.variationId
68
- * @param {string} options.channelId
68
+ * @param {array} options.channelIds
69
69
  * @param {string} options.apiHost
70
70
  * @param {string} options.storeHash
71
71
  * @param {string} options.accessToken
72
- * @returns {Promise<any>}
72
+ * @returns {Promise<Object[]>}
73
73
  */
74
74
  async function activateThemeByVariationId({
75
75
  variationId,
76
- channelId,
76
+ channelIds,
77
77
  apiHost,
78
78
  storeHash,
79
79
  accessToken,
80
80
  }) {
81
81
  try {
82
- return await networkUtils.sendApiRequest({
83
- url: `${apiHost}/stores/${storeHash}/v3/themes/actions/activate`,
84
- headers: {
85
- 'content-type': 'application/json',
86
- },
87
- method: 'POST',
88
- accessToken,
89
- data: {
90
- variation_id: variationId,
91
- channel_id: channelId,
92
- which: 'original',
93
- },
94
- });
82
+ const promises = channelIds.map((id) =>
83
+ networkUtils.sendApiRequest({
84
+ url: `${apiHost}/stores/${storeHash}/v3/themes/actions/activate`,
85
+ headers: {
86
+ 'content-type': 'application/json',
87
+ },
88
+ method: 'POST',
89
+ accessToken,
90
+ data: {
91
+ variation_id: variationId,
92
+ channel_id: Number(id),
93
+ which: 'original',
94
+ },
95
+ }),
96
+ );
97
+ return Promise.all(promises);
95
98
  } catch (err) {
96
99
  err.name =
97
100
  err.response && err.response.status === 504
@@ -1,4 +1,4 @@
1
- const frontmatterRegex = /---\r?\n(?:.|\s)*?\r?\n---\r?\n/g;
1
+ const frontmatterRegex = /---[\s\S]+?---/g;
2
2
 
3
3
  /**
4
4
  *
package/package-lock.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bigcommerce/stencil-cli",
3
- "version": "3.8.3",
3
+ "version": "3.9.1",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@bigcommerce/stencil-cli",
3
- "version": "3.8.3",
3
+ "version": "3.9.1",
4
4
  "description": "CLI tool to run BigCommerce Stores locally for theme development.",
5
5
  "main": "index.js",
6
6
  "engines": {
7
- "node": "^10.17 || ^12"
7
+ "node": "^10.17 || ^12",
8
+ "npm": ">=v6.11.3 <7.0.0"
8
9
  },
9
10
  "scripts": {
10
11
  "lint": "eslint . && prettier --check . --ignore-unknown",