@coze-arch/cli 0.0.1-alpha.dffbaa → 0.0.1-alpha.f37dff
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/__templates__/expo/.coze +7 -2
- package/lib/__templates__/expo/.cozeproj/scripts/dev_build.sh +46 -0
- package/lib/__templates__/expo/.cozeproj/scripts/dev_run.sh +220 -0
- package/lib/__templates__/expo/.cozeproj/scripts/prod_build.sh +47 -0
- package/lib/__templates__/expo/.cozeproj/scripts/prod_run.sh +34 -0
- package/lib/__templates__/expo/README.md +66 -7
- package/lib/__templates__/expo/_gitignore +1 -1
- package/lib/__templates__/expo/_npmrc +2 -4
- package/lib/__templates__/expo/client/app/_layout.tsx +1 -1
- package/lib/__templates__/expo/client/app/home.tsx +1 -0
- package/lib/__templates__/expo/client/app/index.tsx +1 -0
- package/lib/__templates__/expo/client/app.config.ts +75 -0
- package/lib/__templates__/expo/client/assets/images/coze-logo.png +0 -0
- package/lib/__templates__/expo/client/components/ThemedText.tsx +33 -0
- package/lib/__templates__/expo/client/components/ThemedView.tsx +38 -0
- package/lib/__templates__/expo/client/constants/theme.ts +780 -48
- package/lib/__templates__/expo/client/hooks/useColorScheme.ts +34 -1
- package/lib/__templates__/expo/client/hooks/useTheme.ts +1 -1
- package/lib/__templates__/expo/client/metro.config.js +121 -0
- package/lib/__templates__/expo/client/package.json +93 -0
- package/lib/__templates__/expo/client/screens/home/index.tsx +8 -38
- package/lib/__templates__/expo/client/screens/home/styles.ts +16 -52
- package/lib/__templates__/expo/client/tsconfig.json +24 -0
- package/lib/__templates__/expo/package.json +13 -103
- package/lib/__templates__/expo/pnpm-lock.yaml +421 -867
- package/lib/__templates__/expo/pnpm-workspace.yaml +3 -0
- package/lib/__templates__/expo/server/package.json +31 -0
- package/lib/__templates__/expo/{src → server/src}/index.ts +8 -2
- package/lib/__templates__/expo/server/tsconfig.json +24 -0
- package/lib/__templates__/expo/template.config.js +1 -0
- package/lib/__templates__/expo/tsconfig.json +1 -24
- package/lib/__templates__/nextjs/.coze +1 -0
- package/lib/__templates__/nextjs/next.config.ts +10 -0
- package/lib/__templates__/nextjs/package.json +2 -1
- package/lib/__templates__/nextjs/scripts/prepare.sh +9 -0
- package/lib/__templates__/nextjs/src/app/globals.css +99 -87
- package/lib/__templates__/nextjs/src/app/page.tsx +2 -3
- package/lib/__templates__/nextjs/src/components/ui/resizable.tsx +29 -22
- package/lib/__templates__/nextjs/src/components/ui/sidebar.tsx +228 -230
- package/lib/__templates__/nextjs/template.config.js +24 -0
- package/lib/__templates__/templates.json +61 -43
- package/lib/__templates__/vite/.coze +1 -0
- package/lib/__templates__/vite/eslint.config.mjs +9 -0
- package/lib/__templates__/vite/package.json +6 -3
- package/lib/__templates__/vite/pnpm-lock.yaml +961 -120
- package/lib/__templates__/vite/scripts/prepare.sh +9 -0
- package/lib/__templates__/vite/template.config.js +4 -0
- package/lib/cli.js +144 -31
- package/package.json +8 -3
- package/lib/__templates__/expo/.cozeproj/scripts/deploy_build.sh +0 -116
- package/lib/__templates__/expo/.cozeproj/scripts/deploy_run.sh +0 -239
- package/lib/__templates__/expo/app.json +0 -63
- package/lib/__templates__/expo/babel.config.js +0 -9
- package/lib/__templates__/expo/client/app/(tabs)/_layout.tsx +0 -43
- package/lib/__templates__/expo/client/app/(tabs)/home.tsx +0 -1
- package/lib/__templates__/expo/client/app/(tabs)/index.tsx +0 -7
- package/lib/__templates__/expo/client/app/+not-found.tsx +0 -79
- package/lib/__templates__/expo/client/index.js +0 -12
- package/lib/__templates__/expo/metro.config.js +0 -53
- package/lib/__templates__/nextjs/.vscode/settings.json +0 -121
- package/lib/__templates__/vite/.vscode/settings.json +0 -7
- /package/lib/__templates__/expo/{eslint-formatter-simple.mjs → client/eslint-formatter-simple.mjs} +0 -0
- /package/lib/__templates__/expo/{eslint.config.mjs → client/eslint.config.mjs} +0 -0
|
@@ -37,7 +37,11 @@ export const paramsSchema = {
|
|
|
37
37
|
additionalProperties: false,
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
const description = `Vite(简单项目):\`coze init \${COZE_WORKSPACE_PATH} --template vite\`
|
|
41
|
+
- 适用:轻量级 SPA、纯前端交互、仪表盘等轻量级项目。`;
|
|
42
|
+
|
|
40
43
|
const config = {
|
|
44
|
+
description: description,
|
|
41
45
|
paramsSchema,
|
|
42
46
|
|
|
43
47
|
defaultParams: {
|
package/lib/cli.js
CHANGED
|
@@ -1248,17 +1248,139 @@ const convertDotfileName = (filePath) => {
|
|
|
1248
1248
|
};
|
|
1249
1249
|
|
|
1250
1250
|
/**
|
|
1251
|
-
*
|
|
1251
|
+
* 执行文件渲染钩子
|
|
1252
1252
|
*
|
|
1253
|
-
* @param
|
|
1254
|
-
* @param
|
|
1253
|
+
* @param templateConfig - 模板配置
|
|
1254
|
+
* @param fileInfo - 文件渲染信息
|
|
1255
1255
|
* @param context - 模板上下文
|
|
1256
|
+
* @returns 处理后的文件信息,或 null 表示跳过该文件
|
|
1256
1257
|
*/
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1259
|
-
|
|
1258
|
+
const executeFileRenderHook = async (
|
|
1259
|
+
templateConfig,
|
|
1260
|
+
fileInfo,
|
|
1260
1261
|
context,
|
|
1261
1262
|
) => {
|
|
1263
|
+
if (!templateConfig.onFileRender) {
|
|
1264
|
+
return fileInfo;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const result = await templateConfig.onFileRender(
|
|
1268
|
+
fileInfo,
|
|
1269
|
+
context,
|
|
1270
|
+
);
|
|
1271
|
+
|
|
1272
|
+
// false: 跳过文件
|
|
1273
|
+
if (result === false) {
|
|
1274
|
+
return null;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// undefined/void: 使用默认内容
|
|
1278
|
+
if (result === undefined || result === null) {
|
|
1279
|
+
return fileInfo;
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// string: 作为 content,其他不变
|
|
1283
|
+
if (typeof result === 'string') {
|
|
1284
|
+
return {
|
|
1285
|
+
...fileInfo,
|
|
1286
|
+
content: result,
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
// FileRenderInfo: 使用新对象的信息
|
|
1291
|
+
return result;
|
|
1292
|
+
};
|
|
1293
|
+
|
|
1294
|
+
/**
|
|
1295
|
+
* 处理单个文件
|
|
1296
|
+
*/
|
|
1297
|
+
const processSingleFile = async (options
|
|
1298
|
+
|
|
1299
|
+
|
|
1300
|
+
|
|
1301
|
+
|
|
1302
|
+
|
|
1303
|
+
) => {
|
|
1304
|
+
const { file, templatePath, outputPath, context, templateConfig } = options;
|
|
1305
|
+
|
|
1306
|
+
const srcPath = path.join(templatePath, file);
|
|
1307
|
+
const destFile = convertDotfileName(file);
|
|
1308
|
+
|
|
1309
|
+
logger.verbose(
|
|
1310
|
+
` - Processing: ${file}${destFile !== file ? ` -> ${destFile}` : ''}`,
|
|
1311
|
+
);
|
|
1312
|
+
|
|
1313
|
+
// 判断是否为二进制文件
|
|
1314
|
+
const isBinary = !shouldRenderFile(srcPath);
|
|
1315
|
+
let content;
|
|
1316
|
+
let wasRendered = false;
|
|
1317
|
+
|
|
1318
|
+
if (isBinary) {
|
|
1319
|
+
// 二进制文件,读取为 buffer 然后转为 base64
|
|
1320
|
+
const buffer = await fs$1.readFile(srcPath);
|
|
1321
|
+
content = buffer.toString('base64');
|
|
1322
|
+
} else {
|
|
1323
|
+
// 文本文件,渲染后的内容
|
|
1324
|
+
content = await renderTemplate(srcPath, context);
|
|
1325
|
+
wasRendered = true;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// 构造文件信息对象
|
|
1329
|
+
const fileInfo = {
|
|
1330
|
+
path: file,
|
|
1331
|
+
destPath: destFile,
|
|
1332
|
+
content,
|
|
1333
|
+
isBinary,
|
|
1334
|
+
wasRendered,
|
|
1335
|
+
};
|
|
1336
|
+
|
|
1337
|
+
// 执行文件渲染钩子
|
|
1338
|
+
const processedFileInfo = await executeFileRenderHook(
|
|
1339
|
+
templateConfig,
|
|
1340
|
+
fileInfo,
|
|
1341
|
+
context,
|
|
1342
|
+
);
|
|
1343
|
+
|
|
1344
|
+
// 如果返回 null,跳过该文件
|
|
1345
|
+
if (processedFileInfo === null) {
|
|
1346
|
+
logger.verbose(' ⊘ Skipped by onFileRender hook');
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// 使用处理后的目标路径
|
|
1351
|
+
const finalDestPath = path.join(outputPath, processedFileInfo.destPath);
|
|
1352
|
+
|
|
1353
|
+
// 确保目标目录存在
|
|
1354
|
+
await ensureDir(path.dirname(finalDestPath));
|
|
1355
|
+
|
|
1356
|
+
// 写入文件
|
|
1357
|
+
if (processedFileInfo.isBinary) {
|
|
1358
|
+
// 二进制文件:如果内容没变,直接复制;否则从 base64 解码写入
|
|
1359
|
+
if (processedFileInfo.content === content) {
|
|
1360
|
+
await fs$1.copyFile(srcPath, finalDestPath);
|
|
1361
|
+
logger.verbose(' ✓ Copied (binary)');
|
|
1362
|
+
} else {
|
|
1363
|
+
const buffer = Buffer.from(processedFileInfo.content, 'base64');
|
|
1364
|
+
await fs$1.writeFile(finalDestPath, buffer);
|
|
1365
|
+
logger.verbose(' ✓ Written (binary, modified by hook)');
|
|
1366
|
+
}
|
|
1367
|
+
} else {
|
|
1368
|
+
// 文本文件
|
|
1369
|
+
await fs$1.writeFile(finalDestPath, processedFileInfo.content, 'utf-8');
|
|
1370
|
+
logger.verbose(' ✓ Rendered and written');
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
/**
|
|
1375
|
+
* 复制并处理模板文件到目标目录
|
|
1376
|
+
*/
|
|
1377
|
+
const processTemplateFiles = async (options
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
|
|
1381
|
+
|
|
1382
|
+
) => {
|
|
1383
|
+
const { templatePath, outputPath, context, templateConfig } = options;
|
|
1262
1384
|
logger.verbose('Processing template files:');
|
|
1263
1385
|
logger.verbose(` - Template path: ${templatePath}`);
|
|
1264
1386
|
logger.verbose(` - Output path: ${outputPath}`);
|
|
@@ -1289,29 +1411,15 @@ const processTemplateFiles = async (
|
|
|
1289
1411
|
}
|
|
1290
1412
|
|
|
1291
1413
|
await Promise.all(
|
|
1292
|
-
files.map(
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
)
|
|
1300
|
-
|
|
1301
|
-
// 确保目标目录存在
|
|
1302
|
-
await ensureDir(path.dirname(destPath));
|
|
1303
|
-
|
|
1304
|
-
if (shouldRenderFile(srcPath)) {
|
|
1305
|
-
// 渲染文本文件
|
|
1306
|
-
const rendered = await renderTemplate(srcPath, context);
|
|
1307
|
-
await fs$1.writeFile(destPath, rendered, 'utf-8');
|
|
1308
|
-
logger.verbose(' ✓ Rendered and written');
|
|
1309
|
-
} else {
|
|
1310
|
-
// 直接复制二进制文件
|
|
1311
|
-
await fs$1.copyFile(srcPath, destPath);
|
|
1312
|
-
logger.verbose(' ✓ Copied');
|
|
1313
|
-
}
|
|
1314
|
-
}),
|
|
1414
|
+
files.map(file =>
|
|
1415
|
+
processSingleFile({
|
|
1416
|
+
file,
|
|
1417
|
+
templatePath,
|
|
1418
|
+
outputPath,
|
|
1419
|
+
context,
|
|
1420
|
+
templateConfig,
|
|
1421
|
+
}),
|
|
1422
|
+
),
|
|
1315
1423
|
);
|
|
1316
1424
|
|
|
1317
1425
|
logger.verbose('✓ All files processed successfully');
|
|
@@ -1498,7 +1606,12 @@ const execute = async (
|
|
|
1498
1606
|
const absoluteOutputPath = await prepareOutputDirectory(outputPath);
|
|
1499
1607
|
|
|
1500
1608
|
// 6. 处理模板文件
|
|
1501
|
-
await processTemplateFiles(
|
|
1609
|
+
await processTemplateFiles({
|
|
1610
|
+
templatePath,
|
|
1611
|
+
outputPath: absoluteOutputPath,
|
|
1612
|
+
context,
|
|
1613
|
+
templateConfig,
|
|
1614
|
+
});
|
|
1502
1615
|
|
|
1503
1616
|
// 7. 执行 onAfterRender 钩子
|
|
1504
1617
|
await executeAfterRenderHook(templateConfig, context, absoluteOutputPath);
|
|
@@ -1741,7 +1854,7 @@ const registerCommand = program => {
|
|
|
1741
1854
|
});
|
|
1742
1855
|
};
|
|
1743
1856
|
|
|
1744
|
-
var version = "0.0.1-alpha.
|
|
1857
|
+
var version = "0.0.1-alpha.f37dff";
|
|
1745
1858
|
var packageJson = {
|
|
1746
1859
|
version: version};
|
|
1747
1860
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coze-arch/cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.f37dff",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "coze coding devtools cli",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"prebuild": "tsx scripts/prebuild.ts",
|
|
21
21
|
"build": "tsx scripts/build.ts",
|
|
22
|
+
"create": "tsx scripts/create-template.ts",
|
|
22
23
|
"lint": "eslint ./ --cache",
|
|
23
24
|
"postpublish": "bash scripts/sync-npmmirror.sh",
|
|
24
25
|
"test": "vitest --run --passWithNoTests",
|
|
25
26
|
"test:all": "bash scripts/test-coverage.sh",
|
|
26
27
|
"test:cov": "vitest --run --passWithNoTests --coverage",
|
|
27
|
-
"test:e2e": "bash scripts/e2e.sh",
|
|
28
|
+
"test:e2e": "NODE_ENV=test bash scripts/e2e.sh",
|
|
28
29
|
"test:perf": "vitest bench --run --config vitest.perf.config.ts",
|
|
29
30
|
"test:perf:compare": "bash scripts/compare-perf.sh",
|
|
30
31
|
"test:perf:save": "bash scripts/run-perf-with-output.sh"
|
|
@@ -48,21 +49,25 @@
|
|
|
48
49
|
"@coze-arch/ts-config": "workspace:*",
|
|
49
50
|
"@coze-arch/vitest-config": "workspace:*",
|
|
50
51
|
"@coze-coding/lambda": "workspace:*",
|
|
52
|
+
"@inquirer/prompts": "^3.2.0",
|
|
51
53
|
"@types/ejs": "^3.1.5",
|
|
52
54
|
"@types/iarna__toml": "^2.0.5",
|
|
53
55
|
"@types/js-yaml": "^4.0.9",
|
|
56
|
+
"@types/minimatch": "^5.1.2",
|
|
54
57
|
"@types/minimist": "^1.2.5",
|
|
55
58
|
"@types/node": "^24",
|
|
56
59
|
"@types/shelljs": "^0.10.0",
|
|
57
60
|
"@vitest/coverage-v8": "~4.0.16",
|
|
58
61
|
"json-schema-to-typescript": "^15.0.3",
|
|
62
|
+
"minimatch": "^10.0.1",
|
|
59
63
|
"rollup": "^4.41.1",
|
|
60
64
|
"sucrase": "^3.35.0",
|
|
61
65
|
"tsx": "^4.20.6",
|
|
62
66
|
"vitest": "~4.0.16"
|
|
63
67
|
},
|
|
64
68
|
"publishConfig": {
|
|
65
|
-
"access": "public"
|
|
69
|
+
"access": "public",
|
|
70
|
+
"registry": "https://registry.npmjs.org"
|
|
66
71
|
},
|
|
67
72
|
"cozePublishConfig": {
|
|
68
73
|
"bin": {
|
|
@@ -1,116 +0,0 @@
|
|
|
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:-$ROOT_DIR}"
|
|
6
|
-
LOG_DIR="${COZE_LOG_DIR:-$ROOT_DIR/logs}"
|
|
7
|
-
LOG_FILE="$LOG_DIR/app.log"
|
|
8
|
-
mkdir -p "$LOG_DIR"
|
|
9
|
-
|
|
10
|
-
# ==================== 配置项 ====================
|
|
11
|
-
SERVER_DIR="app"
|
|
12
|
-
EXPO_DIR="expo"
|
|
13
|
-
CHECK_HASH_SCRIPT="$ROOT_DIR/check_hash.py"
|
|
14
|
-
# ==================== 工具函数 ====================
|
|
15
|
-
info() {
|
|
16
|
-
echo -e "\033[32m[INFO] $1\033[0m"
|
|
17
|
-
}
|
|
18
|
-
warn() {
|
|
19
|
-
echo -e "\033[33m[WARN] $1\033[0m"
|
|
20
|
-
}
|
|
21
|
-
error() {
|
|
22
|
-
echo -e "\033[31m[ERROR] $1\033[0m"
|
|
23
|
-
exit 1
|
|
24
|
-
}
|
|
25
|
-
check_command() {
|
|
26
|
-
if ! command -v "$1" &> /dev/null; then
|
|
27
|
-
error "命令 $1 未找到,请先安装"
|
|
28
|
-
fi
|
|
29
|
-
}
|
|
30
|
-
write_log() {
|
|
31
|
-
local level="${1:-INFO}"
|
|
32
|
-
local msg="${2:-}"
|
|
33
|
-
node -e '
|
|
34
|
-
const fs=require("fs");
|
|
35
|
-
const path=process.argv[1];
|
|
36
|
-
const level=(process.argv[2]||"").toUpperCase();
|
|
37
|
-
let msg=String(process.argv[3]||"");
|
|
38
|
-
const ts=Date.now();
|
|
39
|
-
const dt=new Date();
|
|
40
|
-
const parts=new Intl.DateTimeFormat("en-GB",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:false}).formatToParts(dt);
|
|
41
|
-
const o={};
|
|
42
|
-
for(const p of parts){o[p.type]=p.value}
|
|
43
|
-
const dt_str=`${o.year}-${o.month}-${o.day} ${o.hour}:${o.minute}:${o.second}`;
|
|
44
|
-
msg=msg.replace(/\\n/g,"\n");
|
|
45
|
-
msg=`${dt_str} [${level}] ${msg}`;
|
|
46
|
-
const record=JSON.stringify({level, message: msg, timestamp: ts},null,0);
|
|
47
|
-
const fd=fs.openSync(path,"a");
|
|
48
|
-
fs.writeSync(fd, record+"\n", null, "utf8");
|
|
49
|
-
fs.fsyncSync(fd);
|
|
50
|
-
fs.closeSync(fd);
|
|
51
|
-
' "$LOG_FILE" "$level" "$msg"
|
|
52
|
-
|
|
53
|
-
case "$level" in
|
|
54
|
-
INFO) info "$msg" ;;
|
|
55
|
-
WARN) warn "$msg" ;;
|
|
56
|
-
ERROR) echo -e "\033[31m[ERROR] $msg\033[0m" ;;
|
|
57
|
-
*) info "$msg" ;;
|
|
58
|
-
esac
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
# ==================== 前置检查 ====================
|
|
62
|
-
write_log "INFO" "==================== 开始构建 ===================="
|
|
63
|
-
|
|
64
|
-
write_log "INFO" "检查根目录 pre_install.py"
|
|
65
|
-
if [ -f "$PREVIEW_DIR/pre_install.py" ]; then
|
|
66
|
-
write_log "INFO" "执行:python $PREVIEW_DIR/pre_install.py"
|
|
67
|
-
python "$PREVIEW_DIR/pre_install.py" || write_log "ERROR" "pre_install.py 执行失败"
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
write_log "INFO" "开始执行构建脚本(build_dev.sh)..."
|
|
71
|
-
write_log "INFO" "正在检查依赖命令是否存在..."
|
|
72
|
-
# 检查核心命令
|
|
73
|
-
# check_command "pip"
|
|
74
|
-
# check_command "python"
|
|
75
|
-
check_command "pnpm"
|
|
76
|
-
check_command "npm"
|
|
77
|
-
|
|
78
|
-
# ==================== 步骤 1:安装项目依赖 ====================
|
|
79
|
-
write_log "INFO" "==================== 安装项目依赖 ===================="
|
|
80
|
-
if [ ! -f "package.json" ]; then
|
|
81
|
-
write_log "ERROR" "项目目录下无 package.json,不是合法的 Node.js 项目"
|
|
82
|
-
fi
|
|
83
|
-
# 步骤 2.1/2.2:安装 Expo 项目依赖(按哈希变化执行)
|
|
84
|
-
if [ -f "$CHECK_HASH_SCRIPT" ]; then
|
|
85
|
-
write_log "INFO" "检测 package.json 哈希是否变化"
|
|
86
|
-
set +e
|
|
87
|
-
python "$CHECK_HASH_SCRIPT" --config "package.json" --check
|
|
88
|
-
rc_node=$?
|
|
89
|
-
set -e
|
|
90
|
-
if [ $rc_node -eq 1 ]; then
|
|
91
|
-
write_log "INFO" "依赖哈希已变化,执行:pnpm install"
|
|
92
|
-
pnpm install --registry=https://registry.npmmirror.com || write_log "ERROR" "Expo 项目依赖安装失败(pnpm 执行出错)"
|
|
93
|
-
|
|
94
|
-
write_log "INFO" "依赖哈希已变化,执行:npm run install-missing"
|
|
95
|
-
npm run install-missing || write_log "ERROR" "npm run install-missing 执行失败,请检查 package.json 中的脚本配置"
|
|
96
|
-
|
|
97
|
-
set +e
|
|
98
|
-
python "$CHECK_HASH_SCRIPT" --config "package.json" --update
|
|
99
|
-
set -e
|
|
100
|
-
else
|
|
101
|
-
write_log "INFO" "跳过 pnpm install 与 npm run install-missing"
|
|
102
|
-
fi
|
|
103
|
-
else
|
|
104
|
-
write_log "WARN" "未找到 check_hash.py,默认执行:pnpm install 与 npm run install-missing"
|
|
105
|
-
pnpm install --registry=https://registry.npmmirror.com || write_log "ERROR" "Expo 项目依赖安装失败(pnpm 执行出错)"
|
|
106
|
-
npm run install-missing || write_log "ERROR" "npm run install-missing 执行失败,请检查 package.json 中的脚本配置"
|
|
107
|
-
fi
|
|
108
|
-
|
|
109
|
-
write_log "INFO" "检查根目录 post_install.py"
|
|
110
|
-
if [ -f "$ROOT_DIR/post_install.py" ]; then
|
|
111
|
-
write_log "INFO" "执行:python $ROOT_DIR/post_install.py"
|
|
112
|
-
python "$ROOT_DIR/post_install.py" || write_log "ERROR" "post_install.py 执行失败"
|
|
113
|
-
fi
|
|
114
|
-
|
|
115
|
-
write_log "INFO" "==================== 依赖安装完成!====================\n"
|
|
116
|
-
write_log "INFO" "下一步:执行 ./deploy_run.sh 启动服务"
|
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
2
|
-
LOG_DIR="${COZE_LOG_DIR:-$ROOT_DIR/logs}"
|
|
3
|
-
LOG_FILE="$LOG_DIR/app.log"
|
|
4
|
-
mkdir -p "$LOG_DIR"
|
|
5
|
-
|
|
6
|
-
# ==================== 配置项 ====================
|
|
7
|
-
# Server 服务配置
|
|
8
|
-
SERVER_HOST="0.0.0.0"
|
|
9
|
-
SERVER_PORT="9091"
|
|
10
|
-
# Expo 项目配置
|
|
11
|
-
EXPO_HOST="0.0.0.0"
|
|
12
|
-
EXPO_DIR="expo"
|
|
13
|
-
EXPO_PORT="9090"
|
|
14
|
-
WEB_URL="${COZE_PROJECT_DOMAIN_DEFAULT:-http://127.0.0.1:${SERVER_PORT}}"
|
|
15
|
-
ASSUME_YES="1"
|
|
16
|
-
EXPO_PUBLIC_BACKEND_BASE_URL="${EXPO_PUBLIC_BACKEND_BASE_URL:-$WEB_URL}"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
EXPO_PACKAGER_PROXY_URL="${EXPO_PUBLIC_BACKEND_BASE_URL}"
|
|
20
|
-
export EXPO_PUBLIC_BACKEND_BASE_URL EXPO_PACKAGER_PROXY_URL
|
|
21
|
-
# 运行时变量(为避免 set -u 的未绑定错误,预置为空)
|
|
22
|
-
SERVER_PID=""
|
|
23
|
-
EXPO_PID=""
|
|
24
|
-
# ==================== 工具函数 ====================
|
|
25
|
-
info() {
|
|
26
|
-
echo -e "\033[32m[INFO] $1\033[0m"
|
|
27
|
-
}
|
|
28
|
-
warn() {
|
|
29
|
-
echo -e "\033[33m[WARN] $1\033[0m"
|
|
30
|
-
}
|
|
31
|
-
error() {
|
|
32
|
-
echo -e "\033[31m[ERROR] $1\033[0m"
|
|
33
|
-
exit 1
|
|
34
|
-
}
|
|
35
|
-
check_command() {
|
|
36
|
-
if ! command -v "$1" &> /dev/null; then
|
|
37
|
-
error "命令 $1 未找到,请先安装"
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
while [ $# -gt 0 ]; do
|
|
41
|
-
case "$1" in
|
|
42
|
-
-y|--yes)
|
|
43
|
-
ASSUME_YES="1"
|
|
44
|
-
shift
|
|
45
|
-
;;
|
|
46
|
-
*)
|
|
47
|
-
shift
|
|
48
|
-
;;
|
|
49
|
-
esac
|
|
50
|
-
done
|
|
51
|
-
is_port_free() {
|
|
52
|
-
! lsof -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1
|
|
53
|
-
}
|
|
54
|
-
choose_next_free_port() {
|
|
55
|
-
local start=$1
|
|
56
|
-
local p=$start
|
|
57
|
-
while ! is_port_free "$p"; do
|
|
58
|
-
p=$((p+1))
|
|
59
|
-
done
|
|
60
|
-
echo "$p"
|
|
61
|
-
}
|
|
62
|
-
ensure_port() {
|
|
63
|
-
local var_name=$1
|
|
64
|
-
local port_val=$2
|
|
65
|
-
if is_port_free "$port_val"; then
|
|
66
|
-
info "端口未占用:$port_val"
|
|
67
|
-
eval "$var_name=$port_val"
|
|
68
|
-
else
|
|
69
|
-
warn "端口已占用:$port_val"
|
|
70
|
-
local choice
|
|
71
|
-
if [ "$ASSUME_YES" = "1" ]; then choice="Y"; else read -r -p "是否关闭该端口的进程?[Y/n] " choice || choice="Y"; fi
|
|
72
|
-
if [ -z "$choice" ] || [ "$choice" = "y" ] || [ "$choice" = "Y" ]; then
|
|
73
|
-
if command -v lsof &> /dev/null; then
|
|
74
|
-
local pids
|
|
75
|
-
pids=$(lsof -t -i tcp:"$port_val" -sTCP:LISTEN 2>/dev/null || true)
|
|
76
|
-
if [ -n "$pids" ]; then
|
|
77
|
-
info "正在关闭进程:$pids"
|
|
78
|
-
kill -9 $pids 2>/dev/null || warn "关闭进程失败:$pids"
|
|
79
|
-
eval "$var_name=$port_val"
|
|
80
|
-
else
|
|
81
|
-
warn "未获取到占用该端口的进程"
|
|
82
|
-
eval "$var_name=$port_val"
|
|
83
|
-
fi
|
|
84
|
-
else
|
|
85
|
-
warn "缺少 lsof,无法自动关闭进程"
|
|
86
|
-
eval "$var_name=$port_val"
|
|
87
|
-
fi
|
|
88
|
-
else
|
|
89
|
-
local new_port
|
|
90
|
-
new_port=$(choose_next_free_port "$port_val")
|
|
91
|
-
info "使用新的端口:$new_port"
|
|
92
|
-
eval "$var_name=$new_port"
|
|
93
|
-
fi
|
|
94
|
-
fi
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
write_log() {
|
|
98
|
-
local level="${1:-INFO}"
|
|
99
|
-
local msg="${2:-}"
|
|
100
|
-
node -e '
|
|
101
|
-
const fs=require("fs");
|
|
102
|
-
const path=process.argv[1];
|
|
103
|
-
const level=(process.argv[2]||"").toUpperCase();
|
|
104
|
-
let msg=String(process.argv[3]||"");
|
|
105
|
-
const ts=Date.now();
|
|
106
|
-
msg=msg.replace(/\\n/g,"\n");
|
|
107
|
-
const dt=new Date();
|
|
108
|
-
const parts=new Intl.DateTimeFormat("en-GB",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:false}).formatToParts(dt);
|
|
109
|
-
const o={};
|
|
110
|
-
for(const p of parts){o[p.type]=p.value}
|
|
111
|
-
const dt_str=`${o.year}-${o.month}-${o.day} ${o.hour}:${o.minute}:${o.second}`;
|
|
112
|
-
msg=`${dt_str} [${level}] ${msg}`;
|
|
113
|
-
const record=JSON.stringify({level, message: msg, timestamp: ts},null,0);
|
|
114
|
-
const fd=fs.openSync(path,"a");
|
|
115
|
-
fs.writeSync(fd, record+"\n", null, "utf8");
|
|
116
|
-
fs.fsyncSync(fd);
|
|
117
|
-
fs.closeSync(fd);
|
|
118
|
-
' "$LOG_FILE" "$level" "$msg"
|
|
119
|
-
case "$level" in
|
|
120
|
-
INFO) echo -e "\033[32m[INFO] $msg\033[0m" ;;
|
|
121
|
-
WARN) echo -e "\033[33m[WARN] $msg\033[0m" ;;
|
|
122
|
-
ERROR) echo -e "\033[31m[ERROR] $msg\033[0m" ;;
|
|
123
|
-
*) echo -e "\033[32m[INFO] $msg\033[0m" ;;
|
|
124
|
-
esac
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
wait_port_connectable() {
|
|
128
|
-
local host=$1 port=$2 retries=${3:-10}
|
|
129
|
-
for _ in $(seq 1 "$retries"); do
|
|
130
|
-
nc -z "$host" "$port" && return 0
|
|
131
|
-
sleep 1
|
|
132
|
-
done
|
|
133
|
-
return 1
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
start_expo() {
|
|
137
|
-
local offline="${1:-0}"
|
|
138
|
-
if [ "$offline" = "1" ]; then
|
|
139
|
-
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" nohup npx expo start --port "$EXPO_PORT" > logs/expo.log 2>&1 &
|
|
140
|
-
else
|
|
141
|
-
EXPO_NO_DOCTOR=1 EXPO_PUBLIC_BACKEND_BASE_URL="$EXPO_PUBLIC_BACKEND_BASE_URL" EXPO_PACKAGER_PROXY_URL="$EXPO_PACKAGER_PROXY_URL" nohup npx expo start --port "$EXPO_PORT" > logs/expo.log 2>&1 &
|
|
142
|
-
fi
|
|
143
|
-
EXPO_PID=$!
|
|
144
|
-
echo "$EXPO_PID"
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
detect_expo_fetch_failed() {
|
|
148
|
-
local timeout="${1:-8}"
|
|
149
|
-
local waited=0
|
|
150
|
-
local log_file="logs/expo.log"
|
|
151
|
-
while [ "$waited" -lt "$timeout" ]; do
|
|
152
|
-
if [ -f "$log_file" ] && grep -q "TypeError: fetch failed" "$log_file" 2>/dev/null; then
|
|
153
|
-
return 0
|
|
154
|
-
fi
|
|
155
|
-
sleep 1
|
|
156
|
-
waited=$((waited+1))
|
|
157
|
-
done
|
|
158
|
-
return 1
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
# ==================== 前置检查 ====================
|
|
162
|
-
write_log "INFO" "==================== 开始启动 ===================="
|
|
163
|
-
write_log "INFO" "开始执行服务启动脚本(start_dev.sh)..."
|
|
164
|
-
write_log "INFO" "正在检查依赖命令和目录是否存在..."
|
|
165
|
-
# 检查核心命令
|
|
166
|
-
# check_command "python"
|
|
167
|
-
check_command "npm"
|
|
168
|
-
check_command "pnpm"
|
|
169
|
-
check_command "lsof"
|
|
170
|
-
check_command "bash"
|
|
171
|
-
|
|
172
|
-
info "准备日志目录:logs"
|
|
173
|
-
mkdir -p logs
|
|
174
|
-
# 端口占用预检查与处理
|
|
175
|
-
ensure_port SERVER_PORT "$SERVER_PORT"
|
|
176
|
-
ensure_port EXPO_PORT "$EXPO_PORT"
|
|
177
|
-
|
|
178
|
-
# ==================== 启动 Server 服务 ====================
|
|
179
|
-
write_log "INFO" "检查 Nginx 端口 (5000)..."
|
|
180
|
-
if is_port_free 5000; then
|
|
181
|
-
write_log "INFO" "端口 5000 未被占用,正在启动 Nginx..."
|
|
182
|
-
service nginx start
|
|
183
|
-
else
|
|
184
|
-
write_log "INFO" "端口 5000 已被占用,跳过 Nginx 启动"
|
|
185
|
-
fi
|
|
186
|
-
|
|
187
|
-
write_log "INFO" "==================== 启动 server 服务 ===================="
|
|
188
|
-
write_log "INFO" "正在执行:npm run server"
|
|
189
|
-
PORT="$SERVER_PORT" nohup npm run server > logs/app.log 2>&1 &
|
|
190
|
-
SERVER_PID=$!
|
|
191
|
-
if [ -z "${SERVER_PID}" ]; then
|
|
192
|
-
write_log "ERROR" "无法获取 server 后台进程 PID"
|
|
193
|
-
fi
|
|
194
|
-
write_log "INFO" "server 服务已启动,进程 ID:${SERVER_PID:-unknown}"
|
|
195
|
-
|
|
196
|
-
write_log "INFO" "==================== 启动 Expo 项目 ===================="
|
|
197
|
-
write_log "INFO" "正在执行:npx expo start --port ${EXPO_PORT}"
|
|
198
|
-
write_log "INFO" "开始启动 Expo 服务"
|
|
199
|
-
EXPO_PID=$(start_expo 0)
|
|
200
|
-
if detect_expo_fetch_failed 8; then
|
|
201
|
-
write_log "WARN" "Expo 启动检测到网络错误:TypeError: fetch failed,启用离线模式重试"
|
|
202
|
-
if [ -n "${EXPO_PID}" ]; then kill -9 "$EXPO_PID" 2>/dev/null || true; fi
|
|
203
|
-
: > logs/expo.log
|
|
204
|
-
EXPO_PID=$(start_expo 1)
|
|
205
|
-
fi
|
|
206
|
-
# 输出以下环境变量,确保 Expo 项目能正确连接到 Server 服务
|
|
207
|
-
write_log "INFO" "Expo 环境变量配置:"
|
|
208
|
-
write_log "INFO" "EXPO_PUBLIC_BACKEND_BASE_URL=${EXPO_PUBLIC_BACKEND_BASE_URL}"
|
|
209
|
-
write_log "INFO" "EXPO_PACKAGER_PROXY_URL=${EXPO_PACKAGER_PROXY_URL}"
|
|
210
|
-
if [ -z "${EXPO_PID}" ]; then
|
|
211
|
-
write_log "ERROR" "无法获取 Expo 后台进程 PID"
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
write_log "INFO" "所有服务已启动。Server PID: ${SERVER_PID}, Expo PID: ${EXPO_PID}"
|
|
215
|
-
|
|
216
|
-
write_log "INFO" "检查 Server 服务端口:$SERVER_HOST:$SERVER_PORT"
|
|
217
|
-
if wait_port_connectable "$SERVER_HOST" "$SERVER_PORT" 10 2; then
|
|
218
|
-
write_log "INFO" "端口可连接:$SERVER_HOST:$SERVER_PORT"
|
|
219
|
-
else
|
|
220
|
-
write_log "WARN" "端口不可连接:$SERVER_HOST:$SERVER_PORT 10 次)"
|
|
221
|
-
fi
|
|
222
|
-
|
|
223
|
-
write_log "INFO" "检查 Expo 服务端口:$EXPO_HOST:$EXPO_PORT"
|
|
224
|
-
if wait_port_connectable "$EXPO_HOST" "$EXPO_PORT" 10 2; then
|
|
225
|
-
write_log "INFO" "端口可连接:$EXPO_HOST:$EXPO_PORT"
|
|
226
|
-
else
|
|
227
|
-
write_log "WARN" "端口不可连接:$EXPO_HOST:$EXPO_PORT(已尝试 10 次)"
|
|
228
|
-
fi
|
|
229
|
-
|
|
230
|
-
write_log "INFO" "服务端口检查完成"
|
|
231
|
-
|
|
232
|
-
write_log "INFO" "检查根目录 post_run.py"
|
|
233
|
-
if [ -f "$ROOT_DIR/post_run.py" ]; then
|
|
234
|
-
write_log "INFO" "启动检查中"
|
|
235
|
-
python "$ROOT_DIR/post_run.py" --port "$EXPO_PORT" || write_log "ERROR" "post_run.py 执行失败"
|
|
236
|
-
write_log "INFO" "启动检查结束"
|
|
237
|
-
fi
|
|
238
|
-
|
|
239
|
-
write_log "INFO" "==================== 服务启动完成 ===================="
|