@coze-arch/cli 0.0.1-alpha.db1c06 → 0.0.1-alpha.de4758

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 (132) hide show
  1. package/README.md +1 -0
  2. package/lib/__templates__/expo/_npmrc +1 -0
  3. package/lib/__templates__/expo/client/components/Screen.tsx +2 -2
  4. package/lib/__templates__/expo/client/eslint.config.mjs +7 -0
  5. package/lib/__templates__/expo/client/scripts/install-missing-deps.js +10 -10
  6. package/lib/__templates__/expo/eslint-plugins/forbid-emoji/index.js +9 -0
  7. package/lib/__templates__/expo/eslint-plugins/forbid-emoji/rule.js +112 -0
  8. package/lib/__templates__/expo/eslint-plugins/forbid-emoji/tech.md +94 -0
  9. package/lib/__templates__/expo/eslint-plugins/restrict-linear-gradient/index.js +9 -0
  10. package/lib/__templates__/expo/eslint-plugins/restrict-linear-gradient/rule.js +120 -0
  11. package/lib/__templates__/expo/eslint-plugins/restrict-linear-gradient/tech.md +58 -0
  12. package/lib/__templates__/nextjs/AGENTS.md +54 -0
  13. package/lib/__templates__/nextjs/README.md +5 -0
  14. package/lib/__templates__/nextjs/eslint.config.mjs +5 -0
  15. package/lib/__templates__/nextjs/next.config.ts +1 -2
  16. package/lib/__templates__/nextjs/package.json +2 -5
  17. package/lib/__templates__/nextjs/pnpm-lock.yaml +1028 -5
  18. package/lib/__templates__/nextjs/scripts/build.sh +4 -1
  19. package/lib/__templates__/nextjs/scripts/dev.sh +8 -2
  20. package/lib/__templates__/nextjs/scripts/start.sh +7 -1
  21. package/lib/__templates__/nextjs/src/app/layout.tsx +1 -1
  22. package/lib/__templates__/nextjs/src/app/page.tsx +1 -2
  23. package/lib/__templates__/nextjs/src/server.ts +35 -0
  24. package/lib/__templates__/nextjs/tsconfig.json +1 -1
  25. package/lib/__templates__/nuxt-vue/.coze +12 -0
  26. package/lib/__templates__/nuxt-vue/AGENTS.md +42 -0
  27. package/lib/__templates__/nuxt-vue/README.md +73 -0
  28. package/lib/__templates__/nuxt-vue/_gitignore +24 -0
  29. package/lib/__templates__/nuxt-vue/_npmrc +23 -0
  30. package/lib/__templates__/nuxt-vue/app/app.vue +6 -0
  31. package/lib/__templates__/nuxt-vue/app/pages/index.vue +23 -0
  32. package/lib/__templates__/nuxt-vue/assets/css/main.css +24 -0
  33. package/lib/__templates__/nuxt-vue/nuxt.config.ts +116 -0
  34. package/lib/__templates__/nuxt-vue/package.json +35 -0
  35. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +8759 -0
  36. package/lib/__templates__/nuxt-vue/postcss.config.mjs +8 -0
  37. package/lib/__templates__/nuxt-vue/public/favicon.ico +0 -0
  38. package/lib/__templates__/nuxt-vue/public/robots.txt +2 -0
  39. package/lib/__templates__/nuxt-vue/scripts/build.sh +14 -0
  40. package/lib/__templates__/nuxt-vue/scripts/dev.sh +39 -0
  41. package/lib/__templates__/nuxt-vue/scripts/prepare.sh +14 -0
  42. package/lib/__templates__/nuxt-vue/scripts/start.sh +21 -0
  43. package/lib/__templates__/nuxt-vue/server/api/hello.ts +10 -0
  44. package/lib/__templates__/nuxt-vue/server/middleware/logger.ts +10 -0
  45. package/lib/__templates__/nuxt-vue/server/routes/health.ts +10 -0
  46. package/lib/__templates__/nuxt-vue/tailwind.config.js +13 -0
  47. package/lib/__templates__/nuxt-vue/template.config.js +87 -0
  48. package/lib/__templates__/nuxt-vue/tsconfig.json +18 -0
  49. package/lib/__templates__/taro/README.md +57 -45
  50. package/lib/__templates__/taro/config/index.ts +106 -41
  51. package/lib/__templates__/taro/config/prod.ts +4 -5
  52. package/lib/__templates__/taro/eslint.config.mjs +62 -6
  53. package/lib/__templates__/taro/package.json +19 -4
  54. package/lib/__templates__/taro/patches/@tarojs__plugin-mini-ci@4.1.9.patch +30 -0
  55. package/lib/__templates__/taro/pnpm-lock.yaml +912 -206
  56. package/lib/__templates__/taro/src/app.css +140 -36
  57. package/lib/__templates__/taro/src/components/ui/accordion.tsx +159 -0
  58. package/lib/__templates__/taro/src/components/ui/alert-dialog.tsx +260 -0
  59. package/lib/__templates__/taro/src/components/ui/alert.tsx +60 -0
  60. package/lib/__templates__/taro/src/components/ui/aspect-ratio.tsx +36 -0
  61. package/lib/__templates__/taro/src/components/ui/avatar.tsx +84 -0
  62. package/lib/__templates__/taro/src/components/ui/badge.tsx +37 -0
  63. package/lib/__templates__/taro/src/components/ui/breadcrumb.tsx +117 -0
  64. package/lib/__templates__/taro/src/components/ui/button-group.tsx +83 -0
  65. package/lib/__templates__/taro/src/components/ui/button.tsx +67 -0
  66. package/lib/__templates__/taro/src/components/ui/calendar.tsx +394 -0
  67. package/lib/__templates__/taro/src/components/ui/card.tsx +108 -0
  68. package/lib/__templates__/taro/src/components/ui/carousel.tsx +228 -0
  69. package/lib/__templates__/taro/src/components/ui/checkbox.tsx +58 -0
  70. package/lib/__templates__/taro/src/components/ui/code-block.tsx +169 -0
  71. package/lib/__templates__/taro/src/components/ui/collapsible.tsx +71 -0
  72. package/lib/__templates__/taro/src/components/ui/command.tsx +385 -0
  73. package/lib/__templates__/taro/src/components/ui/context-menu.tsx +614 -0
  74. package/lib/__templates__/taro/src/components/ui/dialog.tsx +256 -0
  75. package/lib/__templates__/taro/src/components/ui/drawer.tsx +192 -0
  76. package/lib/__templates__/taro/src/components/ui/dropdown-menu.tsx +561 -0
  77. package/lib/__templates__/taro/src/components/ui/field.tsx +228 -0
  78. package/lib/__templates__/taro/src/components/ui/hover-card.tsx +282 -0
  79. package/lib/__templates__/taro/src/components/ui/input-group.tsx +197 -0
  80. package/lib/__templates__/taro/src/components/ui/input-otp.tsx +136 -0
  81. package/lib/__templates__/taro/src/components/ui/input.tsx +56 -0
  82. package/lib/__templates__/taro/src/components/ui/label.tsx +24 -0
  83. package/lib/__templates__/taro/src/components/ui/menubar.tsx +595 -0
  84. package/lib/__templates__/taro/src/components/ui/navigation-menu.tsx +264 -0
  85. package/lib/__templates__/taro/src/components/ui/pagination.tsx +118 -0
  86. package/lib/__templates__/taro/src/components/ui/popover.tsx +291 -0
  87. package/lib/__templates__/taro/src/components/ui/portal.tsx +19 -0
  88. package/lib/__templates__/taro/src/components/ui/progress.tsx +28 -0
  89. package/lib/__templates__/taro/src/components/ui/radio-group.tsx +64 -0
  90. package/lib/__templates__/taro/src/components/ui/resizable.tsx +346 -0
  91. package/lib/__templates__/taro/src/components/ui/scroll-area.tsx +34 -0
  92. package/lib/__templates__/taro/src/components/ui/select.tsx +438 -0
  93. package/lib/__templates__/taro/src/components/ui/separator.tsx +30 -0
  94. package/lib/__templates__/taro/src/components/ui/sheet.tsx +262 -0
  95. package/lib/__templates__/taro/src/components/ui/skeleton.tsx +17 -0
  96. package/lib/__templates__/taro/src/components/ui/slider.tsx +203 -0
  97. package/lib/__templates__/taro/src/components/ui/sonner.tsx +1 -0
  98. package/lib/__templates__/taro/src/components/ui/switch.tsx +55 -0
  99. package/lib/__templates__/taro/src/components/ui/table.tsx +142 -0
  100. package/lib/__templates__/taro/src/components/ui/tabs.tsx +114 -0
  101. package/lib/__templates__/taro/src/components/ui/textarea.tsx +54 -0
  102. package/lib/__templates__/taro/src/components/ui/toast.tsx +517 -0
  103. package/lib/__templates__/taro/src/components/ui/toggle-group.tsx +120 -0
  104. package/lib/__templates__/taro/src/components/ui/toggle.tsx +77 -0
  105. package/lib/__templates__/taro/src/components/ui/tooltip.tsx +455 -0
  106. package/lib/__templates__/taro/src/lib/hooks/use-keyboard-offset.ts +37 -0
  107. package/lib/__templates__/taro/src/lib/measure.ts +115 -0
  108. package/lib/__templates__/taro/src/lib/platform.ts +12 -0
  109. package/lib/__templates__/taro/src/lib/utils.ts +6 -0
  110. package/lib/__templates__/taro/src/presets/dev-debug.ts +23 -0
  111. package/lib/__templates__/taro/src/presets/h5-container.tsx +15 -0
  112. package/lib/__templates__/taro/src/presets/h5-navbar.tsx +97 -30
  113. package/lib/__templates__/taro/src/presets/h5-styles.ts +192 -5
  114. package/lib/__templates__/taro/src/presets/index.tsx +4 -4
  115. package/lib/__templates__/templates.json +32 -0
  116. package/lib/__templates__/vite/AGENTS.md +41 -0
  117. package/lib/__templates__/vite/README.md +190 -11
  118. package/lib/__templates__/vite/_gitignore +1 -0
  119. package/lib/__templates__/vite/eslint.config.mjs +6 -1
  120. package/lib/__templates__/vite/package.json +10 -3
  121. package/lib/__templates__/vite/pnpm-lock.yaml +755 -15
  122. package/lib/__templates__/vite/scripts/build.sh +4 -1
  123. package/lib/__templates__/vite/scripts/dev.sh +9 -2
  124. package/lib/__templates__/vite/scripts/start.sh +9 -3
  125. package/lib/__templates__/vite/server/routes/index.ts +31 -0
  126. package/lib/__templates__/vite/server/server.ts +65 -0
  127. package/lib/__templates__/vite/server/vite.ts +67 -0
  128. package/lib/__templates__/vite/tsconfig.json +4 -3
  129. package/lib/__templates__/vite/vite.config.ts +4 -0
  130. package/lib/cli.js +3256 -768
  131. package/package.json +9 -3
  132. package/lib/__templates__/taro/src/presets/wx-debug.ts +0 -23
