listpage_cli 0.0.293 → 0.0.295

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 (39) hide show
  1. package/bin/adapters/cli-interaction.js +172 -0
  2. package/bin/adapters/node-fs-adapter.js +109 -0
  3. package/bin/app/dispatch.js +15 -0
  4. package/bin/app/execute.js +33 -0
  5. package/bin/app/parse-args.js +39 -0
  6. package/bin/cli.js +61 -75
  7. package/bin/commands/build-project-command.js +9 -0
  8. package/bin/commands/deploy-project-command.js +9 -0
  9. package/bin/commands/init-command.js +9 -0
  10. package/bin/commands/install-skill-command.js +9 -0
  11. package/bin/copy.js +14 -126
  12. package/bin/domain/command-result.js +34 -0
  13. package/bin/domain/interaction-result.js +14 -0
  14. package/bin/domain/package-name.js +12 -0
  15. package/bin/ports/build-project-command.js +2 -0
  16. package/bin/ports/deploy-project-command.js +2 -0
  17. package/bin/ports/filesystem-capability.js +2 -0
  18. package/bin/ports/fs-port.js +22 -0
  19. package/bin/ports/init-command.js +2 -0
  20. package/bin/ports/install-skill-command.js +2 -0
  21. package/bin/prompts.js +105 -16
  22. package/bin/services/artifact-validator.js +47 -0
  23. package/bin/services/build-project-service.js +190 -0
  24. package/bin/services/command-runner.js +45 -0
  25. package/bin/services/config-loader.js +113 -0
  26. package/bin/services/deploy-project-service.js +237 -0
  27. package/bin/services/filesystem-capability-service.js +137 -0
  28. package/bin/services/init-service.js +64 -0
  29. package/bin/services/install-skill-service.js +34 -0
  30. package/bin/shared/json-with-comments.js +9 -0
  31. package/package.json +6 -4
  32. package/templates/backend-template/package.json.tmpl +1 -1
  33. package/templates/frontend-template/package.json.tmpl +2 -2
  34. package/templates/package-app-template/package.json +1 -1
  35. package/templates/skills-template/listpage/examples.md +565 -0
  36. package/skills/listpage/examples.md +0 -243
  37. package/templates/rush-template/docs/ListPage-AI/347/224/237/346/210/220/350/247/204/350/214/203.md +0 -305
  38. /package/{skills → templates/skills-template}/listpage/SKILL.md +0 -0
  39. /package/{skills → templates/skills-template}/listpage/api.md +0 -0
package/bin/copy.js CHANGED
@@ -1,7 +1,4 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.copyRushTemplate = copyRushTemplate;
7
4
  exports.updateRushJsonProjects = updateRushJsonProjects;
@@ -11,142 +8,33 @@ exports.copyDeployScriptTemplate = copyDeployScriptTemplate;
11
8
  exports.ensureDir = ensureDir;
12
9
  exports.isDirEmpty = isDirEmpty;
13
10
  exports.copySkillDir = copySkillDir;
14
- exports.syncDirWithRename = syncDirWithRename;
15
- const fs_1 = require("fs");
16
- const path_1 = __importDefault(require("path"));
17
- const utils_1 = require("./utils");
11
+ const node_fs_adapter_1 = require("./adapters/node-fs-adapter");
12
+ const filesystem_capability_service_1 = require("./services/filesystem-capability-service");
13
+ const fsAdapter = (0, node_fs_adapter_1.createNodeFsAdapter)();
14
+ const filesystemCapability = (0, filesystem_capability_service_1.createFilesystemCapabilityService)(fsAdapter, {
15
+ templateRootDir: fsAdapter.resolve(__dirname, "..", "templates"),
16
+ });
18
17
  function copyRushTemplate(targetDir, vars) {
19
- const source = getTemplateDir("rush");
20
- copyDir(source, targetDir, vars);
18
+ filesystemCapability.copyRushTemplate(targetDir, vars);
21
19
  }
22
20
  function updateRushJsonProjects(filepath, feAppName, beAppName) {
23
- const projects = [];
24
- if (feAppName) {
25
- projects.push({
26
- packageName: (0, utils_1.composePkgName)(feAppName),
27
- projectFolder: `apps/${feAppName}`,
28
- reviewCategory: "production",
29
- });
30
- }
31
- if (beAppName) {
32
- const pkg = (0, utils_1.composePkgName)(beAppName);
33
- projects.push({
34
- packageName: pkg,
35
- projectFolder: `servers/${beAppName}`,
36
- reviewCategory: "production",
37
- });
38
- }
39
- const content = (0, fs_1.readFileSync)(filepath, "utf8");
40
- const json = (0, utils_1.readJsonWithComments)(content);
41
- json.projects = projects;
42
- (0, fs_1.writeFileSync)(filepath, JSON.stringify(json, null, 2), { encoding: "utf-8" });
21
+ filesystemCapability.updateRushJsonProjects(filepath, feAppName, beAppName);
43
22
  }
