listpage_cli 0.0.294 → 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.
- package/bin/adapters/cli-interaction.js +172 -0
- package/bin/app/parse-args.js +6 -1
- package/bin/cli.js +12 -7
- package/bin/commands/deploy-project-command.js +9 -0
- package/bin/domain/package-name.js +12 -0
- package/bin/ports/deploy-project-command.js +2 -0
- package/bin/services/artifact-validator.js +45 -0
- package/bin/services/command-runner.js +4 -3
- package/bin/services/deploy-project-service.js +237 -0
- package/bin/services/filesystem-capability-service.js +7 -6
- package/bin/shared/json-with-comments.js +9 -0
- package/package.json +1 -1
- package/templates/backend-template/package.json.tmpl +1 -1
- package/templates/frontend-template/package.json.tmpl +2 -2
- package/templates/package-app-template/package.json +1 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runPrompt = runPrompt;
|
|
7
|
+
exports.askRushQuestions = askRushQuestions;
|
|
8
|
+
exports.askProjectPath = askProjectPath;
|
|
9
|
+
exports.askOverwrite = askOverwrite;
|
|
10
|
+
exports.askInstallDeployScript = askInstallDeployScript;
|
|
11
|
+
exports.printHelp = printHelp;
|
|
12
|
+
exports.printVersion = printVersion;
|
|
13
|
+
const enquirer_1 = require("enquirer");
|
|
14
|
+
const fs_1 = require("fs");
|
|
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
|
+
}
|
|
89
|
+
async function askRushQuestions() {
|
|
90
|
+
const frontend = await runPrompt({
|
|
91
|
+
type: "input",
|
|
92
|
+
name: "name",
|
|
93
|
+
message: "请输入前端项目名称,为空表示不创建前端项目",
|
|
94
|
+
initial: "",
|
|
95
|
+
});
|
|
96
|
+
if (frontend.status !== "value") {
|
|
97
|
+
return frontend;
|
|
98
|
+
}
|
|
99
|
+
const backend = await runPrompt({
|
|
100
|
+
type: "input",
|
|
101
|
+
name: "name",
|
|
102
|
+
message: "请输入后端项目名称,为空表示不创建后端项目",
|
|
103
|
+
initial: "",
|
|
104
|
+
});
|
|
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
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function askProjectPath() {
|
|
114
|
+
const result = await runPrompt({
|
|
115
|
+
type: "input",
|
|
116
|
+
name: "path",
|
|
117
|
+
message: "请填写项目名称或路径,如果填.表示直接把项目放到当前目录下",
|
|
118
|
+
initial: ".",
|
|
119
|
+
});
|
|
120
|
+
if (result.status !== "value") {
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
const p = (result.value.path || ".").trim();
|
|
124
|
+
return (0, interaction_result_1.interactionValue)(p || ".");
|
|
125
|
+
}
|
|
126
|
+
async function askOverwrite() {
|
|
127
|
+
const result = await runPrompt({
|
|
128
|
+
type: "confirm",
|
|
129
|
+
name: "ok",
|
|
130
|
+
message: "目标目录非空,是否覆盖?",
|
|
131
|
+
initial: false,
|
|
132
|
+
});
|
|
133
|
+
if (result.status !== "value") {
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
return (0, interaction_result_1.interactionValue)(result.value.ok);
|
|
137
|
+
}
|
|
138
|
+
async function askInstallDeployScript() {
|
|
139
|
+
const result = await runPrompt({
|
|
140
|
+
type: "confirm",
|
|
141
|
+
name: "ok",
|
|
142
|
+
message: "是否添加部署脚本?这个脚本允许你可以帮你快速构建docker镜像,并发布到阿里云等环境,但是需要本机有docker环境。",
|
|
143
|
+
initial: false,
|
|
144
|
+
});
|
|
145
|
+
if (result.status !== "value") {
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
return (0, interaction_result_1.interactionValue)(result.value.ok);
|
|
149
|
+
}
|
|
150
|
+
function printHelp() {
|
|
151
|
+
const h = [
|
|
152
|
+
"用法: listpage_cli init",
|
|
153
|
+
"说明: 进入中文引导式交互,按提示填写即可",
|
|
154
|
+
"用法: listpage_cli install-skill [skillName] [--project]",
|
|
155
|
+
"说明: 安装技能到 Cursor。默认 skillName 为 test,安装到当前命令执行目录的 .cursor/skills/",
|
|
156
|
+
"用法: listpage_cli build-project",
|
|
157
|
+
"说明: 非交互校验当前目录是否为有效项目根(需存在 listpage.config.json)",
|
|
158
|
+
"用法: listpage_cli deploy-project",
|
|
159
|
+
"说明: 先校验 .listpage/output 产物,再按 login/build/tag/push 执行 Docker 部署",
|
|
160
|
+
].join("\n");
|
|
161
|
+
console.log(h);
|
|
162
|
+
}
|
|
163
|
+
function printVersion() {
|
|
164
|
+
try {
|
|
165
|
+
const p = path_1.default.join(__dirname, "..", "..", "package.json");
|
|
166
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(p, "utf8"));
|
|
167
|
+
console.log(pkg.version || "");
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
console.log("");
|
|
171
|
+
}
|
|
172
|
+
}
|
package/bin/app/parse-args.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseArgs = parseArgs;
|
|
4
|
-
const KNOWN_COMMANDS = new Set([
|
|
4
|
+
const KNOWN_COMMANDS = new Set([
|
|
5
|
+
"init",
|
|
6
|
+
"install-skill",
|
|
7
|
+
"build-project",
|
|
8
|
+
"deploy-project",
|
|
9
|
+
]);
|
|
5
10
|
function parseArgs(argv) {
|
|
6
11
|
const rawArgs = [...argv];
|
|
7
12
|
const commandToken = rawArgs[0];
|
package/bin/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const
|
|
4
|
+
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
8
|
const install_skill_command_1 = require("./commands/install-skill-command");
|
|
9
9
|
const build_project_command_1 = require("./commands/build-project-command");
|
|
10
|
+
const deploy_project_command_1 = require("./commands/deploy-project-command");
|
|
10
11
|
const node_fs_adapter_1 = require("./adapters/node-fs-adapter");
|
|
11
12
|
const filesystem_capability_service_1 = require("./services/filesystem-capability-service");
|
|
12
13
|
const fsAdapter = (0, node_fs_adapter_1.createNodeFsAdapter)();
|
|
@@ -15,10 +16,10 @@ const filesystemCapability = (0, filesystem_capability_service_1.createFilesyste
|
|
|
15
16
|
});
|
|
16
17
|
const initCommandHandler = (0, init_command_1.createInitCommandHandler)({
|
|
17
18
|
prompts: {
|
|
18
|
-
askProjectPath:
|
|
19
|
-
askOverwrite:
|
|
20
|
-
askRushQuestions:
|
|
21
|
-
askInstallDeployScript:
|
|
19
|
+
askProjectPath: cli_interaction_1.askProjectPath,
|
|
20
|
+
askOverwrite: cli_interaction_1.askOverwrite,
|
|
21
|
+
askRushQuestions: cli_interaction_1.askRushQuestions,
|
|
22
|
+
askInstallDeployScript: cli_interaction_1.askInstallDeployScript,
|
|
22
23
|
},
|
|
23
24
|
fs: fsAdapter,
|
|
24
25
|
files: filesystemCapability,
|
|
@@ -34,14 +35,18 @@ const installSkillCommandHandler = (0, install_skill_command_1.createInstallSkil
|
|
|
34
35
|
const buildProjectCommandHandler = (0, build_project_command_1.createBuildProjectCommandHandler)({
|
|
35
36
|
fs: fsAdapter,
|
|
36
37
|
});
|
|
38
|
+
const deployProjectCommandHandler = (0, deploy_project_command_1.createDeployProjectCommandHandler)({
|
|
39
|
+
fs: fsAdapter,
|
|
40
|
+
});
|
|
37
41
|
async function main() {
|
|
38
42
|
const result = await (0, execute_1.executeCommand)(process.argv.slice(2), {
|
|
39
|
-
printHelp:
|
|
40
|
-
printVersion:
|
|
43
|
+
printHelp: cli_interaction_1.printHelp,
|
|
44
|
+
printVersion: cli_interaction_1.printVersion,
|
|
41
45
|
handlers: {
|
|
42
46
|
init: initCommandHandler,
|
|
43
47
|
"install-skill": installSkillCommandHandler,
|
|
44
48
|
"build-project": buildProjectCommandHandler,
|
|
49
|
+
"deploy-project": deployProjectCommandHandler,
|
|
45
50
|
},
|
|
46
51
|
});
|
|
47
52
|
applyCommandResult(result);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDeployProjectCommandHandler = createDeployProjectCommandHandler;
|
|
4
|
+
const deploy_project_service_1 = require("../services/deploy-project-service");
|
|
5
|
+
function createDeployProjectCommandHandler(deps) {
|
|
6
|
+
return async (_input) => {
|
|
7
|
+
return (0, deploy_project_service_1.runDeployProjectFlow)(deps);
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -1,2 +1,47 @@
|
|
|
1
1
|
"use strict";
|
|
2
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
|
+
}
|
|
@@ -5,11 +5,11 @@ exports.createCommandRunner = createCommandRunner;
|
|
|
5
5
|
const node_child_process_1 = require("node:child_process");
|
|
6
6
|
exports.COMMAND_RUNNER_STAGE = "build";
|
|
7
7
|
class ExecutionError extends Error {
|
|
8
|
-
constructor(code, command, exitCode, message) {
|
|
8
|
+
constructor(code, stage, command, exitCode, message) {
|
|
9
9
|
super(message);
|
|
10
|
-
this.stage = exports.COMMAND_RUNNER_STAGE;
|
|
11
10
|
this.name = "ExecutionError";
|
|
12
11
|
this.code = code;
|
|
12
|
+
this.stage = stage;
|
|
13
13
|
this.command = command;
|
|
14
14
|
this.exitCode = exitCode;
|
|
15
15
|
}
|
|
@@ -17,6 +17,7 @@ class ExecutionError extends Error {
|
|
|
17
17
|
exports.ExecutionError = ExecutionError;
|
|
18
18
|
function createCommandRunner(deps) {
|
|
19
19
|
const spawnSyncImpl = deps?.spawnSyncImpl ?? node_child_process_1.spawnSync;
|
|
20
|
+
const stage = deps?.stage ?? exports.COMMAND_RUNNER_STAGE;
|
|
20
21
|
return async (input) => {
|
|
21
22
|
const args = input.args ?? [];
|
|
22
23
|
const output = spawnSyncImpl(input.command, args, {
|
|
@@ -35,7 +36,7 @@ function createCommandRunner(deps) {
|
|
|
35
36
|
? "COMMAND_NOT_FOUND"
|
|
36
37
|
: "COMMAND_EXECUTION_FAILED";
|
|
37
38
|
const messagePrefix = isNotFound ? "命令不存在" : "命令执行异常";
|
|
38
|
-
throw new ExecutionError(code, input.command, 1, `[${
|
|
39
|
+
throw new ExecutionError(code, stage, input.command, 1, `[${stage}][${code}] ${messagePrefix}: ${input.command} (${output.error.message})`);
|
|
39
40
|
}
|
|
40
41
|
return {
|
|
41
42
|
exitCode: output.status ?? 1,
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runDeployProjectFlow = runDeployProjectFlow;
|
|
4
|
+
const command_result_1 = require("../domain/command-result");
|
|
5
|
+
const config_loader_1 = require("./config-loader");
|
|
6
|
+
const command_runner_1 = require("./command-runner");
|
|
7
|
+
const artifact_validator_1 = require("./artifact-validator");
|
|
8
|
+
const DEPLOY_STAGE = "deploy";
|
|
9
|
+
const ARTIFACT_STAGE = "artifact";
|
|
10
|
+
const CONFIG_ERROR_CODE_MAP = {
|
|
11
|
+
CONFIG_NOT_FOUND: "E_CONFIG_NOT_FOUND",
|
|
12
|
+
CONFIG_READ_FAILED: "E_CONFIG_READ_FAILED",
|
|
13
|
+
CONFIG_INVALID_JSON: "E_CONFIG_INVALID_JSON",
|
|
14
|
+
CONFIG_REQUIRED_FIELD_MISSING: "E_CONFIG_REQUIRED_FIELD_MISSING",
|
|
15
|
+
};
|
|
16
|
+
async function runDeployProjectFlow(deps) {
|
|
17
|
+
const projectRoot = deps.fs.cwd();
|
|
18
|
+
const configPath = (0, config_loader_1.getDefaultConfigPath)(projectRoot, deps.fs.join);
|
|
19
|
+
let configResult;
|
|
20
|
+
try {
|
|
21
|
+
configResult = (0, config_loader_1.loadProjectConfig)({
|
|
22
|
+
readText: deps.fs.readText,
|
|
23
|
+
}, configPath);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error instanceof config_loader_1.ConfigLoaderError) {
|
|
27
|
+
return (0, command_result_1.commandError)(error.message, CONFIG_ERROR_CODE_MAP[error.code], 1);
|
|
28
|
+
}
|
|
29
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
+
return (0, command_result_1.commandError)(message, command_result_1.COMMAND_ERROR_CODES.invalidPath, 1);
|
|
31
|
+
}
|
|
32
|
+
let executionInput;
|
|
33
|
+
try {
|
|
34
|
+
executionInput = parseDeployInput(projectRoot, configResult.config, deps.fs.resolve);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
38
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}] 部署配置无效: ${message}`, "E_CONFIG_REQUIRED_FIELD_MISSING", 1);
|
|
39
|
+
}
|
|
40
|
+
const validator = (0, artifact_validator_1.createArtifactValidator)({
|
|
41
|
+
resolve: deps.fs.resolve,
|
|
42
|
+
exists: deps.fs.exists,
|
|
43
|
+
isDirectory: deps.fs.isDirectory,
|
|
44
|
+
readDir: deps.fs.readDir,
|
|
45
|
+
});
|
|
46
|
+
const validationResult = validator({
|
|
47
|
+
outputDir: executionInput.outputDir,
|
|
48
|
+
requiredRelativePaths: collectRequiredArtifacts(configResult.config),
|
|
49
|
+
});
|
|
50
|
+
if (!validationResult.ok) {
|
|
51
|
+
const message = validationResult.issues.map((issue) => issue.message).join("; ");
|
|
52
|
+
return (0, command_result_1.commandError)(message, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
53
|
+
}
|
|
54
|
+
if (deps.executeDeploy) {
|
|
55
|
+
try {
|
|
56
|
+
const result = await deps.executeDeploy({
|
|
57
|
+
projectRoot,
|
|
58
|
+
configPath: configResult.configPath,
|
|
59
|
+
config: configResult.config,
|
|
60
|
+
outputDir: executionInput.outputDir,
|
|
61
|
+
remoteImage: executionInput.remoteImage,
|
|
62
|
+
docker: executionInput.docker,
|
|
63
|
+
});
|
|
64
|
+
return normalizeDeployResult(result);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return (0, command_result_1.commandError)(formatDeployThrownError(error), command_result_1.COMMAND_ERROR_CODES.executionFailed, getNonZeroExitCode(error));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const commandRunner = deps.createRunner
|
|
71
|
+
? deps.createRunner()
|
|
72
|
+
: (0, command_runner_1.createCommandRunner)({ stage: DEPLOY_STAGE });
|
|
73
|
+
return runDockerDeploy(commandRunner, executionInput);
|
|
74
|
+
}
|
|
75
|
+
function parseDeployInput(projectRoot, config, resolve) {
|
|
76
|
+
const dockerRaw = asObject(config.docker, "docker");
|
|
77
|
+
const registry = asObject(dockerRaw.registry, "docker.registry");
|
|
78
|
+
const build = asOptionalObject(dockerRaw.build);
|
|
79
|
+
const imageName = asRequiredString(dockerRaw.imageName ?? dockerRaw.appName, "docker.imageName/appName");
|
|
80
|
+
const imageTag = asRequiredString(dockerRaw.imageTag, "docker.imageTag");
|
|
81
|
+
const registryUrl = asRequiredString(registry.url, "docker.registry.url");
|
|
82
|
+
const registryNamespace = asRequiredString(registry.namespace, "docker.registry.namespace");
|
|
83
|
+
const username = asRequiredString(registry.username, "docker.registry.username");
|
|
84
|
+
const password = asOptionalString(registry.password);
|
|
85
|
+
const platform = asOptionalString(build?.platform);
|
|
86
|
+
const outputDir = resolve(projectRoot, ".listpage", "output");
|
|
87
|
+
return {
|
|
88
|
+
outputDir,
|
|
89
|
+
remoteImage: `${registryUrl}/${registryNamespace}/${imageName}:${imageTag}`,
|
|
90
|
+
docker: {
|
|
91
|
+
imageName,
|
|
92
|
+
imageTag,
|
|
93
|
+
registryUrl,
|
|
94
|
+
registryNamespace,
|
|
95
|
+
username,
|
|
96
|
+
password,
|
|
97
|
+
platform,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function collectRequiredArtifacts(config) {
|
|
102
|
+
const required = new Set(["Dockerfile"]);
|
|
103
|
+
const frontend = config.frontend;
|
|
104
|
+
if (Array.isArray(frontend)) {
|
|
105
|
+
for (const item of frontend) {
|
|
106
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const record = item;
|
|
110
|
+
const packageSubDir = asOptionalString(record.packageSubDir) ?? "public";
|
|
111
|
+
const artifactAlias = asOptionalString(record.artifactAlias) ?? "dist";
|
|
112
|
+
required.add(`${packageSubDir}/${artifactAlias}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (config.backend && typeof config.backend === "object" && !Array.isArray(config.backend)) {
|
|
116
|
+
const backend = config.backend;
|
|
117
|
+
const packageSubDir = asOptionalString(backend.packageSubDir) ?? "server";
|
|
118
|
+
const artifactAlias = asOptionalString(backend.artifactAlias) ?? "dist";
|
|
119
|
+
required.add(`${packageSubDir}/${artifactAlias}`);
|
|
120
|
+
}
|
|
121
|
+
return [...required];
|
|
122
|
+
}
|
|
123
|
+
async function runDockerDeploy(runner, input) {
|
|
124
|
+
const { docker } = input;
|
|
125
|
+
const localImage = `${docker.imageName}:${docker.imageTag}`;
|
|
126
|
+
const loginArgs = docker.password
|
|
127
|
+
? [
|
|
128
|
+
"login",
|
|
129
|
+
`--username=${docker.username}`,
|
|
130
|
+
`--password=${docker.password}`,
|
|
131
|
+
docker.registryUrl,
|
|
132
|
+
]
|
|
133
|
+
: [
|
|
134
|
+
"login",
|
|
135
|
+
`--username=${docker.username}`,
|
|
136
|
+
"--password-stdin",
|
|
137
|
+
docker.registryUrl,
|
|
138
|
+
];
|
|
139
|
+
const buildArgs = [
|
|
140
|
+
"buildx",
|
|
141
|
+
"build",
|
|
142
|
+
...(docker.platform ? ["--platform", docker.platform] : []),
|
|
143
|
+
"-t",
|
|
144
|
+
localImage,
|
|
145
|
+
".",
|
|
146
|
+
];
|
|
147
|
+
const stageCommands = [
|
|
148
|
+
{ stage: "login", args: loginArgs, cwd: input.outputDir },
|
|
149
|
+
{ stage: "build", args: buildArgs, cwd: input.outputDir },
|
|
150
|
+
{
|
|
151
|
+
stage: "tag",
|
|
152
|
+
args: ["tag", localImage, input.remoteImage],
|
|
153
|
+
cwd: input.outputDir,
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
stage: "push",
|
|
157
|
+
args: ["push", input.remoteImage],
|
|
158
|
+
cwd: input.outputDir,
|
|
159
|
+
},
|
|
160
|
+
];
|
|
161
|
+
try {
|
|
162
|
+
for (const command of stageCommands) {
|
|
163
|
+
const result = await runner({
|
|
164
|
+
command: "docker",
|
|
165
|
+
args: command.args,
|
|
166
|
+
cwd: command.cwd,
|
|
167
|
+
});
|
|
168
|
+
if (result.exitCode !== 0) {
|
|
169
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][${command.stage}] Docker 命令执行失败: docker ${command.args.join(" ")} (exit=${result.exitCode})`, command_result_1.COMMAND_ERROR_CODES.executionFailed, result.exitCode);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof command_runner_1.ExecutionError) {
|
|
175
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}] ${error.message}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, error.exitCode);
|
|
176
|
+
}
|
|
177
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
178
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}] 部署命令执行异常: ${message}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
179
|
+
}
|
|
180
|
+
return (0, command_result_1.commandOk)(`[${DEPLOY_STAGE}] 部署完成: ${input.remoteImage}\n` +
|
|
181
|
+
`[${DEPLOY_STAGE}] 运行示例: docker run -d -p 3000:3000 --name ${docker.imageName}-container ${input.remoteImage}`);
|
|
182
|
+
}
|
|
183
|
+
function normalizeDeployResult(result) {
|
|
184
|
+
if (result.ok) {
|
|
185
|
+
if (result.exitCode === 0) {
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}] 部署执行器返回成功但退出码非零 (exit=${result.exitCode})`, command_result_1.COMMAND_ERROR_CODES.executionFailed, result.exitCode);
|
|
189
|
+
}
|
|
190
|
+
if (result.exitCode !== 0) {
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
return (0, command_result_1.commandError)(result.message ??
|
|
194
|
+
`[${DEPLOY_STAGE}] 部署执行器返回失败但退出码为 0`, result.errorCode ?? command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
195
|
+
}
|
|
196
|
+
function asObject(value, fieldName) {
|
|
197
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
198
|
+
throw new Error(`缺少对象字段: ${fieldName}`);
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
function asOptionalObject(value) {
|
|
203
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
return value;
|
|
207
|
+
}
|
|
208
|
+
function asRequiredString(value, fieldName) {
|
|
209
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
210
|
+
return value.trim();
|
|
211
|
+
}
|
|
212
|
+
throw new Error(`缺少字段: ${fieldName}`);
|
|
213
|
+
}
|
|
214
|
+
function asOptionalString(value) {
|
|
215
|
+
if (typeof value !== "string") {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
const normalized = value.trim();
|
|
219
|
+
return normalized === "" ? undefined : normalized;
|
|
220
|
+
}
|
|
221
|
+
function formatDeployThrownError(error) {
|
|
222
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
223
|
+
return `[${DEPLOY_STAGE}] 部署执行异常: ${message}`;
|
|
224
|
+
}
|
|
225
|
+
function getNonZeroExitCode(error) {
|
|
226
|
+
if (!error || typeof error !== "object") {
|
|
227
|
+
return 1;
|
|
228
|
+
}
|
|
229
|
+
const candidate = error;
|
|
230
|
+
if (typeof candidate.exitCode === "number" && candidate.exitCode !== 0) {
|
|
231
|
+
return candidate.exitCode;
|
|
232
|
+
}
|
|
233
|
+
if (typeof candidate.status === "number" && candidate.status !== 0) {
|
|
234
|
+
return candidate.status;
|
|
235
|
+
}
|
|
236
|
+
return 1;
|
|
237
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createFilesystemCapabilityService = createFilesystemCapabilityService;
|
|
4
|
-
const
|
|
4
|
+
const package_name_1 = require("../domain/package-name");
|
|
5
|
+
const json_with_comments_1 = require("../shared/json-with-comments");
|
|
5
6
|
function createFilesystemCapabilityService(fs, options) {
|
|
6
7
|
return {
|
|
7
8
|
isDirEmpty: (dir) => {
|
|
@@ -15,31 +16,31 @@ function createFilesystemCapabilityService(fs, options) {
|
|
|
15
16
|
const projects = [];
|
|
16
17
|
if (feAppName) {
|
|
17
18
|
projects.push({
|
|
18
|
-
packageName: (0,
|
|
19
|
+
packageName: (0, package_name_1.composePkgName)(feAppName),
|
|
19
20
|
projectFolder: `apps/${feAppName}`,
|
|
20
21
|
reviewCategory: "production",
|
|
21
22
|
});
|
|
22
23
|
}
|
|
23
24
|
if (beAppName) {
|
|
24
25
|
projects.push({
|
|
25
|
-
packageName: (0,
|
|
26
|
+
packageName: (0, package_name_1.composePkgName)(beAppName),
|
|
26
27
|
projectFolder: `servers/${beAppName}`,
|
|
27
28
|
reviewCategory: "production",
|
|
28
29
|
});
|
|
29
30
|
}
|
|
30
31
|
const content = fs.readText(filepath);
|
|
31
|
-
const json = (0,
|
|
32
|
+
const json = (0, json_with_comments_1.parseJsonWithComments)(content);
|
|
32
33
|
json.projects = projects;
|
|
33
34
|
fs.writeText(filepath, JSON.stringify(json, null, 2));
|
|
34
35
|
},
|
|
35
36
|
copyFrontendTemplate: (targetDir, vars) => {
|
|
36
|
-
const appName = (0,
|
|
37
|
+
const appName = (0, package_name_1.composePkgName)(vars.FRONTEND_NAME);
|
|
37
38
|
const resolvedTargetDir = fs.join(targetDir, `apps/${appName}`);
|
|
38
39
|
const source = getTemplateDir(fs, options.templateRootDir, "frontend");
|
|
39
40
|
copyDir(fs, source, resolvedTargetDir, vars);
|
|
40
41
|
},
|
|
41
42
|
copyBackendTemplate: (targetDir, vars) => {
|
|
42
|
-
const appName = (0,
|
|
43
|
+
const appName = (0, package_name_1.composePkgName)(vars.BACKEND_NAME);
|
|
43
44
|
const resolvedTargetDir = fs.join(targetDir, `servers/${appName}`);
|
|
44
45
|
const source = getTemplateDir(fs, options.templateRootDir, "backend");
|
|
45
46
|
copyDir(fs, source, resolvedTargetDir, vars);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseJsonWithComments = parseJsonWithComments;
|
|
4
|
+
function parseJsonWithComments(str) {
|
|
5
|
+
const noBlock = str.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
6
|
+
const noLine = noBlock.replace(/(^|\s)\/\/.*$/gm, "");
|
|
7
|
+
const noTrailingComma = noLine.replace(/,\s*([}\]])/g, "$1");
|
|
8
|
+
return JSON.parse(noTrailingComma);
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"react": "^19.2.0",
|
|
14
14
|
"react-dom": "^19.2.0",
|
|
15
|
-
"listpage-next": "~0.0.
|
|
15
|
+
"listpage-next": "~0.0.295",
|
|
16
16
|
"react-router-dom": ">=6.0.0",
|
|
17
17
|
"@ant-design/v5-patch-for-react-19": "~1.0.3",
|
|
18
18
|
"ahooks": "^3.9.5",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"styled-components": "^6.1.19",
|
|
24
24
|
"mobx": "~6.15.0",
|
|
25
25
|
"@ant-design/icons": "~6.0.2",
|
|
26
|
-
"listpage-components": "~0.0.
|
|
26
|
+
"listpage-components": "~0.0.295",
|
|
27
27
|
"lucide-react": "~0.575.0"
|
|
28
28
|
"mobx-react-lite": "~4.1.1"
|
|
29
29
|
},
|