shoplazza-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.
Files changed (181) hide show
  1. package/.editorconfig +28 -0
  2. package/.prettierrc +9 -0
  3. package/LICENSE +21 -0
  4. package/README.md +208 -0
  5. package/bin/shoplazza +117 -0
  6. package/fixtures/assets/blog.scss +74 -0
  7. package/fixtures/assets/cart_modal.scss +450 -0
  8. package/fixtures/assets/collection_detail.js +234 -0
  9. package/fixtures/assets/collection_detail.scss +345 -0
  10. package/fixtures/assets/collection_list.scss +11 -0
  11. package/fixtures/assets/collection_slider.scss +169 -0
  12. package/fixtures/assets/feature_columns.scss +26 -0
  13. package/fixtures/assets/feature_product.scss +109 -0
  14. package/fixtures/assets/footer.js +58 -0
  15. package/fixtures/assets/footer.scss +337 -0
  16. package/fixtures/assets/four_images.scss +29 -0
  17. package/fixtures/assets/gallery.scss +55 -0
  18. package/fixtures/assets/header.js +178 -0
  19. package/fixtures/assets/header.scss +929 -0
  20. package/fixtures/assets/image_text.scss +72 -0
  21. package/fixtures/assets/logo_bar.scss +11 -0
  22. package/fixtures/assets/newsletter.scss +90 -0
  23. package/fixtures/assets/not_found.scss +39 -0
  24. package/fixtures/assets/page_detail.scss +16 -0
  25. package/fixtures/assets/pagination.scss +150 -0
  26. package/fixtures/assets/postcss.config.js +6 -0
  27. package/fixtures/assets/product_description.scss +88 -0
  28. package/fixtures/assets/product_detail.js +634 -0
  29. package/fixtures/assets/product_detail.scss +1106 -0
  30. package/fixtures/assets/relative_product.scss +45 -0
  31. package/fixtures/assets/reviews.scss +70 -0
  32. package/fixtures/assets/rich_text.scss +71 -0
  33. package/fixtures/assets/search.js +87 -0
  34. package/fixtures/assets/search.scss +67 -0
  35. package/fixtures/assets/slide.scss +51 -0
  36. package/fixtures/assets/slider.scss +141 -0
  37. package/fixtures/assets/theme.css +976 -0
  38. package/fixtures/assets/theme.scss +1100 -0
  39. package/fixtures/assets/three_images.scss +20 -0
  40. package/fixtures/assets/tools.scss +23 -0
  41. package/fixtures/assets/two_images.scss +24 -0
  42. package/fixtures/assets/video.scss +45 -0
  43. package/fixtures/assets/video_text.scss +63 -0
  44. package/fixtures/config/settings_data.json +107 -0
  45. package/fixtures/config/settings_schema.json +690 -0
  46. package/fixtures/layout/theme.liquid +76 -0
  47. package/fixtures/locales/ar-SA.json +212 -0
  48. package/fixtures/locales/de-DE.json +290 -0
  49. package/fixtures/locales/en-US.json +290 -0
  50. package/fixtures/locales/es-ES.json +290 -0
  51. package/fixtures/locales/fr-FR.json +290 -0
  52. package/fixtures/locales/id-ID.json +212 -0
  53. package/fixtures/locales/it-IT.json +212 -0
  54. package/fixtures/locales/ja-JP.json +289 -0
  55. package/fixtures/locales/ko-KR.json +290 -0
  56. package/fixtures/locales/nl-NL.json +290 -0
  57. package/fixtures/locales/pl-PL.json +290 -0
  58. package/fixtures/locales/pt-PT.json +212 -0
  59. package/fixtures/locales/ru-RU.json +212 -0
  60. package/fixtures/locales/th-TH.json +212 -0
  61. package/fixtures/locales/zh-CN.json +290 -0
  62. package/fixtures/locales/zh-TW.json +290 -0
  63. package/fixtures/sections/apps.liquid +47 -0
  64. package/fixtures/sections/blog.liquid +137 -0
  65. package/fixtures/sections/collection_desc.liquid +34 -0
  66. package/fixtures/sections/collection_detail.liquid +436 -0
  67. package/fixtures/sections/collection_image.liquid +104 -0
  68. package/fixtures/sections/collection_list.liquid +161 -0
  69. package/fixtures/sections/collection_name.liquid +34 -0
  70. package/fixtures/sections/collection_slider.liquid +330 -0
  71. package/fixtures/sections/feature_columns.liquid +275 -0
  72. package/fixtures/sections/feature_product.liquid +227 -0
  73. package/fixtures/sections/footer.liquid +488 -0
  74. package/fixtures/sections/four_images.liquid +160 -0
  75. package/fixtures/sections/gallery.liquid +258 -0
  76. package/fixtures/sections/header.liquid +1157 -0
  77. package/fixtures/sections/html.liquid +40 -0
  78. package/fixtures/sections/image_text.liquid +350 -0
  79. package/fixtures/sections/instagram_plus.liquid +393 -0
  80. package/fixtures/sections/logo_bar.liquid +183 -0
  81. package/fixtures/sections/newsletter.liquid +225 -0
  82. package/fixtures/sections/not_found.liquid +39 -0
  83. package/fixtures/sections/overlay_image.liquid +648 -0
  84. package/fixtures/sections/page_detail.liquid +39 -0
  85. package/fixtures/sections/photo_collection.liquid +433 -0
  86. package/fixtures/sections/product_description.liquid +208 -0
  87. package/fixtures/sections/product_detail.liquid +611 -0
  88. package/fixtures/sections/products.liquid +216 -0
  89. package/fixtures/sections/relative_product.liquid +121 -0
  90. package/fixtures/sections/reviews.liquid +115 -0
  91. package/fixtures/sections/rich_text.liquid +157 -0
  92. package/fixtures/sections/search.liquid +163 -0
  93. package/fixtures/sections/slide.liquid +719 -0
  94. package/fixtures/sections/three_images.liquid +157 -0
  95. package/fixtures/sections/two_images.liquid +125 -0
  96. package/fixtures/sections/video.liquid +95 -0
  97. package/fixtures/sections/video_text.liquid +128 -0
  98. package/fixtures/snippets/bgset.liquid +21 -0
  99. package/fixtures/snippets/card_title.liquid +8 -0
  100. package/fixtures/snippets/cart_modal.liquid +74 -0
  101. package/fixtures/snippets/collection.liquid +77 -0
  102. package/fixtures/snippets/collection_filter_modal.liquid +56 -0
  103. package/fixtures/snippets/default_image_4.liquid +14 -0
  104. package/fixtures/snippets/default_image_6.liquid +18 -0
  105. package/fixtures/snippets/default_image_8.liquid +23 -0
  106. package/fixtures/snippets/four_images_item.liquid +8 -0
  107. package/fixtures/snippets/header_ads.liquid +95 -0
  108. package/fixtures/snippets/hero_image.liquid +94 -0
  109. package/fixtures/snippets/icon_video_play_large.liquid +1 -0
  110. package/fixtures/snippets/icon_video_play_medium.liquid +4 -0
  111. package/fixtures/snippets/icon_video_play_small.liquid +4 -0
  112. package/fixtures/snippets/lazyimg.liquid +22 -0
  113. package/fixtures/snippets/lazyimg_art.liquid +36 -0
  114. package/fixtures/snippets/lazysizes.liquid +41 -0
  115. package/fixtures/snippets/link.liquid +2 -0
  116. package/fixtures/snippets/pagination.liquid +48 -0
  117. package/fixtures/snippets/product.liquid +126 -0
  118. package/fixtures/snippets/product_art_tpl.liquid +152 -0
  119. package/fixtures/snippets/product_info_body.liquid +337 -0
  120. package/fixtures/snippets/product_info_tpl.liquid +423 -0
  121. package/fixtures/snippets/product_label.liquid +46 -0
  122. package/fixtures/snippets/settings.liquid +295 -0
  123. package/fixtures/snippets/social-meta-tags.liquid +106 -0
  124. package/fixtures/snippets/video_html.liquid +11 -0
  125. package/fixtures/snippets/video_source.liquid +98 -0
  126. package/fixtures/snippets/video_thumb_icon.liquid +2 -0
  127. package/fixtures/templates/404.liquid +1 -0
  128. package/fixtures/templates/collection.liquid +92 -0
  129. package/fixtures/templates/index.liquid +206 -0
  130. package/fixtures/templates/page.liquid +1 -0
  131. package/fixtures/templates/product.liquid +99 -0
  132. package/fixtures/templates/search.liquid +1 -0
  133. package/jest.config.js +192 -0
  134. package/lib/__tests__/log.test.js +15 -0
  135. package/lib/__tests__/utils.test.js +69 -0
  136. package/lib/auth/__mocks__/getCode.js +7 -0
  137. package/lib/auth/__mocks__/index.js +0 -0
  138. package/lib/auth/child.js +23 -0
  139. package/lib/auth/getCode.js +35 -0
  140. package/lib/auth/index.js +91 -0
  141. package/lib/commands/__tests__/login.test.js +77 -0
  142. package/lib/commands/__tests__/logout.test.js +29 -0
  143. package/lib/commands/__tests__/store.test.js +44 -0
  144. package/lib/commands/__tests__/switch.test.js +45 -0
  145. package/lib/commands/login.js +99 -0
  146. package/lib/commands/logout.js +14 -0
  147. package/lib/commands/store.js +14 -0
  148. package/lib/commands/switch.js +52 -0
  149. package/lib/commands/theme/__tests__/delete.test.js +49 -0
  150. package/lib/commands/theme/__tests__/init.test.js +21 -0
  151. package/lib/commands/theme/__tests__/list.test.js +80 -0
  152. package/lib/commands/theme/__tests__/package.test.js +17 -0
  153. package/lib/commands/theme/__tests__/publish.test.js +61 -0
  154. package/lib/commands/theme/__tests__/pull.test.js +69 -0
  155. package/lib/commands/theme/__tests__/push.test.js +63 -0
  156. package/lib/commands/theme/__tests__/serve.test.js +107 -0
  157. package/lib/commands/theme/delete.js +64 -0
  158. package/lib/commands/theme/init.js +51 -0
  159. package/lib/commands/theme/list.js +28 -0
  160. package/lib/commands/theme/package.js +37 -0
  161. package/lib/commands/theme/publish.js +56 -0
  162. package/lib/commands/theme/pull.js +62 -0
  163. package/lib/commands/theme/push.js +106 -0
  164. package/lib/commands/theme/serve.js +153 -0
  165. package/lib/commands/theme/share.js +20 -0
  166. package/lib/commands/version.js +6 -0
  167. package/lib/config.js +5 -0
  168. package/lib/db/__mocks__/index.js +9 -0
  169. package/lib/db/__tests__/analytics.test.js +19 -0
  170. package/lib/db/__tests__/user.test.js +20 -0
  171. package/lib/db/analytics.js +48 -0
  172. package/lib/db/index.js +9 -0
  173. package/lib/db/user.js +68 -0
  174. package/lib/log.js +13 -0
  175. package/lib/openAPI/__mocks__/index.js +20 -0
  176. package/lib/openAPI/api.js +76 -0
  177. package/lib/openAPI/index.js +46 -0
  178. package/lib/report.js +37 -0
  179. package/lib/tracing.js +50 -0
  180. package/lib/utils.js +48 -0
  181. package/package.json +54 -0