44
23
  function copyFrontendTemplate(targetDir, vars) {
45
- const appName = (0, utils_1.composePkgName)(vars.FRONTEND_NAME);
46
- targetDir = path_1.default.join(targetDir, `apps/${appName}`);
47
- const source = getTemplateDir("frontend");
48
- copyDir(source, targetDir, vars);
24
+ filesystemCapability.copyFrontendTemplate(targetDir, vars);
49
25
  }
50
26
  function copyBackendTemplate(targetDir, vars) {
51
- const appName = (0, utils_1.composePkgName)(vars.BACKEND_NAME);
52
- targetDir = path_1.default.join(targetDir, `servers/${appName}`);
53
- const source = getTemplateDir("backend");
54
- copyDir(source, targetDir, vars);
27
+ filesystemCapability.copyBackendTemplate(targetDir, vars);
55
28
  }
56
29
  function copyDeployScriptTemplate(targetDir, vars) {
57
- targetDir = path_1.default.join(targetDir, `common/scripts/package-app`);
58
- const source = getTemplateDir("package-app");
59
- copyDir(source, targetDir, vars);
60
- }
61
- function getTemplateDir(type) {
62
- return path_1.default.join(__dirname, "..", "templates", `${type}-template`);
63
- }
64
- function copyDir(source, destination, vars) {
65
- ensureDir(destination);
66
- const files = (0, fs_1.readdirSync)(source);
67
- files.forEach((file) => {
68
- const s = path_1.default.join(source, file);
69
- const d = path_1.default.join(destination, file);
70
- const stat = (0, fs_1.statSync)(s);
71
- if (stat.isDirectory()) {
72
- copyDir(s, d, vars);
73
- }
74
- else {
75
- ensureDir(path_1.default.dirname(d));
76
- const isTmpl = file.endsWith(".tmpl");
77
- // 正常文件直接复制
78
- if (!isTmpl) {
79
- (0, fs_1.copyFileSync)(s, d);
80
- return;
81
- }
82
- // 模板文件替换变量后复制
83
- const template = (0, fs_1.readFileSync)(s, "utf8");
84
- const content = compileTemplateContent(template, vars);
85
- (0, fs_1.writeFileSync)(d.slice(0, -5), content, { encoding: "utf-8" });
86
- }
87
- });
30
+ filesystemCapability.copyDeployScriptTemplate(targetDir, vars);
88
31
  }
89
32
  function ensureDir(dir) {
90
- if (!(0, fs_1.existsSync)(dir))
91
- (0, fs_1.mkdirSync)(dir, { recursive: true });
92
- }
93
- function compileTemplateContent(str, vars) {
94
- let out = str;
95
- for (const [k, v] of Object.entries(vars)) {
96
- const re = new RegExp(`__${k}__`, "g");
97
- out = out.replace(re, v);
98
- }
99
- return out;
33
+ fsAdapter.ensureDir(dir);
100
34
  }
101
35
  function isDirEmpty(dir) {
102
- return !(0, fs_1.existsSync)(dir) || (0, fs_1.readdirSync)(dir).length === 0;
36
+ return filesystemCapability.isDirEmpty(dir);
103
37
  }
