@ttmg/cli 0.1.9-beta.7 → 0.1.9-beta.9

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 (34) hide show
  1. package/README.md +19 -51
  2. package/dist/index.js +233 -142
  3. package/dist/index.js.map +1 -1
  4. package/dist/package.json +4 -6
  5. package/dist/public/assets/index-B-OUKblc.js +67 -0
  6. package/dist/public/assets/index-B-OUKblc.js.br +0 -0
  7. package/dist/public/assets/index-BNwkkghM.css +1 -0
  8. package/dist/public/assets/index-BNwkkghM.css.br +0 -0
  9. package/dist/public/assets/index-Bo2GE0iI.js +67 -0
  10. package/dist/public/assets/index-Bo2GE0iI.js.br +0 -0
  11. package/dist/public/assets/index-BuMld4Sl.css +1 -0
  12. package/dist/public/assets/index-BuMld4Sl.css.br +0 -0
  13. package/dist/public/assets/index-Cq-NkIfK.js +67 -0
  14. package/dist/public/assets/index-Cq-NkIfK.js.br +0 -0
  15. package/dist/public/assets/index-CrM2XxXK.js +67 -0
  16. package/dist/public/assets/index-CrM2XxXK.js.br +0 -0
  17. package/dist/public/assets/index-D1dpaK4P.js +67 -0
  18. package/dist/public/assets/index-D1dpaK4P.js.br +0 -0
  19. package/dist/public/assets/index-D80wbca2.js +67 -0
  20. package/dist/public/assets/index-D80wbca2.js.br +0 -0
  21. package/dist/public/assets/index-DIkT1K6H.js +67 -0
  22. package/dist/public/assets/index-DIkT1K6H.js.br +0 -0
  23. package/dist/public/assets/index-D_6UwS0L.css +1 -0
  24. package/dist/public/assets/index-D_6UwS0L.css.br +0 -0
  25. package/dist/public/assets/index-Dq6XWWnQ.js +67 -0
  26. package/dist/public/assets/index-Dq6XWWnQ.js.br +0 -0
  27. package/dist/public/assets/index-OaV93pLr.js +67 -0
  28. package/dist/public/assets/index-OaV93pLr.js.br +0 -0
  29. package/dist/public/assets/index-rO4GFCVV.js +67 -0
  30. package/dist/public/assets/index-rO4GFCVV.js.br +0 -0
  31. package/dist/public/assets/index-vHZ0A7MO.css +1 -0
  32. package/dist/public/assets/index-vHZ0A7MO.css.br +0 -0
  33. package/dist/public/index.html +2 -2
  34. package/package.json +4 -6
package/README.md CHANGED
@@ -1,72 +1,40 @@
1
- ## @ttmg/cli
1
+ ### @ttmg/cli
2
+ `ttmg` is a command-line tool designed for managing and developing mini-game projects. It supports initialization, development, debugging, and packaging for both H5 and native mini-games.
3
+ `ttmg` 是一款专为小游戏项目管理与开发设计的命令行工具,支持 H5 小游戏和原生小游戏的初始化、开发调试及打包构建。
4
+ #### Installation 安装
2
5
 
3
- `ttmg` 是一个用于管理和开发小游戏项目的命令行工具,支持 H5 小游戏和原生小游戏的初始化、开发调试和打包构建。
4
-
5
- ### 安装
6
-
7
- 你可以通过 npm 本地安装或全局安装:
6
+ You can install `ttmg` globally or as a project dependency via npm:
7
+ 你可以通过 npm 全局或本地安装 `ttmg`:
8
8
 
9
9
  ```
10
10
  npm install @ttmg/cli -g
11
11
  ```
12
12
 
13
- 或者在项目中作为开发依赖:
13
+ Or add it as a development dependency in your project:
14
+ 或者作为项目开发依赖安装:
14
15
 
15
16
  ```
16
17
  npm install @ttmg/cli --save-dev
17
18
  ```
18
19
 
19
- ### 使用方法
20
-
21
- 安装后,可以在命令行中使用 `ttmg` 命令。
22
-
23
- #### 查看帮助
24
-
25
- ```
26
- ttmg --help
27
- ```
28
-
29
- #### 命令说明
30
-
31
- ##### 1. 初始化项目
32
-
33
- - 对基于游戏引擎构建好的 H5 小游戏进行初始化
34
-
35
- ```
36
- ttmg init --h5
37
- ```
38
-
39
- - 对基于游戏引擎构建好的 Native 小游戏进行初始化
40
-
41
- ```
42
- ttmg init --native
43
- ```
44
-
45
- ##### 2. 开发调试
20
+ #### Login 登录
46
21
 
