Skip to content

Commit 030b82f

Browse files
committed
feat: strf-9258 Stencil Pull: Provide an activate option
1 parent dae631a commit 030b82f

File tree

8 files changed

+369
-116
lines changed

8 files changed

+369
-116
lines changed

bin/stencil-pull.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require('colors');
44

55
const { PACKAGE_INFO, API_HOST } = require('../constants');
66
const program = require('../lib/commander');
7-
const stencilPull = require('../lib/stencil-pull');
7+
const StencilPull = require('../lib/stencil-pull');
88
const { checkNodeVersion } = require('../lib/cliCommon');
99
const { printCliResultErrorAndExit } = require('../lib/cliCommon');
1010

@@ -22,6 +22,7 @@ program
2222
'specify the channel ID of the storefront to pull configuration from',
2323
parseInt,
2424
)
25+
.option('-a, --activate [variationname]', 'specify the variation of the theme to activate')
2526
.parse(process.argv);
2627

2728
checkNodeVersion();
@@ -32,11 +33,8 @@ const options = {
3233
saveConfigName: cliOptions.filename,
3334
channelId: cliOptions.channel_id,
3435
saved: cliOptions.saved || false,
35-
applyTheme: true, // fix to be compatible with stencil-push.utils
36+
applyTheme: true,
37+
activate: cliOptions.activate,
3638
};
3739

38-
stencilPull(options, (err) => {
39-
if (err) {
40-
printCliResultErrorAndExit(err);
41-
}
42-
});
40+
new StencilPull().run(options).catch(printCliResultErrorAndExit);