104
38
  function copySkillDir(sourceDir, targetDir) {
105
- syncDirWithRename(sourceDir, targetDir);
106
- }
107
- function syncDirWithRename(source, destination) {
108
- ensureDir(destination);
109
- const items = (0, fs_1.readdirSync)(source);
110
- items.forEach((name) => {
111
- const s = path_1.default.join(source, name);
112
- const d = path_1.default.join(destination, name);
113
- const sStat = (0, fs_1.statSync)(s);
114
- if (sStat.isDirectory()) {
115
- if ((0, fs_1.existsSync)(d)) {
116
- const dStat = (0, fs_1.statSync)(d);
117
- if (!dStat.isDirectory()) {
118
- const copyPath = getCopyPath(d);
119
- (0, fs_1.renameSync)(d, copyPath);
120
- }
121
- }
122
- ensureDir(d);
123
- syncDirWithRename(s, d);
124
- }
125
- else {
126
- ensureDir(path_1.default.dirname(d));
127
- if ((0, fs_1.existsSync)(d)) {
128
- const dStat = (0, fs_1.statSync)(d);
129
- if (dStat.isFile()) {
130
- const copyPath = getCopyPath(d);
131
- (0, fs_1.renameSync)(d, copyPath);
132
- }
133
- }
134
- (0, fs_1.copyFileSync)(s, d);
135
- }
136
- });
137
- }
138
- function getCopyPath(filePath) {
139
- const dir = path_1.default.dirname(filePath);
140
- const ext = path_1.default.extname(filePath);
141
- const base = path_1.default.basename(filePath, ext);
142
- let candidate = path_1.default.join(dir, `${base} copy${ext}`);
143
- if (!(0, fs_1.existsSync)(candidate))
144
- return candidate;
145
- let i = 2;
146
- while (true) {
147
- candidate = path_1.default.join(dir, `${base} copy (${i})${ext}`);
148
- if (!(0, fs_1.existsSync)(candidate))
149
- return candidate;
150
- i++;
151
- }
39
+ filesystemCapability.copySkillDir(sourceDir, targetDir);
152
40
  }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMMAND_ERROR_CODES = void 0;