package/README.md CHANGED
@@ -14,6 +14,7 @@ Coze Coding 的项目模板引擎,提供基于前端技术栈的项目初始
14
14
 
15
15
  ```bash
16
16
  rush update
17
+
17
18
  ```
18
19
 
19
20
  ## 使用
@@ -1,3 +1,4 @@
1
+ loglevel=error
1
2
  registry=https://registry.npmmirror.com
2
3
 
3
4
  strictStorePkgContentCheck=false
@@ -32,7 +32,7 @@ import {
32
32
  *
33
33
  * ## 2. 沉浸式 Header (推荐)
34
34
  * - 场景:Header 背景色/图片需要延伸到状态栏 (如首页、个人中心)。
35
- * - 用法:`<Screen safeAreaEdges={['left', 'right', 'bottom']}>` (去掉 'top')
35
+ * - 用法:`<Screen safeAreaEdges={['left', 'right', 'bottom']}>` (去掉 'top')
36
36
  * - 配合:页面内部 Header 组件必须手动添加 paddingTop:
37
37
  * ```tsx
38
38
  * const insets = useSafeAreaInsets();
@@ -41,7 +41,7 @@ import {
41
41
  *
42
42
  * ## 3. 底部有 TabBar 或 悬浮按钮
43
43
  * - 场景:页面底部有固定导航栏,或者需要精细控制底部留白。
44
- * - 用法:`<Screen safeAreaEdges={['top', 'left', 'right']}>` (去掉 'bottom')
44
+ * - 用法:`<Screen safeAreaEdges={['top', 'left', 'right']}>` (去掉 'bottom')
45
45
  * - 配合:
46
46
  * - 若是滚动页:`<ScrollView contentContainerStyle={{ paddingBottom: insets.bottom + 80 }}>`
47
47
  * - 若是固定页:`<View style={{ paddingBottom: insets.bottom + 60 }}>`
@@ -8,6 +8,8 @@ import pluginImport from 'eslint-plugin-import';
8
8
  import fontawesome6 from '../eslint-plugins/fontawesome6/index.js';
9
9
  import reanimated from '../eslint-plugins/reanimated/index.js';
10
10
  import reactnative from '../eslint-plugins/react-native/index.js';
11
+ import forbidEmoji from '../eslint-plugins/forbid-emoji/index.js';
12
+ import restrictLinearGradient from '../eslint-plugins/restrict-linear-gradient/index.js';
11
13
 
12
14
  export default [
13
15
  {
@@ -20,6 +22,7 @@ export default [
20
22
  'tailwind.config.js', // 排除 Tailwind 配置文件
21
23
  '**/*.d.ts',
22
24
  'eslint.config.*',
25
+ 'metro.config.*',
23
26
  ],
24
27
  },
25
28
  regexp.configs["flat/recommended"],
@@ -60,6 +63,8 @@ export default [
60
63
  fontawesome6,
61
64
  reanimated,
62
65
  reactnative,
66
+ forbidEmoji,
67
+ restrictLinearGradient,
63
68
  },
64
69
  rules: {
65
70
  // 关闭代码风格规则
@@ -80,6 +85,8 @@ export default [
80
85
  'react/jsx-uses-react': 'off',
81
86
  'fontawesome6/valid-name': 'error',
82
87
  'reanimated/ban-mix-use': 'error',
88
+ 'forbidEmoji/no-emoji': 'error',
89
+ 'restrictLinearGradient/no-linear-gradient-backgroundcolor': 'error',
83
90
  // 禁止使用 via.placeholder.com 服务
84
91
  'no-restricted-syntax': [
85
92
  'error',
@@ -9,7 +9,7 @@ const { execSync } = require('child_process');
9
9
  const fs = require('fs');
10
10
  const path = require('path');
11
11
 
12
- console.log('🔍 检测缺失的依赖...\n');
12
+ console.log('检测缺失的依赖...\n');
13
13
 
14
14
  try {
15
15
  // 运行 depcheck 并获取 JSON 输出
@@ -61,11 +61,11 @@ try {
61
61
  });
62
62
 
63
63
  if (missingPackages.length === 0) {
64
- console.log('✅ 没有发现缺失的依赖!');
64
+ console.log('没有发现缺失的依赖');
65
65
  process.exit(0);
66
66
  }
67
67
 
68
- console.log('📦 发现以下缺失的依赖:');
68
+ console.log('发现以下缺失的依赖:');
69
69
  missingPackages.forEach((pkg, index) => {
70
70
  const files = missing[pkg];
71
71
  console.log(` ${index + 1}. ${pkg}`);
@@ -74,7 +74,7 @@ try {
74
74
  );
75
75
  });
76
76
 
77
- console.log('\n🚀 开始安装...\n');
77
+ console.log('\n开始安装...\n');
78
78
 
79
79
  // 使用 expo install 安装所有缺失的包
80
80
  const packagesToInstall = missingPackages.join(' ');
@@ -84,22 +84,22 @@ try {
84
84
  stdio: 'inherit',
85
85
  });
86
86
 
87
- console.log('\n✅ 所有缺失的依赖已安装完成!');
87
+ console.log('\n所有缺失的依赖已安装完成');
88
88
  } catch (installError) {
89
- console.log('\n⚠️ expo install 失败,尝试使用 npm install...\n');
89
+ console.log('\nexpo install 失败,尝试使用 npm install...\n');
90
90
 
91
91
  execSync(`npm install ${packagesToInstall}`, {
92
92
  stdio: 'inherit',
93
93
  });
94
94
 
95
- console.log('\n所有缺失的依赖已通过 npm 安装完成!');
95
+ console.log('\n所有缺失的依赖已通过 npm 安装完成');
96
96
  }
97
97
  } catch (error) {
98
98
  if (error.message.includes('depcheck')) {
99
- console.error('depcheck 未安装或运行失败');
100
- console.log('💡 尝试运行: npm install -g depcheck');
99
+ console.error('depcheck 未安装或运行失败');
100
+ console.log('尝试运行: npm install -g depcheck');
101
101
  } else {
102
- console.error('发生错误:', error.message);
102
+ console.error('发生错误:', error.message);
103
103
  }
104
104
  process.exit(1);
105
105
  }
@@ -0,0 +1,9 @@
1
+ const noEmoji = require('./rule')
2
+
3
+ const plugin = {
4
+ rules: {
5
+ 'no-emoji': noEmoji,
6
+ },
7
+ }
8
+
9
+ module.exports = plugin
@@ -0,0 +1,112 @@
1
+ const DEFAULTS = {
2
+ ignorePatterns: [],
3
+ }
4
+
5
+ const RE_EMOJI =
6
+ // 匹配完整 emoji 序列:基础图形 + 可选变体选择符/肤色 + 可选 ZWJ 连接序列;以及国旗与 keycap
7
+ /(?:\p{Extended_Pictographic}(?:\uFE0F|[\u{1F3FB}-\u{1F3FF}])?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|[\u{1F3FB}-\u{1F3FF}])?)*)|(?:[\u{1F1E6}-\u{1F1FF}]{2})|(?:[#*0-9]\uFE0F?\u20E3)/gu
8
+
9
+ function buildIgnoreRegexes(patterns) {
10
+ if (!Array.isArray(patterns)) return []
11
+ const result = []
12
+ for (const pattern of patterns) {
13
+ if (typeof pattern !== 'string') continue
14
+ try {
15
+ result.push(new RegExp(pattern))
16
+ } catch (_) {
17
+ continue
18
+ }
19
+ }
20
+ return result
21
+ }
22
+
23
+ function shouldIgnore(text, ignoreRegexes) {
24
+ if (!ignoreRegexes.length) return false
25
+ for (const re of ignoreRegexes) {
26
+ if (re.test(text)) return true
27
+ }
28
+ return false
29
+ }
30
+
31
+ function hasEmoji(text) {
32
+ if (!text) return false
33
+ RE_EMOJI.lastIndex = 0
34
+ return RE_EMOJI.test(text)
35
+ }
36
+
37
+ function collectEmojis(text) {
38
+ if (!text) return []
39
+ RE_EMOJI.lastIndex = 0
40
+ const result = []
41
+ const seen = new Set()
42
+ for (const match of text.matchAll(RE_EMOJI)) {
43
+ if (match[0] && !seen.has(match[0])) {
44
+ seen.add(match[0])
45
+ result.push(match[0])
46
+ }
47
+ }
48
+ return result
49
+ }
50
+
51
+ module.exports = {
52
+ meta: {
53
+ type: 'problem',
54
+ docs: {
55
+ description: 'Disallow emoji in code',
56
+ recommended: 'error',
57
+ },
58
+ schema: [
59
+ {
60
+ type: 'object',
61
+ additionalProperties: false,
62
+ properties: {
63
+ ignorePatterns: {
64
+ type: 'array',
65
+ items: { type: 'string' },
66
+ default: DEFAULTS.ignorePatterns,
67
+ },
68
+ },
69
+ },
70
+ ],
71
+ messages: {
72
+ noEmoji: '禁止在代码中使用 emoji:{{emojis}},请移除或者使用 @expo/vector-icons 图标',
73
+ },
74
+ },
75
+
76
+ create(context) {
77
+ const options = context.options && context.options[0] ? context.options[0] : {}
78
+ const ignoreRegexes = buildIgnoreRegexes(
79
+ Array.isArray(options.ignorePatterns) ? options.ignorePatterns : DEFAULTS.ignorePatterns
80
+ )
81
+
82
+ function checkText(node, text) {
83
+ if (!text) return
84
+ if (shouldIgnore(text, ignoreRegexes)) return
85
+ if (hasEmoji(text)) {
86
+ const emojis = collectEmojis(text)
87
+ const emojiText = emojis.length ? emojis.join(' ') : ''
88
+ context.report({ node, messageId: 'noEmoji', data: { emojis: emojiText } })
89
+ }
90
+ }
91
+
92
+ return {
93
+ Literal(node) {
94
+ if (typeof node.value !== 'string') return
95
+ checkText(node, node.value)
96
+ },
97
+ TemplateLiteral(node) {
98
+ for (const quasi of node.quasis) {
99
+ const cooked = quasi.value && typeof quasi.value.cooked === 'string'
100
+ ? quasi.value.cooked
101
+ : quasi.value && typeof quasi.value.raw === 'string'
102
+ ? quasi.value.raw
103
+ : ''
104
+ checkText(quasi, cooked)
105
+ }
106
+ },
107
+ JSXText(node) {
108
+ checkText(node, node.value)
109
+ },
110
+ }
111
+ },
112
+ }
@@ -0,0 +1,94 @@
1
+ # forbid-emoji 技术设计
2
+
3
+ ## 目标
4
+ - 在代码中检测并禁止使用 emoji
5
+ - 覆盖常见的字符串与 JSX 场景,避免明显漏报
6
+ - 保持实现简单、可维护,避免引入新依赖
7
+
8
+ ## 规则命名与对外形态
9
+ - 插件目录:demo/eslint-plugins/forbid-emoji
10
+ - 规则名:forbid-emoji/no-emoji
11
+ - 规则类型:problem
12
+ - 默认等级:error
13
+
14
+ ## 规则行为定义
15
+ ### 报错
16
+ 当以下任一位置包含 emoji 时,报错:
17
+ - 字符串字面量
18
+ - 模板字符串的静态片段
19
+ - JSXText 文本
20
+ - JSXAttribute 中可静态解析为字符串的值
21
+
22
+ ### 不报错
23
+ 以下场景不触发:
24
+ - 标识符、对象键名、属性名中的字符
25
+ - 数值、正则字面量、BigInt
26
+ - 模板字符串的表达式部分(仅检测静态片段)
27
+ - 被用户显式忽略的路径或内容
28
+
29
+ ## 配置项
30
+ ```json
31
+ {
32
+ "ignorePatterns": []
33
+ }
34
+ ```
35
+ - ignorePatterns:字符串数组,命中任一正则则跳过该文本片段
36
+
37
+ ## 检测策略
38
+ ### AST 访问节点
39
+ - Literal:仅处理 typeof value === 'string'
40
+ - TemplateLiteral:遍历 quasis,使用 cooked 值
41
+ - JSXText:使用 node.value
42
+ - JSXAttribute:可静态解析为字符串时处理
43
+
44
+ ### 忽略策略
45
+ 当文本命中 ignorePatterns 的任一正则时,不再做 emoji 检测,减少误报与白名单配置成本。
46
+
47
+ ## Emoji 识别方案
48
+ ### 组合序列处理
49
+ 考虑到真实文本中的 emoji 组合形式,需要识别以下组合序列并保证命中:
50
+ - 变体选择符:U+FE0F
51
+ - 皮肤色修饰符:U+1F3FB–U+1F3FF
52
+ - ZWJ 组合:U+200D
53
+ - 区域指示符组成的国旗:U+1F1E6–U+1F1FF 的成对序列
54
+ - Keycap 组合:[#*0-9] + U+FE0F? + U+20E3
55
+
56
+ 实现上直接使用完整序列正则匹配,覆盖 ZWJ 连接序列、国旗与 keycap。
57
+
58
+ ### 正则示意
59
+ ```
60
+ const RE_EMOJI =
61
+ /(?:\p{Extended_Pictographic}(?:\uFE0F|[\u{1F3FB}-\u{1F3FF}])?(?:\u200D\p{Extended_Pictographic}(?:\uFE0F|[\u{1F3FB}-\u{1F3FF}])?)*)|(?:[\u{1F1E6}-\u{1F1FF}]{2})|(?:[#*0-9]\uFE0F?\u20E3)/gu
62
+ ```
63
+ 命中即判定为 emoji。
64
+
65
+ ## 报错信息
66
+ - messageId: noEmoji
67
+ - 文案:禁止在代码中使用 emoji
68
+
69
+ ## 典型示例
70
+ ### 触发
71
+ - "hello🙂"
72
+ - `title: "Nice 👍"`
73
+ - <Text>欢迎🎉</Text>
74
+
75
+ ### 不触发
76
+ - const icon = "face-smile"
77
+ - <Text>{user.name}</Text>
78
+ - "hello" + emojiName
79
+
80
+ ## 边界与决策
81
+ - 模板字符串表达式部分:不深入解析,避免执行风险与复杂度
82
+ - 正则字面量:不扫描,避免干扰正则语义
83
+ - 注释:默认不扫描
84
+ - 误报控制:提供 ignorePatterns,由使用者按文件或内容片段进行排除
85
+
86
+ ## 性能考虑
87
+ - 仅在字符串节点上进行扫描
88
+ - 正则预编译并复用
89
+ - 对空字符串与短文本快速返回
90
+
91
+ ## 测试计划
92
+ - 覆盖 Literal、TemplateLiteral、JSXText、JSXAttribute 四类节点
93
+ - 覆盖 ZWJ 组合、变体选择符、国旗、keycap、皮肤色修饰符
94
+ - 覆盖 ignorePatterns 行为
@@ -0,0 +1,9 @@
1
+ const noLinearGradientBackgroundColor = require('./rule')
2
+
3
+ const plugin = {
4
+ rules: {
5
+ 'no-linear-gradient-backgroundcolor': noLinearGradientBackgroundColor,
6
+ },
7
+ }
8
+
9
+ module.exports = plugin
@@ -0,0 +1,120 @@
1
+ const LINEAR_GRADIENT_RE = /linear-gradient/i
2
+
3
+ function getPropertyKeyName(property) {
4
+ if (!property || property.type !== 'Property') return null
5
+ if (property.key.type === 'Identifier') return property.key.name
6
+ if (property.key.type === 'Literal' && typeof property.key.value === 'string') {
7
+ return property.key.value
8
+ }
9
+ return null
10
+ }
11
+
12
+ function getStaticStringValue(node) {
13
+ if (!node) return null
14
+ if (node.type === 'Literal' && typeof node.value === 'string') return node.value
15
+ if (node.type === 'TemplateLiteral') {
16
+ return node.quasis.map(quasi => quasi.value?.cooked || '').join('')
17
+ }
18
+ return null
19
+ }
20
+
21
+ function isLinearGradientString(text) {
22
+ if (!text) return false
23
+ return LINEAR_GRADIENT_RE.test(text)
24
+ }
25
+
26
+ function checkStyleObjectExpression(objectExpression, report) {
27
+ if (!objectExpression || objectExpression.type !== 'ObjectExpression') return
28
+ for (const property of objectExpression.properties) {
29
+ if (!property || property.type !== 'Property') continue
30
+ const keyName = getPropertyKeyName(property)
31
+ if (keyName !== 'backgroundColor') continue
32
+ const valueText = getStaticStringValue(property.value)
33
+ if (isLinearGradientString(valueText)) {
34
+ report(property.key || property)
35
+ }
36
+ }
37
+ }
38
+
39
+ function isStyleSheetMethodCall(node, methodName) {
40
+ if (!node || node.type !== 'CallExpression') return false
41
+ const callee = node.callee
42
+ if (!callee || callee.type !== 'MemberExpression') return false
43
+ if (callee.object.type !== 'Identifier' || callee.object.name !== 'StyleSheet') return false
44
+ if (callee.property.type === 'Identifier') return callee.property.name === methodName
45
+ return false
46
+ }
47
+
48
+ function checkStyleExpression(expression, report) {
49
+ if (!expression) return
50
+ if (expression.type === 'ObjectExpression') {
51
+ checkStyleObjectExpression(expression, report)
52
+ return
53
+ }
54
+ if (expression.type === 'ArrayExpression') {
55
+ for (const element of expression.elements) {
56
+ if (!element) continue
57
+ if (element.type === 'SpreadElement') {
58
+ checkStyleExpression(element.argument, report)
59
+ } else {
60
+ checkStyleExpression(element, report)
61
+ }
62
+ }
63
+ return
64
+ }
65
+ // 例:StyleSheet.flatten([styles.a, { backgroundColor: 'linear-gradient(...)' }])
66
+ if (isStyleSheetMethodCall(expression, 'flatten')) {
67
+ const arg = expression.arguments[0]
68
+ checkStyleExpression(arg, report)
69
+ return
70
+ }
71
+ // 例:StyleSheet.compose(styles.a, { backgroundColor: `linear-gradient(${angle},#fff,#000)` })
72
+ if (isStyleSheetMethodCall(expression, 'compose')) {
73
+ const [first, second] = expression.arguments
74
+ checkStyleExpression(first, report)
75
+ checkStyleExpression(second, report)
76
+ }
77
+ }
78
+
79
+ module.exports = {
80
+ meta: {
81
+ type: 'problem',
82
+ docs: {
83
+ description: 'Disallow linear-gradient string in backgroundColor',
84
+ recommended: 'error',
85
+ },
86
+ schema: [],
87
+ messages: {
88
+ noLinearGradientBackgroundColor:
89
+ 'backgroundColor 和 linear-gradient 无法一起使用,请将 backgroundColor 改为 experimental_backgroundImage',
90
+ },
91
+ },
92
+
93
+ create(context) {
94
+ function report(node) {
95
+ context.report({ node, messageId: 'noLinearGradientBackgroundColor' })
96
+ }
97
+
98
+ return {
99
+ CallExpression(node) {
100
+ if (!isStyleSheetMethodCall(node, 'create')) return
101
+ const firstArg = node.arguments[0]
102
+ if (!firstArg || firstArg.type !== 'ObjectExpression') return
103
+
104
+ checkStyleObjectExpression(firstArg, report)
105
+
106
+ for (const property of firstArg.properties) {
107
+ if (!property || property.type !== 'Property') continue
108
+ if (!property.value || property.value.type !== 'ObjectExpression') continue
109
+ checkStyleObjectExpression(property.value, report)
110
+ }
111
+ },
112
+
113
+ JSXAttribute(node) {
114
+ if (!node.name || node.name.name !== 'style') return
115
+ if (!node.value || node.value.type !== 'JSXExpressionContainer') return
116
+ checkStyleExpression(node.value.expression, report)
117
+ },
118
+ }
119
+ },
120
+ }
@@ -0,0 +1,58 @@
1
+ # restrict-linear-gradient 技术设计
2
+
3
+ ## 目标
4
+ - 禁止在 backgroundColor 中使用 linear-gradient 字符串
5
+ - 覆盖 StyleSheet.create 与 JSX style 的常见写法
6
+ - 实现轻量、易维护,不引入新依赖
7
+
8
+ ## 规则命名与对外形态
9
+ - 插件目录:expo/eslint-plugins/restrict-linear-gradient
10
+ - 规则名:restrict-linear-gradient/no-linear-gradient-backgroundcolor
11
+ - 规则类型:problem
12
+ - 默认等级:error
13
+
14
+ ## 规则行为定义
15
+ ### 报错
16
+ 当以下任一位置出现 backgroundColor 且值包含 linear-gradient( 时,报错:
17
+ - StyleSheet.create 的样式对象中
18
+ - JSX 元素 style 属性内的对象或数组成员
19
+
20
+ ### 不报错
21
+ 以下场景不触发:
22
+ - 非 backgroundColor 的属性
23
+ - backgroundColor 的非字符串值
24
+ - 模板字符串包含表达式的动态值
25
+ - 与 style 无关的对象字面量
26
+
27
+ ## 检测策略
28
+ ### StyleSheet.create
29
+ 遍历 StyleSheet.create 的首个参数对象,检查:
30
+ - 顶层 backgroundColor
31
+ - 每个样式项对应的对象内的 backgroundColor
32
+
33
+ ### JSX style
34
+ 定位 JSXAttribute(name=style) 后,检查表达式:
35
+ - ObjectExpression
36
+ - ArrayExpression 的各元素
37
+ - StyleSheet.flatten / StyleSheet.compose 的参数
38
+
39
+ ## 线性渐变识别
40
+ 使用正则 /linear-gradient/i 检测字符串字面量与模板字符串静态片段。
41
+
42
+ ## 报错信息
43
+ - messageId: noLinearGradientBackgroundColor
44
+ - 文案:backgroundColor 和 linear-gradient 无法一起使用,请将 backgroundColor 改为 experimental_backgroundImage
45
+
46
+ ## 典型示例
47
+ ### 触发
48
+ - StyleSheet.create({ a: { backgroundColor: 'linear-gradient(135deg,#F97316 0%,#FB923C 100%)' } })
49
+ - <View style={{ backgroundColor: "linear-gradient(135deg,#F97316 0%,#FB923C 100%)" }} />
50
+ - <View style={[{ backgroundColor: `linear-gradient(135deg,#F97316 0%,#FB923C 100%)` }]} />
51
+
52
+ ### 不触发
53
+ - { background: 'linear-gradient(...)' }
54
+ - { backgroundColor: someVar }
55
+
56
+ ## 性能考虑
57
+ - 仅扫描目标节点与静态字符串
58
+ - 早返回空节点与非字符串值
@@ -0,0 +1,54 @@
1
+ # 项目上下文
2
+
3
+ ### 版本技术栈
4
+
5
+ - **Framework**: Next.js 16 (App Router)
6
+ - **Core**: React 19
7
+ - **Language**: TypeScript 5
8
+ - **UI 组件**: shadcn/ui (基于 Radix UI)
9
+ - **Styling**: Tailwind CSS 4
10
+
11
+ ## 目录结构
12
+
13
+ ```
14
+ ├── public/ # 静态资源
15
+ ├── scripts/ # 构建与启动脚本
16
+ │ ├── build.sh # 构建脚本
17
+ │ ├── dev.sh # 开发环境启动脚本
18
+ │ ├── prepare.sh # 预处理脚本
19
+ │ └── start.sh # 生产环境启动脚本
20
+ ├── src/
21
+ │ ├── app/ # 页面路由与布局
22
+ │ ├── components/ui/ # Shadcn UI 组件库
23
+ │ ├── hooks/ # 自定义 Hooks
24
+ │ ├── lib/ # 工具库
25
+ │ │ └── utils.ts # 通用工具函数 (cn)
26
+ │ └── server.ts # 自定义服务端入口
27
+ ├── next.config.ts # Next.js 配置
28
+ ├── package.json # 项目依赖管理
29
+ └── tsconfig.json # TypeScript 配置
30
+ ```
31
+
32
+ - 项目文件(如 app 目录、pages 目录、components 等)默认初始化到 `src/` 目录下。
33
+
34
+ ## 包管理规范
35
+
36
+ **仅允许使用 pnpm** 作为包管理器,**严禁使用 npm 或 yarn**。
37
+ **常用命令**:
38
+ - 安装依赖:`pnpm add <package>`
39
+ - 安装开发依赖:`pnpm add -D <package>`
40
+ - 安装所有依赖:`pnpm install`
41
+ - 移除依赖:`pnpm remove <package>`
42
+
43
+ ## 开发规范
44
+
45
+ - **项目理解加速**:初始可以依赖项目下`package.json`文件理解项目类型,如果没有或无法理解退化成阅读其他文件。
46
+ - **Hydration 错误预防**:严禁在 JSX 渲染逻辑中直接使用 typeof window、Date.now()、Math.random() 等动态数据。必须使用 'use client' 并配合 useEffect + useState 确保动态内容仅在客户端挂载后渲染;同时严禁非法 HTML 嵌套(如 <p> 嵌套 <div>)。
47
+
48
+
49
+ ## UI 设计与组件规范 (UI & Styling Standards)
50
+
51
+ - 模板默认预装核心组件库 `shadcn/ui`,位于`src/components/ui/`目录下
52
+ - Next.js 项目**必须默认**采用 shadcn/ui 组件、风格和规范,**除非用户指定用其他的组件和规范。**
53
+
54
+
@@ -43,6 +43,11 @@ src/
43
43
  ├── lib/ # 工具函数库
44
44
  │ └── utils.ts # cn() 等工具函数
45
45
  └── hooks/ # 自定义 React Hooks(可选)
46
+
47
+ server/
48
+ ├── index.ts # 自定义服务器入口
49
+ ├── tsconfig.json # Server TypeScript 配置
50
+ └── dist/ # 编译输出目录(自动生成)
46
51
  ```
47
52
 
48
53
  ## 核心开发规范
@@ -12,6 +12,11 @@ const eslintConfig = defineConfig([
12
12
  'out/**',
13
13
  'build/**',
14
14
  'next-env.d.ts',
15
+ // Build artifacts:
16
+ 'server.js',
17
+ 'dist/**',
18
+ // Script files (CommonJS):
19
+ 'scripts/**/*.js',
15
20
  ]),
16
21
  ]);
17
22
 
@@ -1,8 +1,7 @@
1
1
  import type { NextConfig } from 'next';
2
- import path from 'path';
3
2
 
4
3
  const nextConfig: NextConfig = {
5
- // outputFileTracingRoot: path.resolve(__dirname, '../../'),
4
+ // outputFileTracingRoot: path.resolve(__dirname, '../../'), // Uncomment and add 'import path from "path"' if needed
6
5
  /* config options here */
7
6
  allowedDevOrigins: ['*.dev.coze.site'],
8
7
  images: {
@@ -82,15 +82,12 @@
82
82
  "react-dev-inspector": "^2.0.1",
83
83
  "shadcn": "latest",
84
84
  "tailwindcss": "^4",
85
+ "tsup": "^8.3.5",
86
+ "tsx": "^4.19.2",
85
87
  "typescript": "^5"
86
88
  },
87
89
  "packageManager": "pnpm@9.0.0",
88
90
  "engines": {
89
91
  "pnpm": ">=9.0.0"
90
- },
91
- "pnpm": {
92
- "overrides": {
93
- "esbuild": "^0.25.12"
94
- }
95
92
  }
96
93
  }