@@ -0,0 +1,91 @@
1
+ const axios = require('axios');
2
+ const chalk = require('chalk');
3
+ const Sentry = require('@sentry/node');
4
+ const querystring = require('querystring');
5
+ const { get, set, empty } = require('../db/user');
6
+ const log = require('../log');
7
+ const { SSO_AUTH_URL, ACCOUNT_URL, CLIENT_ID, REDIRECT_URI } = require('../config');
8
+
9
+ exports.postAccessToken = async (code) => {
10
+ try {
11
+ const { data } = await axios.post(
12
+ `${SSO_AUTH_URL}/api/oauth/token`,
13
+ querystring.stringify({
14
+ client_id: CLIENT_ID,
15
+ code,
16
+ grant_type: 'authorization_code',
17
+ redirect_uri: REDIRECT_URI
18
+ }),
19
+ {
20
+ headers: {
21
+ 'Content-Type': 'application/x-www-form-urlencoded'
22
+ }
23
+ }
24
+ );
25
+ empty();
26
+ set({
27
+ access_token: data.access_token,
28
+ session_id: data.session_id
29
+ });
30
+ } catch (err) {
31
+ log.error(chalk.red('\n✗ Failed to post access token'));
32
+ process.exit(-1);
33
+ }
34
+ };
35
+
36
+ exports.getUserInfo = async () => {
37
+ try {
38
+ const { data } = await axios.get(`${SSO_AUTH_URL}/api/sso/current/users`, {
39
+ headers: {
40
+ Cookie: `awesomev2=${get('session_id')}`
41
+ }
42
+ });
43
+ const activeUser = data.users.find((user) => user.active) || data.users[0];
44
+ set({
45
+ user_id: activeUser.user_id
46
+ });
47
+ } catch (err) {
48
+ log.error(chalk.red('✗ Failed to get user info'));
49
+ process.exit(-1);
50
+ }
51
+ };
52
+
53
+ exports.postExchangeToken = async (storeDomain, options = {}) => {
54
+ return new Promise(async (resolve, reject) => {
55
+ try {
56
+ const { data } = await axios.post(
57
+ `${ACCOUNT_URL}/api/accounts/store/token`,
58
+ {
59
+ user_id: get('user_id'),
60
+ domain: storeDomain
61
+ },
62
+ {
63
+ headers: {
64
+ Cookie: `awesomev2=${get('session_id')}`
65
+ }
66
+ }
67
+ );
68
+ set({
69
+ store_domain: storeDomain,
70
+ exchange_token: data.access_token
71
+ });
72
+ resolve();
73
+ } catch (err) {
74
+ reject(err);
75
+ Sentry.captureException(err);
76
+ empty();
77
+ if (!options.ignoreLogError) {
78
+ if (err?.response?.status === 401) {
79
+ log.error(chalk.red(`\n✗ You are not authorized to edit themes on ${storeDomain}.`));
80
+ log.info(
81
+ chalk.green(
82
+ 'Check if your user is activated, has permission to edit themes at the store, and try to re-login.'
83
+ )
84
+ );
85
+ } else {
86
+ log.error(chalk.red(`\n✗ ${storeDomain} is not a valid store.`));
87
+ }
88
+ }
89
+ }
90
+ });
91
+ };
@@ -0,0 +1,77 @@
1
+ const { login } = require('../login');
2
+ const axios = require('axios');
3
+ const { get, empty } = require('../../db/user');
4
+ const MockAdapter = require('axios-mock-adapter');
5
+ const { SSO_AUTH_URL, ACCOUNT_URL } = require('../../config');
6
+
7
+ jest.mock('../../db');
8
+ jest.mock('../../auth/getCode');
9
+ jest.mock('inquirer', () => ({
10
+ prompt: () => Promise.resolve({ confirm: 'Yes' })
11
+ }));
12
+ jest.mock('ora', () => () => ({
13
+ start: () => ({
14
+ stop: () => {}
15
+ })
16
+ }));
17
+
18
+ describe('login', () => {
19
+ let mock;
20
+
21
+ beforeEach(() => {
22
+ mock = new MockAdapter(axios);
23
+ empty();
24
+ });
25
+
26
+ afterEach(() => {
27
+ mock.reset();
28
+ empty();
29
+ });
30
+
31
+ it('success', async () => {
32
+ mock.onPost(`${SSO_AUTH_URL}/api/oauth/token`).replyOnce(200, {
33
+ access_token: 'access_token',
34
+ session_id: 'session_id'
35
+ });
36
+
37
+ mock.onGet(`${SSO_AUTH_URL}/api/sso/current/users`).replyOnce(200, {
38
+ users: [
39
+ {
40
+ user_id: 'user_id'
41
+ }
42
+ ]
43
+ });
44
+
45
+ mock.onPost(`${ACCOUNT_URL}/api/accounts/store/token`).replyOnce(200, {
46
+ access_token: 'exchange_token'
47
+ });
48
+
49
+ console.log = jest.fn;
50
+ await login({
51
+ store: 'developer.myshoplaza.com'
52
+ });
53
+ expect(get('access_token')).toBe('access_token');
54
+ expect(get('session_id')).toBe('session_id');
55
+ expect(get('user_id')).toBe('user_id');
56
+ expect(get('exchange_token')).toBe('exchange_token');
57
+ expect(get('store_domain')).toBe('developer.myshoplaza.com');
58
+ });
59
+
60
+ it('failed', async () => {
61
+ const mockExit = jest.spyOn(process, 'exit').mockImplementation((number) => {
62
+ throw new Error('process.exit: ' + number);
63
+ });
64
+
65
+ mock.onPost(`${SSO_AUTH_URL}/api/oauth/token`).replyOnce(500, {});
66
+ await login({
67
+ store: 'developer-failed.myshoplaza.com'
68
+ });
69
+ expect(mockExit).toHaveBeenCalledWith(-1);
70
+
71
+ expect(get('access_token')).toBe(null);
72
+ expect(get('session_id')).toBe(null);
73
+ expect(get('user_id')).toBe(null);
74
+ expect(get('exchange_token')).toBe(null);
75
+ expect(get('store_domain')).toBe(null);
76
+ });
77
+ });
@@ -0,0 +1,29 @@
1
+ const logout = require('../logout');
2
+ const { get, set, empty } = require('../../db/user');
3
+
4
+ jest.mock('../../db');
5
+
6
+ describe('logout', () => {
7
+ beforeEach(() => {
8
+ empty();
9
+ });
10
+
11
+ it('success', async () => {
12
+ set({
13
+ user_id: 'user_id',
14
+ session_id: 'session_id',
15
+ access_token: 'access_token',
16
+ exchange_token: 'exchange_token'
17
+ });
18
+ expect(get('access_token')).toBe('access_token');
19
+ expect(get('session_id')).toBe('session_id');
20
+ expect(get('user_id')).toBe('user_id');
21
+ expect(get('exchange_token')).toBe('exchange_token');
22
+ console.log = jest.fn;
23
+ logout();
24
+ expect(get('access_token')).toBe(null);
25
+ expect(get('session_id')).toBe(null);
26
+ expect(get('user_id')).toBe(null);
27
+ expect(get('exchange_token')).toBe(null);
28
+ });
29
+ });
@@ -0,0 +1,44 @@
1
+ const store = require('../store');
2
+ const chalk = require('chalk');
3
+ const { set, empty } = require('../../db/user');
4
+ const MockAdapter = require('axios-mock-adapter');
5
+ const openAPI = require('../../openAPI');
6
+
7
+ jest.mock('../../db');
8
+ jest.mock('../../openAPI');
9
+
10
+ describe('store', () => {
11
+ let mock;
12
+
13
+ beforeEach(() => {
14
+ set({
15
+ store_domain: 'developer.myshoplaza.com'
16
+ });
17
+ mock = new MockAdapter(openAPI);
18
+ });
19
+
20
+ afterEach(() => {
21
+ mock.reset();
22
+ empty();
23
+ });
24
+
25
+ it('success', async () => {
26
+ mock.onGet(`https://developer.myshoplaza.com/openapi/2020-07/shop`).replyOnce(200, {
27
+ shop: {
28
+ domain: 'developer.myshoplaza.com'
29
+ }
30
+ });
31
+ console.log = jest.fn();
32
+ await store();
33
+ expect(console.log.mock.calls[0][0]).toBe(
34
+ `You're currently logged into ${chalk.green('developer.myshoplaza.com')}`
35
+ );
36
+ });
37
+
38
+ it('failed', async () => {
39
+ mock.onGet(`https://developer.myshoplaza.com/openapi/2020-07/shop`).replyOnce(500);
40
+ console.log = jest.fn();
41
+ await store();
42
+ expect(console.log.mock.calls[0][0]).toBe(chalk.red(`✗ Failed to get store`));
43
+ });
44
+ });
@@ -0,0 +1,45 @@
1
+ const axios = require('axios');
2
+ const chalk = require('chalk');
3
+ const switchCommand = require('../switch');
4
+ const { get, set, empty } = require('../../db/user');
5
+ const MockAdapter = require('axios-mock-adapter');
6
+ const { ACCOUNT_URL } = require('../../config');
7
+
8
+ jest.mock('../../db');
9
+
10
+ describe('switch', () => {
11
+ let mock;
12
+
13
+ beforeEach(() => {
14
+ mock = new MockAdapter(axios);
15
+ empty();
16
+ set({ access_token: 'access_token' });
17
+ });
18
+
19
+ afterEach(() => {
20
+ mock.reset();
21
+ empty();
22
+ });
23
+
24
+ it('switch success', async () => {
25
+ mock.onPost(`${ACCOUNT_URL}/api/accounts/store/token`).replyOnce(200, {
26
+ access_token: 'exchange_token'
27
+ });
28
+ console.log = jest.fn;
29
+ await switchCommand({
30
+ store: 'developer.myshoplaza.com'
31
+ });
32
+ expect(get('access_token')).toBe('access_token');
33
+ expect(get('exchange_token')).toBe('exchange_token');
34
+ });
35
+
36
+ it('switch failed', async () => {
37
+ mock.onPost(`${ACCOUNT_URL}/api/accounts/store/token`).replyOnce(500, {});
38
+ console.log = jest.fn;
39
+ await switchCommand({
40
+ store: 'developer-failed.myshoplaza.com'
41
+ });
42
+ expect(get('access_token')).toBe(null);
43
+ expect(get('exchange_token')).toBe(null);
44
+ });
45
+ });
@@ -0,0 +1,99 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require('inquirer');
3
+ const Sentry = require('@sentry/node');
4
+ const ora = require('ora');
5
+ const { get } = require('../db/user');
6
+ const { hasBeenSetAnalytics, setAnalyticsConfig } = require('../db/analytics');
7
+ const log = require('../log');
8
+ const { getShopDetail } = require('../openAPI/api');
9
+ const getCode = require('../auth/getCode');
10
+ const { postAccessToken, getUserInfo, postExchangeToken } = require('../auth');
11
+ let spinner;
12
+
13
+ const checkAndLogin = async (store) => {
14
+ if (get('exchange_token')) {
15
+ if (get('store_domain') === store) {
16
+ try {
17
+ await getShopDetail({ ignoreLogError: true });
18
+ log.info(`${chalk.green('✓')} Already logged in to ${chalk.green(store)}`);
19
+ return;
20
+ } catch (err) {
21
+ // Ignore
22
+ }
23
+ } else {
24
+ try {
25
+ await postExchangeToken(store, { ignoreLogError: true });
26
+ log.info(`Logged into ${chalk.green(get('store_domain'))}`);
27
+ return;
28
+ } catch (err) {
29
+ // Ignore
30
+ }
31
+ }
32
+ }
33
+
34
+ const code = await getCode();
35
+ spinner = ora(`Logging in to ${chalk.green(store)}`).start();
36
+ await postAccessToken(code);
37
+ await getUserInfo();
38
+ await postExchangeToken(store);
39
+ spinner?.stop?.();
40
+ log.info(`${chalk.green('✓')} Finalizing authentication`);
41
+ log.info(`Logged into ${chalk.green(get('store_domain'))}`);
42
+ requestAnalyticsIfNeeded();
43
+ };
44
+
45
+ const requestAnalyticsIfNeeded = async () => {
46
+ const user_id = get('user_id');
47
+ if (hasBeenSetAnalytics(user_id)) return;
48
+ try {
49
+ const answers = await inquirer.prompt([
50
+ {
51
+ name: 'confirm',
52
+ type: 'list',
53
+ message: `Are you sure you want to enable usage reporting?`,
54
+ choices: ['Yes', 'No']
55
+ }
56
+ ]);
57
+ setAnalyticsConfig({
58
+ user_id,
59
+ enabled: answers.confirm === 'Yes' ? 1 : 0
60
+ });
61
+ } catch (error) {
62
+ log.error(chalk.red('✗ Failed to authenticate'));
63
+ Sentry.captureException(err);
64
+ }
65
+ };
66
+
67
+ exports.login = async (options) => {
68
+ try {
69
+ if (options.store) {
70
+ await checkAndLogin(options.store);
71
+ } else {
72
+ const answers = await inquirer.prompt([
73
+ {
74
+ name: 'store',
75
+ type: 'input',
76
+ message: 'The store domain (Eg: developer.myshoplaza.com)'
77
+ }
78
+ ]);
79
+ if (answers.store) {
80
+ if (!/.+\.myshoplaza\.com/.test(answers.store)) {
81
+ log.error(
82
+ chalk.red(
83
+ `✗ Invalid store provided ${answers.store}. Please provide the store in the following format: developer.myshoplaza.com`
84
+ )
85
+ );
86
+ return;
87
+ }
88
+ await checkAndLogin(answers.store);
89
+ } else {
90
+ log.error(chalk.red(`✗ Please input the store domain.`));
91
+ return;
92
+ }
93
+ }
94
+ } catch (error) {
95
+ spinner?.stop?.();
96
+ log.error(chalk.red(`✗ Failed to authenticate`));
97
+ Sentry.captureException(error);
98
+ }
99
+ };
@@ -0,0 +1,14 @@
1
+ const Sentry = require('@sentry/node');
2
+ const chalk = require('chalk');
3
+ const { empty } = require('../db/user');
4
+ const log = require('../log');
5
+
6
+ module.exports = () => {
7
+ try {
8
+ empty();
9
+ log.info('Successfully logged out of your account');
10
+ } catch (error) {
11
+ log.error(chalk.red(`✗ Failed to logout`));
12
+ Sentry.captureException(error);
13
+ }
14
+ };
@@ -0,0 +1,14 @@
1
+ const chalk = require('chalk');
2
+ const Sentry = require('@sentry/node');
3
+ const { getShopDetail } = require('../openAPI/api');
4
+ const log = require('../log');
5
+
6
+ module.exports = async () => {
7
+ try {
8
+ const { data } = await getShopDetail();
9
+ log.info(`You're currently logged into ${chalk.green(data.shop.domain)}`);
10
+ } catch (error) {
11
+ log.error(chalk.red(`✗ Failed to get store`));
12
+ Sentry.captureException(error);
13
+ }
14
+ };
@@ -0,0 +1,52 @@
1
+ const chalk = require('chalk');
2
+ const inquirer = require('inquirer');
3
+ const Sentry = require('@sentry/node');
4
+ const { get } = require('../db/user');
5
+ const log = require('../log');
6
+ const { postExchangeToken } = require('../auth');
7
+
8
+ const switchStore = async (store) => {
9
+ if (!get('access_token')) {
10
+ log.error(chalk.red(`✗ Please login again with ${chalk.cyan('shoplazza login')}`));
11
+ } else {
12
+ try {
13
+ await postExchangeToken(store);
14
+ log.info(`Switched store to ${chalk.green(store)}`);
15
+ } catch (error) {
16
+ log.error(chalk.red(`✗ Failed to switch store`));
17
+ Sentry.captureException(error);
18
+ }
19
+ }
20
+ };
21
+
22
+ module.exports = async (options) => {
23
+ try {
24
+ if (options.store) {
25
+ return switchStore(options.store);
26
+ } else {
27
+ const answers = await inquirer.prompt([
28
+ {
29
+ name: 'store',
30
+ type: 'input',
31
+ message: 'The store domain (Eg: developer.myshoplaza.com )'
32
+ }
33
+ ]);
34
+ if (answers.store) {
35
+ if (!/.+\.myshoplaza\.com/.test(answers.store)) {
36
+ log.error(
37
+ chalk.red(
38
+ `✗ Invalid store provided ${answers.store}. Please provide the store in the following format: developer.myshoplaza.com`
39
+ )
40
+ );
41
+ process.exit(-1);
42
+ }
43
+ return switchStore(answers.store);
44
+ } else {
45
+ log.error(chalk.red(`✗ Please input the store domain.`));
46
+ }
47
+ }
48
+ } catch (error) {
49
+ log.error(chalk.red(`✗ Failed to switch store`));
50
+ Sentry.captureException(error);
51
+ }
52
+ };
@@ -0,0 +1,49 @@
1
+ const MockAdapter = require('axios-mock-adapter');
2
+ const chalk = require('chalk');
3
+ const deleteCommand = require('../delete');
4
+ const openAPI = require('../../../openAPI');
5
+ const { set, empty } = require('../../../db/user');
6
+
7
+ jest.mock('inquirer', () => ({
8
+ prompt: () => Promise.resolve({ confirm: 'Yes' })
9
+ }));
10
+ jest.mock('../../../db');
11
+ jest.mock('../../../openAPI/index');
12
+
13
+ describe('delete theme', () => {
14
+ let mock;
15
+
16
+ beforeEach(() => {
17
+ set({
18
+ store_domain: 'developer.myshoplaza.com'
19
+ });
20
+ mock = new MockAdapter(openAPI);
21
+ });
22
+
23
+ afterEach(() => {
24
+ mock.reset();
25
+ empty();
26
+ });
27
+
28
+ it('delete success', async () => {
29
+ mock.onGet(`https://developer.myshoplaza.com/openapi/2020-07/themes/theme_id`).replyOnce(200, {
30
+ data: { name: 'test' }
31
+ });
32
+ mock.onDelete(`https://developer.myshoplaza.com/openapi/2020-07/themes/theme_id`).replyOnce(200);
33
+
34
+ console.log = jest.fn();
35
+ await deleteCommand({ theme: 'theme_id' });
36
+ expect(console.log.mock.calls[0][0]).toBe(chalk.green(`✓ test (theme_id) theme deleted`));
37
+ });
38
+
39
+ it('delete failed', async () => {
40
+ mock.onGet(`https://developer.myshoplaza.com/openapi/2020-07/themes/theme_id`).replyOnce(200, {
41
+ data: { name: 'test' }
42
+ });
43
+ mock.onDelete(`https://developer.myshoplaza.com/openapi/2020-07/themes/theme_id`).replyOnce(500);
44
+
45
+ console.log = jest.fn();
46
+ await deleteCommand({ theme: 'theme_id' });
47
+ expect(console.log.mock.calls[0][0]).toBe(chalk.red(`✗ Failed to delete theme`));
48
+ });
49
+ });
@@ -0,0 +1,21 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const init = require('../init');
4
+ const execa = require('execa');
5
+
6
+ jest.mock('execa', () => jest.fn());
7
+
8
+ describe('theme init', () => {
9
+ it('clone repo', async () => {
10
+ jest.setTimeout(20 * 1000);
11
+ if (fs.existsSync(path.join(process.cwd(), 'test'))) {
12
+ fs.rmSync(path.join(process.cwd(), 'test'), { recursive: true });
13
+ }
14
+ expect(fs.existsSync(path.join(process.cwd(), 'test'))).toBe(false);
15
+ console.log = jest.fn;
16
+ await init({ name: 'test' });
17
+ expect(execa).toHaveBeenCalledWith('git', ['clone', 'https://github.com/Shoplazza/bing', 'test']);
18
+ expect(fs.existsSync(path.join(process.cwd(), 'test'))).toBe(true);
19
+ fs.rmSync(path.join(process.cwd(), 'test'), { recursive: true });
20
+ });
21
+ });
@@ -0,0 +1,80 @@
1
+ const chalk = require('chalk');
2
+ const MockAdapter = require('axios-mock-adapter');
3
+ const listCommand = require('../list');
4
+ const openAPI = require('../../../openAPI');
5
+ const { set, empty } = require('../../../db/user');
6
+
7
+ jest.mock('ora', () => () => ({
8
+ start: () => ({
9
+ stop: () => {}
10
+ })
11
+ }));
12
+ jest.mock('../../../db');
13
+ jest.mock('../../../openAPI/index');
14
+
15
+ describe('theme list', () => {
16
+ let mock;
17
+
18
+ beforeEach(() => {
19
+ set({
20
+ store_domain: 'developer.myshoplaza.com'
21
+ });
22
+ mock = new MockAdapter(openAPI);
23
+ });
24
+
25
+ afterEach(() => {
26
+ mock.reset();
27
+ empty();
28
+ });
29
+
30
+ it('list info', async () => {
31
+ mock.onGet(`https://developer.myshoplaza.com/openapi/2020-07/themes`).replyOnce(200, {
32
+ data: {
33
+ themes: [
34
+ {
35
+ id: 'theme_id1',
36
+ name: 'theme1'
37
+ }
38
+ ]
39
+ }
40
+ });
41
+ mock.onGet(`https://developer.myshoplaza.com/openapi/2020-07/themes/default-theme`).replyOnce(200, {
42
+ data: {
43
+ id: 'theme_id',
44
+ name: 'default_theme'
45
+ }
46
+ });
47
+
48
+ console.log = jest.fn();
49
+ await listCommand();
50
+ expect(console.log.mock.calls[0][0]).toBe(
51
+ `${chalk.yellow('⭑')} List of ${chalk.green('developer.myshoplaza.com')} themes:`
52
+ );
53
+ expect(console.log.mock.calls[1][0]).toBe(
54
+ [
55
+ {
56
+ id: 'theme_id',
57
+ name: 'default_theme'
58
+ },
59
+ {
60
+ id: 'theme_id1',
61
+ name: 'theme1'
62
+ }
63
+ ].reduce(
64
+ (acc, theme) =>
65
+ (acc += `${theme.name} (${chalk.green(theme.id)}) ${
66
+ theme.id === 'theme_id' ? chalk.green('[live]') : chalk.yellow('[unpublished]')
67
+ }\n`),
68
+ ''
69
+ )
70
+ );
71
+ });
72
+
73
+ it('fetch failed', async () => {
74
+ mock.onGet(`https://developer.myshoplaza.com/openapi/2020-07/themes/default-theme`).replyOnce(500);
75
+
76
+ console.log = jest.fn();
77
+ await listCommand();
78
+ expect(console.log.mock.calls[0][0]).toBe(chalk.red(`✗ Failed to get theme list`));
79
+ });
80
+ });
@@ -0,0 +1,17 @@
1
+ const chalk = require('chalk');
2
+ const path = require('path');
3
+ const fs = require('fs-extra');
4
+ const { packageCommand } = require('../package');
5
+ const { fstat } = require('fs');
6
+
7
+ describe('theme package', () => {
8
+ it('zip theme', async () => {
9
+ process.chdir(path.join(process.cwd(), '/fixtures'));
10
+ console.log = jest.fn();
11
+ await packageCommand();
12
+ expect(console.log.mock.calls[0][0]).toBe(`${chalk.green('✓')} Theme packaged in TestLifeStyle-1.0.zip`);
13
+ expect(fs.existsSync(path.join(process.cwd(), 'TestLifeStyle-1.0.zip'))).toBe(true);
14
+ fs.rmSync(path.join(process.cwd(), 'TestLifeStyle-1.0.zip'));
15
+ process.chdir(path.join(process.cwd(), '../'));
16
+ });
17
+ });