bin/stencil-start.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ program
1010
.version(PACKAGE_INFO.version)
1111
.option('-o, --open', 'Automatically open default browser')
1212
.option('-v, --variation [name]', 'Set which theme variation to use while developing')
13-
.option('-c, --channelId [channelId]', 'Set the channel id for the storefront')
13+
.option('-c, --channelId [channelId]', 'Set the channel id for the storefront', parseInt)
1414
.option('--host [hostname]', 'specify the api host')
1515
.option(
1616
'--tunnel [name]',

lib/stencil-pull.js

Lines changed: 206 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,208 @@
1-
const async = require('async');
2-
const stencilPushUtils = require('./stencil-push.utils');
3-
const stencilPullUtils = require('./stencil-pull.utils');
4-
5-
function stencilPull(options = {}, callback) {
6-
async.waterfall(
7-
[
8-
async.constant(options),
9-
stencilPushUtils.readStencilConfigFile,
10-
stencilPushUtils.getStoreHash,
11-
stencilPushUtils.getChannels,
12-
stencilPushUtils.promptUserForChannel,
13-
stencilPullUtils.getChannelActiveTheme,
14-
stencilPullUtils.getThemeConfiguration,
15-
stencilPullUtils.mergeThemeConfiguration,
16-
],
17-
callback,
18-
);
1+
const fsModule = require('fs');
2+
const _ = require('lodash');
3+
const StencilConfigManager = require('./StencilConfigManager');
4+
const themeApiClientModule = require('./theme-api-client');
5+
const stencilPushUtilsModule = require('./stencil-push.utils');
6+
const fsUtilsModule = require('./utils/fsUtils');
7+
8+
require('colors');
9+
10+
class StencilPull {
11+
constructor({
12+
stencilConfigManager = new StencilConfigManager(),
13+
themeApiClient = themeApiClientModule,
14+
stencilPushUtils = stencilPushUtilsModule,
15+
fsUtils = fsUtilsModule,
16+
fs = fsModule,
17+
} = {}) {
18+
this._stencilConfigManager = stencilConfigManager;
19+
this._themeApiClient = themeApiClient;
20+
this._stencilPushUtils = stencilPushUtils;
21+
this._fsUtils = fsUtils;
22+
this._fs = fs;
23+
}
24+
25+
/**
26+
* @param {Object} cliOptions
27+
*/
28+
async run(cliOptions) {
29+
const stencilConfig = await this._stencilConfigManager.read();
30+
const storeHash = await this._themeApiClient.getStoreHash({
31+
storeUrl: stencilConfig.normalStoreUrl,
32+
});
33+
34+
let { channelId } = cliOptions;
35+
if (!channelId) {
36+
const channels = await this._themeApiClient.getStoreChannels({
37+
accessToken: stencilConfig.accessToken,
38+
apiHost: cliOptions.apiHost,
39+
storeHash,
40+
});
41+
42+
channelId = await this._stencilPushUtils.promptUserToSelectChannel(channels);
43+
}
44+
45+
const activeTheme = await this.getActiveTheme({
46+
accessToken: stencilConfig.accessToken,
47+
apiHost: cliOptions.apiHost,
48+
storeHash,
49+
channelId,
50+
});
51+
52+
console.log('ok'.green + ` -- Fetched theme details for channel ${channelId}`);
53+
54+
const variations = await this._themeApiClient.getVariationsByThemeId({
55+
accessToken: stencilConfig.accessToken,
56+
apiHost: cliOptions.apiHost,
57+
themeId: activeTheme.active_theme_uuid,
58+
storeHash,
59+
});
60+
61+
const variationId = this._stencilPushUtils.getActivatedVariation(
62+
variations,
63+
cliOptions.activate,
64+
);
65+
66+
const remoteThemeConfiguration = await this.getThemeConfiguration({
67+
saved: cliOptions.saved,
68+
activeTheme,
69+
accessToken: stencilConfig.accessToken,
70+
apiHost: cliOptions.apiHost,
71+
storeHash,
72+
variationId,
73+
});
74+
75+
console.log(
76+
'ok'.green + ` -- Fetched ${cliOptions.saved ? 'saved' : 'active'} configuration`,
77+
);
78+
79+
await this.mergeThemeConfiguration({
80+
variationId,
81+
activate: cliOptions.activate,
82+
remoteThemeConfiguration,
83+
saveConfigName: cliOptions.saveConfigName,
84+
});
85+
86+
return true;
87+
}
88+
89+
/**
90+
* @param {Object} options
91+
* @param {String} options.accessToken
92+
* @param {String} options.apiHost
93+
* @param {String} options.storeHash
94+
* @param {Number} options.channelId
95+
*/
96+
async getActiveTheme({ accessToken, apiHost, storeHash, channelId }) {
97+
const activeTheme = await this._themeApiClient.getChannelActiveTheme({
98+
accessToken,
99+
apiHost,
100+
storeHash,
101+
channelId,
102+
});
103+
104+
return activeTheme;
105+
}
106+
107+
/**
108+
* @param {Object} options
109+
* @param {Boolean} options.saved
110+
* @param {Object} options.activeTheme
111+
* @param {String} options.accessToken
112+
* @param {String} options.apiHost
113+
* @param {String} options.storeHash
114+
* @param {String} options.variationId
115+
*/
116+
async getThemeConfiguration({
117+
saved,
118+
activeTheme,
119+
accessToken,
120+
apiHost,
121+
storeHash,
122+
variationId,
123+
}) {
124+
const configurationId = saved
125+
? activeTheme.saved_theme_configuration_uuid
126+
: activeTheme.active_theme_configuration_uuid;
127+
128+
const remoteThemeConfiguration = await this._themeApiClient.getThemeConfiguration({
129+
accessToken,
130+
apiHost,
131+
storeHash,
132+
themeId: activeTheme.active_theme_uuid,
133+
configurationId,
134+
variationId,
135+
});
136+
137+
return remoteThemeConfiguration;
138+
}
139+
140+
/**
141+
* @param {Object} options
142+
* @param {String} options.variationId
143+
* @param {String} options.activate
144+
* @param {Object} options.remoteThemeConfiguration
145+
* @param {Object} options.remoteThemeConfiguration
146+
*/
147+
async mergeThemeConfiguration({
148+
variationId,
149+
activate,
150+
remoteThemeConfiguration,
151+
saveConfigName,
152+
}) {
153+
const localConfig = await this._fsUtils.parseJsonFile('config.json');
154+
let diffDetected = false;
155+
let { settings } = localConfig;
156+
157+
if (variationId) {
158+
({ settings } = localConfig.variations.find((v) => v.name === activate));
159+
}
160+
161+
// For any keys the remote configuration has in common with the local configuration,
162+
// overwrite the local configuration if the remote configuration differs
163+
for (const [key, remoteVal] of Object.entries(remoteThemeConfiguration.settings)) {
164+
if (!(key in settings)) {
165+
continue;
166+
}
167+
const localVal = settings[key];
168+
169+
// Check for different types, and throw an error if they are found
170+
if (typeof localVal !== typeof remoteVal) {
171+
throw new Error(
172+
`Theme configuration key "${key}" cannot be merged because it is not of the same type. ` +
173+
`Remote configuration is of type ${typeof remoteVal} while local configuration is of type ${typeof localVal}.`,
174+
);
175+
}
176+
177+
// If a different value is found, overwrite the local config
178+
if (!_.isEqual(localVal, remoteVal)) {
179+
settings[key] = remoteVal;
180+
diffDetected = true;
181+
}
182+
}
183+
184+
// Does a file need to be written?
185+
if (diffDetected || saveConfigName !== 'config.json') {
186+
if (diffDetected) {
187+
console.log(
188+
'ok'.green + ' -- Remote configuration merged with local configuration',
189+
);
190+
} else {
191+
console.log(
192+
'ok'.green +
193+
' -- Remote and local configurations are in sync for all common keys',
194+
);
195+
}
196+
197+
await this._fs.promises.writeFile(saveConfigName, JSON.stringify(localConfig, null, 2));
198+
console.log('ok'.green + ` -- Configuration written to ${saveConfigName}`);
199+
} else {
200+
console.log(
201+
'ok'.green +
202+
` -- Remote and local configurations are in sync for all common keys, no action taken`,
203+
);
204+
}
205+
}
19206
}
20207

21-
module.exports = stencilPull;
208+
module.exports = StencilPull;

0 commit comments

Comments
 (0)