47
- 启动本地开发环境,打开浏览器进行调试。
48
-
49
- - H5 小游戏调试
50
-
51
- ```
52
- ttmg dev --h5
53
- ```
54
-
55
- - 原生小游戏调试
22
+ Before running any `ttmg` commands, please log in with your TikTok account. Once logged in, the tool will automatically use your account for project initialization, development, debugging, and packaging.
23
+ 在首次使用 `ttmg` 命令前,请先登录你的 TikTok 账号。登录后,工具会自动使用该账号进行项目初始化、开发调试和打包构建。
56
24
 
57
25
  ```
58
- ttmg dev --native
26
+ ttmg login
59
27
  ```
60
28
 
61
- ##### 3. 项目打包
62
-
63
- 打包项目用于发布。
29
+ #### Debugging 调试
64
30
 
65
- - 打包 H5 小游戏
31
+ - By default, debug native mini-games:
32
+ - 默认调试原生小游戏:
66
33
 
67
34
  ```
68
- ttmg build --h5
35
+ ttmg dev
69
36
  ```
70
37
 
71
- - 打包原生小游戏
72
- ttmg build --native
38
+ - 调试 H5 小游戏:
39
+ - To debug H5 mini-games:
40
+ ttmg dev --h5
package/dist/index.js CHANGED
@@ -17,13 +17,15 @@ var axios = require('axios');
17
17
  var handlebars = require('handlebars');
18
18
  var esbuild = require('esbuild');
19
19
  var archiver = require('archiver');
20
+ var http = require('http');
20
21
  var ttmgPack = require('ttmg-pack');
21
22
  var expressStaticGzip = require('express-static-gzip');
23
+ var fileUpload = require('express-fileupload');
22
24
  var chokidar = require('chokidar');
23
25
  var WebSocket = require('ws');
24
26
  var glob = require('glob');
25
27
  var got = require('got');
26
- var FormData = require('form-data');
28
+ var FormData$1 = require('form-data');
27
29
 
28
30
  function _interopNamespaceDefault(e) {
29
31
  var n = Object.create(null);
@@ -243,27 +245,26 @@ async function checkUpdate() {
243
245
  });
244
246
  }
245
247
 
246
- let config$1 = null;
248
+ let config = null;
247
249
  const CONFIG_PATH = path.join(os.homedir(), '.ttmgrc');
