listpage_cli 0.0.312 → 0.0.313

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.
@@ -156,9 +156,12 @@ function printHelp() {
156
156
  ` 用法: ${helpColor("listpage_cli init", "dim")}`,
157
157
  " 说明: 进入中文引导式交互,按提示填写即可",
158
158
  "",
159
- ` ${helpColor("install-skill", "green")}`,
160
- ` 用法: ${helpColor("listpage_cli install-skill [skillName] [--project]", "dim")}`,
161
- " 说明: 安装技能到 Cursor;默认 skillName 为 test,安装到当前命令执行目录的 .cursor/skills/",
159
+ ` ${helpColor("skill", "green")}`,
160
+ ` 用法: ${helpColor("listpage_cli skill list", "dim")}`,
161
+ " 说明: 列出 CLI 包内可安装的技能名称(模板目录下的子文件夹)",
162
+ "",
163
+ ` 用法: ${helpColor("listpage_cli skill add [技能名称]", "dim")}`,
164
+ " 说明: 安装技能到 Cursor;省略技能名时默认为 test,安装到当前命令执行目录的 .cursor/skills/",
162
165
  "",
163
166
  ` ${helpColor("project", "green")}`,
164
167
  ` 用法: ${helpColor("listpage_cli project build", "dim")}`,
@@ -88,6 +88,14 @@ function createNodeFsAdapter() {
88
88
  throw toFsPortError("writeBuffer", targetPath, error);
89
89
  }
90
90
  },
91
+ removeDirRecursive: (targetPath) => {
92
+ try {
93
+ (0, fs_1.rmSync)(targetPath, { recursive: true, force: true });
94
+ }
95
+ catch (error) {
96
+ throw toFsPortError("removeDirRecursive", targetPath, error);
97
+ }
98
+ },
91
99
  };
92
100
  }