4
+ exports.commandOk = commandOk;
5
+ exports.commandError = commandError;
6
+ exports.isCommandResult = isCommandResult;
7
+ exports.COMMAND_ERROR_CODES = {
8
+ unknownCommand: "E_UNKNOWN_COMMAND",
9
+ handlerNotFound: "E_HANDLER_NOT_FOUND",
10
+ invalidCommandResult: "E_INVALID_COMMAND_RESULT",
11
+ userCancelled: "E_USER_CANCELLED",
12
+ invalidInteraction: "E_INVALID_INTERACTION",
13
+ unexpectedError: "E_UNEXPECTED_ERROR",
14
+ skillNotFound: "E_SKILL_NOT_FOUND",
15
+ invalidPath: "E_INVALID_PATH",
16
+ fsOperationFailed: "E_FS_OPERATION_FAILED",
17
+ executionFailed: "E_EXECUTION_FAILED",
18
+ };
19
+ function commandOk(message) {
20
+ return { ok: true, exitCode: 0, message };
21
+ }
22
+ function commandError(message, errorCode, exitCode = 1) {
23
+ return { ok: false, exitCode, message, errorCode };
24
+ }
25
+ function isCommandResult(value) {
26
+ if (!value || typeof value !== "object") {
27
+ return false;
28
+ }
29
+ const candidate = value;
30
+ return (typeof candidate.ok === "boolean" &&
31
+ typeof candidate.exitCode === "number" &&
32
+ (candidate.message === undefined || typeof candidate.message === "string") &&
33
+ (candidate.errorCode === undefined || typeof candidate.errorCode === "string"));
34
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.interactionValue = interactionValue;
4
+ exports.interactionCancelled = interactionCancelled;
5
+ exports.interactionInvalid = interactionInvalid;
6
+ function interactionValue(value) {
7
+ return { status: "value", value };
8
+ }
9
+ function interactionCancelled() {
10
+ return { status: "cancelled" };
11
+ }
12
+ function interactionInvalid(reason) {
13
+ return { status: "invalid", reason };
14
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.composePkgName = composePkgName;
4
+ function composePkgName(name) {
5
+ return safePkgName(name);
6
+ }
7
+ function safePkgName(name) {
8
+ return name
9
+ .toLowerCase()
10
+ .replace(/\s+/g, "-")
11
+ .replace(/[^a-z0-9-_.]/g, "-");
12
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FsPortError = exports.FS_PORT_ERROR_CODES = void 0;
4
+ exports.FS_PORT_ERROR_CODES = {
5
+ invalidPath: "E_INVALID_PATH",
6
+ permissionDenied: "E_FS_PERMISSION_DENIED",
7
+ notFound: "E_FS_NOT_FOUND",
8
+ operationFailed: "E_FS_OPERATION_FAILED",
9
+ };
10
+ class FsPortError extends Error {
11
+ constructor(code, operation, targetPath, message, cause) {
12
+ super(message);
13
+ this.name = "FsPortError";
14
+ this.code = code;
15
+ this.operation = operation;
16
+ this.targetPath = targetPath;
17
+ if (cause !== undefined) {
18
+ this.cause = cause;
19
+ }
20
+ }
21
+ }
22
+ exports.FsPortError = FsPortError;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/bin/prompts.js CHANGED
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runPrompt = runPrompt;
6
7
  exports.askRushQuestions = askRushQuestions;
7
8
  exports.askProjectPath = askProjectPath;
8
9
  exports.askOverwrite = askOverwrite;
@@ -12,60 +13,148 @@ exports.printVersion = printVersion;
12
13
  const enquirer_1 = require("enquirer");
13
14
  const fs_1 = require("fs");
14
15
  const path_1 = __importDefault(require("path"));
16
+ const interaction_result_1 = require("./domain/interaction-result");
17
+ let promptTestScript = null;
18
+ let promptTestScriptIndex = 0;
19
+ function getTestPromptMode() {
20
+ const mode = process.env.LISTPAGE_CLI_PROMPT_TEST_MODE;
21
+ if (mode === "cancel" || mode === "invalid") {
22
+ return mode;
23
+ }
24
+ return undefined;
25
+ }
26
+ function getNextScriptedPromptResult() {
27
+ if (promptTestScript === null) {
28
+ const raw = process.env.LISTPAGE_CLI_PROMPT_TEST_SCRIPT;
29
+ if (!raw) {
30
+ promptTestScript = [];
31
+ return undefined;
32
+ }
33
+ try {
34
+ const parsed = JSON.parse(raw);
35
+ if (Array.isArray(parsed)) {
36
+ promptTestScript = parsed;
37
+ }
38
+ else {
39
+ promptTestScript = [];
40
+ }
41
+ }
42
+ catch {
43
+ promptTestScript = [];
44
+ }
45
+ }
46
+ const next = promptTestScript[promptTestScriptIndex];
47
+ promptTestScriptIndex += 1;
48
+ return next;
49
+ }
50
+ function isCancelError(error) {
51
+ if (!(error instanceof Error)) {
52
+ return false;
53
+ }
54
+ const normalized = error.message.toLowerCase();
55
+ return (normalized.includes("cancel") ||
56
+ normalized.includes("aborted") ||
57
+ normalized.includes("interrupted"));
58
+ }
59
+ async function runPrompt(options, runner = enquirer_1.prompt) {
60
+ const testMode = getTestPromptMode();
61
+ if (testMode === "cancel") {
62
+ return (0, interaction_result_1.interactionCancelled)();
63
+ }
64
+ if (testMode === "invalid") {
65
+ return (0, interaction_result_1.interactionInvalid)("测试模式: 模拟无效输入");
66
+ }
67
+ const scripted = getNextScriptedPromptResult();
68
+ if (scripted !== undefined) {
69
+ if (scripted === "cancel") {
70
+ return (0, interaction_result_1.interactionCancelled)();
71
+ }
72
+ if (scripted === "invalid") {
73
+ return (0, interaction_result_1.interactionInvalid)("测试脚本: 模拟无效输入");
74
+ }
75
+ return (0, interaction_result_1.interactionValue)(scripted);
76
+ }
77
+ try {
78
+ const value = (await runner(options));
79
+ return (0, interaction_result_1.interactionValue)(value);
80
+ }
81
+ catch (error) {
82
+ if (isCancelError(error)) {
83
+ return (0, interaction_result_1.interactionCancelled)();
84
+ }
85
+ const reason = error instanceof Error ? error.message : String(error);
86
+ return (0, interaction_result_1.interactionInvalid)(reason);
87
+ }
88
+ }
15
89
  async function askRushQuestions() {
16
- const { name: frontendName } = await (0, enquirer_1.prompt)({
90
+ const frontend = await runPrompt({
17
91
  type: "input",
18
92
  name: "name",
19
93
  message: "请输入前端项目名称,为空表示不创建前端项目",
20
94
  initial: "",
21
95
  });
22
- const { name: backendName } = await (0, enquirer_1.prompt)({
96
+ if (frontend.status !== "value") {
97
+ return frontend;
98
+ }
99
+ const backend = await runPrompt({
23
100
  type: "input",
24
101
  name: "name",
25
102
  message: "请输入后端项目名称,为空表示不创建后端项目",
26
103
  initial: "",
27
104
  });
28
- return { frontendName, backendName };
105
+ if (backend.status !== "value") {
106
+ return backend;
107
+ }
108
+ return (0, interaction_result_1.interactionValue)({
109
+ frontendName: frontend.value.name,
110
+ backendName: backend.value.name,
111
+ });
29
112
  }
30
113
  async function askProjectPath() {
31
- const ans = await (0, enquirer_1.prompt)({
114
+ const result = await runPrompt({
32
115
  type: "input",
33
116
  name: "path",
34
117
  message: "请填写项目名称或路径,如果填.表示直接把项目放到当前目录下",
35
118
  initial: ".",
36
119
  });
37
- const p = (ans.path || ".").trim();
38
- return p || ".";
120
+ if (result.status !== "value") {
121
+ return result;
122
+ }
123
+ const p = (result.value.path || ".").trim();
124
+ return (0, interaction_result_1.interactionValue)(p || ".");
39
125
  }
40
126
  async function askOverwrite() {
41
- const { ok } = await (0, enquirer_1.prompt)({
127
+ const result = await runPrompt({
42
128
  type: "confirm",
43
129
  name: "ok",
44
130
  message: "目标目录非空,是否覆盖?",
45
131
  initial: false,
46
132
  });
47
- if (!ok) {
48
- console.error("已取消");
49
- process.exit(1);
133
+ if (result.status !== "value") {
134
+ return result;
50
135
  }
136
+ return (0, interaction_result_1.interactionValue)(result.value.ok);
51
137
  }
52
138
  async function askInstallDeployScript() {
53
- const { ok } = await (0, enquirer_1.prompt)({
139
+ const result = await runPrompt({
54
140
  type: "confirm",
55
141
  name: "ok",
56
142
  message: "是否添加部署脚本?这个脚本允许你可以帮你快速构建docker镜像,并发布到阿里云等环境,但是需要本机有docker环境。",
57
143
  initial: false,
58
144
  });
59
- return ok;
145
+ if (result.status !== "value") {
146
+ return result;
147
+ }
148
+ return (0, interaction_result_1.interactionValue)(result.value.ok);
60
149
  }
61
150
  function printHelp() {
62
151
  const h = [
63
152
  "用法: listpage_cli init",
64
153
  "说明: 进入中文引导式交互,按提示填写即可",
65
- "用法: listpage_cli sync-docs",
66
- "说明: 将模板中的 .trae docs 同步到执行目录的同级目录",
67
- "用法: listpage_cli install-skill [listpage] [--project]",
68
- "说明: 安装 listpage 技能到 Cursor。默认安装到 ~/.cursor/skills/;加 --project 则安装到当前项目 .cursor/skills/",
154
+ "用法: listpage_cli install-skill [skillName] [--project]",
155
+ "说明: 安装技能到 Cursor。默认 skillName test,安装到当前命令执行目录的 .cursor/skills/",
156
+ "用法: listpage_cli build-project",
157
+ "说明: 非交互校验当前目录是否为有效项目根(需存在 listpage.config.json)",
69
158
  ].join("\n");
70
159
  console.log(h);
71
160
  }
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createArtifactValidator = createArtifactValidator;
4
+ const ARTIFACT_STAGE = "artifact";
5
+ function createArtifactValidator(fs) {
6
+ return (expectation) => {
7
+ const issues = [];
8
+ if (!fs.exists(expectation.outputDir)) {
9
+ issues.push({
10
+ stage: ARTIFACT_STAGE,
11
+ message: `[${ARTIFACT_STAGE}] 构建产物目录不存在: ${expectation.outputDir}`,
12
+ });
13
+ return { ok: false, issues };
14
+ }
15
+ if (!fs.isDirectory(expectation.outputDir)) {
16
+ issues.push({
17
+ stage: ARTIFACT_STAGE,
18
+ message: `[${ARTIFACT_STAGE}] 构建产物路径不是目录: ${expectation.outputDir}`,
19
+ });
20
+ return { ok: false, issues };
21
+ }
22
+ const rootEntries = fs.readDir(expectation.outputDir);
23
+ if (rootEntries.length === 0) {
24
+ issues.push({
25
+ stage: ARTIFACT_STAGE,
26
+ message: `[${ARTIFACT_STAGE}] 构建产物目录为空: ${expectation.outputDir}`,
27
+ });
28
+ }
29
+ for (const relativePath of expectation.requiredRelativePaths) {
30
+ const normalized = relativePath.trim();
31
+ if (!normalized) {
32
+ continue;
33
+ }
34
+ const absolutePath = fs.resolve(expectation.outputDir, normalized);
35
+ if (!fs.exists(absolutePath)) {
36
+ issues.push({
37
+ stage: ARTIFACT_STAGE,
38
+ message: `[${ARTIFACT_STAGE}] 缺少必需产物: ${normalized} (base=${expectation.outputDir})`,
39
+ });
40
+ }
41
+ }
42
+ return {
43
+ ok: issues.length === 0,
44
+ issues,
45
+ };
46
+ };
47
+ }