shoplazza-cli 0.0.3 → 0.0.5

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/README.md CHANGED
@@ -9,6 +9,7 @@
9
9
  ---
10
10
 
11
11
  Shoplazza CLI is a cross-platform command line tool that you can use to build Shoplazza themes.
12
+
12
13
  ## Features
13
14
 
14
15
  Shoplazza CLI accelerates your theme development process with the following features:
@@ -22,7 +23,8 @@ Shoplazza CLI accelerates your theme development process with the following feat
22
23
  - Work on Linux, macOS, and Windows.
23
24
 
24
25
  ## Installation [shoplazza-cli](https://www.npmjs.com/package/shoplazza-themekit)
25
- ``` terminal
26
+
27
+ ```terminal
26
28
  $ npm install shoplazza-cli -g
27
29
  ```
28
30
 
@@ -37,150 +39,235 @@ Before you start using Shoplazza CLI to develop themes, make sure that you do th
37
39
  - Make sure that you're connected to the internet. Most Shoplazza CLI commands need an internet connection to run.
38
40
 
39
41
  ## Getting started
42
+
40
43
  ### Authenticate
41
- ``` terminal
44
+
45
+ ```terminal
42
46
  $ shoplazza login --store developer.myshoplaza.com
43
47
  ```
48
+
44
49
  > In your browser window, log into the account that's attached to the store that you want to use for development.
45
50
 
46
51
  ### Create a new theme
47
- ``` terminal
52
+
53
+ ```terminal
48
54
  $ shoplazza theme init
49
55
  ```
56
+
50
57
  > Use `shoplazza theme init` to create a new theme on your local machine. This command clones a Git repository to your local machine to use as the starting point for building a theme.
51
58
 
52
59
  ### Connect to existing theme
53
- ``` terminal
60
+
61
+ ```terminal
54
62
  $ shoplazza theme pull
55
63
  ```
64
+
56
65
  > Pull the theme onto your local machine using `shoplazza theme pull`. You're prompted to select a theme from the list of themes on the store.
57
66
 
58
67
  ### Preview, test, and share your theme
59
- ``` terminal
68
+
69
+ ```terminal
60
70
  $ shoplazza theme serve
61
71
  ```
72
+
62
73
  > After you create or navigate to your theme, you can run `shoplazza theme serve` to interact with the theme in a browser.
63
74
 
64
75
  ### Push your theme to your store
65
- ``` terminal
76
+
77
+ ```terminal
66
78
  $ shoplazza theme push
67
79
  ```
80
+
68
81
  > Use `shoplazza theme push` to upload your local theme files to Shoplazza, overwriting the remote versions.
69
82
 
70
83
  ### Publish your theme
71
- ``` terminal
84
+
85
+ ```terminal
72
86
  $ shoplazza theme publish
73
87
  ```
88
+
74
89
  > Use `shoplazza theme publish` to select and publish an unpublished theme from your theme library. If you want to publish your local theme, then you need to run `shoplazza theme push` first.
75
90
 
76
91
  ### Find your theme ID
77
- ``` terminal
92
+
93
+ ```terminal
78
94
  $ shoplazza theme list
79
95
  ```
80
- > You might want to use a theme's ID to pull, push, publish, or delete a theme using Shoplazza CLI.
81
96
 
97
+ > You might want to use a theme's ID to pull, push, publish, or delete a theme using Shoplazza CLI.
82
98
 
83
99
  ## Core commands
84
100
 
85
101
  ### help
86
- ``` terminal
102
+
103
+ ```terminal
87
104
  $ shoplazza help
88
105
  ```
106
+
89
107
  > Lists the available commands and describes what they do.
90
108
 
91
109
  ### login
92
- ``` terminal
110
+
111
+ ```terminal
93
112
  $ shoplazza login --store developer.myshoplaza.com
113
+
114
+ $ shoplazza login --partner
94
115
  ```
95
- > Authenticates and logs you into the specified store with Shoplazza CLI.
116
+
117
+ > --store: Authenticates and logs you into the specified store with Shoplazza CLI.
118
+ > --partner: Authenticates and logs you into the partner with Shoplazza CLI.
96
119
 
97
120
  ### logout
98
- ``` terminal
121
+
122
+ ```terminal
99
123
  $ shoplazza logout
