@nocobase/cli 2.2.0-test.16 → 3.0.0-alpha.1
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/dist/commands/app/start.js +1 -21
- package/dist/commands/config/set.js +1 -1
- package/dist/commands/init.js +12 -121
- package/dist/commands/install.js +39 -55
- package/dist/commands/portal/config.js +88 -0
- package/dist/commands/portal/create.js +7 -6
- package/dist/commands/portal/deploy.js +2 -2
- package/dist/commands/portal/destroy.js +6 -6
- package/dist/commands/portal/dev.js +3 -3
- package/dist/commands/portal/index.js +1 -1
- package/dist/commands/portal/info.js +3 -3
- package/dist/commands/portal/list.js +5 -5
- package/dist/commands/portal/pull.js +11 -4
- package/dist/commands/portal/push.js +4 -4
- package/dist/lib/api-client.js +7 -0
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/env-config.js +1 -1
- package/dist/lib/env-proxy.js +68 -6
- package/dist/lib/managed-env-file.js +58 -2
- package/dist/lib/managed-init-env.js +1 -1
- package/dist/lib/naming.js +9 -0
- package/dist/lib/portal-config.js +133 -0
- package/dist/lib/portal-configure.js +117 -0
- package/dist/lib/portal-create.js +24 -79
- package/dist/lib/portal-deploy.js +23 -15
- package/dist/lib/portal-destroy.js +3 -3
- package/dist/lib/portal-dev.js +3 -3
- package/dist/lib/portal-info.js +2 -2
- package/dist/lib/portal-list.js +32 -18
- package/dist/lib/portal-source.js +150 -43
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/locale/en-US.json +67 -43
- package/dist/locale/zh-CN.json +59 -35
- package/nocobase-ctl.config.json +111 -0
- package/package.json +4 -3
- package/dist/lib/portal-template.js +0 -190
package/dist/locale/zh-CN.json
CHANGED
|
@@ -182,26 +182,50 @@
|
|
|
182
182
|
"npmPackMultipleTarballs": "npm pack 为 {{source}} 生成了多个 tarball。",
|
|
183
183
|
"invalidPortalName": "Portal 名称 \"{{value}}\" 无效。请使用小写字母、数字、下划线或连字符,并以小写字母或数字开头。",
|
|
184
184
|
"invalidPortalAppName": "从 apiBaseUrl 解析出的 Portal app 名称 \"{{value}}\" 无效。请使用字母、数字、下划线或连字符,并以字母或数字开头。",
|
|
185
|
-
"missingApiBaseUrl": "无法创建 Portal
|
|
186
|
-
"outsideParent": "拒绝修改 {{parentDir}} 之外的 Portal
|
|
187
|
-
"sshUnsupported": "第一版暂不支持为 ssh env 创建 Portal
|
|
188
|
-
"workspaceExists": "Portal
|
|
185
|
+
"missingApiBaseUrl": "无法创建 Portal,因为当前 env 没有 apiBaseUrl。",
|
|
186
|
+
"outsideParent": "拒绝修改 {{parentDir}} 之外的 Portal:{{portalDir}}",
|
|
187
|
+
"sshUnsupported": "第一版暂不支持为 ssh env 创建 Portal。",
|
|
188
|
+
"workspaceExists": "Portal 已存在:{{portalDir}}\n如需删除并重新创建,请追加 --force。"
|
|
189
189
|
},
|
|
190
190
|
"messages": {
|
|
191
191
|
"skipInstall": "未找到 {{portalDir}}/package.json,已跳过 pnpm install。",
|
|
192
192
|
"created": "Portal \"{{portal}}\" 已创建:{{portalDir}}。",
|
|
193
193
|
"app": "App:{{app}}",
|
|
194
|
-
"base": "Base:{{base}}"
|
|
194
|
+
"base": "Base:{{base}}",
|
|
195
|
+
"sourceStorage": "源码存储:{{sourceStorage}}"
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
"portalConfig": {
|
|
199
|
+
"errors": {
|
|
200
|
+
"invalidSourceStorage": "源码存储 \"{{value}}\" 无效。请使用 \"nocobase\" 或 \"git\"。",
|
|
201
|
+
"invalidGitPath": "--git-path 必须是 Git 仓库内的相对路径。",
|
|
202
|
+
"gitOptionsForNocobaseStorage": "--git-repo、--git-branch 和 --git-path 只能与 --source-storage git 一起使用。",
|
|
203
|
+
"gitRepoRequired": "--source-storage 为 git 时必须提供 --git-repo。",
|
|
204
|
+
"gitRepoInvalid": "--git-repo 必须是完整的 Git remote URL。",
|
|
205
|
+
"updateFailed": "Portal 配置更新失败,状态码 {{status}}\n{{details}}"
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"portalConfigure": {
|
|
209
|
+
"errors": {
|
|
210
|
+
"envNotConfigured": "env \"{{envName}}\" 尚未配置。请先运行 `nb env add {{envName}} --api-base-url <url>`。",
|
|
211
|
+
"noEnvConfigured": "还没有配置 NocoBase env。请先运行 `nb init --ui` 创建一个。",
|
|
212
|
+
"noChanges": "没有提供 Portal 配置变更。请传入 --source-storage 或 --git-* 参数。",
|
|
213
|
+
"workspaceMissing": "Portal 不存在:{{portalDir}}\n请先运行 `nb portal create {{portal}}` 或 `nb portal pull {{portal}}`。"
|
|
214
|
+
},
|
|
215
|
+
"messages": {
|
|
216
|
+
"updated": "Portal \"{{portal}}\" 配置已更新:{{portalDir}}/portal.config.json。",
|
|
217
|
+
"remoteSynced": "远端 Portal 记录:已同步",
|
|
218
|
+
"remoteSkipped": "远端 Portal 记录:未找到,仅更新本地配置"
|
|
195
219
|
}
|
|
196
220
|
},
|
|
197
221
|
"portalDeploy": {
|
|
198
222
|
"errors": {
|
|
199
223
|
"envNotConfigured": "env \"{{envName}}\" 尚未配置。请先运行 `nb env add {{envName}} --api-base-url <url>`。",
|
|
200
224
|
"noEnvConfigured": "还没有配置 NocoBase env。请先运行 `nb init --ui` 创建一个。",
|
|
201
|
-
"workspaceMissing": "Portal
|
|
202
|
-
"packageJsonMissing": "Portal
|
|
225
|
+
"workspaceMissing": "Portal 不存在:{{portalDir}}\n请先运行 `nb portal create {{portal}}`。",
|
|
226
|
+
"packageJsonMissing": "Portal 无效:{{portalDir}} 中缺少 package.json。",
|
|
203
227
|
"distMissing": "Portal build 没有生成 {{distDir}}/index.html。",
|
|
204
|
-
"unsupportedEnvKind": "第一版暂不支持为 {{kind}} env 部署 Portal
|
|
228
|
+
"unsupportedEnvKind": "第一版暂不支持为 {{kind}} env 部署 Portal。",
|
|
205
229
|
"uploadFailed": "Portal dist 上传失败,状态码 {{status}}\n{{details}}",
|
|
206
230
|
"recordSyncFailed": "Portal 记录同步失败,状态码 {{status}}\n{{details}}"
|
|
207
231
|
},
|
|
@@ -220,7 +244,7 @@
|
|
|
220
244
|
"errors": {
|
|
221
245
|
"envNotConfigured": "env \"{{envName}}\" 尚未配置。请先运行 `nb env add {{envName}} --api-base-url <url>`。",
|
|
222
246
|
"noEnvConfigured": "还没有配置 NocoBase env。请先运行 `nb init --ui` 创建一个。",
|
|
223
|
-
"unsupportedEnvKind": "第一版暂不支持为 {{kind}} env 列出 Portal
|
|
247
|
+
"unsupportedEnvKind": "第一版暂不支持为 {{kind}} env 列出 Portal。",
|
|
224
248
|
"listFailed": "Portal 列表获取失败,状态码 {{status}}\n{{details}}"
|
|
225
249
|
},
|
|
226
250
|
"messages": {
|
|
@@ -229,7 +253,7 @@
|
|
|
229
253
|
"table": {
|
|
230
254
|
"name": "名称",
|
|
231
255
|
"url": "访问 URL",
|
|
232
|
-
"
|
|
256
|
+
"portalType": "Portal 类型",
|
|
233
257
|
"path": "本地路径",
|
|
234
258
|
"enabled": "启用",
|
|
235
259
|
"localSynced": "本地已同步"
|
|
@@ -244,7 +268,7 @@
|
|
|
244
268
|
"fields": {
|
|
245
269
|
"name": "名称",
|
|
246
270
|
"url": "访问 URL",
|
|
247
|
-
"
|
|
271
|
+
"portalType": "Portal 类型",
|
|
248
272
|
"path": "本地路径",
|
|
249
273
|
"enabled": "启用",
|
|
250
274
|
"localSynced": "本地已同步"
|
|
@@ -255,9 +279,9 @@
|
|
|
255
279
|
"envNotConfigured": "env \"{{envName}}\" 尚未配置。请先运行 `nb env add {{envName}} --api-base-url <url>`。",
|
|
256
280
|
"noEnvConfigured": "还没有配置 NocoBase env。请先运行 `nb init --ui` 创建一个。",
|
|
257
281
|
"confirmationRequired": "非交互模式下拒绝删除 Portal。请追加 --yes 后重试。",
|
|
258
|
-
"outsideParent": "拒绝删除 {{parentDir}} 之外的 Portal
|
|
259
|
-
"workspaceMissing": "Portal
|
|
260
|
-
"unsupportedEnvKind": "第一版暂不支持为 {{kind}} env 删除 Portal
|
|
282
|
+
"outsideParent": "拒绝删除 {{parentDir}} 之外的 Portal:{{portalDir}}",
|
|
283
|
+
"workspaceMissing": "Portal 不存在:{{portalDir}}\n如需忽略本地文件缺失,请追加 --force。",
|
|
284
|
+
"unsupportedEnvKind": "第一版暂不支持为 {{kind}} env 删除 Portal。",
|
|
261
285
|
"recordDestroyFailed": "Portal 记录删除失败,状态码 {{status}}\n{{details}}"
|
|
262
286
|
},
|
|
263
287
|
"prompts": {
|
|
@@ -269,16 +293,16 @@
|
|
|
269
293
|
"app": "App:{{app}}",
|
|
270
294
|
"base": "Base:{{base}}",
|
|
271
295
|
"record": "记录:{{status}}",
|
|
272
|
-
"workspace": "
|
|
296
|
+
"workspace": "Portal:{{status}}({{dir}})"
|
|
273
297
|
}
|
|
274
298
|
},
|
|
275
299
|
"portalDev": {
|
|
276
300
|
"errors": {
|
|
277
301
|
"envNotConfigured": "env \"{{envName}}\" 尚未配置。请先运行 `nb env add {{envName}} --api-base-url <url>`。",
|
|
278
302
|
"noEnvConfigured": "还没有配置 NocoBase env。请先运行 `nb init --ui` 创建一个。",
|
|
279
|
-
"workspaceMissing": "Portal
|
|
280
|
-
"packageJsonMissing": "Portal
|
|
281
|
-
"sshUnsupported": "第一版暂不支持为 ssh env 启动 Portal
|
|
303
|
+
"workspaceMissing": "Portal 不存在:{{portalDir}}\n请先运行 `nb portal create {{portal}}`。",
|
|
304
|
+
"packageJsonMissing": "Portal 无效:{{portalDir}} 中缺少 package.json。",
|
|
305
|
+
"sshUnsupported": "第一版暂不支持为 ssh env 启动 Portal 开发服务。"
|
|
282
306
|
},
|
|
283
307
|
"messages": {
|
|
284
308
|
"starting": "正在启动 Portal \"{{portal}}\"...",
|
|
@@ -461,15 +485,15 @@
|
|
|
461
485
|
"message": "应用子路径(例如 /nocobase/)",
|
|
462
486
|
"placeholder": "/ 或 /nocobase/"
|
|
463
487
|
},
|
|
464
|
-
"
|
|
465
|
-
"message": "
|
|
466
|
-
"noCodeLabel": "
|
|
467
|
-
"noCodeHint": "
|
|
468
|
-
"
|
|
469
|
-
"
|
|
488
|
+
"portalType": {
|
|
489
|
+
"message": "Portal 类型",
|
|
490
|
+
"noCodeLabel": "无代码 Portal",
|
|
491
|
+
"noCodeHint": "通过可视化配置创建,AI 可以协助调整配置。访问路径:/v/<name>",
|
|
492
|
+
"aiLabel": "AI Portal",
|
|
493
|
+
"aiHint": "通过 AI Agent 和代码创建,可用自然语言提出修改要求。访问路径:/x/<name>"
|
|
470
494
|
},
|
|
471
495
|
"portalName": {
|
|
472
|
-
"message": "
|
|
496
|
+
"message": "Portal 名称",
|
|
473
497
|
"placeholder": "admin"
|
|
474
498
|
},
|
|
475
499
|
"portalTemplate": {
|
|
@@ -576,15 +600,15 @@
|
|
|
576
600
|
"skipDownload": {
|
|
577
601
|
"message": "跳过下载 NocoBase,直接复用已有的本地应用文件或 Docker 镜像"
|
|
578
602
|
},
|
|
579
|
-
"
|
|
580
|
-
"message": "
|
|
581
|
-
"noCodeLabel": "
|
|
582
|
-
"noCodeHint": "
|
|
583
|
-
"
|
|
584
|
-
"
|
|
603
|
+
"portalType": {
|
|
604
|
+
"message": "Portal 类型",
|
|
605
|
+
"noCodeLabel": "无代码 Portal",
|
|
606
|
+
"noCodeHint": "通过可视化配置创建,AI 可以协助调整配置。访问路径:/v/<name>",
|
|
607
|
+
"aiLabel": "AI Portal",
|
|
608
|
+
"aiHint": "通过 AI Agent 和代码创建,可用自然语言提出修改要求。访问路径:/x/<name>"
|
|
585
609
|
},
|
|
586
610
|
"portalName": {
|
|
587
|
-
"message": "
|
|
611
|
+
"message": "Portal 名称",
|
|
588
612
|
"placeholder": "admin"
|
|
589
613
|
},
|
|
590
614
|
"portalTemplate": {
|
|
@@ -612,9 +636,9 @@
|
|
|
612
636
|
"title": "应用来源和版本",
|
|
613
637
|
"description": "选择应用的获取方式,以及要使用的来源和版本。"
|
|
614
638
|
},
|
|
615
|
-
"
|
|
616
|
-
"title": "
|
|
617
|
-
"description": "
|
|
639
|
+
"portalType": {
|
|
640
|
+
"title": "配置 Portal",
|
|
641
|
+
"description": "设置默认 Portal 的标识和类型。"
|
|
618
642
|
},
|
|
619
643
|
"configureDatabase": {
|
|
620
644
|
"title": "配置数据库",
|
package/nocobase-ctl.config.json
CHANGED
|
@@ -202,6 +202,117 @@
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
},
|
|
205
|
+
"ai": {
|
|
206
|
+
"name": "ai",
|
|
207
|
+
"description": "Discover LLM providers and manage LLM services and AI employees.",
|
|
208
|
+
"include": true,
|
|
209
|
+
"resources": {
|
|
210
|
+
"includes": ["ai", "llmServices", "aiEmployees"],
|
|
211
|
+
"excludes": [],
|
|
212
|
+
"overrides": {
|
|
213
|
+
"ai": {
|
|
214
|
+
"name": "llm-providers",
|
|
215
|
+
"description": "Discover providers and models and test unsaved LLM settings.",
|
|
216
|
+
"topLevel": false,
|
|
217
|
+
"operations": {
|
|
218
|
+
"includes": [
|
|
219
|
+
"ai:listLLMProviders",
|
|
220
|
+
"ai:listProviderModels",
|
|
221
|
+
"ai:testFlight",
|
|
222
|
+
"ai:listModels",
|
|
223
|
+
"ai:listLLMServices"
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"llmServices": {
|
|
228
|
+
"name": "llm-services",
|
|
229
|
+
"description": "Manage saved LLM service configurations.",
|
|
230
|
+
"topLevel": false,
|
|
231
|
+
"operations": {
|
|
232
|
+
"includes": [
|
|
233
|
+
"llmServices:list",
|
|
234
|
+
"llmServices:get",
|
|
235
|
+
"llmServices:create",
|
|
236
|
+
"llmServices:update",
|
|
237
|
+
"llmServices:destroy"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"aiEmployees": {
|
|
242
|
+
"name": "employees",
|
|
243
|
+
"description": "Manage AI employees.",
|
|
244
|
+
"topLevel": false,
|
|
245
|
+
"operations": {
|
|
246
|
+
"includes": [
|
|
247
|
+
"aiEmployees:list",
|
|
248
|
+
"aiEmployees:get",
|
|
249
|
+
"aiEmployees:create",
|
|
250
|
+
"aiEmployees:update",
|
|
251
|
+
"aiEmployees:destroy"
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"kb": {
|
|
259
|
+
"name": "kb",
|
|
260
|
+
"description": "Manage vector databases, knowledge bases, documents, vectorization, and retrieval tests.",
|
|
261
|
+
"include": true,
|
|
262
|
+
"resources": {
|
|
263
|
+
"includes": ["aiVectorDatabases", "aiKnowledgeBase", "aiKnowledgeBaseDocs"],
|
|
264
|
+
"excludes": [],
|
|
265
|
+
"overrides": {
|
|
266
|
+
"aiVectorDatabases": {
|
|
267
|
+
"name": "vector-databases",
|
|
268
|
+
"description": "Manage vector database connections.",
|
|
269
|
+
"topLevel": false,
|
|
270
|
+
"operations": {
|
|
271
|
+
"includes": [
|
|
272
|
+
"aiVectorDatabases:listProviders",
|
|
273
|
+
"aiVectorDatabases:testConnection",
|
|
274
|
+
"aiVectorDatabases:list",
|
|
275
|
+
"aiVectorDatabases:get",
|
|
276
|
+
"aiVectorDatabases:create",
|
|
277
|
+
"aiVectorDatabases:update",
|
|
278
|
+
"aiVectorDatabases:destroy"
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"aiKnowledgeBase": {
|
|
283
|
+
"name": "kb",
|
|
284
|
+
"segments": ["kb"],
|
|
285
|
+
"description": "Manage knowledge bases and retrieval tests.",
|
|
286
|
+
"topLevel": true,
|
|
287
|
+
"operations": {
|
|
288
|
+
"includes": [
|
|
289
|
+
"aiKnowledgeBase:list",
|
|
290
|
+
"aiKnowledgeBase:get",
|
|
291
|
+
"aiKnowledgeBase:create",
|
|
292
|
+
"aiKnowledgeBase:update",
|
|
293
|
+
"aiKnowledgeBase:destroy",
|
|
294
|
+
"aiKnowledgeBase:runHitTest",
|
|
295
|
+
"aiKnowledgeBase:listExternalVectorStoreProviders"
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
"aiKnowledgeBaseDocs": {
|
|
300
|
+
"name": "documents",
|
|
301
|
+
"description": "Manage knowledge base documents and vectorization.",
|
|
302
|
+
"topLevel": false,
|
|
303
|
+
"operations": {
|
|
304
|
+
"includes": [
|
|
305
|
+
"aiKnowledgeBaseDocs:list",
|
|
306
|
+
"aiKnowledgeBaseDocs:get",
|
|
307
|
+
"aiKnowledgeBaseDocs:upload",
|
|
308
|
+
"aiKnowledgeBaseDocs:vectorization",
|
|
309
|
+
"aiKnowledgeBaseDocs:destroy"
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
205
316
|
"flow-engine": {
|
|
206
317
|
"name": "flow-engine",
|
|
207
318
|
"description": "Manage flow surface composition, configuration, layout, and mutation APIs.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-alpha.1",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"description": "Create and restore NocoBase backups."
|
|
73
73
|
},
|
|
74
74
|
"portal": {
|
|
75
|
-
"description": "Manage
|
|
75
|
+
"description": "Manage portals."
|
|
76
76
|
},
|
|
77
77
|
"skills": {
|
|
78
78
|
"description": "Inspect or synchronize NocoBase AI coding skills for the current workspace."
|
|
@@ -146,5 +146,6 @@
|
|
|
146
146
|
"repository": {
|
|
147
147
|
"type": "git",
|
|
148
148
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
149
|
-
}
|
|
149
|
+
},
|
|
150
|
+
"gitHead": "22d8be8ece179cfa5c8a4ebb2c896c92bd418e03"
|
|
150
151
|
}
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This file is part of the NocoBase (R) project.
|
|
3
|
-
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
-
* Authors: NocoBase Team.
|
|
5
|
-
*
|
|
6
|
-
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
-
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
-
*/
|
|
9
|
-
import { cp, mkdir, mkdtemp, rm, stat } from 'node:fs/promises';
|
|
10
|
-
import os from 'node:os';
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
import { fileURLToPath } from 'node:url';
|
|
13
|
-
import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
14
|
-
import { resolvePortalTemplate } from './portal-create.js';
|
|
15
|
-
import { run } from './run-npm.js';
|
|
16
|
-
const DEFAULT_PORTAL_APP_NAME = 'main';
|
|
17
|
-
const DEFAULT_PORTAL_NAME = 'admin';
|
|
18
|
-
const PORTAL_CLIENT_PREFIX = 'x';
|
|
19
|
-
function trimValue(value) {
|
|
20
|
-
return String(value ?? '').trim();
|
|
21
|
-
}
|
|
22
|
-
function normalizePortalName(value) {
|
|
23
|
-
const segment = String(value || '')
|
|
24
|
-
.trim()
|
|
25
|
-
.replace(/^\/+|\/+$/g, '');
|
|
26
|
-
return segment || DEFAULT_PORTAL_NAME;
|
|
27
|
-
}
|
|
28
|
-
function normalizePortalAppName(value) {
|
|
29
|
-
const segment = String(value || '')
|
|
30
|
-
.trim()
|
|
31
|
-
.replace(/^\/+|\/+$/g, '');
|
|
32
|
-
return segment || DEFAULT_PORTAL_APP_NAME;
|
|
33
|
-
}
|
|
34
|
-
function validatePortalSegment(kind, value) {
|
|
35
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(value)) {
|
|
36
|
-
throw new Error(`Invalid ${kind} "${value}". Use letters, numbers, underscores, or hyphens, and start with a letter or number.`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
async function pathExists(filePath) {
|
|
40
|
-
try {
|
|
41
|
-
await stat(filePath);
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function resolveLocalTemplatePath(templateSource) {
|
|
49
|
-
if (templateSource.startsWith('file://')) {
|
|
50
|
-
return fileURLToPath(templateSource);
|
|
51
|
-
}
|
|
52
|
-
return templateSource;
|
|
53
|
-
}
|
|
54
|
-
function isGitTemplateSource(templateSource) {
|
|
55
|
-
return (templateSource.startsWith('git@') ||
|
|
56
|
-
templateSource.startsWith('git+') ||
|
|
57
|
-
/^https?:\/\//i.test(templateSource) ||
|
|
58
|
-
templateSource.endsWith('.git'));
|
|
59
|
-
}
|
|
60
|
-
async function getLocalTemplateDir(templateSource) {
|
|
61
|
-
let localPath;
|
|
62
|
-
try {
|
|
63
|
-
localPath = resolveLocalTemplatePath(templateSource);
|
|
64
|
-
}
|
|
65
|
-
catch {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
let result;
|
|
69
|
-
try {
|
|
70
|
-
result = await stat(localPath);
|
|
71
|
-
}
|
|
72
|
-
catch {
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
if (!result.isDirectory()) {
|
|
76
|
-
throw new Error(`Portal template "${templateSource}" is invalid: expected a directory.`);
|
|
77
|
-
}
|
|
78
|
-
return localPath;
|
|
79
|
-
}
|
|
80
|
-
async function resolveInitialPortalTemplate(params) {
|
|
81
|
-
const localTemplateDir = await getLocalTemplateDir(params.templateSource);
|
|
82
|
-
if (localTemplateDir) {
|
|
83
|
-
return {
|
|
84
|
-
dir: localTemplateDir,
|
|
85
|
-
source: params.templateSource,
|
|
86
|
-
type: 'local',
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (!isGitTemplateSource(params.templateSource)) {
|
|
90
|
-
return resolvePortalTemplate(params.templateSource, {
|
|
91
|
-
npmRegistry: params.npmRegistry,
|
|
92
|
-
runCommand: params.runCommand,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
await params.runCommand('git', ['clone', '--depth', '1', params.templateSource, params.tempDir], {
|
|
96
|
-
errorName: 'git clone',
|
|
97
|
-
stdio: params.verbose ? 'inherit' : 'ignore',
|
|
98
|
-
});
|
|
99
|
-
return {
|
|
100
|
-
dir: params.tempDir,
|
|
101
|
-
source: params.templateSource,
|
|
102
|
-
type: 'local',
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
async function copyTemplate(sourceDir, targetDir) {
|
|
106
|
-
await mkdir(path.dirname(targetDir), { recursive: true });
|
|
107
|
-
await cp(sourceDir, targetDir, {
|
|
108
|
-
recursive: true,
|
|
109
|
-
filter: (source) => !source.split(path.sep).includes('.git'),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
async function buildPortalHtml(params) {
|
|
113
|
-
const stdio = params.verbose ? 'inherit' : 'ignore';
|
|
114
|
-
await params.runCommand('yarn', ['build:html'], {
|
|
115
|
-
cwd: params.portalDir,
|
|
116
|
-
env: buildPortalCommandEnv({
|
|
117
|
-
NOCOBASE_API_URL: '/api',
|
|
118
|
-
NOCOBASE_PORTAL_BASE: `/${PORTAL_CLIENT_PREFIX}/${params.portalName}/`,
|
|
119
|
-
}),
|
|
120
|
-
envMode: 'replace',
|
|
121
|
-
errorName: 'yarn build:html',
|
|
122
|
-
stdio,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
export async function prepareInitialPortalTemplate(options) {
|
|
126
|
-
const developmentMode = trimValue(options.developmentMode);
|
|
127
|
-
if (developmentMode !== 'vibe-coding') {
|
|
128
|
-
return { prepared: false, skippedReason: 'no-code' };
|
|
129
|
-
}
|
|
130
|
-
const storagePath = trimValue(options.storagePath);
|
|
131
|
-
if (!storagePath) {
|
|
132
|
-
throw new Error('Cannot prepare an initial Portal template without a storage path.');
|
|
133
|
-
}
|
|
134
|
-
const templateUrl = trimValue(options.portalTemplate);
|
|
135
|
-
if (!templateUrl) {
|
|
136
|
-
throw new Error('Initial Portal template is required when development mode is "vibe-coding".');
|
|
137
|
-
}
|
|
138
|
-
const appName = normalizePortalAppName(options.appName);
|
|
139
|
-
const portalName = normalizePortalName(options.portalName);
|
|
140
|
-
validatePortalSegment('portal app name', appName);
|
|
141
|
-
validatePortalSegment('Portal name', portalName);
|
|
142
|
-
const portalDir = path.join(storagePath, 'portals', appName, portalName);
|
|
143
|
-
if (await pathExists(portalDir)) {
|
|
144
|
-
if (await pathExists(path.join(portalDir, 'dist', 'index.html'))) {
|
|
145
|
-
return { prepared: false, skippedReason: 'already-prepared' };
|
|
146
|
-
}
|
|
147
|
-
await rm(portalDir, { recursive: true, force: true });
|
|
148
|
-
}
|
|
149
|
-
options.onStartTask?.(`Preparing Portal "${portalName}" from template...`);
|
|
150
|
-
const runCommand = options.runCommand ?? run;
|
|
151
|
-
const tempDir = await mkdtemp(path.join(os.tmpdir(), 'nocobase-portal-template-'));
|
|
152
|
-
let cleanupPortalDir = false;
|
|
153
|
-
let template;
|
|
154
|
-
try {
|
|
155
|
-
template = await resolveInitialPortalTemplate({
|
|
156
|
-
templateSource: templateUrl,
|
|
157
|
-
tempDir,
|
|
158
|
-
npmRegistry: options.npmRegistry,
|
|
159
|
-
verbose: options.verbose,
|
|
160
|
-
runCommand,
|
|
161
|
-
});
|
|
162
|
-
const templateDir = template.dir;
|
|
163
|
-
if (!(await pathExists(path.join(templateDir, 'package.json')))) {
|
|
164
|
-
throw new Error(`Portal template "${templateUrl}" is invalid: package.json is missing.`);
|
|
165
|
-
}
|
|
166
|
-
cleanupPortalDir = true;
|
|
167
|
-
await copyTemplate(templateDir, portalDir);
|
|
168
|
-
await rm(path.join(portalDir, 'node_modules'), { recursive: true, force: true });
|
|
169
|
-
await buildPortalHtml({
|
|
170
|
-
portalDir,
|
|
171
|
-
portalName,
|
|
172
|
-
verbose: options.verbose,
|
|
173
|
-
runCommand,
|
|
174
|
-
});
|
|
175
|
-
cleanupPortalDir = false;
|
|
176
|
-
options.onSucceedTask?.(`Portal "${portalName}" is ready.`);
|
|
177
|
-
return { prepared: true };
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
if (cleanupPortalDir) {
|
|
181
|
-
await rm(portalDir, { recursive: true, force: true });
|
|
182
|
-
}
|
|
183
|
-
options.onFailTask?.(`Failed to prepare Portal "${portalName}".`);
|
|
184
|
-
throw error;
|
|
185
|
-
}
|
|
186
|
-
finally {
|
|
187
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
188
|
-
await template?.cleanup?.();
|
|
189
|
-
}
|
|
190
|
-
}
|