248
- const getTTmgrcConfig = () => {
249
- if (config$1) {
250
- return config$1;
250
+ const getTTMGRC = () => {
251
+ if (config) {
252
+ return config;
251
253
  }
252
254
  else {
253
255
  // only check one time
254
256
  if (!fs.existsSync(CONFIG_PATH)) {
255
- fs.writeFileSync(CONFIG_PATH, '{}');
256
- return {};
257
+ return null;
257
258
  }
258
259
  else {
259
260
  // safe parse
260
261
  let res = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
261
- config$1 = res;
262
+ config = res;
262
263
  return res;
263
264
  }
264
265
  }
265
266
  };
266
- const setTTmgrcConfig = (config) => {
267
+ const setTTMGRC = (config) => {
267
268
  // updata cache config
268
269
  config = config;
269
270
  fs.writeFileSync(CONFIG_PATH, JSON.stringify(config));
@@ -335,27 +336,26 @@ async function login() {
335
336
  user_id: response?.data?.data?.user_id,
336
337
  cookie: response?.headers['set-cookie'].join('; '),
337
338
  };
338
- setTTmgrcConfig(data);
339
+ setTTMGRC(data);
339
340
  spinner$2.succeed(chalk.bold.green('login successfully!'));
340
341
  process.exit(0);
341
342
  }
342
343
  }
343
344
 
344
- /**
345
- * 获取配置文件中的 cookie
346
- */
347
- const config = getTTmgrcConfig();
348
- const cookie = config.cookie;
349
- axios.defaults.headers.common['Cookie'] = cookie;
350
345
  // ppe_dev_tool
351
346
  async function request({ url, method, data, headers, params, }) {
347
+ const config = getTTMGRC();
348
+ const cookie = config?.cookie;
352
349
  try {
353
350
  const res = await axios({
354
351
  url,
355
352
  method,
356
353
  data,
357
354
  params,
358
- headers,
355
+ headers: {
356
+ ...(headers || {}),
357
+ Cookie: cookie,
358
+ },
359
359
  });
360
360
  return {
361
361
  data: res.data,
@@ -399,16 +399,35 @@ async function fetchGameInfo(gameId) {
399
399
  }
400
400
  }
401
401
 
402
+ async function uploadGameToPlatform({ data, name, gameId, desc = '--', }) {
403
+ const formData = new FormData();
404
+ formData.append('file', new Blob([data], { type: 'application/zip' }), name);
405
+ formData.append('client_key', gameId);
406
+ formData.append('desc', desc);
407
+ const response = await request({
408
+ method: 'POST',
409
+ url: 'https://developers.tiktok.com/tiktok/v3/devportal/minigame/devtool/upload',
410
+ data: formData,
411
+ headers: {
412
+ 'x-use-ppe': '1',
413
+ 'x-tt-env': 'ppe_dev_tool',
414
+ // @ts-ignore
415
+ 'Content-Type': `multipart/form-data; boundary=${formData._boundary}`,
416
+ },
417
+ });
418
+ return {
419
+ data: response?.data,
420
+ error: response?.error,
421
+ };
422
+ }
423
+
402
424
  function getCurrentUser() {
403
425
  try {
404
- const config = getTTmgrcConfig();
405
- return {
406
- email: config?.email || '',
407
- user_id: config?.user_id || '',
408
- };
426
+ const config = getTTMGRC();
427
+ return config;
409
428
  }
410
429
  catch (err) {
411
- return {};
430
+ return null;
412
431
  }
413
432
  }
414
433
 
@@ -729,10 +748,67 @@ function getOutputDir() {
729
748
  return path.join(os.homedir(), '__TTMG__', clientKey);
730
749
  }
731
750
 
732
- const DEV_PORT = 9528;
733
- const DEV_WS_PORT = 9529;
751
+ const DEV_PORT = 3000;
752
+ const DEV_WS_PORT = 4000;
734
753
  path.join(os.homedir(), '__TTMG__');
735
754
 
755
+ // store.ts
756
+ class Store {
757
+ constructor(initialState) {
758
+ this.listeners = [];
759
+ this.state = initialState;
760
+ }
761
+ // 获取单例实例
762
+ static getInstance(initialState) {
763
+ if (!Store.instance) {
764
+ Store.instance = new Store(initialState);
765
+ }
766
+ return Store.instance;
767
+ }
768
+ // 获取状态
769
+ getState() {
770
+ return this.state;
771
+ }
772
+ // 设置状态
773
+ setState(newState) {
774
+ this.state = { ...this.state, ...newState };
775
+ this.listeners.forEach(listener => listener(this.state));
776
+ }
777
+ // 订阅状态变化
778
+ subscribe(listener) {
779
+ this.listeners.push(listener);
780
+ return () => {
781
+ this.listeners = this.listeners.filter(l => l !== listener);
782
+ };
783
+ }
784
+ // 重置状态
785
+ reset(newState) {
786
+ this.state = newState;
787
+ this.listeners.forEach(listener => listener(this.state));
788
+ }
789
+ }
790
+ const store = Store.getInstance({
791
+ clientHost: '',
792
+ clientHttpPort: '',
793
+ clientWsPort: '',
794
+ clientWsHost: '',
795
+ clientKey: '',
796
+ nodeServerPort: DEV_PORT,
797
+ nodeWsPort: DEV_WS_PORT,
798
+ packages: {},
799
+ isUnderCompiling: false,
800
+ isWaitingForUpload: false,
801
+ projectInfo: {
802
+ projectSize: 0,
803
+ mainPkg: {
804
+ pkgSize: 0,
805
+ isMatchSizeLimit: false,
806
+ },
807
+ independentPkgs: {},
808
+ },
809
+ checkResults: [],
810
+ });
811
+
736
812
  function showTips(context) {
737
813
  console.log(chalk.gray('─────────────────────────────────────────────'));
738
814
  console.log(chalk.yellow.bold('1.') +
@@ -753,76 +829,55 @@ function showTips(context) {
753
829
  }
754
830
 
755
831
  const successCode = 0;
832
+ const errorCode = -1;
756
833
  const outputDir = getOutputDir();
757
834
  const publicPath = path.join(__dirname, 'public');
758
- let server;
759
835
  async function start() {
760
836
  const startTime = Date.now();
761
- if (server) {
762
- closeServer();
763
- }
764
837
  const app = express();
838
+ app.use(fileUpload()); // 启用 express-fileupload 中间件
839
+ // --- 中间件和路由设置 ---
765
840
  app.use(expressStaticGzip(publicPath, {
766
841
  enableBrotli: true,
767
842
  orderPreference: ['br'],
768
843
  }));
769
- /**
770
- * 支持跨域请求
771
- */
772
844
  app.use((req, res, next) => {
773
845
  res.header('Access-Control-Allow-Origin', '*');
774
846
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
775
847
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
776
848
  next();
777
849
  });
778
- const port = DEV_PORT;
779
850
  app.use(express.json());
780
- // 解析 FormData body
781
851
  app.use(express.urlencoded({ extended: true }));
782
- /**
783
- * 支持静态资源
784
- */
785
852
  app.use('/game/files', express.static(outputDir));
786
853
  app.get('/game/config', async (req, res) => {
787
- const basic = await ttmgPack.getPkgs({
788
- entryDir: process.cwd(),
789
- });
790
- const { email, user_id } = getCurrentUser();
854
+ const basic = await ttmgPack.getPkgs({ entryDir: process.cwd() });
855
+ const user = getCurrentUser();
791
856
  const { clientKey } = getClientKey();
792
857
  res.send({
793
858
  error: null,
794
859
  data: {
795
- email,
796
- user_id,
860
+ user,
797
861
  code: successCode,
798
- nodeWsPort: DEV_WS_PORT,
862
+ nodeWsPort: store.getState().nodeWsPort,
799
863
  clientKey: clientKey,
800
- schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${DEV_WS_PORT}`,
864
+ schema: `https://www.tiktok.com/ttmg/dev/${clientKey}?host=${getLocalIP()}&port=${store.getState().nodeWsPort}`,
801
865
  ...basic,
802
866
  devToolVersion: require(path.join(__dirname, 'package.json')).version,
803
867
  },
804
868
  });
805
869
  });
806
870
  app.get('/game/detail', async (req, res) => {
807
- const basic = await ttmgPack.getPkgs({
808
- entryDir: process.cwd(),
809
- });
871
+ const basic = await ttmgPack.getPkgs({ entryDir: process.cwd() });
810
872
  const { clientKey } = getClientKey();
811
873
  const { error, data: gameInfo } = await fetchGameInfo(clientKey);
812
874
  if (error) {
813
- res.send({
814
- error,
815
- data: null,
816
- });
875
+ res.send({ error, data: null });
817
876
  return;
818
877
  }
819
- res.send({
820
- error: null,
821
- data: {
822
- ...basic,
823
- ...gameInfo,
824
- },
825
- });
878
+ else {
879
+ res.send({ error: null, data: { ...basic, ...gameInfo } });
880
+ }
826
881
  });
827
882
  app.get('/game/check', async (req, res) => {
828
883
  const checkResult = await ttmgPack.checkPkgs({
@@ -832,7 +887,7 @@ async function start() {
832
887
  output: outputDir,
833
888
  dev: {
834
889
  enable: true,
835
- port: DEV_PORT,
890
+ port: store.getState().nodeServerPort,
836
891
  host: 'localhost',
837
892
  enableSourcemap: false,
838
893
  enableLog: false,
@@ -845,36 +900,113 @@ async function start() {
845
900
  },
846
901
  },
847
902
  });
848
- res.send({
849
- code: successCode,
850
- data: checkResult,
851
- });
903
+ res.send({ code: successCode, data: checkResult });
904
+ });
905
+ /**
906
+ * @description 上传游戏代码到服务器
907
+ */
908
+ app.post('/game/upload', async (req, res) => {
909
+ const fileKeys = Object.keys(req.files);
910
+ const uploadedFile = req.files[fileKeys[0]];
911
+ if (!uploadedFile) {
912
+ res.status(400).send({ code: errorCode, data: 'No file uploaded' }); // 使用正确的 HTTP 状态码
913
+ return;
914
+ }
915
+ try {
916
+ // 通过 header 获取 desc
917
+ const desc = req.headers['ttmg-game-desc'];
918
+ // 直接传递需要的信息
919
+ const { data, error } = await uploadGameToPlatform({
920
+ data: uploadedFile.data, // 这是 Buffer
921
+ name: uploadedFile.name, // 这是文件名
922
+ gameId: getClientKey().clientKey,
923
+ desc,
924
+ });
925
+ if (error) {
926
+ res.send({ code: errorCode, error });
927
+ }
928
+ else {
929
+ res.send({ code: successCode, data });
930
+ }
931
+ }
932
+ catch (error) {
933
+ // 错误处理可以更具体
934
+ let errorMessage = 'An unknown error occurred.';
935
+ if (error instanceof Error) {
936
+ errorMessage = error.message;
937
+ }
938
+ // 打印详细错误到服务器日志,方便排查
939
+ res.status(500).send({ code: errorCode, data: errorMessage }); // 使用正确的 HTTP 状态码
940
+ }
852
941
  });
853
942
  app.get('*', (req, res) => {
854
943
  res.sendFile(path.join(publicPath, 'index.html'));
855
944
  });
856
- // 启动服务
857
- server = await new Promise((resolve, reject) => {
858
- const server = app.listen(port, () => {
859
- /**
860
- * 获取当前版本号
861
- */
862
- const version = require(path.join(__dirname, 'package.json')).version;
863
- console.log(chalk.green.bold(`TTMG`), chalk.green(`v${version}`), chalk.gray(`ready in`), chalk.bold(`${Date.now() - startTime}ms`));
864
- resolve(server);
945
+ // --- 中间件和路由设置结束 ---
946
+ // 步骤 2: 用配置好的 app 实例创建一个 http.Server。我们只创建这一次!
947
+ const server = http.createServer(app);
948
+ /**
949
+ * @description 尝试在指定端口启动服务。这是个纯粹的辅助函数。
950
+ * @param {number} port - 要尝试的端口号。
951
+ * @returns {Promise<boolean>} 成功返回 true,因端口占用失败则返回 false。
952
+ */
953
+ function tryListen(port) {
954
+ return new Promise(resolve => {
955
+ // 定义错误处理函数
956
+ const onError = err => {
957
+ // 清理掉另一个事件的监听器,防止内存泄漏
958
+ server.removeListener('listening', onListening);
959
+ if (err.code === 'EADDRINUSE') {
960
+ console.log(chalk(`Port ${port} is already in use, trying ${port + 1}...`));
961
+ resolve(false); // 明确表示因端口占用而失败
962
+ }
963
+ else {
964
+ // 对于其他致命错误,直接退出进程
965
+ console.log(chalk.red.bold(err.message));
966
+ process.exit(1);
967
+ }
968
+ };
969
+ // 定义成功处理函数
970
+ const onListening = () => {
971
+ // 清理掉另一个事件的监听器
972
+ server.removeListener('error', onError);
973
+ resolve(true); // 明确表示成功
974
+ };
975
+ // 使用 .once() 来确保监听器只执行一次然后自动移除
976
+ server.once('error', onError);
977
+ server.once('listening', onListening);
978
+ // 执行监听动作
979
+ server.listen(port);
865
980
  });
866
- });
867
- const baseUrl = `http://localhost:${port}`;
981
+ }
982
+ // 步骤 3: 使用循环来线性、串行地尝试启动服务
983
+ let isListening = false;
984
+ const maxRetries = 20; // 设置一个最大重试次数,以防万一
985
+ for (let i = 0; i < maxRetries; i++) {
986
+ const currentPort = store.getState().nodeServerPort;
987
+ isListening = await tryListen(currentPort);
988
+ if (isListening) {
989
+ break; // 成功,跳出循环
990
+ }
991
+ else {
992
+ // 失败(端口占用),更新端口号,准备下一次循环
993
+ store.setState({ nodeServerPort: currentPort + 1 });
994
+ }
995
+ }
996
+ // 步骤 4: 检查最终结果,如果所有尝试都失败了,则退出
997
+ if (!isListening) {
998
+ console.log(chalk.red.bold(`Failed to start server after trying ${maxRetries} ports.`));
999
+ process.exit(1);
1000
+ }
1001
+ // --- 服务启动成功后的逻辑 ---
1002
+ // @ts-ignore
1003
+ const finalPort = server.address().port; // 从成功的 server 实例安全地获取最终端口
1004
+ const version = require(path.join(__dirname, 'package.json')).version;
1005
+ console.log(chalk.green.bold(`TTMG`), chalk.green(`v${version}`), chalk.gray(`ready in`), chalk.bold(`${Date.now() - startTime}ms`));
1006
+ const baseUrl = `http://localhost:${finalPort}`;
868
1007
  showTips({ server: baseUrl });
869
1008
  openUrl(baseUrl);
870
1009
  }
871
- function closeServer() {
872
- server.close(() => {
873
- console.log('Dev server closed');
874
- server = null;
875
- process.exit(0);
876
- });
877
- }
878
1010
 
879
1011
  /**
880
1012
  * 一个类型安全的、通用的事件发射器类。
@@ -951,63 +1083,12 @@ class TypedEventEmitter {
951
1083
  }
952
1084
  const eventEmitter = new TypedEventEmitter();
953
1085
 
954
- class Store {
955
- constructor(initialState) {
956
- this.listeners = [];
957
- this.state = initialState;
958
- }
959
- // 获取单例实例
960
- static getInstance(initialState) {
961
- if (!Store.instance) {
962
- Store.instance = new Store(initialState);
963
- }
964
- return Store.instance;
965
- }
966
- // 获取状态
967
- getState() {
968
- return this.state;
969
- }
970
- // 设置状态
971
- setState(newState) {
972
- this.state = { ...this.state, ...newState };
973
- this.listeners.forEach(listener => listener(this.state));
974
- }
975
- // 订阅状态变化
976
- subscribe(listener) {
977
- this.listeners.push(listener);
978
- return () => {
979
- this.listeners = this.listeners.filter(l => l !== listener);
980
- };
981
- }
982
- // 重置状态
983
- reset(newState) {
984
- this.state = newState;
985
- this.listeners.forEach(listener => listener(this.state));
986
- }
987
- }
988
- const store = Store.getInstance({
989
- clientHost: '',
990
- clientHttpPort: '',
991
- clientWsPort: '',
992
- clientWsHost: '',
993
- clientKey: '',
994
- packages: {},
995
- isUnderCompiling: false,
996
- isWaitingForUpload: false,
997
- projectInfo: {
998
- projectSize: 0,
999
- mainPkg: {
1000
- pkgSize: 0,
1001
- isMatchSizeLimit: false,
1002
- },
1003
- independentPkgs: {},
1004
- },
1005
- checkResults: [],
1006
- });
1007
-
1008
1086
  class WsServer {
1009
1087
  constructor() {
1010
- this.ws = new WebSocket.Server({ port: DEV_WS_PORT });
1088
+ this.startWsServer(store.getState().nodeWsPort);
1089
+ }
1090
+ startWsServer(port) {
1091
+ this.ws = new WebSocket.Server({ port });
1011
1092
  this.ws.on('connection', ws => {
1012
1093
  const { clientHttpPort, clientHost, clientWsPort } = store.getState();
1013
1094
  if (clientHost) {
@@ -1041,8 +1122,6 @@ class WsServer {
1041
1122
  * 关闭调试服务
1042
1123
  */
1043
1124
  this.ws.close();
1044
- closeServer();
1045
- console.log('close server');
1046
1125
  break;
1047
1126
  }
1048
1127
  }
@@ -1112,6 +1191,18 @@ class WsServer {
1112
1191
  }
1113
1192
  });
1114
1193
  });
1194
+ this.ws.on('error', err => {
1195
+ if (err.code === 'EADDRINUSE') {
1196
+ store.setState({
1197
+ nodeWsPort: store.getState().nodeWsPort + 1,
1198
+ });
1199
+ this.startWsServer(store.getState().nodeWsPort);
1200
+ }
1201
+ else {
1202
+ console.log(chalk.red.bold(err.message));
1203
+ process.exit(1);
1204
+ }
1205
+ });
1115
1206
  }
1116
1207
  send(params) {
1117
1208
  this.ws.clients.forEach(client => {
@@ -1225,7 +1316,7 @@ function compile(context) {
1225
1316
  context: {
1226
1317
  clientKey,
1227
1318
  outputDir,
1228
- devPort: DEV_PORT,
1319
+ devPort: store.getState().nodeServerPort,
1229
1320
  entryDir,
1230
1321
  },
1231
1322
  });
@@ -1331,7 +1422,7 @@ async function zipDirectory(sourceDir, outPath) {
1331
1422
  }
1332
1423
  async function uploadZip(zipPath, callback) {
1333
1424
  const startTime = Date.now();
1334
- const form = new FormData();
1425
+ const form = new FormData$1();
1335
1426
  form.append('file', fs.createReadStream(zipPath), {
1336
1427
  filename: 'upload.zip',
1337
1428
  contentType: 'application/zip',
@@ -1467,7 +1558,7 @@ async function upload() {
1467
1558
  });
1468
1559
  }
1469
1560
 
1470
- var version = "0.1.9-beta.7";
1561
+ var version = "0.1.9-beta.9";
1471
1562
  var pkg = {
1472
1563
  version: version};
1473
1564