124
+
125
+ $ shoplazza logout --partner
100
126
  ```
101
- > Logs you out of the Shoplazza account and store, the logout command clears credentials. You need to reauthenticate the next time that you connect to a store.
127
+
128
+ > Default to logout your store, logs you out of the Shoplazza account and store, the logout command clears credentials. You need to reauthenticate the next time that you connect to a store.
129
+ > --partner: Clear your partner info, and you need to reauthenticate the next time.
130
+
102
131
 
103
132
  ### switch
104
- ``` terminal
133
+
134
+ ```terminal
105
135
  $ shoplazza switch --store developer.myshoplaza.com
106
136
  ```
137
+
107
138
  > Switches between stores without logging out and logging in again.
108
139
 
109
140
  ### store
110
- ``` terminal
141
+
142
+ ```terminal
111
143
  $ shoplazza store
112
144
  ```
145
+
113
146
  > Displays the store that you're currently connected to.
114
147
 
115
148
  ### version
116
- ``` terminal
149
+
150
+ ```terminal
117
151
  $ shoplazza version
118
152
  ```
153
+
119
154
  > Displays the version of Shoplazza CLI that you're running.
120
155
 
121
156
  ## Theme commands
122
157
 
123
158
  ### init
124
- ``` terminal
159
+
160
+ ```terminal
125
161
  $ shoplazza theme init [--name]
126
162
  ```
163
+
127
164
  > Clones a Git repository to your local machine to use as the starting point for building a theme.
128
165
 
129
166
  ### serve
130
- ``` terminal
167
+
168
+ ```terminal
131
169
  $ shoplazza theme serve [--theme]
132
170
  ```
171
+
133
172
  > Uploads the current theme to the store that you're connected to, and returns the preview link.
134
173
 
135
174
  ### list
136
- ``` terminal
175
+
176
+ ```terminal
137
177
  $ shoplazza theme list
138
178
  ```
179
+
139
180
  > Lists the themes in your store, along with their IDs and statuses.
140
181
 
141
182
  ### pull
142
- ``` terminal
183
+
184
+ ```terminal
143
185
  $ shoplazza theme pull [--theme]
144
186
  ```
187
+
145
188
  > Retrieves theme files from Shoplazza, if no theme is specified, then you're prompted to select the theme to pull from the list of the themes in your store.
146
189
 
147
190
  ### push
148
- ``` terminal
191
+
192
+ ```terminal
149
193
  $ shoplazza theme push [--theme]
150
194
  ```
195
+
151
196
  > Uploads your local theme files to Shoplazza, overwriting the remote theme if specified, if no theme is specified, then you're prompted to select the theme to overwrite from the list of the themes in your store.
152
197
 
153
198
  ### share
154
- ``` terminal
199
+
200
+ ```terminal
155
201
  $ shoplazza theme share
156
202
  ```
203
+
157
204
  > Uploads your theme as a new, unpublished theme in your theme library. The command return a preview link that you can share with others.
158
205
 
159
206
  ### publish
160
- ``` terminal
207
+
208
+ ```terminal
161
209
  $ shoplazza theme publish [--theme]
162
210
  ```
211
+
163
212
  > Publishes an unpublished theme from your theme library, if no theme ID is specified, then you're prompted to select the theme that you want to publish from the list of themes in your store.
164
213
 
165
214
  ### package
166
- ``` terminal
215
+
216
+ ```terminal
167
217
  $ shoplazza theme package
168
218
  ```
219
+
169
220
  > Packages your local theme files into a ZIP file that can be uploaded to Shoplazza. The ZIP file uses the name theme_name-theme_version.zip, based on parameters in your `settings_schema.json` file.
170
221
 
171
222
  ### delete
172
- ``` terminal
223
+
224
+ ```terminal
173
225
  $ shoplazza theme delete [--theme]
