generator-mico-cli 0.2.7 → 0.2.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 (21) hide show
  1. package/generators/micro-react/index.js +8 -3
  2. package/generators/micro-react/templates/.eslintrc.js +1 -0
  3. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +5 -5
  4. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +6 -30
  5. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +6 -6
  6. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +6 -6
  7. package/generators/micro-react/templates/apps/layout/src/app.tsx +18 -6
  8. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +4 -0
  9. package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +2 -2
  10. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +2 -2
  11. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +72 -6
  12. package/generators/micro-react/templates/apps/layout/src/hooks/useRoutePermissionRefresh.ts +2 -2
  13. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +0 -1
  14. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +2 -2
  15. package/generators/micro-react/templates/apps/layout/src/services/user.ts +1 -0
  16. package/generators/micro-react/templates/apps/layout/tailwind.config.js +3 -0
  17. package/generators/subapp-react/index.js +10 -1
  18. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +0 -1
  19. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +0 -1
  20. package/generators/subapp-react/templates/homepage/src/common/request.ts +27 -0
  21. package/package.json +1 -1
@@ -139,15 +139,20 @@ module.exports = class extends Generator {
139
139
  }
140
140
  }
141
141
 
142
+ install() {
143
+ this.log('');
144
+ this.log('📦 正在安装依赖...');
145
+ this.spawnCommandSync('pnpm', ['install'], {
146
+ cwd: this.destDir
147
+ });
148
+ }
149
+
142
150
  end() {
143
151
  this.log('');
144
152
  this.log('✅ 项目创建成功!');
145
153
  this.log('');
146
154
  this.log(' 后续步骤:');
147
155
  this.log('');
148
- this.log(' # 安装依赖');
149
- this.log(' pnpm install');
150
- this.log('');
151
156
  this.log(' # 启动开发服务器');
152
157
  this.log(' pnpm dev');
153
158
  this.log('');
@@ -25,6 +25,7 @@ module.exports = {
25
25
  'dist/',
26
26
  'apps/',
27
27
  'scripts/',
28
+ '**/*.d.ts',
28
29
  ],
29
30
  };
30
31
 
@@ -43,15 +43,15 @@ const config: ReturnType<typeof defineConfig> = {
43
43
  },
44
44
  define: {
45
45
  'process.env.NODE_ENV': 'development',
46
- 'process.env.APP_ID': 'mibot_dev',
46
+ 'process.env.APP_ID': '<%= projectName %>',
47
47
  'process.env.API_BASE_URL': '',
48
- 'process.env.PROXY_SUFFIX': '/',
48
+ 'process.env.PROXY_SUFFIX': '/proxy/audit_svr',
49
49
  'process.env.LOGIN_ENDPOINT':
50
- '',
50
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/',
51
51
  'process.env.REFRESH_ENDPOINT':
52
- '',
52
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/refresh/',
53
53
  'process.env.EXTERNAL_LOGIN_PATH':
54
- '',
54
+ 'https://micous-idp.cig.tencentcs.com/sso/xxxxxxx/cas',
55
55
  },
56
56
  };
57
57
 
@@ -1,43 +1,19 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
- import fs from 'fs';
5
- import path from 'path';
6
-
7
- // 使用 fs 读取 JSON 避免成为配置依赖,修改 menus.json 不会触发服务器重启
8
- const mockMenus = JSON.parse(
9
- fs.readFileSync(path.join(__dirname, '../mock/menus.json'), 'utf-8'),
10
- );
11
4
 
12
5
  const config: ReturnType<typeof defineConfig> = {
13
- publicPath: '/',
14
- /**
15
- * @name 注入到 HTML head 的脚本
16
- * @description 开发环境注入 mock 菜单数据
17
- * @doc https://umijs.org/docs/api/config#headscripts
18
- */
19
- headScripts: [
20
- {
21
- content: `
22
- window.__MICO_MENUS__ = ${JSON.stringify(mockMenus)};
23
- window.__MICO_CONFIG__ = {
24
- appName: 'Mico Center',
25
- apiBaseUrl: '',
26
- };
27
- `,
28
- },
29
- ],
30
6
  define: {
31
7
  'process.env.NODE_ENV': 'development',
32
- 'process.env.APP_ID': 'mibot_dev',
33
- 'process.env.API_BASE_URL': 'https://dashboard-api-test.micoplatform.com',
34
- 'process.env.PROXY_SUFFIX': '/',
8
+ 'process.env.APP_ID': '<%= projectName %>',
9
+ 'process.env.API_BASE_URL': '',
10
+ 'process.env.PROXY_SUFFIX': '/proxy/audit_svr',
35
11
  'process.env.LOGIN_ENDPOINT':
36
- '',
12
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/',
37
13
  'process.env.REFRESH_ENDPOINT':
38
- '',
14
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/refresh/',
39
15
  'process.env.EXTERNAL_LOGIN_PATH':
40
- '',
16
+ 'https://micous-idp.cig.tencentcs.com/sso/xxxxxxx/cas',
41
17
  },
42
18
  };
