@coze-arch/cli 0.0.1-alpha.035e0e

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 (156) hide show
  1. package/README.md +142 -0
  2. package/bin/main +2 -0
  3. package/lib/__templates__/expo/.coze +12 -0
  4. package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +46 -0
  5. package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +220 -0
  6. package/lib/__templates__/expo/.cozeproj/scripts/prod_build.sh +47 -0
  7. package/lib/__templates__/expo/.cozeproj/scripts/prod_run.sh +34 -0
  8. package/lib/__templates__/expo/.cozeproj/scripts/server_dev_run.sh +45 -0
  9. package/lib/__templates__/expo/README.md +72 -0
  10. package/lib/__templates__/expo/_gitignore +11 -0
  11. package/lib/__templates__/expo/_npmrc +20 -0
  12. package/lib/__templates__/expo/client/app/_layout.tsx +33 -0
  13. package/lib/__templates__/expo/client/app/demo.tsx +1 -0
  14. package/lib/__templates__/expo/client/app/index.tsx +1 -0
  15. package/lib/__templates__/expo/client/app.config.ts +75 -0
  16. package/lib/__templates__/expo/client/assets/fonts/SpaceMono-Regular.ttf +0 -0
  17. package/lib/__templates__/expo/client/assets/images/adaptive-icon.png +0 -0
  18. package/lib/__templates__/expo/client/assets/images/default-avatar.png +0 -0
  19. package/lib/__templates__/expo/client/assets/images/favicon.png +0 -0
  20. package/lib/__templates__/expo/client/assets/images/icon.png +0 -0
  21. package/lib/__templates__/expo/client/assets/images/partial-react-logo.png +0 -0
  22. package/lib/__templates__/expo/client/assets/images/react-logo.png +0 -0
  23. package/lib/__templates__/expo/client/assets/images/react-logo@2x.png +0 -0
  24. package/lib/__templates__/expo/client/assets/images/react-logo@3x.png +0 -0
  25. package/lib/__templates__/expo/client/assets/images/splash-icon.png +0 -0
  26. package/lib/__templates__/expo/client/components/Screen.tsx +330 -0
  27. package/lib/__templates__/expo/client/components/SmartDateInput.tsx +238 -0
  28. package/lib/__templates__/expo/client/components/ThemedText.tsx +33 -0
  29. package/lib/__templates__/expo/client/components/ThemedView.tsx +38 -0
  30. package/lib/__templates__/expo/client/constants/theme.ts +854 -0
  31. package/lib/__templates__/expo/client/contexts/AuthContext.tsx +49 -0
  32. package/lib/__templates__/expo/client/declarations.d.ts +5 -0
  33. package/lib/__templates__/expo/client/eslint-formatter-simple.mjs +49 -0
  34. package/lib/__templates__/expo/client/eslint.config.mjs +98 -0
  35. package/lib/__templates__/expo/client/hooks/useColorScheme.ts +34 -0
  36. package/lib/__templates__/expo/client/hooks/useTheme.ts +13 -0
  37. package/lib/__templates__/expo/client/metro.config.js +121 -0
  38. package/lib/__templates__/expo/client/package.json +93 -0
  39. package/lib/__templates__/expo/client/screens/demo/index.tsx +25 -0
  40. package/lib/__templates__/expo/client/screens/demo/styles.ts +28 -0
  41. package/lib/__templates__/expo/client/scripts/install-missing-deps.js +104 -0
  42. package/lib/__templates__/expo/client/tsconfig.json +24 -0
  43. package/lib/__templates__/expo/client/utils/index.ts +54 -0
  44. package/lib/__templates__/expo/package.json +22 -0
  45. package/lib/__templates__/expo/pnpm-lock.yaml +13975 -0
  46. package/lib/__templates__/expo/pnpm-workspace.yaml +3 -0
  47. package/lib/__templates__/expo/server/package.json +32 -0
  48. package/lib/__templates__/expo/server/src/index.ts +18 -0
  49. package/lib/__templates__/expo/server/tsconfig.json +24 -0
  50. package/lib/__templates__/expo/template.config.js +50 -0
  51. package/lib/__templates__/expo/tsconfig.json +1 -0
  52. package/lib/__templates__/nextjs/.coze +12 -0
  53. package/lib/__templates__/nextjs/README.md +358 -0
  54. package/lib/__templates__/nextjs/_gitignore +99 -0
  55. package/lib/__templates__/nextjs/_npmrc +23 -0
  56. package/lib/__templates__/nextjs/components.json +21 -0
  57. package/lib/__templates__/nextjs/eslint.config.mjs +18 -0
  58. package/lib/__templates__/nextjs/next-env.d.ts +6 -0
  59. package/lib/__templates__/nextjs/next.config.ts +19 -0
  60. package/lib/__templates__/nextjs/package.json +86 -0
  61. package/lib/__templates__/nextjs/pnpm-lock.yaml +10493 -0
  62. package/lib/__templates__/nextjs/postcss.config.mjs +7 -0
  63. package/lib/__templates__/nextjs/public/file.svg +1 -0
  64. package/lib/__templates__/nextjs/public/globe.svg +1 -0
  65. package/lib/__templates__/nextjs/public/next.svg +1 -0
  66. package/lib/__templates__/nextjs/public/vercel.svg +1 -0
  67. package/lib/__templates__/nextjs/public/window.svg +1 -0
  68. package/lib/__templates__/nextjs/scripts/build.sh +14 -0
  69. package/lib/__templates__/nextjs/scripts/dev.sh +33 -0
  70. package/lib/__templates__/nextjs/scripts/prepare.sh +9 -0
  71. package/lib/__templates__/nextjs/scripts/start.sh +15 -0
  72. package/lib/__templates__/nextjs/src/app/favicon.ico +0 -0
  73. package/lib/__templates__/nextjs/src/app/globals.css +137 -0
  74. package/lib/__templates__/nextjs/src/app/layout.tsx +72 -0
  75. package/lib/__templates__/nextjs/src/app/page.tsx +78 -0
  76. package/lib/__templates__/nextjs/src/app/robots.ts +11 -0
  77. package/lib/__templates__/nextjs/src/components/ui/accordion.tsx +66 -0
  78. package/lib/__templates__/nextjs/src/components/ui/alert-dialog.tsx +157 -0
  79. package/lib/__templates__/nextjs/src/components/ui/alert.tsx +66 -0
  80. package/lib/__templates__/nextjs/src/components/ui/aspect-ratio.tsx +11 -0
  81. package/lib/__templates__/nextjs/src/components/ui/avatar.tsx +53 -0
  82. package/lib/__templates__/nextjs/src/components/ui/badge.tsx +46 -0
  83. package/lib/__templates__/nextjs/src/components/ui/breadcrumb.tsx +109 -0
  84. package/lib/__templates__/nextjs/src/components/ui/button-group.tsx +83 -0
  85. package/lib/__templates__/nextjs/src/components/ui/button.tsx +62 -0
  86. package/lib/__templates__/nextjs/src/components/ui/calendar.tsx +220 -0
  87. package/lib/__templates__/nextjs/src/components/ui/card.tsx +92 -0
  88. package/lib/__templates__/nextjs/src/components/ui/carousel.tsx +241 -0
  89. package/lib/__templates__/nextjs/src/components/ui/chart.tsx +357 -0
  90. package/lib/__templates__/nextjs/src/components/ui/checkbox.tsx +32 -0
  91. package/lib/__templates__/nextjs/src/components/ui/collapsible.tsx +33 -0
  92. package/lib/__templates__/nextjs/src/components/ui/command.tsx +184 -0
  93. package/lib/__templates__/nextjs/src/components/ui/context-menu.tsx +252 -0
  94. package/lib/__templates__/nextjs/src/components/ui/dialog.tsx +143 -0
  95. package/lib/__templates__/nextjs/src/components/ui/drawer.tsx +135 -0
  96. package/lib/__templates__/nextjs/src/components/ui/dropdown-menu.tsx +257 -0
  97. package/lib/__templates__/nextjs/src/components/ui/empty.tsx +104 -0
  98. package/lib/__templates__/nextjs/src/components/ui/field.tsx +248 -0
  99. package/lib/__templates__/nextjs/src/components/ui/form.tsx +167 -0
  100. package/lib/__templates__/nextjs/src/components/ui/hover-card.tsx +44 -0
  101. package/lib/__templates__/nextjs/src/components/ui/input-group.tsx +170 -0
  102. package/lib/__templates__/nextjs/src/components/ui/input-otp.tsx +77 -0
  103. package/lib/__templates__/nextjs/src/components/ui/input.tsx +21 -0
  104. package/lib/__templates__/nextjs/src/components/ui/item.tsx +193 -0
  105. package/lib/__templates__/nextjs/src/components/ui/kbd.tsx +28 -0
  106. package/lib/__templates__/nextjs/src/components/ui/label.tsx +24 -0
  107. package/lib/__templates__/nextjs/src/components/ui/menubar.tsx +276 -0
  108. package/lib/__templates__/nextjs/src/components/ui/navigation-menu.tsx +168 -0
  109. package/lib/__templates__/nextjs/src/components/ui/pagination.tsx +127 -0
  110. package/lib/__templates__/nextjs/src/components/ui/popover.tsx +48 -0
  111. package/lib/__templates__/nextjs/src/components/ui/progress.tsx +31 -0
  112. package/lib/__templates__/nextjs/src/components/ui/radio-group.tsx +45 -0
  113. package/lib/__templates__/nextjs/src/components/ui/resizable.tsx +63 -0
  114. package/lib/__templates__/nextjs/src/components/ui/scroll-area.tsx +58 -0
  115. package/lib/__templates__/nextjs/src/components/ui/select.tsx +190 -0
  116. package/lib/__templates__/nextjs/src/components/ui/separator.tsx +28 -0
  117. package/lib/__templates__/nextjs/src/components/ui/sheet.tsx +139 -0
  118. package/lib/__templates__/nextjs/src/components/ui/sidebar.tsx +724 -0
  119. package/lib/__templates__/nextjs/src/components/ui/skeleton.tsx +13 -0
  120. package/lib/__templates__/nextjs/src/components/ui/slider.tsx +63 -0
  121. package/lib/__templates__/nextjs/src/components/ui/sonner.tsx +40 -0
  122. package/lib/__templates__/nextjs/src/components/ui/spinner.tsx +16 -0
  123. package/lib/__templates__/nextjs/src/components/ui/switch.tsx +31 -0
  124. package/lib/__templates__/nextjs/src/components/ui/table.tsx +116 -0
  125. package/lib/__templates__/nextjs/src/components/ui/tabs.tsx +66 -0
  126. package/lib/__templates__/nextjs/src/components/ui/textarea.tsx +18 -0
  127. package/lib/__templates__/nextjs/src/components/ui/toggle-group.tsx +83 -0
  128. package/lib/__templates__/nextjs/src/components/ui/toggle.tsx +47 -0
  129. package/lib/__templates__/nextjs/src/components/ui/tooltip.tsx +61 -0
  130. package/lib/__templates__/nextjs/src/hooks/use-mobile.ts +19 -0
  131. package/lib/__templates__/nextjs/src/lib/utils.ts +6 -0
  132. package/lib/__templates__/nextjs/template.config.js +85 -0
  133. package/lib/__templates__/nextjs/tsconfig.json +34 -0
  134. package/lib/__templates__/templates.json +87 -0
  135. package/lib/__templates__/vite/.coze +12 -0
  136. package/lib/__templates__/vite/README.md +239 -0
  137. package/lib/__templates__/vite/_gitignore +66 -0
  138. package/lib/__templates__/vite/_npmrc +23 -0
  139. package/lib/__templates__/vite/eslint.config.mjs +9 -0
  140. package/lib/__templates__/vite/index.html +13 -0
  141. package/lib/__templates__/vite/package.json +28 -0
  142. package/lib/__templates__/vite/pnpm-lock.yaml +4716 -0
  143. package/lib/__templates__/vite/postcss.config.js +6 -0
  144. package/lib/__templates__/vite/scripts/build.sh +14 -0
  145. package/lib/__templates__/vite/scripts/dev.sh +32 -0
  146. package/lib/__templates__/vite/scripts/prepare.sh +9 -0
  147. package/lib/__templates__/vite/scripts/start.sh +15 -0
  148. package/lib/__templates__/vite/src/index.css +21 -0
  149. package/lib/__templates__/vite/src/index.ts +5 -0
  150. package/lib/__templates__/vite/src/main.ts +64 -0
  151. package/lib/__templates__/vite/tailwind.config.js +9 -0
  152. package/lib/__templates__/vite/template.config.js +90 -0
  153. package/lib/__templates__/vite/tsconfig.json +16 -0
  154. package/lib/__templates__/vite/vite.config.ts +15 -0
  155. package/lib/cli.js +1916 -0
  156. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # @coze-coding/cli