93
101
  function toFsPortError(operation, targetPath, error) {
@@ -7,7 +7,7 @@ exports.parseCommandOptions = parseCommandOptions;
7
7
  exports.parseCommandPositionals = parseCommandPositionals;
8
8
  const KNOWN_COMMANDS = new Set([
9
9
  "init",
10
- "install-skill",
10
+ "skill",
11
11
  "project",
12
12
  "lark",
13
13
  ]);
package/bin/cli.js CHANGED
@@ -5,7 +5,7 @@ const cli_interaction_1 = require("./adapters/cli-interaction");
5
5
  const execute_1 = require("./app/execute");
6
6
  const command_result_1 = require("./domain/command-result");
7
7
  const init_command_1 = require("./commands/init-command");
8
- const install_skill_command_1 = require("./commands/install-skill-command");
8
+ const skill_command_1 = require("./commands/skill-command");
9
9
  const project_command_1 = require("./commands/project-command");
10
10
  const lark_command_1 = require("./commands/lark-command");
11
11
  const node_fs_adapter_1 = require("./adapters/node-fs-adapter");
@@ -23,7 +23,7 @@ const initCommandHandler = (0, init_command_1.createInitCommandHandler)({
23
23
  fs: fsAdapter,
24
24
  files: filesystemCapability,
25
25
  });
26
- const installSkillCommandHandler = (0, install_skill_command_1.createInstallSkillCommandHandler)({
26
+ const skillCommandHandler = (0, skill_command_1.createSkillCommandHandler)({
27
27
  fs: fsAdapter,
28
28
  files: filesystemCapability,
29
29
  config: {
@@ -45,7 +45,7 @@ async function main() {
45
45
  printVersion: cli_interaction_1.printVersion,
46
46
  handlers: {
47
47
  init: initCommandHandler,
48
- "install-skill": installSkillCommandHandler,
48
+ skill: skillCommandHandler,
49
49
  project: projectCommandHandler,
50
50
  lark: larkCommandHandler,
51
51
  },
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSkillAddCommandHandler = createSkillAddCommandHandler;
4
+ const skill_add_service_1 = require("../../services/skill-add-service");
5
+ function createSkillAddCommandHandler(deps) {
6
+ return async (input) => {
7
+ return (0, skill_add_service_1.runSkillAddFlow)(input, deps);
8
+ };
9
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSkillListCommandHandler = createSkillListCommandHandler;
4
+ const skill_list_service_1 = require("../../services/skill-list-service");
5
+ function createSkillListCommandHandler(deps) {
6
+ return async (_input) => {
7
+ return (0, skill_list_service_1.runSkillListFlow)(deps);
8
+ };
9
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSkillCommandHandler = createSkillCommandHandler;
4
+ const command_result_1 = require("../domain/command-result");
5
+ const add_command_1 = require("./skill/add-command");
6
+ const list_command_1 = require("./skill/list-command");
7
+ function createSkillCommandHandler(deps) {
8
+ const add = (0, add_command_1.createSkillAddCommandHandler)(deps);
9
+ const list = (0, list_command_1.createSkillListCommandHandler)(deps);
10
+ return async (input) => {
11
+ const sub = input.positionals[0];
12
+ switch (sub) {
13
+ case "add":
14
+ return add(input);
15
+ case "list":
16
+ return list(input);
17
+ default:
18
+ return (0, command_result_1.commandError)("用法: listpage_cli skill add [技能名称]\n listpage_cli skill list", command_result_1.COMMAND_ERROR_CODES.unknownCommand, 1);
19
+ }
20
+ };
21
+ }
@@ -1,18 +1,21 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.runInstallSkillFlow = runInstallSkillFlow;
3
+ exports.runSkillAddFlow = runSkillAddFlow;
4
4
  const command_result_1 = require("../domain/command-result");
5
- function runInstallSkillFlow(input, deps) {
6
- const skillName = input.positionals[0] || deps.config.defaultSkillName;
5
+ function runSkillAddFlow(input, deps) {
6
+ const skillName = input.positionals[1] ?? deps.config.defaultSkillName;
7
7
  const sourceDir = deps.fs.join(deps.config.getSkillTemplateRoot(), skillName);
8
8
  if (!deps.fs.exists(sourceDir)) {
9
9
  return (0, command_result_1.commandError)(`技能 ${skillName} 不存在: ${sourceDir}`, command_result_1.COMMAND_ERROR_CODES.skillNotFound, 1);
10
10
  }
11
- const targetDirResult = resolveTargetDir(input, skillName, deps.fs);
11
+ const targetDirResult = resolveTargetDir(skillName, deps.fs);
12
12
  if (targetDirResult.kind === "error") {
13
13
  return targetDirResult.result;
14
14
  }
15
15
  try {
16
+ if (deps.fs.exists(targetDirResult.targetDir)) {
17
+ deps.fs.removeDirRecursive(targetDirResult.targetDir);
18
+ }
16
19
  deps.files.copySkillDir(sourceDir, targetDirResult.targetDir);
17
20
  return (0, command_result_1.commandOk)(`已安装 ${skillName} 技能到 ${targetDirResult.targetDir}`);
18
21
  }
@@ -21,7 +24,7 @@ function runInstallSkillFlow(input, deps) {
21
24
  return (0, command_result_1.commandError)(`安装技能失败(${skillName} -> ${targetDirResult.targetDir}): ${message}`, command_result_1.COMMAND_ERROR_CODES.fsOperationFailed, 1);
22
25
  }
23
26
  }
24
- function resolveTargetDir(input, skillName, fs) {
27
+ function resolveTargetDir(skillName, fs) {
25
28
  const baseDir = fs.cwd();
26
29
  if (!baseDir || !baseDir.trim()) {
27
30
  return {
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runSkillListFlow = runSkillListFlow;
4
+ const command_result_1 = require("../domain/command-result");
5
+ function runSkillListFlow(deps) {
6
+ const root = deps.config.getSkillTemplateRoot();
7
+ if (!deps.fs.exists(root)) {
8
+ return (0, command_result_1.commandError)(`技能模板目录不存在: ${root}`, command_result_1.COMMAND_ERROR_CODES.invalidPath, 1);
9
+ }
10
+ try {
11
+ const names = deps.fs
12
+ .readDir(root)
13
+ .filter((name) => {
14
+ const entry = deps.fs.join(root, name);
15
+ return deps.fs.exists(entry) && deps.fs.isDirectory(entry);
16
+ })
17
+ .sort((a, b) => a.localeCompare(b));
18
+ if (names.length === 0) {
19
+ return (0, command_result_1.commandOk)("(暂无可用技能)");
20
+ }
21
+ return (0, command_result_1.commandOk)(names.join("\n"));
22
+ }
23
+ catch (error) {
24
+ const message = error instanceof Error ? error.message : String(error);
25
+ return (0, command_result_1.commandError)(`列出技能失败: ${message}`, command_result_1.COMMAND_ERROR_CODES.fsOperationFailed, 1);
26
+ }
27
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "listpage_cli",
3
- "version": "0.0.312",
3
+ "version": "0.0.313",
4
4
  "private": false,
5
5
  "bin": {
6
6
  "listpage_cli": "bin/cli.js"
@@ -25,7 +25,7 @@
25
25
  "class-transformer": "^0.5.1",
26
26
  "class-validator": "~0.14.2",
27
27
  "rxjs": "^7.8.1",
28
- "listpage-next-nest": "~0.0.312"
28
+ "listpage-next-nest": "~0.0.313"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@nestjs/schematics": "^11.0.0",
@@ -10,8 +10,8 @@
10
10
  "preview": "rsbuild preview"
11
11
  },
12
12
  "dependencies": {
13
- "listpage-http": "0.0.312",
14
- "listpage-components": "0.0.312",
13
+ "listpage-http": "0.0.313",
14
+ "listpage-components": "0.0.313",
15
15
  "antd": "6.3.1",
16
16
  "ahooks": "^3.9.5",
17
17
  "@ant-design/icons": "~6.0.2",
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: listpage-cli-deploy
3
+ description: Runs listpage_cli project build, release, and deploy in sequence (Docker build/push to registry and remote container). After a successful deploy, derive and tell the user the app access URL from listpage.config.json and backend listen port (see skill body). Use ONLY when the user explicitly asks to deploy using listpage_cli—e.g. mentions deploying with listpage_cli, listpage_cli 部署, or similar. Do NOT use for generic deployment, Docker/compose/K8s/CI, or Aliyun push without an explicit listpage_cli request. Default tag/environment is dev; users may name other environments in listpage.config.json docker.releaseProfiles (e.g. test, prod).
4
+ ---
5
+
6
+ # listpage_cli 自动化部署
7
+
8
+ ## 何时使用本技能
9
+
10
+ - **仅当用户明确说要通过 `listpage_cli` 部署**(例如:用 listpage_cli 部署、listpage_cli 发版等**且语境指向该 CLI**)时再应用本技能。
11
+ - **不要**因泛泛的「部署」「上线」「打镜像」「推阿里云」等就启用;除非用户同时点明 **listpage_cli**。
12
+
13
+ ## Tag 与环境
14
+
15
+ - `project release` / `project deploy` 的 **`<tag>`** 在习惯上对应**环境名**(开发 / 测试 / 生产等),与 `listpage.config.json` 里 **`docker.releaseProfiles`** 下的 key 一致;同一仓库可配置多套 profile(如 `dev`、`test`、`prod`),每套可有各自的 `tag`、`envFile` 等。
16
+ - **若用户未指定环境或 tag,默认使用 `dev`**。
17
+
18
+ 在**项目根目录**(需存在 `listpage.config.json`)按顺序执行三条命令即可完成构建、镜像推送与远程容器部署。
19
+
20
+ ## 前置条件
21
+
22
+ - 已安装并可执行 `listpage_cli`(可先运行 `listpage_cli --help` 核对子命令)。
23
+ - 本机已安装 **Docker**,且 **Docker 守护进程已启动**(`release` / `deploy` 会用到本地 Docker 与远程配置)。
24
+ - `listpage.config.json` 中 `docker.registry`、`docker.remote`、`docker.releaseProfiles` 等已按目标环境填好(见下文「常见坑」)。
25
+
26
+ ## 部署步骤(顺序固定)
27
+
28
+ 在项目根目录执行:
29
+
30
+ ```bash
31
+ listpage_cli project build
32
+ ```
33
+
34
+ - 校验当前目录为有效项目根,构建产物写入 **`.listpage/output`**。
35
+
36
+ ```bash
37
+ listpage_cli project release <tag>
38
+ ```
39
+
40
+ - 默认:`listpage_cli project release dev`(未指定环境时)。
41
+ - `<tag>` 与 `listpage.config.json` 中 `docker.releaseProfiles` 的环境 key 对齐(如 `dev` / `test` / `prod`);也可用 CLI 覆盖项,见 `listpage_cli --help` 中 `project release` 说明。
42
+ - 流程:校验 `.listpage/output` → 登录 / 构建 / 打 tag / 推送到配置的镜像仓库。
43
+
44
+ ```bash
45
+ listpage_cli project deploy <tag>
46
+ ```
47
+
48
+ - 默认:`listpage_cli project deploy dev`(与 `release` 使用同一环境名)。
49
+ - 使用 `docker.remote` + `docker.container` 部署;支持 `ports`、`envFile`、`env` 等合并。
50
+ - 若镜像已在目标环境且仅需更新容器步骤,可使用 CLI 中的 `--skip-image`(以 `listpage_cli --help` 为准)。
51
+
52
+ **CLI 参数优先级**(来自官方说明):`CLI 参数 > profile > 配置文件中的 base 项`。可选参数包括 `--profile`、`--platform`、`--env KEY=VALUE` 等,需要时查阅 `listpage_cli --help`。
53
+
54
+ ## 部署成功后:告知用户访问地址
55
+
56
+ 在 **`project deploy` 成功结束**后,必须结合**项目根目录的 `listpage.config.json`**(以及必要时后端监听端口)拼出**应用访问地址**,并用一句话告诉用户。**不要**把 `docker.registry` 密码、`envFile` 内容等敏感信息写入回复。
57
+
58
+ ### 推导规则(按顺序)
59
+
60
+ 1. **主机(host)**
61
+ 使用 `docker.remote.host`(远程部署目标机的 IP 或域名)。**不要**用 `docker.remote.port`(那是连接 Docker API 的端口,例如 2376,不是浏览器访问端口)。
62
+
63
+ 2. **协议(scheme)**
64
+ - **默认**:面向用户的应用 URL 使用 **`http`**。
65
+ - `docker.remote.protocol`(如 `https`)仅表示连接 **Docker daemon** 的方式,**不能**直接当作浏览器访问应用的协议。
66
+ - 若用户在前方挂了 **Nginx / 负载均衡 / 全站 HTTPS**,实际对外可能是 **`https`**;技能无法从配置文件唯一确定时,以 **`http` + host + 端口** 为主,并**简短说明**「若前面有 HTTPS 终止,请改用 https」。
67
+
68
+ 3. **路径(pathname / 前缀)**
69
+ - **一般情况**:路径前缀与 **`docker.container.name`** 一致,格式为 **`/<container.name>`**(注意前导 `/`,且通常**全小写**与配置一致)。
70
+ - **少数差异**:若前端 `assetPrefix` / `base`(如 `apps/fe` 的 `rsbuild.config.ts`)与 `docker.container.name` 不一致,应以**前端实际配置的 public 路径**为准(部署仍成功但路径不同时需核对前端)。默认在技能中优先 **`docker.container.name`**。
71
+
72
+ 4. **端口(浏览器访问的 host 端口)**
73
+ 读取 `docker.container.ports` 中每一项 **`"hostPort:containerPort"`** 字符串(与 Docker `-p` 一致)。
74
+ - 先确定**容器内后端 HTTP 监听端口**:查看 `servers/be`(或配置里 `artifacts.backend.projectDir`)中 `listen(...)`、`Dockerfile` 的 `EXPOSE` 或 `PORT` 环境变量;常见为 **3000**。
75
+ - 在 `ports` 数组里找到 **右侧(container)端口等于该后端端口** 的那一条,取**左侧(host)端口**作为 URL 中的端口。
76
+ - **多条映射时**:始终使用「**对应后端对外提供 Web 服务的那一个容器端口**」所映射到主机上的端口,而不是 Docker API 或其它服务端口。
77
+ - **单条映射**(如 `"3333:3000"`):后端在容器内监听 3000 → 浏览器访问 **`3333`**。
78
+
79
+ 5. **拼 URL**
80
+
81
+ `{scheme}://{docker.remote.host}:{上一步得到的 host 端口}/{路径前缀去掉多余斜杠}`
82
+
83
+ 路径前缀:若规则 3 得到 `/<name>`,则完整示例形态为:
84
+ `http://{host}:{hostPort}/{container.name}/`
85
+ 末尾是否带 `/` 可按习惯,保持与前端 `assetPrefix` 一致即可。
86
+
87
+ ### 示例(与配置字段对应)
88
+
89
+ 若 `docker.remote.host` 为 `localhost`,`docker.container.name` 为 `contract-payment-collection`,`docker.container.ports` 为 `["3333:3000"]`,且后端在容器内监听 **3000**,则访问地址为:
90
+
91
+ `http://localhost:3333/contract-payment-collection`
92
+
93
+ (若用户仅配置了 http 场景,上述即为默认输出;支持 https 时由用户或前置代理决定,可在说明中一句带过。)
94
+
95
+ ## 常见坑与配置要点
96
+
97
+ ### 1. Dockerfile 基础镜像(如 `FROM node:25-slim`)
98
+
99
+ 本地构建镜像时,若基础镜像未拉取过,`docker build` 可能失败或变慢。部署前可在本机先执行 `docker pull node:25-slim`(或与 `servers/be/Dockerfile` 中 `FROM` 一致的镜像),保证与构建环境一致。
100
+
101
+ ### 2. `docker.remote.tls`(ca / cert / key)
102
+
103
+ `listpage.config.json` 里 `docker.remote.tls` 的 `ca`、`cert`、`key` 路径指向的证书**与目标远程 Docker 主机绑定**;换一台部署机或重建 TLS 时,需替换为对应文件并保持路径可访问。
104
+
105
+ ### 3. `docker.build.platform`(如 `linux/amd64`)
106
+
107
+ 该字段影响**构建出的镜像架构**。例如:在 Apple Silicon Mac 上若要把镜像跑到 **x86 云服务器**,通常使用 `linux/amd64`;若目标为本机或 ARM 机器,需改为匹配的 `platform`(或通过 CLI 传 `--platform` 覆盖)。与运行环境不一致会导致容器无法运行或性能异常。
108
+
109
+ ### 4. 阿里云镜像仓库账号
110
+
111
+ `docker.registry` 中的 `url`、`namespace`、`username`、`password` 为**敏感信息**。技能与文档中只描述「在配置中填写」,不要在对话、提交记录或示例中粘贴真实密码;
112
+
113
+ ## 快速排错
114
+
115
+ | 现象 | 可检查项 |
116
+ |------|----------|
117
+ | `build` 失败 | 是否在项目根目录、`listpage.config.json` 是否完整、前后端构建脚本是否正常 |
118
+ | `release` 失败 | Docker 是否启动、基础镜像是否可拉取、registry 账号与网络、platform 是否与预期一致 |
119
+ | `deploy` 失败 | `docker.remote` 地址/端口/协议、TLS 证书是否匹配当前主机、`envFile` 路径是否存在 |
120
+
121
+ ## 参考命令说明
122
+
123
+ 执行 `listpage_cli --help` 可查看 `project build` / `project release` / `project deploy` 的完整参数与备注。
@@ -1,9 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createInstallSkillCommandHandler = createInstallSkillCommandHandler;
4
- const install_skill_service_1 = require("../services/install-skill-service");
5
- function createInstallSkillCommandHandler(deps) {
6
- return async (input) => {
7
- return (0, install_skill_service_1.runInstallSkillFlow)(input, deps);
8
- };
9
- }