174
226
  ```
227
+
175
228
  > Deletes a theme from your store, if no theme is specified, then you're prompted to select the theme that you want to delete from the list of themes in your store.
176
229
 
230
+ ## app
231
+
232
+ ### generate
233
+
234
+ ```terminal
235
+ $ shoplazza app generate extension
236
+ ```
237
+
238
+ > Choose Your app type and download template.
239
+
240
+ ### build
241
+
242
+ ```terminal
243
+ $ shoplazza app build
244
+ ```
245
+
246
+ > Minify css / js files and generate manifest.json and zip.
247
+
248
+ ### deploy
249
+
250
+ ```terminal
251
+ $ shoplazza app deploy extension
252
+ ```
253
+
254
+ > Deploy your zip to oss for publish.
255
+
256
+ ### pupblish
257
+
258
+ ```terminal
259
+ $ shoplazza app publish extension
260
+ ```
261
+
262
+ > Choose your extension version and publish to your store.
177
263
 
178
264
  ## Theme Directory
179
265
 
180
266
  You can run certain theme commands, such as shoplazza theme serve, only if the directory you're using matches the default Shoplazza theme directory structure. This structure represents a buildless theme, or a theme that has already gone through any necessary file transformations. If you use build tools to generate theme files, then you might need to run commands from the directory where the generated files are stored.
181
267
 
182
268
  The default Shoplazza theme directory structure is as follows:
183
- ``` terminal
269
+
270
+ ```terminal
184
271
  └── project
185
272
  ├── assets
186
273
  ├── config
package/bin/shoplazza CHANGED
@@ -8,6 +8,8 @@ const pkg = require('../package.json');
8
8
  const report = require('../lib/report');
9
9
  require('../lib/tracing');
10
10
 
11
+ const { generateExtension, deployExtension, publishExtension, buildExtension, retry } = require('../lib/app');
12
+
11
13
  Sentry.init({
12
14
  dsn: 'https://89964605acaf4db8839f2d5237396d6c@sentry.shoplazza.com/730',
13
15
  integrations: [
@@ -32,11 +34,14 @@ program
32
34
  .command('login')
33
35
  .description('Authenticates and logs you into the specified store with Shoplazza CLI.')
34
36
  .option('-s, --store <store>', 'The store domain (Eg: developer.myshoplaza.com)')
37
+ .option('-p, --partner', 'Log into partner app')
35
38
  .action(require('../lib/commands/login').login);
36
39
 
37
40
  program
38
41
  .command('logout')
39
- .description('Logs you out of the Shoplazza account.')
42
+ .description('Logs you out of the Shoplazza account, logs out your store account without any options.')
43
+ .option('-s, --store', 'Logs out your store account')
44
+ .option('-p, --partner', 'Logs out your partner account')
40
45
  .action(require('../lib/commands/logout'));
41
46
 
42
47
  program
@@ -104,6 +109,19 @@ theme
104
109
  .option('-t, --theme <theme>', 'The ID of the theme that you want to delete.')
105
110
  .action(require('../lib/commands/theme/delete'));
106
111
 
112
+ const app = program.command('app').description('Shoplazza app extensions cli');
113
+
114
+ app.command('generate').command('extension').description('Generate your extension').action(generateExtension);
115
+ app.command('deploy').command('extension').description('Deploy your extension to cdn').action(deployExtension);
116
+ app.command('publish').command('extension').description('Publish your extension').action(publishExtension);
117
+ app.command('build').description('Build your extension').action(buildExtension);
118
+ app
119
+ .command('retry')
120
+ .description('Retry to do some tasks.')
121
+ .option('-p, --partner', 'Retry get and choose your partner list.')
122
+ .option('-a, --app', 'Retry get and choose your app list.')
123
+ .action(retry);
124
+
107
125
  program.parse(process.argv);
108
126
  !program.args.length && program.help();
109
127
 
@@ -0,0 +1 @@
1
+ body { color: red; }
@@ -0,0 +1,66 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+
6
+ const { zipTheme } = require('../../utils');
7
+ const { line, done } = require('../log');
8
+ const { buildThemeAssets } = require('../extensions/theme-app');
9
+
10
+ const isFileExist = (path) => {
11
+ return fs.existsSync(path);
12
+ };
13
+
14
+ const clearManifest = async () => {
15
+ try {
16
+ if (isFileExist('./assets-manifest.json') && isFileExist('./assets')) {
17
+ const mainfest = JSON.parse(fs.readFileSync('./assets-manifest.json', { encoding: 'utf-8' }));
18
+ await Promise.all(
19
+ Object.values(mainfest).map((path) => {
20
+ if (isFileExist(path)) {
21
+ return fs.unlink(path);
22
+ }
23
+ return Promise.resolve(true);
24
+ })
25
+ );
26
+ await fs.unlink('./assets-manifest.json');
27
+ }
28
+ return true;
29
+ } catch (e) {
30
+ console.log(e);
31
+ }
32
+ };
33
+
34
+ const getZipName = () => {
35
+ return path.basename(process.cwd());
36
+ };
37
+
38
+ const generateZip = async (name) => {
39
+ const zipPath = `${process.cwd()}/${name}.zip`;
40
+ isFileExist(zipPath) && (await fs.unlink(zipPath));
41
+ zipTheme(process.cwd(), name);
42
+ };
43
+
44
+ const zipApp = async () => {
45
+ const spinner = ora(chalk.cyan('Building your app and generate zip package ...')).start();
46
+ try {
47
+ const zipName = getZipName();
48
+ await generateZip(zipName);
49
+ spinner.succeed(chalk.cyan(`Success to build your app and generate zip package: ${chalk.green(`${zipName}.zip`)}`));
50
+ return true;
51
+ } catch (e) {
52
+ spinner.fail(e.message);
53
+ }
54
+ };
55
+
56
+ const build = async () => {
57
+ line();
58
+ (await clearManifest()) && (await buildThemeAssets('./assets')) && (await zipApp());
59
+ done();
60
+ line();
61
+ };
62
+
63
+ module.exports = {
64
+ build,
65
+ getZipName
66
+ };
@@ -0,0 +1,213 @@
1
+ const axios = require('axios');
2
+ const ora = require('ora');
3
+ const chalk = require('chalk');
4
+ const fs = require('fs-extra');
5
+ const SparkMD5 = require('spark-md5');
6
+ const FormData = require('form-data');
7
+ const path = require('path');
8
+
9
+ const { PARNTER_URL } = require('../constants');
10
+ const { getValue, PARTNER_KEYS, getApp, set } = require('../db/partner');
11
+ const { getZipName } = require('./build');
12
+ const { inputVersion } = require('../inquirers/version');
13
+ const { line, done } = require('../log');
14
+
15
+ const getZipPath = () => {
16
+ const spinner = ora(chalk.cyan('Finding your built zip ...')).start();
17
+ try {
18
+ const zipName = getZipName();
19
+ const filename = fs.readdirSync(process.cwd()).find((name) => {
20
+ const extname = path.extname(name).replace('.', '');
21
+ return extname === 'zip' && name.startsWith(zipName);
22
+ });
23
+
24
+ if (!filename) {
25
+ throw new Error('not zip');
26
+ }
27
+
28
+ const fileInfo = fs.statSync(`${process.cwd()}/${filename}`);
29
+
30
+ spinner.succeed(
31
+ chalk.cyan(
32
+ `Success to find your zip: ${chalk.green(`${filename} ${(fileInfo.size / (1024 * 1024)).toFixed(2)}M`)}`
33
+ )
34
+ );
35
+ return path.resolve(process.cwd(), filename);
36
+ } catch (e) {
37
+ spinner.fail(chalk.red('Failed to find your zip, be sure you have built app in root dir!'));
38
+ }
39
+ };
40
+
41
+ const getBufferAndMd5 = async (filePath) => {
42
+ const spinner = ora(chalk.cyan('Begin to read zip and generate md5 code ...')).start();
43
+
44
+ try {
45
+ return await new Promise((resolve, reject) => {
46
+ const spark = new SparkMD5.ArrayBuffer();
47
+ let buffers = [];
48
+
49
+ const rs = fs.createReadStream(filePath, { autoClose: true });
50
+ rs.on('data', (data) => {
51
+ buffers.push(data);
52
+ spark.append(data);
53
+ });
54
+
55
+ rs.on('end', () => {
56
+ const md5 = spark.end();
57
+ const completedBuffer = Buffer.concat(buffers);
58
+ spinner.succeed(chalk.cyan('Success to analyse zip'));
59
+ resolve([completedBuffer, md5 + '.zip']);
60
+ });
61
+
62
+ rs.on('error', (err) => {
63
+ reject(err);
64
+ });
65
+ });
66
+ } catch (e) {
67
+ spinner.fail(chalk.red('Failed to analyse zip, please try again!'));
68
+ }
69
+ };
70
+
71
+ const getSign = async () => {
72
+ const spinner = ora(chalk.cyan('Waiting get file sign ...')).start();
73
+
74
+ try {
75
+ const app = getApp();
76
+ const partnerId = getValue(PARTNER_KEYS.PARTNER_ID);
77
+ const sessionId = getValue(PARTNER_KEYS.SESSION_ID);
78
+
79
+ const url = `${PARNTER_URL}/api/partner/apps/${app.uid}/theme_extensions/file/sign`;
80
+ const res = await axios.get(url, {
81
+ headers: {
82
+ Cookie: `awesomev2=${sessionId}`,
83
+ 'x-shoplazza-partner-id': partnerId
84
+ }
85
+ });
86
+ spinner.succeed(chalk.cyan('Success to get file sign'));
87
+ return res.data;
88
+ } catch (e) {
89
+ spinner.fail(chalk.red(e.message || e));
90
+ }
91
+ };
92
+
93
+ const deployOss = async () => {
94
+ const path = getZipPath();
95
+ if (path) {
96
+ const [buffer, md5] = await getBufferAndMd5(path);
97
+ const signData = await getSign();
98
+ if (!signData) {
99
+ return;
100
+ }
101
+
102
+ const spinner = ora(chalk.cyan('Deploying your zip to cdn ...')).start();
103
+
104
+ try {
105
+ const formData = new FormData();
106
+ formData.append('policy', signData.policy);
107
+ formData.append('OSSAccessKeyId', signData.access_id);
108
+ formData.append('success_action_status', 200);
109
+ formData.append('signature', signData.sign);
110
+ formData.append('key', md5);
111
+ formData.append('file', buffer);
112
+
113
+ const url = `https:${signData.write_host}/`;
114
+ const res = await axios.post(url, formData, {
115
+ global: true,
116
+ maxContentLength: 100000000,
117
+ maxBodyLength: 1000000000
118
+ });
119
+
120
+ if (res.status !== 200) {
121
+ throw new Error(`${res.status} ${resstatusText}`);
122
+ }
123
+ spinner.succeed();
124
+ return md5;
125
+ } catch (e) {
126
+ spinner.fail();
127
+ console.log(chalk.red(e.message || e));
128
+ }
129
+ }
130
+ };
131
+
132
+ const deployPartner = async () => {
133
+ const md5 = await deployOss();
134
+ if (!md5) {
135
+ return;
136
+ }
137
+
138
+ const spinner = ora(chalk.cyan('Deploying your zip to PARTNER ...')).start();
139
+ try {
140
+ const app = getApp();
141
+ if (!app) {
142
+ spinner.fail(chalk.red('Please choose your partner first!'));
143
+ return;
144
+ }
145
+
146
+ const partnerId = getValue(PARTNER_KEYS.PARTNER_ID);
147
+ const url = `${PARNTER_URL}/api/partner/apps/${app.uid}/theme_extensions`;
148
+ const res = await axios.put(
149
+ url,
150
+ {
151
+ title: getZipName(),
152
+ name: getZipName(),
153
+ file_name: md5
154
+ },
155
+ {
156
+ headers: {
157
+ cookie: `awesomev2=${getValue(PARTNER_KEYS.SESSION_ID)};`,
158
+ 'x-shoplazza-partner-id': partnerId
159
+ }
160
+ }
161
+ );
162
+
163
+ set({ [PARTNER_KEYS.EXTENSION_ID]: res.data.extension_id });
164
+ spinner.succeed();
165
+ return true;
166
+ } catch (e) {
167
+ spinner.fail();
168
+ console.log(e.message || e);
169
+ console.log(chalk.red(JSON.stringify(e.response.data)));
170
+ }
171
+ };
172
+
173
+ const createVersion = async (version) => {
174
+ const spinner = ora(chalk.cyan('Creating your version task ...')).start();
175
+
176
+ try {
177
+ const app = getApp();
178
+ const extensionId = getValue(PARTNER_KEYS.EXTENSION_ID);
179
+ const partnerId = getValue(PARTNER_KEYS.PARTNER_ID);
180
+
181
+ const url = `${PARNTER_URL}/api/partner/apps/${app.uid}/theme_extensions/${extensionId}/version_tasks`;
182
+ await axios.post(
183
+ url,
184
+ {
185
+ version
186
+ },
187
+ {
188
+ headers: {
189
+ cookie: `awesomev2=${getValue(PARTNER_KEYS.SESSION_ID)};`,
190
+ 'x-shoplazza-partner-id': partnerId
191
+ }
192
+ }
193
+ );
194
+ spinner.succeed();
195
+ return true;
196
+ } catch (e) {
197
+ spinner.fail();
198
+ console.log(chalk.red(e.message || e));
199
+ console.log(chalk.red(JSON.stringify(e.response?.data)));
200
+ }
201
+ };
202
+
203
+ const deploy = async () => {
204
+ line();
205
+ ora(chalk.cyan('Deploy Begin')).succeed();
206
+ let version;
207
+ (await deployPartner()) && (version = await inputVersion()) && (await createVersion(version)) && done();
208
+ line();
209
+ };
210
+
211
+ module.exports = {
212
+ deploy
213
+ };
@@ -0,0 +1,50 @@
1
+ const inquirer = require('inquirer');
2
+ const { set, PARTNER_KEYS } = require('../db/partner');
3
+ const { themeAppHandler, checkoutUIHandler } = require('../extensions');
4
+ const { line } = require('../log');
5
+
6
+ const EXTENSION_TYPES = {
7
+ CHECKOUT_UI: 'checkout_ui',
8
+ THEME_APP: 'theme_app'
9
+ };
10
+
11
+ const generate = async () => {
12
+ line();
13
+ const answer = await inquirer.prompt([
14
+ {
15
+ type: 'list',
16
+ name: 'extensionType',
17
+ message: 'Choose your extension type ↓',
18
+ default: EXTENSION_TYPES.THEME_APP,
19
+ prefix: '*',
20
+ choices: [
21
+ {
22
+ name: 'Theme App Extension',
23
+ value: EXTENSION_TYPES.THEME_APP
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ type: 'input',
29
+ name: 'extensionName',
30
+ message: 'Input your extesion name: ',
31
+ default: 'my-extension',
32
+ prefix: '*'
33
+ }
34
+ ]);
35
+ line();
36
+
37
+ set({ [PARTNER_KEYS.EXTENSION_TYPE]: answer.extensionType });
38
+
39
+ if (answer.extensionType === EXTENSION_TYPES.THEME_APP) {
40
+ await themeAppHandler(answer.extensionName);
41
+ } else if (answer.extensionType === EXTENSION_TYPES.CHECKOUT_UI) {
42
+ await checkoutUIHandler(answer.extensionName);
43
+ }
44
+
45
+ line();
46
+ };
47
+
48
+ module.exports = {
49
+ generate
50
+ };
@@ -0,0 +1,52 @@
1
+ const { default: axios } = require('axios');
2
+ const { selectVersion } = require('../inquirers/version');
3
+ const { PARNTER_URL } = require('../constants');
4
+ const { get, getValue, PARTNER_KEYS } = require('../db/partner');
5
+ const ora = require('ora');
6
+ const chalk = require('chalk');
7
+ const { getZipName } = require('./build');
8
+ const { done, line } = require('../log');
9
+
10
+ const publishVersion = async ({ version, appId, versionId, extensionId }) => {
11
+ const spinner = ora(chalk.cyan('Waiting publish ...'));
12
+
13
+ try {
14
+ const partnerId = getValue(PARTNER_KEYS.PARTNER_ID);
15
+ if (!partnerId) {
16
+ spinner.fail(chalk.red('Please choose a partner first!'));
17
+ return;
18
+ }
19
+
20
+ const url = `${PARNTER_URL}/api/partner/apps/${appId}/theme_extensions/${extensionId}/publications`;
21
+ await axios.post(
22
+ url,
23
+ {
24
+ name: getZipName(),
25
+ version_id: versionId,
26
+ type: 'enable'
27
+ },
28
+ {
29
+ headers: {
30
+ cookie: `awesomev2=${getValue(PARTNER_KEYS.SESSION_ID)};`,
31
+ 'x-shoplazza-partner-id': partnerId
32
+ }
33
+ }
34
+ );
35
+
36
+ spinner.succeed();
37
+ return true;
38
+ } catch (e) {
39
+ spinner.fail(e.message || e);
40
+ }
41
+ };
42
+
43
+ const publish = async () => {
44
+ const versionData = await selectVersion();
45
+ await publishVersion(versionData);
46
+ done();
47
+ line();
48
+ };
49
+
50
+ module.exports = {
51
+ publish
52
+ };