2
+
3
+ Coze Coding 的项目模板引擎,提供基于前端技术栈的项目初始化能力。
4
+
5
+ ## 特性
6
+
7
+ - 🚀 **快速初始化**: 一键创建预配置的项目模板
8
+ - 🤖 **AI 优先设计**: 专为 AI Agent 消费设计,提供清晰的错误信息和自描述能力
9
+ - 📦 **多模板支持**: 支持 React、Next.js、HTML 等多种技术栈
10
+ - 🔧 **类型安全**: 基于 JSON Schema 的参数校验和 TypeScript 类型生成
11
+ - ⚡ **命令代理**: 统一的 dev/build/start 命令,抹平不同技术栈差异
12
+
13
+ ## 安装
14
+
15
+ ```bash
16
+ rush update
17
+ ```
18
+
19
+ ## 使用
20
+
21
+ ### 初始化项目
22
+
23
+ ```bash
24
+ coze-coding init --template react --app-name my-app --use-typescript --use-tailwind
25
+ ```
26
+
27
+ ### 查看可用模板
28
+
29
+ ```bash
30
+ coze-coding init --help
31
+ ```
32
+
33
+ ### 开发命令
34
+
35
+ ```bash
36
+ # 启动开发服务器
37
+ coze-coding dev
38
+
39
+ # 构建生产版本
40
+ coze-coding build
41
+
42
+ # 启动生产服务器
43
+ coze-coding start
44
+ ```
45
+
46
+ ## 模板参数
47
+
48
+ 每个模板都有自己的参数配置,通过 CLI 参数透传。参数名使用 kebab-case 格式,会自动转换为 camelCase 传入模板配置。
49
+
50
+ 例如:
51
+ - `--app-name` → `appName`
52
+ - `--use-typescript` → `useTypeScript`
53
+ - `--port` → `port`
54
+
55
+ ## 开发模板
56
+
57
+ ### 模板结构
58
+
59
+ ```
60
+ template-name/
61
+ ├── template.config.ts # 模板配置(开发时)
62
+ ├── template.config.js # 模板配置(生产时)
63
+ ├── .coze # 项目配置文件
64
+ └── src/ # 源代码(支持 EJS 模板)
65
+ ```
66
+
67
+ ### 参数定义
68
+
69
+ 使用 JSON Schema 定义模板参数:
70
+
71
+ ```typescript
72
+ import type { JSONSchemaType } from 'ajv';
73
+
74
+ export interface TemplateParams {
75
+ appName: string;
76
+ useTypeScript: boolean;
77
+ port: number;
78
+ }
79
+
80
+ export const paramsSchema: JSONSchemaType<TemplateParams> = {
81
+ type: 'object',
82
+ properties: {
83
+ appName: {
84
+ type: 'string',
85
+ minLength: 1,
86
+ pattern: '^[a-z0-9-]+$',
87
+ cliParam: '--app-name',
88
+ description: 'Application name'
89
+ },
90
+ useTypeScript: {
91
+ type: 'boolean',
92
+ default: true,
93
+ cliParam: '--use-typescript',
94
+ description: 'Enable TypeScript'
95
+ },
96
+ port: {
97
+ type: 'number',
98
+ default: 3000,
99
+ minimum: 1024,
100
+ maximum: 65535,
101
+ cliParam: '--port',
102
+ description: 'Development server port'
103
+ }
104
+ },
105
+ required: ['appName'],
106
+ additionalProperties: false
107
+ };
108
+ ```
109
+
110
+ ### 生命周期钩子
111
+
112
+ ```typescript
113
+ export const onBeforeRender = (context) => {
114
+ // 渲染前处理
115
+ return context;
116
+ };
117
+
118
+ export const onAfterRender = (context, outputPath) => {
119
+ // 渲染后处理
120
+ };
121
+ ```
122
+
123
+ ## 编码规范
124
+
125
+ 本项目严格遵循函数式编程风格:
126
+
127
+ - ✅ 使用纯函数和不可变数据
128
+ - ✅ 函数组合和高阶函数
129
+ - ✅ 使用工厂函数和闭包
130
+ - ❌ 禁止使用 class
131
+ - ❌ 避免副作用和可变状态
132
+
133
+ 详见 [技术设计文档](./docs/technical-design.md#7-编码规范)
134
+
135
+ ## 文档
136
+
137
+ - [需求文档](./docs/requirement.md)
138
+ - [技术设计](./docs/technical-design.md)
139
+
140
+ ## 许可证
141
+
142
+ 内部项目
package/bin/main ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../lib/cli.js');
@@ -0,0 +1,12 @@
1
+ [project]
2
+ entrypoint = "server.js"
3
+ requires = ["nodejs-24"]
4
+
5
+ [dev]
6
+ build = ["bash", ".cozeproj/scripts/dev_build.sh"]
7
+ run = ["bash", ".cozeproj/scripts/dev_run.sh"]
8
+
9
+ [deploy]
10
+ build = ["bash", ".cozeproj/scripts/prod_build.sh"]
11
+ run = ["bash", ".cozeproj/scripts/prod_run.sh"]
12
+ build_app_dir = "./client"
@@ -0,0 +1,46 @@
1
+ #!/bin/bash
2
+ if [ -z "${BASH_VERSION:-}" ]; then exec /usr/bin/env bash "$0" "$@"; fi
3
+ set -euo pipefail
4
+ ROOT_DIR="$(pwd)"
5
+ PREVIEW_DIR="${COZE_PREVIEW_DIR:-/source/preview}"
6
+
7
+ # ==================== 配置项 ====================
8
+ SERVER_DIR="app"
9
+ EXPO_DIR="expo"
10
+ CHECK_HASH_SCRIPT="$ROOT_DIR/check_hash.py"
11
+
12
+ check_command() {
13
+ if ! command -v "$1" &> /dev/null; then
14
+ echo "error:命令 $1 未找到,请先安装"
15
+ fi
16
+ }
17
+
18
+ echo "==================== 开始构建 ===================="
19
+
20
+ echo "检查根目录 pre_install.py"
21
+ if [ -f "$PREVIEW_DIR/pre_install.py" ]; then
22
+ echo "执行:python $PREVIEW_DIR/pre_install.py"
23
+ python "$PREVIEW_DIR/pre_install.py" || echo "pre_install.py 执行失败"
24
+ fi
25
+
26
+ echo "开始执行构建脚本(build_dev.sh)..."
27
+ echo "正在检查依赖命令是否存在..."
28
+ # 检查核心命令
29
+ check_command "pnpm"
30
+ check_command "npm"
31
+
32
+ echo "==================== 安装项目依赖 ===================="
33
+ if [ ! -f "package.json" ]; then
34
+ echo "项目目录下无 package.json,不是合法的 Node.js 项目"
35
+ fi
36
+ # 步骤 2.1/2.2:安装项目依赖
37
+ pnpm install --registry=https://registry.npmmirror.com || echo "Expo 项目依赖安装失败(pnpm 执行出错)"
38
+
39
+ echo "检查根目录 post_install.py"
40
+ if [ -f "$PREVIEW_DIR/post_install.py" ]; then
41
+ echo "执行:python $PREVIEW_DIR/post_install.py"
42
+ python "$PREVIEW_DIR/post_install.py" || echo "post_install.py 执行失败"
43
+ fi
44
+
45
+ echo "==================== 依赖安装完成!====================\n"
46
+ echo "下一步:执行 ./deploy_run.sh 启动服务"
@@ -0,0 +1,220 @@
1
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
2
+ PREVIEW_DIR="${COZE_PREVIEW_DIR:-/source/preview}"
3
+ LOG_DIR="${COZE_LOG_DIR:-$ROOT_DIR/logs}"
4
+ LOG_FILE="$LOG_DIR/app.log"
5
+ mkdir -p "$LOG_DIR"
6
+
7
+ # ==================== 配置项 ====================
8
+ # Server 服务配置
9
+ SERVER_HOST="0.0.0.0"
10
+ SERVER_PORT="9091"
11
+ # Expo 项目配置
12
+ EXPO_HOST="0.0.0.0"
13
+ EXPO_DIR="expo"
14
+ EXPO_PORT="5000"
15
+ WEB_URL="${COZE_PROJECT_DOMAIN_DEFAULT:-http://127.0.0.1:${SERVER_PORT}}"
16
+ ASSUME_YES="1"
17
+ EXPO_PUBLIC_BACKEND_BASE_URL="${EXPO_PUBLIC_BACKEND_BASE_URL:-$WEB_URL}"
18
+
19
+
20
+ EXPO_PACKAGER_PROXY_URL="${EXPO_PUBLIC_BACKEND_BASE_URL}"
21
+ export EXPO_PUBLIC_BACKEND_BASE_URL EXPO_PACKAGER_PROXY_URL
22
+ # 运行时变量(为避免 set -u 的未绑定错误,预置为空)
23
+ SERVER_PID=""
24
+ EXPO_PID=""
25
+ # ==================== 工具函数 ====================
26
+ check_command() {
27
+ if ! command -v "$1" &> /dev/null; then
28
+ echo "error:命令 $1 未找到,请先安装"
29
+ fi
30
+ }
31
+ while [ $# -gt 0 ]; do
32
+ case "$1" in
33
+ -y|--yes)
34
+ ASSUME_YES="1"
35
+ shift
36
+ ;;
37
+ *)
38
+ shift
39
+ ;;
40
+ esac
41
+ done
42
+ is_port_free() {
43
+ ! lsof -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1
44
+ }
45
+ choose_next_free_port() {
46
+ local start=$1
47
+ local p=$start
48
+ while ! is_port_free "$p"; do
49
+ p=$((p+1))
50
+ done
51
+ echo "$p"
52
+ }
53
+ ensure_port() {
54
+ local var_name=$1
55
+ local port_val=$2
56
+ if is_port_free "$port_val"; then
57
+ echo "端口未占用:$port_val"
58
+ eval "$var_name=$port_val"
59
+ else
60
+ echo "端口已占用:$port_val"
61
+ local choice
62
+ if [ "$ASSUME_YES" = "1" ]; then choice="Y"; else read -r -p "是否关闭该端口的进程?[Y/n] " choice || choice="Y"; fi
63
+ if [ -z "$choice" ] || [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
64
+ if command -v lsof &> /dev/null; then
65
+ local pids
66
+ pids=$(lsof -t -i tcp:"$port_val" -sTCP:LISTEN 2>/dev/null || true)
67
+ if [ -n "$pids" ]; then
68
+ echo "正在关闭进程:$pids"
69
+ kill -9 $pids 2>/dev/null || echo "关闭进程失败:$pids"
70
+ eval "$var_name=$port_val"
71
+ else
72
+ echo "未获取到占用该端口的进程"
73
+ eval "$var_name=$port_val"
74
+ fi
75
+ else
76
+ echo "缺少 lsof,无法自动关闭进程"
77
+ eval "$var_name=$port_val"
78
+ fi
79
+ else
80
+ local new_port
81
+ new_port=$(choose_next_free_port "$port_val")
82
+ info "使用新的端口:$new_port"
83
+ eval "$var_name=$new_port"
84
+ fi
85
+ fi
86
+ }
87
+
88
+ pipe_to_log() {
89
+ local source="${1:-CLIENT}"
90
+ local raw_log="${2:-}"
91
+ local line timestamp ts msg record
92
+ while IFS= read -r line || [ -n "$line" ]; do
93
+ if [ -n "$raw_log" ]; then
94
+ echo "$line" >> "$raw_log"
95
+ fi
96
+ line=$(echo "[$source] $line" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\[[0-9;]*m//g')
97
+ msg="${line}"
98
+ echo "$msg"
99
+ done
100
+ }
101
+
102
+ wait_port_connectable() {
103
+ local host=$1 port=$2 retries=${3:-10}
104
+ for _ in $(seq 1 "$retries"); do
105
+ nc -z -w 1 "$host" "$port" >/dev/null 2>&1 && return 0
106
+ sleep 1
107
+ done
108
+ return 1
109
+ }
110
+
111
+ start_expo() {
112
+ local offline="${1:-0}"
113
+
114
+ pushd "$ROOT_DIR/client"
115
+
116
+ if [ "$offline" = "1" ]; then
117
+ ( EXPO_OFFLINE=1 EXPO_NO_DOCTOR=1 EXPO_PUBLIC_BACKEND_BASE_URL="$EXPO_PUBLIC_BACKEND_BASE_URL" EXPO_PACKAGER_PROXY_URL="$EXPO_PACKAGER_PROXY_URL" \
118
+ nohup npx expo start --clear --port "$EXPO_PORT" 2>&1 | pipe_to_log "CLIENT" "$ROOT_DIR/logs/client.log" ) &
119
+ else
120
+ ( EXPO_NO_DOCTOR=1 EXPO_PUBLIC_BACKEND_BASE_URL="$EXPO_PUBLIC_BACKEND_BASE_URL" EXPO_PACKAGER_PROXY_URL="$EXPO_PACKAGER_PROXY_URL" \
121
+ nohup npx expo start --clear --port "$EXPO_PORT" 2>&1 | pipe_to_log "CLIENT" "$ROOT_DIR/logs/client.log" ) &
122
+ fi
123
+ EXPO_PID=$!
124
+ disown $EXPO_PID 2>/dev/null || true
125
+
126
+ popd
127
+ }
128
+
129
+ detect_expo_fetch_failed() {
130
+ local timeout="${1:-8}"
131
+ local waited=0
132
+ local log_file="$ROOT_DIR/logs/client.log"
133
+ while [ "$waited" -lt "$timeout" ]; do
134
+ if [ -f "$log_file" ] && grep -q "TypeError: fetch failed" "$log_file" 2>/dev/null; then
135
+ return 0
136
+ fi
137
+ sleep 1
138
+ waited=$((waited+1))
139
+ done
140
+ return 1
141
+ }
142
+
143
+ # ==================== 前置检查 ====================
144
+ # 关掉nginx进程
145
+ ps -ef | grep nginx | grep -v grep | awk '{print $2}' | xargs -r kill -9
146
+
147
+ echo "检查根目录 pre_install.py"
148
+ if [ -f "$PREVIEW_DIR/pre_install.py" ]; then
149
+ echo "执行:python $PREVIEW_DIR/pre_install.py"
150
+ python "$PREVIEW_DIR/pre_install.py" || echo "pre_install.py 执行失败"
151
+ fi
152
+
153
+ echo "==================== 开始启动 ===================="
154
+ echo "开始执行服务启动脚本(start_dev.sh)..."
155
+ echo "正在检查依赖命令和目录是否存在..."
156
+ # 检查核心命令
157
+ check_command "npm"
158
+ check_command "pnpm"
159
+ check_command "lsof"
160
+ check_command "bash"
161
+
162
+ echo "准备日志目录:$ROOT_DIR/logs"
163
+ mkdir -p "$ROOT_DIR/logs"
164
+ # 端口占用预检查与处理
165
+ ensure_port SERVER_PORT "$SERVER_PORT"
166
+ ensure_port EXPO_PORT "$EXPO_PORT"
167
+
168
+ echo "==================== 启动 server 服务 ===================="
169
+ echo "正在执行:pnpm run dev (server)"
170
+ ( pushd "$ROOT_DIR/server" > /dev/null && SERVER_PORT="$SERVER_PORT" nohup pnpm run dev; popd > /dev/null ) &
171
+ SERVER_PID=$!
172
+ disown $SERVER_PID 2>/dev/null || true
173
+ if [ -z "${SERVER_PID}" ]; then
174
+ echo "无法获取 server 后台进程 PID"
175
+ fi
176
+ echo "server 服务已启动,进程 ID:${SERVER_PID:-unknown}"
177
+
178
+ echo "==================== 启动 Expo 项目 ===================="
179
+ echo "开始启动 Expo 服务,端口 ${EXPO_PORT}"
180
+ start_expo 0
181
+ if detect_expo_fetch_failed 8; then
182
+ echo "Expo 启动检测到网络错误:TypeError: fetch failed,启用离线模式重试"
183
+ if [ -n "${EXPO_PID}" ]; then kill -9 "$EXPO_PID" 2>/dev/null || true; fi
184
+ : > "$ROOT_DIR/logs/client.log"
185
+ start_expo 1
186
+ fi
187
+ # 输出以下环境变量,确保 Expo 项目能正确连接到 Server 服务
188
+ echo "Expo 环境变量配置:"
189
+ echo "EXPO_PUBLIC_BACKEND_BASE_URL=${EXPO_PUBLIC_BACKEND_BASE_URL}"
190
+ echo "EXPO_PACKAGER_PROXY_URL=${EXPO_PACKAGER_PROXY_URL}"
191
+ if [ -z "${EXPO_PID}" ]; then
192
+ echo "无法获取 Expo 后台进程 PID"
193
+ fi
194
+
195
+ echo "所有服务已启动。Server PID: ${SERVER_PID}, Expo PID: ${EXPO_PID}"
196
+
197
+ echo "检查 Server 服务端口:$SERVER_HOST:$SERVER_PORT"
198
+ if wait_port_connectable "$SERVER_HOST" "$SERVER_PORT" 10 2; then
199
+ echo "端口可连接:$SERVER_HOST:$SERVER_PORT"
200
+ else
201
+ echo "端口不可连接:$SERVER_HOST:$SERVER_PORT 10 次)"
202
+ fi
203
+
204
+ echo "检查 Expo 服务端口:$EXPO_HOST:$EXPO_PORT"
205
+ if wait_port_connectable "$EXPO_HOST" "$EXPO_PORT" 10 2; then
206
+ echo "端口可连接:$EXPO_HOST:$EXPO_PORT"
207
+ else
208
+ echo "端口不可连接:$EXPO_HOST:$EXPO_PORT(已尝试 10 次)"
209
+ fi
210
+
211
+ echo "服务端口检查完成"
212
+
213
+ echo "检查根目录 post_run.py"
214
+ if [ -f "$ROOT_DIR/post_run.py" ]; then
215
+ echo "启动检查中"
216
+ python "$ROOT_DIR/post_run.py" --port "$EXPO_PORT" || echo "post_run.py 执行失败"
217
+ echo "启动检查结束"
218
+ fi
219
+
220
+ echo "==================== 服务启动完成 ===================="
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ if [ -z "${BASH_VERSION:-}" ]; then exec /usr/bin/env bash "$0" "$@"; fi
3
+ set -euo pipefail
4
+ ROOT_DIR="$(pwd)"
5
+
6
+ # ==================== 工具函数 ====================
7
+ info() {
8
+ echo "[INFO] $1"
9
+ }
10
+ warn() {
11
+ echo "[WARN] $1"
12
+ }
13
+ error() {
14
+ echo "[ERROR] $1"
15
+ exit 1
16
+ }
17
+ check_command() {
18
+ if ! command -v "$1" &> /dev/null; then
19
+ error "命令 $1 未找到,请先安装"
20
+ fi
21
+ }
22
+
23
+ info "==================== 开始构建 ===================="
24
+ info "开始执行构建脚本(build_prod.sh)..."
25
+ info "正在检查依赖命令是否存在..."
26
+ # 检查核心命令
27
+ check_command "pnpm"
28
+ check_command "npm"
29
+
30
+ # ==================== 安装 Node 依赖 ====================
31
+ info "==================== 安装 Node 依赖 ===================="
32
+ info "开始安装 Node 依赖"
33
+ if [ -f "$ROOT_DIR/package.json" ]; then
34
+ info "进入目录:$ROOT_DIR"
35
+ info "正在执行:pnpm install"
36
+ (cd "$ROOT_DIR" && pnpm install --registry=https://registry.npmmirror.com) || error "Node 依赖安装失败"
37
+ else
38
+ warn "未找到 $ROOT_DIR/package.json 文件,请检查路径是否正确"
39
+ fi
40
+ info "==================== 依赖安装完成!====================\n"
41
+
42
+ info "==================== dist打包 ===================="
43
+ info "开始执行:pnpm run build (server)"
44
+ (pushd "$ROOT_DIR/server" > /dev/null && pnpm run build; popd > /dev/null) || error "dist打包失败"
45
+ info "==================== dist打包完成!====================\n"
46
+
47
+ info "下一步:执行 ./prod_run.sh 启动服务"
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # 产物部署使用
3
+ set -euo pipefail
4
+
5
+ ROOT_DIR="$(pwd)"
6
+
7
+ HOST="${HOST:-0.0.0.0}"
8
+ PORT="${PORT:-5000}"
9
+
10
+ # ==================== 工具函数 ====================
11
+ info() {
12
+ echo "[INFO] $1"
13
+ }
14
+ warn() {
15
+ echo "[WARN] $1"
16
+ }
17
+ error() {
18
+ echo "[ERROR] $1"
19
+ exit 1
20
+ }
21
+ check_command() {
22
+ if ! command -v "$1" &> /dev/null; then
23
+ error "命令 $1 未找到,请先安装"
24
+ fi
25
+ }
26
+
27
+ # ============== 启动服务 ======================
28
+ # 检查核心命令
29
+ check_command "pnpm"
30
+ check_command "npm"
31
+
32
+ info "开始执行:pnpm run start (server)"
33
+ (pushd "$ROOT_DIR/server" > /dev/null && PORT="$PORT" pnpm run start; popd > /dev/null) || error "服务启动失败"
34
+ info "服务启动完成!\n"
@@ -0,0 +1,45 @@
1
+ #!/bin/bash
2
+
3
+ ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
4
+ SERVER_DIR="$ROOT_DIR/server"
5
+ LOG_DIR="$ROOT_DIR/logs"
6
+ LOG_FILE="$LOG_DIR/server.log"
7
+ SERVER_PORT="${SERVER_PORT:-9091}"
8
+
9
+ mkdir -p "$LOG_DIR"
10
+
11
+ pipe_to_log() {
12
+ local source="${1:-SERVER}"
13
+ local raw_log="${2:-}"
14
+ local line
15
+ while IFS= read -r line || [ -n "$line" ]; do
16
+ if [ -n "$raw_log" ]; then
17
+ echo "$line" >> "$raw_log"
18
+ fi
19
+ line=$(echo "[$source] $line" | sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\[[0-9;]*m//g')
20
+ echo "$line"
21
+ done
22
+ }
23
+
24
+ kill_old_server() {
25
+ if command -v lsof &> /dev/null; then
26
+ local pids
27
+ pids=$(lsof -t -i tcp:"$SERVER_PORT" -sTCP:LISTEN 2>/dev/null || true)
28
+ if [ -n "$pids" ]; then
29
+ echo "正在关闭旧的 server 进程:$pids"
30
+ kill -9 $pids 2>/dev/null || echo "关闭进程失败:$pids"
31
+ sleep 1
32
+ fi
33
+ fi
34
+ }
35
+
36
+ echo "==================== Server Dev Run ===================="
37
+ echo "Server 目录:$SERVER_DIR"
38
+ echo "Server 端口:$SERVER_PORT"
39
+ echo "日志文件:$LOG_FILE"
40
+
41
+ kill_old_server
42
+
43
+ echo "启动 server 服务..."
44
+ cd "$SERVER_DIR"
45
+ NODE_ENV=development PORT="$SERVER_PORT" npx tsx ./src/index.ts 2>&1 | pipe_to_log "SERVER" "$LOG_FILE"
@@ -0,0 +1,72 @@
1
+ # Expo App + Express.js
2
+
3
+ ## 目录结构规范(严格遵循)
4
+
5
+ 当前仓库是一个 monorepo(基于 pnpm 的 workspace)
6
+
7
+ - Expo 代码在 client 目录,Express.js 代码在 server 目录
8
+ - 本模板默认无 Tab Bar,可按需改造
9
+
10
+ 目录结构说明
11
+
12
+ ├── server/ # 服务端代码根目录 (Express.js)
13
+ | ├── src/
14
+ │ │ └── index.ts # Express 入口文件
15
+ | └── package.json # 服务端 package.json
16
+ ├── client/ # React Native 前端代码
17
+ │ ├── app/ # Expo Router 路由目录(仅路由配置)
18
+ │ │ ├── _layout.tsx # 根布局文件(必需)
19
+ │ │ ├── home.tsx # 首页
20
+ │ │ └── index.tsx # re-export home.tsx
21
+ │ ├── screens/ # 页面实现目录(与 app/ 路由对应)
22
+ │ │ └── home/
23
+ │ │ ├── index.tsx # 页面组件实现
24
+ │ │ └── styles.ts # 页面样式
25
+ │ ├── components/ # 可复用组件
26
+ │ │ └── Screen.tsx # 页面容器组件(必用)
27
+ │ ├── hooks/ # 自定义 Hooks
28
+ │ ├── contexts/ # React Context 代码
29
+ │ ├── assets/ # 静态资源
30
+ | └── package.json # Expo 应用 package.json
31
+ ├── package.json
32
+ ├── .cozeproj # 预置脚手架脚本(禁止修改)
33
+ └── .coze # 配置文件(禁止修改)
34
+
35
+ ## 安装依赖
36
+
37
+ ### 命令
38
+
39
+ ```bash
40
+ pnpm i
41
+ ```
42
+
43
+ ### 新增依赖约束
44
+
45
+ 如果需要新增依赖,需在 client 和 server 各自的目录添加(原因:隔离前后端的依赖),禁止在根目录直接安装依赖
46
+
47
+ ### 新增依赖标准流程
48
+
49
+ - 编辑 `client/package.json` 或 `server/package.json`
50
+ - 在根目录执行 `pnpm i`
51
+
52
+ ## Expo 开发规范
53
+
54
+ ### 路径别名
55
+
56
+ Expo 配置了 `@/` 路径别名指向 `client/` 目录:
57
+
58
+ ```tsx
59
+ // 正确
60
+ import { Screen } from '@/components/Screen';
61
+
62
+ // 避免相对路径
63
+ import { Screen } from '../../../components/Screen';
64
+ ```
65
+
66
+ ## 本地开发
67
+
68
+ 运行 coze dev 可以同时启动前端和后端服务,如果端口已占用,该命令会先杀掉占用端口的进程再启动,也可以用来重启前端和后端服务
69
+
70
+ ```bash
71
+ coze dev
72
+ ```
@@ -0,0 +1,11 @@
1
+ node_modules/
2
+ dist/
3
+ build/
4
+ .expo/
5
+ .expo-shared/
6
+ .eslintcache
7
+ expo/expo-env.d.ts
8
+ expo-env.d.ts
9
+ .DS_Store
10
+ logs/
11
+ *.tsbuildinfo
@@ -0,0 +1,20 @@
1
+ registry=https://registry.npmmirror.com
2
+
3
+ strictStorePkgContentCheck=false
4
+ verifyStoreIntegrity=false
5
+
6
+ # 网络优化
7
+ network-concurrency=16
8
+ fetch-retries=3
9
+ fetch-timeout=60000
10
+
11
+ # peerDependencies
12
+ strict-peer-dependencies=false
13
+ auto-install-peers=true
14
+
15
+ # lockfile 配置
16
+ lockfile=true
17
+ prefer-frozen-lockfile=true
18
+
19
+ # semver 选择最高版本
20
+ resolution-mode=highest
@@ -0,0 +1,33 @@
1
+ import { useEffect } from 'react';
2
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
3
+ import { Stack } from 'expo-router';
4
+ import { StatusBar } from 'expo-status-bar';
5
+ import { LogBox } from 'react-native';
6
+ import Toast from 'react-native-toast-message';
7
+ import { AuthProvider } from "@/contexts/AuthContext";
8
+
9
+ LogBox.ignoreLogs([
10
+ "TurboModuleRegistry.getEnforcing(...): 'RNMapsAirModule' could not be found",
11
+ // 添加其它想暂时忽略的错误或警告信息
12
+ ]);
13
+
14
+ export default function RootLayout() {
15
+ return (
16
+ <AuthProvider>
17
+ <GestureHandlerRootView style={{ flex: 1 }}>
18
+ <StatusBar style="dark"></StatusBar>
19
+ <Stack screenOptions={{
20
+ // 设置所有页面的切换动画为从右侧滑入,适用于iOS 和 Android
21
+ animation: 'slide_from_right',
22
+ gestureEnabled: true,
23
+ gestureDirection: 'horizontal',
24
+ // 隐藏自带的头部
25
+ headerShown: false
26
+ }}>
27
+ <Stack.Screen name="index" options={{ title: "" }} />
28
+ </Stack>
29
+ <Toast />
30
+ </GestureHandlerRootView>
31
+ </AuthProvider>
32
+ );
33
+ }