43
19
 
@@ -5,15 +5,15 @@ import { defineConfig } from '@umijs/max';
5
5
  const config: ReturnType<typeof defineConfig> = {
6
6
  define: {
7
7
  'process.env.NODE_ENV': 'testing',
8
- 'process.env.APP_ID': 'mibot',
9
- 'process.env.API_BASE_URL': 'https://dashboard-api-test.micoplatform.com',
10
- 'process.env.PROXY_SUFFIX': '/',
8
+ 'process.env.APP_ID': '<%= projectName %>',
9
+ 'process.env.API_BASE_URL': '',
10
+ 'process.env.PROXY_SUFFIX': '/proxy/audit_svr',
11
11
  'process.env.LOGIN_ENDPOINT':
12
- '',
12
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/',
13
13
  'process.env.REFRESH_ENDPOINT':
14
- '',
14
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/refresh/',
15
15
  'process.env.EXTERNAL_LOGIN_PATH':
16
- '',
16
+ 'https://micous-idp.cig.tencentcs.com/sso/xxxxxxx/cas',
17
17
  },
18
18
  };
19
19
 
@@ -10,15 +10,15 @@ const PUBLIC_PATH: string = CDN_PUBLIC_PATH
10
10
  const config: ReturnType<typeof defineConfig> = {
11
11
  define: {
12
12
  'process.env.NODE_ENV': 'production',
13
- 'process.env.APP_ID': 'mibot',
14
- 'process.env.API_BASE_URL': 'https://dashboard-api.micoplatform.com',
15
- 'process.env.PROXY_SUFFIX': '/',
13
+ 'process.env.APP_ID': '<%= projectName %>',
14
+ 'process.env.API_BASE_URL': '',
15
+ 'process.env.PROXY_SUFFIX': '/proxy/audit_svr',
16
16
  'process.env.LOGIN_ENDPOINT':
17
- '',
17
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/',
18
18
  'process.env.REFRESH_ENDPOINT':
19
- '',
19
+ 'https://dashboard-api-test.micoplatform.com/api/yufu_login/refresh/',
20
20
  'process.env.EXTERNAL_LOGIN_PATH':
21
- '',
21
+ 'https://micous-idp.cig.tencentcs.com/sso/xxxxxxx/cas',
22
22
  },
23
23
  // 生产环境:将所有代码打包到一个文件
24
24
  extraBabelPlugins: ['babel-plugin-dynamic-import-node'],
@@ -1,5 +1,5 @@
1
- import { history, Navigate, type RequestConfig } from '@umijs/max';
2
- import { addGlobalUncaughtErrorHandler, prefetchApps } from 'qiankun';
1
+ import { history, type RequestConfig } from '@umijs/max';
2
+ import { addGlobalUncaughtErrorHandler } from 'qiankun';
3
3
  import { errorConfig } from './requestErrorConfig';
4
4
  // 解决「React19 中无法使用 Message/Notification」的问题。 @see https://github.com/arco-design/arco-design/issues/2900#issuecomment-2796571653
5
5
  import * as arco from '@arco-design/web-react';
@@ -10,15 +10,18 @@ import { getStoredAuthToken } from './common/auth/auth-manager';
10
10
  import type { IUserInfo } from './common/auth/type';
11
11
  import { fetchUserInfo } from './services/user';
12
12
  import { extractRoutes, getWindowMenus } from './common/menu';
13
- import { ensureSsoSession } from './common/request/sso';
14
13
  import {
15
14
  clearMicroAppProps,
16
15
  type IMicroAppProps,
17
16
  setMicroAppProps,
18
17
  } from './common/micro';
18
+ import {
19
+ ensureSsoSession,
20
+ handleAuthFailureRedirect,
21
+ } from './common/request/sso';
19
22
  import { initTheme } from './common/theme';
20
23
  import MicroAppLoader from './components/MicroAppLoader';
21
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
24
+ import { isNoAuthRoute } from '@/constants';
22
25
  import './global.less';
23
26
 
24
27
  // ==================== qiankun 全局错误处理 ====================
@@ -110,10 +113,10 @@ export async function getInitialState(): Promise<{
110
113
  };
111
114
 
112
115
  const { location } = history;
113
- const isNoAuthRoute = NO_AUTH_ROUTE_LIST.includes(location.pathname);
116
+ const noAuthRoute = isNoAuthRoute(location.pathname);
114
117
 
115
118
  // 非免认证路由:走 SSO 流程
116
- if (!isNoAuthRoute) {
119
+ if (!noAuthRoute) {
117
120
  await ensureSsoSession();
118
121
  }
119
122
 
@@ -128,6 +131,15 @@ export async function getInitialState(): Promise<{
128
131
  }
129
132
  }
130
133
 
134
+ // 非免认证路由且没有 token,跳转到 SSO 登录
135
+ if (!noAuthRoute) {
136
+ handleAuthFailureRedirect();
137
+ // 返回空状态,页面会被重定向
138
+ return {
139
+ fetchUserInfo: fetchUserInfoFn,
140
+ };
141
+ }
142
+
131
143
  return {
132
144
  fetchUserInfo: fetchUserInfoFn,
133
145
  };
@@ -157,6 +157,10 @@ declare global {
157
157
  apiBaseUrl?: string;
158
158
  /** 默认重定向路径,访问 "/" 时自动跳转到此路径 */
159
159
  defaultPath?: string;
160
+ /** 动态配置的免鉴权路由列表,支持精确匹配和前缀匹配(以 /* 结尾) */
161
+ noAuthRouteList?: string[];
162
+ /** 动态配置的不显示布局路由列表,支持精确匹配和前缀匹配(以 /* 结尾) */
163
+ noLayoutRouteList?: string[];
160
164
  [key: string]: unknown;
161
165
  };
162
166
  __MICO_WORKSPACE__?: WorkspaceConfig | null;
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { request as rawRequest } from '@umijs/max';
18
18
  import { setStoredAuthToken } from '../auth/auth-manager';
19
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
19
+ import { isNoAuthRoute } from '@/constants';
20
20
 
21
21
  // 配置相关
22
22
  import {
@@ -64,7 +64,7 @@ initDefaultInterceptors(isFetchingToken, addToPendingQueue);
64
64
  * 判断当前路由是否跳过认证
65
65
  */
66
66
  const shouldSkipAuth = (): boolean => {
67
- return NO_AUTH_ROUTE_LIST.includes(location.pathname);
67
+ return isNoAuthRoute(location.pathname);
68
68
  };
69
69
 
70
70
  /**
@@ -1,5 +1,5 @@
1
1
  import { findRouteByPath } from '@/common/menu';
2
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
2
+ import { isNoLayoutRoute } from '@/constants';
3
3
  import useMenu from '@/hooks/useMenu';
4
4
  import { Tabs } from '@arco-design/web-react';
5
5
  import { history, useLocation } from '@umijs/max';
@@ -47,7 +47,7 @@ function AppTabs() {
47
47
  const [tabs, setTabs] = useState<TabItem[]>([]);
48
48
 
49
49
  const showTabs = useMemo(() => {
50
- return !NO_AUTH_ROUTE_LIST.includes(location.pathname);
50
+ return !isNoLayoutRoute(location.pathname);
51
51
  }, [location.pathname]);
52
52
 
53
53
  const microAppPrefixes = useMemo(() => {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 应用常量定义
3
3
  */
4
-
4
+ export const DEFAULT_NAME = 'Mico';
5
5
  /**
6
6
  * 路由路径常量
7
7
  */
@@ -19,7 +19,7 @@ export const ROUTES = {
19
19
  } as const;
20
20
 
21
21
  /**
22
- * 无需认证的路由列表
22
+ * 无需认证的路由列表(静态配置)
23
23
  */
24
24
  export const NO_AUTH_ROUTE_LIST: string[] = [
25
25
  ROUTES.LOGIN,
@@ -29,12 +29,78 @@ export const NO_AUTH_ROUTE_LIST: string[] = [
29
29
  ROUTES.NOT_FOUND,
30
30
  ];
31
31
 
32
+ /**
33
+ * 获取合并后的免鉴权路由列表
34
+ * 合并静态常量 + window.__MICO_CONFIG__.noAuthRouteList
35
+ */
36
+ export const getNoAuthRouteList = (): string[] => {
37
+ const dynamicRoutes = window.__MICO_CONFIG__?.noAuthRouteList ?? [];
38
+ return [...new Set([...NO_AUTH_ROUTE_LIST, ...dynamicRoutes])];
39
+ };
40
+
41
+ /**
42
+ * 判断路径是否匹配路由列表(支持精确匹配和前缀匹配)
43
+ * @param pathname - 当前路由路径
44
+ * @param routes - 路由列表
45
+ * @returns 是否匹配
46
+ */
47
+ const matchRouteList = (pathname: string, routes: string[]): boolean => {
48
+ return routes.some((route) => {
49
+ // 前缀匹配:/public/* 匹配 /public/xxx
50
+ if (route.endsWith('/*')) {
51
+ const prefix = route.slice(0, -1); // 去掉末尾的 *,保留 /
52
+ return pathname.startsWith(prefix) || pathname === prefix.slice(0, -1);
53
+ }
54
+ // 精确匹配
55
+ return pathname === route;
56
+ });
57
+ };
58
+
59
+ /**
60
+ * 判断指定路径是否为免鉴权路由
61
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
62
+ * @param pathname - 当前路由路径
63
+ * @returns 是否免鉴权
64
+ */
65
+ export const isNoAuthRoute = (pathname: string): boolean => {
66
+ return matchRouteList(pathname, getNoAuthRouteList());
67
+ };
68
+
69
+ /**
70
+ * 不显示布局的路由列表(静态配置)
71
+ * 注意:403/404 保留布局,方便用户通过导航返回
72
+ */
73
+ export const NO_LAYOUT_ROUTE_LIST: string[] = [
74
+ ROUTES.LOGIN,
75
+ ROUTES.REGISTER,
76
+ ROUTES.REGISTER_RESULT,
77
+ ];
78
+
79
+ /**
80
+ * 获取合并后的不显示布局路由列表
81
+ * 合并静态常量 + window.__MICO_CONFIG__.noLayoutRouteList
82
+ */
83
+ export const getNoLayoutRouteList = (): string[] => {
84
+ const dynamicRoutes = window.__MICO_CONFIG__?.noLayoutRouteList ?? [];
85
+ return [...new Set([...NO_LAYOUT_ROUTE_LIST, ...dynamicRoutes])];
86
+ };
87
+
88
+ /**
89
+ * 判断指定路径是否不显示布局
90
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
91
+ * @param pathname - 当前路由路径
92
+ * @returns 是否不显示布局
93
+ */
94
+ export const isNoLayoutRoute = (pathname: string): boolean => {
95
+ return matchRouteList(pathname, getNoLayoutRouteList());
96
+ };
97
+
32
98
  /**
33
99
  * 主题相关常量
34
100
  */
35
101
  export const THEME = {
36
102
  /** localStorage 存储键 */
37
- STORAGE_KEY: 'audit-center-theme',
103
+ STORAGE_KEY: '<%= ProjectName %>-theme',
38
104
  /** 默认主题 */
39
105
  DEFAULT: 'light' as const,
40
106
  /** 可选主题值 */
@@ -46,9 +112,9 @@ export const THEME = {
46
112
  */
47
113
  export const TIMEZONE = {
48
114
  /** localStorage 存储键(IANA 时区,如 Asia/Shanghai) */
49
- STORAGE_KEY: 'audit-center-timezone',
115
+ STORAGE_KEY: '<%= ProjectName %>-timezone',
50
116
  /** localStorage 存储键(用于展示的地区/名称,可选) */
51
- REGION_STORAGE_KEY: 'audit-center-timezone-region',
117
+ REGION_STORAGE_KEY: '<%= ProjectName %>-timezone-region',
52
118
  } as const;
53
119
 
54
120
  /**
@@ -56,7 +122,7 @@ export const TIMEZONE = {
56
122
  */
57
123
  export const PRESENCE = {
58
124
  /** localStorage 存储键 */
59
- STORAGE_KEY: 'audit-center-presence-status',
125
+ STORAGE_KEY: '<%= ProjectName %>-presence-status',
60
126
  } as const;
61
127
 
62
128
  /**
@@ -1,7 +1,7 @@
1
1
  import { useLocation, useModel } from '@umijs/max';
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import { layoutLogger } from '@/common/logger';
4
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
4
+ import { isNoAuthRoute } from '@/constants';
5
5
 
6
6
  /**
7
7
  * 路由切换时自动刷新用户权限
@@ -39,7 +39,7 @@ export function useRoutePermissionRefresh() {
39
39
  prevPathRef.current = location.pathname;
40
40
 
41
41
  // 免认证路由不需要刷新
42
- if (NO_AUTH_ROUTE_LIST.includes(location.pathname)) {
42
+ if (isNoAuthRoute(location.pathname)) {
43
43
  return;
44
44
  }
45
45
 
@@ -14,7 +14,6 @@ import {
14
14
  IconPoweroff,
15
15
  IconSettings,
16
16
  IconSunFill,
17
- IconUser,
18
17
  } from '@arco-design/web-react/icon';
19
18
  import { useModel } from '@umijs/max';
20
19
  import React, { useCallback } from 'react';
@@ -8,7 +8,7 @@ import {
8
8
  import { getAppNameFromEntry } from '@/common/micro';
9
9
  import AppTabs from '@/components/AppTabs';
10
10
  import MicroAppLoader from '@/components/MicroAppLoader';
11
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
11
+ import { isNoLayoutRoute } from '@/constants';
12
12
  import { useRoutePermissionRefresh } from '@/hooks/useRoutePermissionRefresh';
13
13
  import ForbiddenPage from '@/pages/403';
14
14
  import { Layout, Spin } from '@arco-design/web-react';
@@ -83,7 +83,7 @@ const BasicLayout: React.FC = () => {
83
83
  }, [currentRoute, allRoutes, allowedRoutes, location.pathname, filterOptions]);
84
84
 
85
85
  // 判断是否需要显示布局
86
- const showLayout = !NO_AUTH_ROUTE_LIST.includes(location.pathname);
86
+ const showLayout = !isNoLayoutRoute(location.pathname);
87
87
 
88
88
  // 渲染页面内容
89
89
  const renderContent = () => {
@@ -17,6 +17,7 @@ export interface IUserInfoResponse {
17
17
  export async function fetchUserInfo(): Promise<IUserInfo> {
18
18
  const response = await request<IUserInfoResponse>(USER_INFO_API, {
19
19
  method: 'GET',
20
+ skipProxy: true,
20
21
  });
21
22
  if (response.code === 200 && response.data) {
22
23
  return response.data;
@@ -1,6 +1,9 @@
1
1
  module.exports = {
2
2
  content: [
3
3
  './src/**/*.{js,jsx,ts,tsx,less,css}',
4
+ // 排除 .umi 目录,避免 Tailwind 重建时触发自己的 watch,导致无限循环
5
+ '!./src/.umi/**',
6
+ '!./src/.umi-production/**',
4
7
  './README.md',
5
8
  './docs/**/*.{md,mdx}',
6
9
  ],
@@ -177,12 +177,21 @@ module.exports = class extends Generator {
177
177
  }
178
178
  }
179
179
 
180
+ install() {
181
+ this.log('');
182
+ this.log('📦 正在安装依赖...');
183
+ this.spawnCommandSync('pnpm', ['install'], {
184
+ cwd: this.monorepoRoot
185
+ });
186
+ }
187
+
180
188
  end() {
181
189
  this.log('');
182
190
  this.log('✅ 子应用创建成功!');
183
191
  this.log('');
192
+ this.log(' 后续步骤:');
193
+ this.log('');
184
194
  this.log(` cd apps/${this.appName}`);
185
- this.log(' pnpm install');
186
195
  this.log(' pnpm dev');
187
196
  this.log('');
188
197
  }
@@ -1,7 +1,6 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
- const { CDN_PUBLIC_PATH } = process.env;
5
4
 
6
5
 
7
6
  const config: ReturnType<typeof defineConfig> = {
@@ -1,7 +1,6 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
- const { CDN_PUBLIC_PATH } = process.env;
5
4
 
6
5
 
7
6
  const config: ReturnType<typeof defineConfig> = {
@@ -46,4 +46,31 @@ export function getRequestSource(): 'main-app' | 'umi' {
46
46
  return getMainAppRequest() ? 'main-app' : 'umi';
47
47
  }
48
48
 
49
+ /**
50
+ * 从错误对象中提取错误消息
51
+ * 优先使用后端返回的 msg,否则使用默认消息
52
+ */
53
+ export function getErrorMessage(error: unknown, defaultMsg: string): string {
54
+ if (error && typeof error === 'object') {
55
+ // 尝试从 error.data.msg 获取(umi-request 格式)
56
+ const err = error as Record<string, any>;
57
+ if (err.data?.msg) {
58
+ return err.data.msg;
59
+ }
60
+ // 尝试从 error.response.data.msg 获取(axios 格式)
61
+ if (err.response?.data?.msg) {
62
+ return err.response.data.msg;
63
+ }
64
+ // 尝试从 error.msg 获取
65
+ if (err.msg) {
66
+ return err.msg;
67
+ }
68
+ // 尝试从 error.message 获取
69
+ if (err.message) {
70
+ return err.message;
71
+ }
72
+ }
73
+ return defaultMsg;
74
+ }
75
+
49
76
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-mico-cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": [
6
6
  "yeoman-generator",