@sybz-components/portal-dev 1.0.3 → 1.0.6
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/README.md +75 -9
- package/bin/portal-dev.mjs +6 -4
- package/package.json +1 -1
- package/skills/portal-dev/SKILL.md +21 -6
- package/src/config-file.mjs +45 -6
- package/src/configure.mjs +81 -9
- package/src/portal-dev.mjs +119 -26
package/README.md
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
# @sybz-components/portal-dev
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
成华、石景山门户自动登录 CLI,也可登录只需用户名和密码的自定义网站,并按需启动石景山门户本地联调。
|
|
4
4
|
|
|
5
5
|
## 环境要求
|
|
6
6
|
|
|
7
7
|
- Node.js 18+
|
|
8
8
|
- Google Chrome 或 Microsoft Edge
|
|
9
|
-
- 目标项目已安装依赖,并提供 `dev` script
|
|
10
9
|
|
|
11
10
|
## 安装
|
|
12
11
|
|
|
@@ -24,7 +23,7 @@ pnpm add -g @sybz-components/portal-dev
|
|
|
24
23
|
portal-dev config
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
根据中文提示输入账号名称、用户名和密码即可。密码输入时会显示为 `*`。同一个门户可以重复执行该命令保存多个账号;账号名称相同时会更新原账号。
|
|
28
27
|
|
|
29
28
|
默认配置文件位置:
|
|
30
29
|
|
|
@@ -33,7 +32,7 @@ portal-dev config
|
|
|
33
32
|
|
|
34
33
|
程序会自动创建目录,并尽可能将配置文件权限设为仅当前用户可读写。
|
|
35
34
|
|
|
36
|
-
|
|
35
|
+
配置完成后可以在任意目录直接运行:
|
|
37
36
|
|
|
38
37
|
```bash
|
|
39
38
|
portal-dev
|
|
@@ -44,27 +43,94 @@ portal-dev
|
|
|
44
43
|
```bash
|
|
45
44
|
portal-dev config --portal sjs
|
|
46
45
|
portal-dev config --portal chenghua
|
|
46
|
+
portal-dev config --portal custom
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
## 使用
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
只登录石景山门户,不读取或启动任何前端项目:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
portal-dev
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
|
|
57
|
+
只登录成华门户并进入智能体广场,不读取或启动任何前端项目:
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
60
|
portal-dev --portal chenghua
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
配置多个账号后,不写账号名称会直接使用数组中的第一个账号:
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
-
portal-dev --portal sjs
|
|
67
|
-
|
|
66
|
+
portal-dev --portal sjs
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
在命令末尾写账号名称,可以使用指定账号:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
portal-dev --portal sjs 测试账号
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
默认快捷命令:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
portal-dev ds # 登录石景山
|
|
79
|
+
portal-dev sc # 登录成华
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
配置并登录普通网站:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
portal-dev config --portal custom
|
|
86
|
+
portal-dev --portal custom 我的账号
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
配置时依次输入登录页 URL、用户名和密码。登录时会自动查找常见的用户名、邮箱、密码输入框,以及“登录”、“Login”或“Sign in”按钮;该模式不会执行验证码识别、样板间搜索或 Token 跳转。
|
|
90
|
+
|
|
91
|
+
别名保存在 JSON 中,可以通过向导新增登录或联调命令:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
portal-dev config command
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
例如配置一个别名为 `sjs-dev` 的联调命令后,只需运行:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
portal-dev sjs-dev
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
执行时会登录指定账号、启动本地项目、进入样板间搜索智能体,从匹配的 iframe 地址取得 Token,然后保留查询参数打开本地页面。
|
|
104
|
+
|
|
105
|
+
配置文件中的账号和快捷命令格式:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"version": 4,
|
|
110
|
+
"profiles": {
|
|
111
|
+
"sjs": [{ "name": "我的账号", "username": "user1", "password": "******" }],
|
|
112
|
+
"chenghua": [{ "name": "测试账号", "username": "user2", "password": "******" }],
|
|
113
|
+
"custom": [
|
|
114
|
+
{ "name": "内部系统", "loginUrl": "https://example.com/login", "username": "user3", "password": "******" }
|
|
115
|
+
]
|
|
116
|
+
},
|
|
117
|
+
"commands": [
|
|
118
|
+
{ "alias": "ds", "mode": "login", "portal": "sjs" },
|
|
119
|
+
{ "alias": "sc", "mode": "login", "portal": "chenghua" },
|
|
120
|
+
{
|
|
121
|
+
"alias": "sjs-dev",
|
|
122
|
+
"mode": "dev",
|
|
123
|
+
"portal": "sjs",
|
|
124
|
+
"account": "测试账号",
|
|
125
|
+
"project": "/Users/你的名字/projects/agent-web",
|
|
126
|
+
"localOrigin": "http://localhost:5173",
|
|
127
|
+
"localPath": "/exhibition-hall",
|
|
128
|
+
"roomName": "3D智能展厅智能体",
|
|
129
|
+
"iframeHost": "hia.sjsdoubao.com:31118",
|
|
130
|
+
"iframePath": "/exhibition-hall"
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
68
134
|
```
|
|
69
135
|
|
|
70
136
|
查看命令帮助:
|
package/bin/portal-dev.mjs
CHANGED
|
@@ -34,13 +34,15 @@ if (args[0] === 'config') {
|
|
|
34
34
|
} else if (args.includes('--help') || args.includes('-h')) {
|
|
35
35
|
console.log(`用法:
|
|
36
36
|
portal-dev
|
|
37
|
-
portal-dev config [--portal sjs|chenghua]
|
|
38
|
-
portal-dev
|
|
39
|
-
portal-dev
|
|
37
|
+
portal-dev config [--portal sjs|chenghua|custom]
|
|
38
|
+
portal-dev config command
|
|
39
|
+
portal-dev <快捷命令别名>
|
|
40
|
+
portal-dev --portal sjs|chenghua|custom [账号名称]
|
|
40
41
|
portal-dev skill install [Codex skills 目录]
|
|
41
42
|
|
|
42
43
|
首次使用只需运行一次 portal-dev config。
|
|
43
|
-
|
|
44
|
+
默认快捷命令:portal-dev ds 登录石景山,portal-dev sc 登录成华。
|
|
45
|
+
运行 portal-dev config command 可新增或修改快捷命令。`)
|
|
44
46
|
process.exit(0)
|
|
45
47
|
} else {
|
|
46
48
|
const child = spawn(process.execPath, [resolve(packageDir, 'src/portal-dev.mjs'), ...args], {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: portal-dev
|
|
3
|
-
description:
|
|
3
|
+
description: 启动前端项目的石景山门户本地联调,自动登录成华门户,或使用已配置的 URL 和账号密码登录普通网站。用户提到 dev:portal、门户联调、成华登录、石景山登录、自定义登录或从任意项目启动门户调试时使用。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 门户本地调试
|
|
@@ -15,20 +15,35 @@ description: 启动前端项目的石景山门户本地联调,或自动登录
|
|
|
15
15
|
portal-dev config --portal sjs
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
只登录石景山门户(不依赖前端项目):
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
portal-dev --portal sjs
|
|
21
|
+
portal-dev --portal sjs
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
只登录成华门户(不依赖前端项目):
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
portal-dev --portal chenghua
|
|
27
|
+
portal-dev --portal chenghua
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
登录已配置的普通网站(不走验证码和 Token 等特殊流程):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
portal-dev --portal custom <账号名称>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
默认快捷命令:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
portal-dev ds
|
|
40
|
+
portal-dev sc
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
只有用户明确要求门户本地联调时才执行 JSON 中 `mode: "dev"` 的快捷命令,例如 `portal-dev sjs-dev`。项目、本地地址、路由、智能体名称和 iframe 识别参数均从该命令配置读取。脚本从门户 iframe URL 取得 Token 后,必须保留查询参数打开本地地址,且不得输出 Token。
|
|
44
|
+
|
|
30
45
|
保持进程运行并告诉用户当前状态;只有用户明确要求停止时才终止。不得输出用户名、密码或门户 Token。
|
|
31
46
|
|
|
32
47
|
## 配置
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
账号密码只从当前用户的专属配置文件读取,每个门户可保存多个账号。`portal-dev config` 会新增账号或按账号名称更新已有账号;登录命令不写账号名称时使用数组第一个账号,在命令末尾追加账号名称时使用指定账号。普通网站使用 `portal-dev config --portal custom` 配置登录 URL、账号名称、用户名和密码。缺少凭据时,引导用户在本机终端运行 `portal-dev config`,不索要或展示具体密码。
|
package/src/config-file.mjs
CHANGED
|
@@ -2,6 +2,11 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'n
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { dirname, resolve } from 'node:path'
|
|
4
4
|
|
|
5
|
+
const defaultCommands = () => [
|
|
6
|
+
{ alias: 'ds', mode: 'login', portal: 'sjs' },
|
|
7
|
+
{ alias: 'sc', mode: 'login', portal: 'chenghua' },
|
|
8
|
+
]
|
|
9
|
+
|
|
5
10
|
export const getPortalConfigPath = () => {
|
|
6
11
|
const configRoot =
|
|
7
12
|
process.platform === 'win32' ? resolve(homedir(), 'AppData', 'Roaming') : resolve(homedir(), '.config')
|
|
@@ -10,23 +15,39 @@ export const getPortalConfigPath = () => {
|
|
|
10
15
|
|
|
11
16
|
export const readPortalConfig = () => {
|
|
12
17
|
const configPath = getPortalConfigPath()
|
|
13
|
-
if (!existsSync(configPath)) return { version:
|
|
18
|
+
if (!existsSync(configPath)) return { version: 4, profiles: {}, commands: defaultCommands() }
|
|
14
19
|
try {
|
|
15
20
|
const config = JSON.parse(readFileSync(configPath, 'utf8'))
|
|
16
21
|
if (!config || typeof config !== 'object' || Array.isArray(config)) throw new Error('根节点必须是对象')
|
|
17
|
-
return {
|
|
22
|
+
return {
|
|
23
|
+
...config,
|
|
24
|
+
profiles: config.profiles && typeof config.profiles === 'object' ? config.profiles : {},
|
|
25
|
+
commands: Array.isArray(config.commands) ? config.commands : defaultCommands(),
|
|
26
|
+
}
|
|
18
27
|
} catch (error) {
|
|
19
28
|
throw new Error(`门户配置文件格式错误:${configPath}\n${error.message}`)
|
|
20
29
|
}
|
|
21
30
|
}
|
|
22
31
|
|
|
23
|
-
export const
|
|
32
|
+
export const writePortalAccount = (portal, account) => {
|
|
24
33
|
const configPath = getPortalConfigPath()
|
|
25
34
|
const config = readPortalConfig()
|
|
35
|
+
const normalizedProfiles = {
|
|
36
|
+
...config.profiles,
|
|
37
|
+
sjs: Array.isArray(config.profiles.sjs) ? config.profiles.sjs : [],
|
|
38
|
+
chenghua: Array.isArray(config.profiles.chenghua) ? config.profiles.chenghua : [],
|
|
39
|
+
custom: Array.isArray(config.profiles.custom) ? config.profiles.custom : [],
|
|
40
|
+
}
|
|
41
|
+
const accounts = normalizedProfiles[portal]
|
|
42
|
+
const accountIndex = accounts.findIndex((item) => item.name === account.name)
|
|
43
|
+
const nextAccounts = [...accounts]
|
|
44
|
+
if (accountIndex >= 0) nextAccounts[accountIndex] = account
|
|
45
|
+
else nextAccounts.push(account)
|
|
26
46
|
const nextConfig = {
|
|
27
47
|
...config,
|
|
28
|
-
version:
|
|
29
|
-
profiles: { ...
|
|
48
|
+
version: 4,
|
|
49
|
+
profiles: { ...normalizedProfiles, [portal]: nextAccounts },
|
|
50
|
+
commands: config.commands,
|
|
30
51
|
}
|
|
31
52
|
mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 })
|
|
32
53
|
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, { mode: 0o600 })
|
|
@@ -35,5 +56,23 @@ export const writePortalProfile = (portal, profile) => {
|
|
|
35
56
|
} catch {
|
|
36
57
|
// Windows 等不支持 POSIX 权限的环境由系统用户目录权限保护。
|
|
37
58
|
}
|
|
38
|
-
return configPath
|
|
59
|
+
return { configPath, accountCount: nextAccounts.length }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const writePortalCommand = (command) => {
|
|
63
|
+
const configPath = getPortalConfigPath()
|
|
64
|
+
const config = readPortalConfig()
|
|
65
|
+
const commandIndex = config.commands.findIndex((item) => item.alias === command.alias)
|
|
66
|
+
const nextCommands = [...config.commands]
|
|
67
|
+
if (commandIndex >= 0) nextCommands[commandIndex] = command
|
|
68
|
+
else nextCommands.push(command)
|
|
69
|
+
const nextConfig = { ...config, version: 4, commands: nextCommands }
|
|
70
|
+
mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 })
|
|
71
|
+
writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, { mode: 0o600 })
|
|
72
|
+
try {
|
|
73
|
+
chmodSync(configPath, 0o600)
|
|
74
|
+
} catch {
|
|
75
|
+
// Windows 等不支持 POSIX 权限的环境由系统用户目录权限保护。
|
|
76
|
+
}
|
|
77
|
+
return { configPath, commandCount: nextCommands.length }
|
|
39
78
|
}
|
package/src/configure.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createInterface } from 'node:readline/promises'
|
|
2
|
-
import {
|
|
2
|
+
import { writePortalAccount, writePortalCommand } from './config-file.mjs'
|
|
3
3
|
|
|
4
4
|
const args = process.argv.slice(2)
|
|
5
5
|
const readArg = (name) => {
|
|
@@ -52,18 +52,90 @@ const askPassword = (message) => {
|
|
|
52
52
|
})
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
if (args[0] === 'command') {
|
|
56
|
+
const commandRl = createInterface({ input: process.stdin, output: process.stdout })
|
|
57
|
+
const alias = (await commandRl.question('请输入快捷命令别名(例如:ds、sc、sjs-dev):')).trim()
|
|
58
|
+
if (!alias || !/^[a-zA-Z][\w-]*$/.test(alias)) {
|
|
59
|
+
commandRl.close()
|
|
60
|
+
throw new Error('别名只能包含字母、数字、下划线和短横线,并且必须以字母开头')
|
|
61
|
+
}
|
|
62
|
+
const modeAnswer = (await commandRl.question('请选择命令类型(1=只登录,2=石景山联调,默认1):')).trim()
|
|
63
|
+
const mode = modeAnswer === '2' ? 'dev' : 'login'
|
|
64
|
+
const portalAnswer =
|
|
65
|
+
mode === 'dev' ? 'sjs' : (await commandRl.question('请选择门户(1=石景山,2=成华,3=自定义,默认石景山):')).trim()
|
|
66
|
+
const portal =
|
|
67
|
+
portalAnswer === '3' || portalAnswer === 'custom'
|
|
68
|
+
? 'custom'
|
|
69
|
+
: portalAnswer === '2' || portalAnswer === 'chenghua'
|
|
70
|
+
? 'chenghua'
|
|
71
|
+
: 'sjs'
|
|
72
|
+
const account = (await commandRl.question('请输入账号名称(直接回车使用该门户第一个账号):')).trim()
|
|
73
|
+
const command = { alias, mode, portal, ...(account ? { account } : {}) }
|
|
74
|
+
|
|
75
|
+
if (mode === 'dev') {
|
|
76
|
+
const project = (await commandRl.question('请输入前端项目绝对路径:')).trim()
|
|
77
|
+
if (!project) {
|
|
78
|
+
commandRl.close()
|
|
79
|
+
throw new Error('联调命令必须配置前端项目路径')
|
|
80
|
+
}
|
|
81
|
+
command.project = project
|
|
82
|
+
command.localOrigin =
|
|
83
|
+
(await commandRl.question('请输入本地服务地址(默认 http://localhost:5173):')).trim() || 'http://localhost:5173'
|
|
84
|
+
command.localPath =
|
|
85
|
+
(await commandRl.question('请输入本地接收 Token 的路由(默认 /exhibition-hall):')).trim() || '/exhibition-hall'
|
|
86
|
+
command.roomName =
|
|
87
|
+
(await commandRl.question('请输入样板间智能体名称(默认 3D智能展厅智能体):')).trim() || '3D智能展厅智能体'
|
|
88
|
+
command.iframeHost =
|
|
89
|
+
(await commandRl.question('请输入门户 iframe 域名(默认 hia.sjsdoubao.com:31118):')).trim() ||
|
|
90
|
+
'hia.sjsdoubao.com:31118'
|
|
91
|
+
command.iframePath =
|
|
92
|
+
(await commandRl.question('请输入门户 iframe 路径(默认 /exhibition-hall):')).trim() || '/exhibition-hall'
|
|
93
|
+
}
|
|
94
|
+
commandRl.close()
|
|
95
|
+
const { configPath, commandCount } = writePortalCommand(command)
|
|
96
|
+
console.log(`快捷命令配置完成:portal-dev ${alias}`)
|
|
97
|
+
console.log(`当前共 ${commandCount} 个快捷命令,配置文件:${configPath}`)
|
|
98
|
+
process.exit(0)
|
|
99
|
+
}
|
|
100
|
+
|
|
55
101
|
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
56
102
|
const portalArg = readArg('--portal')
|
|
57
|
-
const portalAnswer = portalArg || (await rl.question('请选择门户(1=石景山,2
|
|
58
|
-
const portal =
|
|
59
|
-
|
|
60
|
-
|
|
103
|
+
const portalAnswer = portalArg || (await rl.question('请选择门户(1=石景山,2=成华,3=自定义,直接回车默认石景山):'))
|
|
104
|
+
const portal =
|
|
105
|
+
portalAnswer === '3' || portalAnswer === 'custom'
|
|
106
|
+
? 'custom'
|
|
107
|
+
: portalAnswer === '2' || portalAnswer === 'chenghua'
|
|
108
|
+
? 'chenghua'
|
|
109
|
+
: 'sjs'
|
|
110
|
+
const portalName = portal === 'custom' ? '自定义网站' : portal === 'chenghua' ? '成华' : '石景山'
|
|
111
|
+
const name = (await rl.question('请输入账号名称(例如:我的账号、测试账号):')).trim()
|
|
112
|
+
if (!name) {
|
|
113
|
+
rl.close()
|
|
114
|
+
throw new Error('账号名称不能为空')
|
|
115
|
+
}
|
|
116
|
+
let loginUrl
|
|
117
|
+
if (portal === 'custom') {
|
|
118
|
+
loginUrl = (await rl.question('请输入登录页 URL(例如:https://example.com/login):')).trim()
|
|
119
|
+
try {
|
|
120
|
+
if (!['http:', 'https:'].includes(new URL(loginUrl).protocol)) throw new Error()
|
|
121
|
+
} catch {
|
|
122
|
+
rl.close()
|
|
123
|
+
throw new Error('登录页 URL 必须是有效的 http 或 https 地址')
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const username = (await rl.question(`请输入${portalName}账号:`)).trim()
|
|
61
127
|
rl.close()
|
|
62
128
|
if (!username) throw new Error('账号不能为空')
|
|
63
|
-
const password = await askPassword(`请输入${portalName}
|
|
129
|
+
const password = await askPassword(`请输入${portalName}密码(输入内容会隐藏):`)
|
|
64
130
|
if (!password) throw new Error('密码不能为空')
|
|
65
131
|
|
|
66
|
-
const configPath =
|
|
132
|
+
const { configPath, accountCount } = writePortalAccount(portal, {
|
|
133
|
+
name,
|
|
134
|
+
username,
|
|
135
|
+
password,
|
|
136
|
+
...(loginUrl ? { loginUrl } : {}),
|
|
137
|
+
})
|
|
67
138
|
|
|
68
|
-
console.log(`配置完成:${
|
|
69
|
-
console.log(
|
|
139
|
+
console.log(`配置完成:${portalName}现有 ${accountCount} 个账号`)
|
|
140
|
+
console.log(`配置文件:${configPath}`)
|
|
141
|
+
console.log(`现在可以在任意目录运行:portal-dev --portal ${portal}`)
|
package/src/portal-dev.mjs
CHANGED
|
@@ -7,17 +7,37 @@ import { recognizeCaptcha } from './recognize-captcha.mjs'
|
|
|
7
7
|
|
|
8
8
|
const sleep = (milliseconds) => new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds))
|
|
9
9
|
const args = process.argv.slice(2)
|
|
10
|
+
const portalConfig = readPortalConfig()
|
|
11
|
+
const commandAlias = args[0] && args[0] !== 'dev' && !args[0].startsWith('-') ? args[0] : undefined
|
|
12
|
+
const savedCommand = commandAlias ? portalConfig.commands.find((command) => command?.alias === commandAlias) : undefined
|
|
13
|
+
if (commandAlias && !savedCommand)
|
|
14
|
+
throw new Error(`未找到快捷命令“${commandAlias}”,请运行 portal-dev config command 创建`)
|
|
10
15
|
const readArg = (name) => {
|
|
11
16
|
const index = args.indexOf(name)
|
|
12
17
|
return index >= 0 ? args[index + 1] : undefined
|
|
13
18
|
}
|
|
19
|
+
const readPositionalAccount = () => {
|
|
20
|
+
const valueOptions = new Set(['--portal', '--project'])
|
|
21
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
22
|
+
const argument = args[index]
|
|
23
|
+
if (valueOptions.has(argument)) {
|
|
24
|
+
index += 1
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
if (argument === 'dev' || argument === commandAlias || argument.startsWith('-')) continue
|
|
28
|
+
return argument
|
|
29
|
+
}
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
14
32
|
|
|
15
|
-
const portal = readArg('--portal') || 'sjs'
|
|
16
|
-
if (!['sjs', 'chenghua'].includes(portal))
|
|
17
|
-
|
|
33
|
+
const portal = savedCommand?.portal || readArg('--portal') || 'sjs'
|
|
34
|
+
if (!['sjs', 'chenghua', 'custom'].includes(portal))
|
|
35
|
+
throw new Error(`不支持的门户:${portal},可选值为 sjs、chenghua、custom`)
|
|
36
|
+
const devMode = savedCommand?.mode === 'dev' || args[0] === 'dev'
|
|
37
|
+
if (devMode && portal !== 'sjs') throw new Error('门户联调目前只支持石景山;成华和自定义命令只负责登录')
|
|
18
38
|
|
|
19
39
|
const findProject = () => {
|
|
20
|
-
const explicit = readArg('--project')
|
|
40
|
+
const explicit = savedCommand?.project || readArg('--project')
|
|
21
41
|
if (explicit) return resolve(explicit)
|
|
22
42
|
let current = process.cwd()
|
|
23
43
|
while (true) {
|
|
@@ -29,34 +49,59 @@ const findProject = () => {
|
|
|
29
49
|
throw new Error('未找到前端项目,请先进入项目目录,或使用 --project <路径> 指定')
|
|
30
50
|
}
|
|
31
51
|
|
|
32
|
-
const projectDir = findProject()
|
|
33
|
-
if (!existsSync(resolve(projectDir, 'package.json')))
|
|
52
|
+
const projectDir = devMode ? findProject() : undefined
|
|
53
|
+
if (projectDir && !existsSync(resolve(projectDir, 'package.json')))
|
|
34
54
|
throw new Error(`目标项目不存在或缺少 package.json:${projectDir}`)
|
|
35
|
-
const
|
|
55
|
+
const configuredAccounts = portalConfig.profiles[portal]
|
|
56
|
+
const portalAccounts = Array.isArray(configuredAccounts)
|
|
57
|
+
? configuredAccounts.filter(
|
|
58
|
+
(account) => account?.name && typeof account.username === 'string' && typeof account.password === 'string',
|
|
59
|
+
)
|
|
60
|
+
: []
|
|
61
|
+
if (!portalAccounts.length) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`尚未配置${portal === 'custom' ? '自定义网站' : portal === 'chenghua' ? '成华' : '石景山'}账号,请先运行 portal-dev config --portal ${portal}`,
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const selectAccount = () => {
|
|
68
|
+
const accountName = savedCommand?.account || readPositionalAccount()
|
|
69
|
+
if (accountName) {
|
|
70
|
+
const matched = portalAccounts.find((account) => account.name === accountName)
|
|
71
|
+
if (!matched) throw new Error(`未找到账号“${accountName}”,请检查账号名称或重新配置`)
|
|
72
|
+
return matched
|
|
73
|
+
}
|
|
74
|
+
return portalAccounts[0]
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const portalAccount = selectAccount()
|
|
36
78
|
|
|
37
79
|
const configs = {
|
|
38
80
|
sjs: {
|
|
39
81
|
loginUrl: 'http://115.190.54.111:1880/passport/login/userLogin',
|
|
40
|
-
username:
|
|
41
|
-
password:
|
|
82
|
+
username: portalAccount.username,
|
|
83
|
+
password: portalAccount.password,
|
|
42
84
|
},
|
|
43
85
|
chenghua: {
|
|
44
86
|
loginUrl: 'https://www.chenghua-ai.com/passport/login/userLogin',
|
|
45
|
-
username:
|
|
46
|
-
password:
|
|
87
|
+
username: portalAccount.username,
|
|
88
|
+
password: portalAccount.password,
|
|
89
|
+
},
|
|
90
|
+
custom: {
|
|
91
|
+
loginUrl: portalAccount.loginUrl,
|
|
92
|
+
username: portalAccount.username,
|
|
93
|
+
password: portalAccount.password,
|
|
47
94
|
},
|
|
48
95
|
}
|
|
49
96
|
const config = configs[portal]
|
|
50
|
-
if (!config.username || !config.password) {
|
|
51
|
-
|
|
52
|
-
`尚未配置${portal === 'chenghua' ? '成华' : '石景山'}门户账号,请先运行 portal-dev config --portal ${portal}`,
|
|
53
|
-
)
|
|
54
|
-
}
|
|
97
|
+
if (!config.username || !config.password) throw new Error(`账号“${portalAccount.name}”的配置不完整,请重新配置`)
|
|
98
|
+
if (portal === 'custom' && !config.loginUrl) throw new Error(`账号“${portalAccount.name}”缺少登录页 URL,请重新配置`)
|
|
55
99
|
|
|
56
|
-
const localOrigin = 'http://localhost:5173'
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
100
|
+
const localOrigin = (savedCommand?.localOrigin || 'http://localhost:5173').replace(/\/$/, '')
|
|
101
|
+
const localPath = savedCommand?.localPath
|
|
102
|
+
const iframeHost = savedCommand?.iframeHost || 'hia.sjsdoubao.com:31118'
|
|
103
|
+
const iframePath = savedCommand?.iframePath || '/exhibition-hall'
|
|
104
|
+
const roomName = savedCommand?.roomName || '3D智能展厅智能体'
|
|
60
105
|
const chromeCandidates = {
|
|
61
106
|
darwin: [
|
|
62
107
|
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
@@ -82,7 +127,7 @@ const isReady = async () => {
|
|
|
82
127
|
}
|
|
83
128
|
|
|
84
129
|
let devServer
|
|
85
|
-
if (
|
|
130
|
+
if (devMode && !(await isReady())) {
|
|
86
131
|
const packageJson = JSON.parse(readFileSync(resolve(projectDir, 'package.json'), 'utf8'))
|
|
87
132
|
if (!packageJson.scripts?.dev) throw new Error(`目标项目没有 dev script:${projectDir}`)
|
|
88
133
|
const runner = existsSync(resolve(projectDir, 'bun.lock'))
|
|
@@ -184,10 +229,58 @@ const login = async () => {
|
|
|
184
229
|
throw new Error('自动登录失败,已达到最多重试次数')
|
|
185
230
|
}
|
|
186
231
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
232
|
+
const loginWithoutCaptcha = async () => {
|
|
233
|
+
const usernameInput = await visibleLocator([
|
|
234
|
+
'input[autocomplete="username"]',
|
|
235
|
+
'input[name="username"]',
|
|
236
|
+
'input[name="account"]',
|
|
237
|
+
'input[name="login"]',
|
|
238
|
+
'input[name="user"]',
|
|
239
|
+
'input[name="email"]',
|
|
240
|
+
'input[type="email"]',
|
|
241
|
+
'input[type="tel"]',
|
|
242
|
+
'input[placeholder*="用户名"]',
|
|
243
|
+
'input[placeholder*="账号"]',
|
|
244
|
+
'input[placeholder*="邮箱"]',
|
|
245
|
+
'input[placeholder*="手机"]',
|
|
246
|
+
])
|
|
247
|
+
const passwordInput = await visibleLocator([
|
|
248
|
+
'input[autocomplete="current-password"]',
|
|
249
|
+
'input[name="password"]',
|
|
250
|
+
'input[type="password"]',
|
|
251
|
+
'input[placeholder*="密码"]',
|
|
252
|
+
])
|
|
253
|
+
if (!usernameInput || !passwordInput) throw new Error('未找到常见的用户名或密码输入框')
|
|
254
|
+
await usernameInput.fill(config.username)
|
|
255
|
+
await passwordInput.fill(config.password)
|
|
256
|
+
const button = await visibleLocator([
|
|
257
|
+
'button[type="submit"]',
|
|
258
|
+
'input[type="submit"]',
|
|
259
|
+
'button:has-text("登录")',
|
|
260
|
+
'[role="button"]:has-text("登录")',
|
|
261
|
+
'button:has-text("Login")',
|
|
262
|
+
'[role="button"]:has-text("Login")',
|
|
263
|
+
'button:has-text("Sign in")',
|
|
264
|
+
'[role="button"]:has-text("Sign in")',
|
|
265
|
+
])
|
|
266
|
+
if (!button) throw new Error('未找到常见的登录按钮')
|
|
267
|
+
const originalUrl = page.url()
|
|
268
|
+
await button.click()
|
|
269
|
+
await Promise.race([
|
|
270
|
+
page.waitForURL((url) => url.href !== originalUrl, { timeout: 15000 }),
|
|
271
|
+
passwordInput.waitFor({ state: 'hidden', timeout: 15000 }),
|
|
272
|
+
]).catch(() => undefined)
|
|
273
|
+
console.log(`自定义网站“${portalAccount.name}”已填写账号密码并点击登录。`)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (portal === 'custom') await loginWithoutCaptcha()
|
|
277
|
+
else if (page.url().includes('/passport/login/') || portal === 'chenghua') await login()
|
|
278
|
+
if (!devMode) {
|
|
279
|
+
if (portal === 'chenghua')
|
|
280
|
+
await page.goto('https://www.chenghua-ai.com/chat/pages/application', { waitUntil: 'domcontentloaded' })
|
|
281
|
+
console.log(
|
|
282
|
+
`${portal === 'custom' ? `自定义网站“${portalAccount.name}”` : `${portal === 'chenghua' ? '成华' : '石景山'}门户`}登录流程已完成,按 Ctrl+C 结束。`,
|
|
283
|
+
)
|
|
191
284
|
await new Promise(() => undefined)
|
|
192
285
|
}
|
|
193
286
|
|
|
@@ -226,7 +319,7 @@ for (let index = 0; index < 180; index += 1) {
|
|
|
226
319
|
}
|
|
227
320
|
|
|
228
321
|
if (!targetUrl) throw new Error(`未找到目标智能体入口:${roomName}`)
|
|
229
|
-
const destination = new URL(`${targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`, localOrigin)
|
|
322
|
+
const destination = new URL(`${localPath || targetUrl.pathname}${targetUrl.search}${targetUrl.hash}`, localOrigin)
|
|
230
323
|
await context.newPage().then((localPage) => localPage.goto(destination.href))
|
|
231
324
|
console.log(`门户本地调试已就绪:${localOrigin}${targetUrl.pathname}`)
|
|
232
325
|
console.log('Token 未打印、未写入文件。按 Ctrl+C 结束。')
|