listpage_cli 0.0.295 → 0.0.296
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 +4 -15
- package/bin/adapters/dockerode-client.js +295 -0
- package/bin/app/parse-args.js +86 -0
- package/bin/cli.js +5 -1
- package/bin/commands/deploy-project-command.js +17 -2
- package/bin/commands/release-project-command.js +24 -0
- package/bin/ports/release-project-command.js +2 -0
- package/bin/services/{artifact-validator.js → build-artifact-validator.js} +2 -2
- package/bin/services/config-value-utils.js +44 -0
- package/bin/services/deploy-project-service.js +303 -150
- package/bin/services/filesystem-capability-service.js +0 -5
- package/bin/services/init-service.js +0 -7
- package/bin/services/release-project-service.js +239 -0
- package/bin/types/deploy-config.js +2 -0
- package/package.json +6 -4
- package/templates/backend-template/package.json.tmpl +1 -1
- package/templates/backend-template/tsconfig.build.json +12 -2
- package/templates/frontend-template/package.json.tmpl +2 -2
- package/templates/rush-template/listpage.config.json.tmpl +89 -0
- package/bin/copy.js +0 -40
- package/bin/prompts.js +0 -170
- package/templates/package-app-template/.gitignore.tmpl +0 -28
- package/templates/package-app-template/README.md +0 -33
- package/templates/package-app-template/package.json +0 -27
- package/templates/package-app-template/src/build.ts +0 -6
- package/templates/package-app-template/src/config.ts.tmpl +0 -45
- package/templates/package-app-template/src/package.ts +0 -5
- package/templates/package-app-template/src/publish.ts +0 -6
- package/templates/package-app-template/tsconfig.json +0 -25
|
@@ -3,10 +3,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.runDeployProjectFlow = runDeployProjectFlow;
|
|
4
4
|
const command_result_1 = require("../domain/command-result");
|
|
5
5
|
const config_loader_1 = require("./config-loader");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const dockerode_client_1 = require("../adapters/dockerode-client");
|
|
7
|
+
const config_value_utils_1 = require("./config-value-utils");
|
|
8
8
|
const DEPLOY_STAGE = "deploy";
|
|
9
|
-
const
|
|
9
|
+
const ANSI = {
|
|
10
|
+
reset: "\u001b[0m",
|
|
11
|
+
dim: "\u001b[2m",
|
|
12
|
+
red: "\u001b[31m",
|
|
13
|
+
green: "\u001b[32m",
|
|
14
|
+
yellow: "\u001b[33m",
|
|
15
|
+
blue: "\u001b[34m",
|
|
16
|
+
magenta: "\u001b[35m",
|
|
17
|
+
cyan: "\u001b[36m",
|
|
18
|
+
};
|
|
19
|
+
const USE_COLOR = process.env.NO_COLOR === undefined;
|
|
10
20
|
const CONFIG_ERROR_CODE_MAP = {
|
|
11
21
|
CONFIG_NOT_FOUND: "E_CONFIG_NOT_FOUND",
|
|
12
22
|
CONFIG_READ_FAILED: "E_CONFIG_READ_FAILED",
|
|
@@ -31,25 +41,22 @@ async function runDeployProjectFlow(deps) {
|
|
|
31
41
|
}
|
|
32
42
|
let executionInput;
|
|
33
43
|
try {
|
|
34
|
-
executionInput =
|
|
44
|
+
executionInput = resolveRuntimeConfig(configResult.config, deps.cliOverrides);
|
|
45
|
+
console.log(`[${DEPLOY_STAGE}][config]`, JSON.stringify(executionInput, null, 2));
|
|
35
46
|
}
|
|
36
47
|
catch (error) {
|
|
37
48
|
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);
|
|
49
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][config] 部署配置无效: ${message}`, "E_CONFIG_REQUIRED_FIELD_MISSING", 1);
|
|
39
50
|
}
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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);
|
|
51
|
+
const createClient = deps.createDockerClient ?? dockerode_client_1.createDockerodeClient;
|
|
52
|
+
let client;
|
|
53
|
+
try {
|
|
54
|
+
client = createClient(executionInput.connection);
|
|
55
|
+
await client.ping();
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][connect] Docker 连接失败: ${message}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
53
60
|
}
|
|
54
61
|
if (deps.executeDeploy) {
|
|
55
62
|
try {
|
|
@@ -57,128 +64,278 @@ async function runDeployProjectFlow(deps) {
|
|
|
57
64
|
projectRoot,
|
|
58
65
|
configPath: configResult.configPath,
|
|
59
66
|
config: configResult.config,
|
|
60
|
-
outputDir: executionInput.outputDir,
|
|
61
67
|
remoteImage: executionInput.remoteImage,
|
|
62
68
|
docker: executionInput.docker,
|
|
69
|
+
deploy: {
|
|
70
|
+
...executionInput.connection,
|
|
71
|
+
containerName: executionInput.runtime.containerName,
|
|
72
|
+
envFilePath: executionInput.runtime.envFilePath,
|
|
73
|
+
env: executionInput.runtime.env,
|
|
74
|
+
ports: executionInput.runtime.ports,
|
|
75
|
+
binds: executionInput.runtime.binds,
|
|
76
|
+
nanoCpus: executionInput.runtime.nanoCpus,
|
|
77
|
+
memory: executionInput.runtime.memory,
|
|
78
|
+
},
|
|
63
79
|
});
|
|
64
80
|
return normalizeDeployResult(result);
|
|
65
81
|
}
|
|
66
82
|
catch (error) {
|
|
67
|
-
|
|
83
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
84
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}] 部署执行异常: ${message}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
68
85
|
}
|
|
69
86
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
+
return runDockerDeploy({
|
|
88
|
+
client,
|
|
89
|
+
projectRoot,
|
|
90
|
+
executionInput,
|
|
91
|
+
resolve: deps.fs.resolve,
|
|
92
|
+
readText: deps.fs.readText,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function resolveRuntimeConfig(config, cliOverrides) {
|
|
96
|
+
const typedConfig = config;
|
|
97
|
+
const dockerRaw = (0, config_value_utils_1.asObject)(typedConfig.docker, "docker");
|
|
98
|
+
const registry = (0, config_value_utils_1.asObject)(dockerRaw.registry, "docker.registry");
|
|
99
|
+
const imageName = (0, config_value_utils_1.asRequiredString)(dockerRaw.imageName, "docker.imageName");
|
|
100
|
+
const profileName = cliOverrides?.profile?.trim() || "dev";
|
|
101
|
+
const profile = resolveProfile(dockerRaw.releaseProfiles, profileName);
|
|
102
|
+
const imageTag = resolveImageTag(cliOverrides?.tag, (0, config_value_utils_1.asOptionalString)(profile.tag), (0, config_value_utils_1.asOptionalString)(dockerRaw.imageTag));
|
|
103
|
+
const containerConfig = resolveContainerConfig((dockerRaw.container ?? {}), profile, cliOverrides);
|
|
104
|
+
const remoteConfig = resolveRemoteConfig(dockerRaw.remote);
|
|
105
|
+
const registryUrl = (0, config_value_utils_1.asRequiredString)(registry.url, "docker.registry.url");
|
|
106
|
+
const registryNamespace = (0, config_value_utils_1.asOptionalString)(registry.namespace);
|
|
107
|
+
const registryUsername = (0, config_value_utils_1.asOptionalString)(registry.username);
|
|
108
|
+
const registryPassword = (0, config_value_utils_1.asOptionalString)(registry.password);
|
|
109
|
+
const repositoryPath = registryNamespace && registryNamespace !== ""
|
|
110
|
+
? `${registryNamespace}/${imageName}`
|
|
111
|
+
: imageName;
|
|
112
|
+
const remoteImage = `${registryUrl}/${repositoryPath}:${imageTag}`;
|
|
113
|
+
const containerName = normalizeContainerName(containerConfig.name || `${imageName}:${imageTag}`);
|
|
114
|
+
if (!containerConfig.ports &&
|
|
115
|
+
(0, config_value_utils_1.asOptionalString)(dockerRaw.container?.port)) {
|
|
116
|
+
throw new Error("旧字段 docker.container.port 不再支持,请改用 docker.container.ports[]");
|
|
117
|
+
}
|
|
87
118
|
return {
|
|
88
|
-
|
|
89
|
-
remoteImage: `${registryUrl}/${registryNamespace}/${imageName}:${imageTag}`,
|
|
119
|
+
remoteImage,
|
|
90
120
|
docker: {
|
|
91
121
|
imageName,
|
|
92
122
|
imageTag,
|
|
93
123
|
registryUrl,
|
|
94
124
|
registryNamespace,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
125
|
+
registryUsername,
|
|
126
|
+
registryPassword,
|
|
127
|
+
},
|
|
128
|
+
connection: remoteConfig,
|
|
129
|
+
runtime: {
|
|
130
|
+
containerName,
|
|
131
|
+
envFilePath: containerConfig.envFile,
|
|
132
|
+
env: containerConfig.env,
|
|
133
|
+
cliEnv: cliOverrides?.env,
|
|
134
|
+
ports: containerConfig.ports,
|
|
135
|
+
binds: containerConfig.volumes,
|
|
136
|
+
nanoCpus: containerConfig.nanoCpus,
|
|
137
|
+
memory: containerConfig.memory,
|
|
98
138
|
},
|
|
99
139
|
};
|
|
100
140
|
}
|
|
101
|
-
function
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
141
|
+
async function runDockerDeploy(input) {
|
|
142
|
+
const { client, executionInput } = input;
|
|
143
|
+
const remoteImage = executionInput.remoteImage;
|
|
144
|
+
logDeployStep("run", `开始部署镜像: ${remoteImage}`);
|
|
145
|
+
try {
|
|
146
|
+
logDeployStep("run", `检查本地镜像是否存在: ${remoteImage}`);
|
|
147
|
+
const exists = await client.imageExists(remoteImage);
|
|
148
|
+
logDeployStep("run", `本地镜像检查结果: exists=${exists ? "yes" : "no"}`);
|
|
149
|
+
if (exists) {
|
|
150
|
+
logDeployStep("run", `删除旧镜像: ${remoteImage}`);
|
|
151
|
+
await client.removeImage(remoteImage);
|
|
152
|
+
logDeployStep("run", `旧镜像删除完成: ${remoteImage}`);
|
|
113
153
|
}
|
|
114
154
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
];
|
|
155
|
+
catch (error) {
|
|
156
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][run] 清理镜像缓存失败: ${toErrorMessage(error)}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
157
|
+
}
|
|
161
158
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
const auth = buildPullAuth(executionInput.docker);
|
|
160
|
+
logDeployStep("run", `开始拉取镜像: ${remoteImage} (auth=${auth ? "yes" : "no"})`);
|
|
161
|
+
await client.pullImage(remoteImage, auth);
|
|
162
|
+
logDeployStep("run", `镜像拉取完成: ${remoteImage}`);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][run] 拉取镜像失败: ${toErrorMessage(error)}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
logDeployStep("run", `检查已有容器: ${executionInput.runtime.containerName}`);
|
|
169
|
+
const existingContainerId = await client.findContainerIdByName(executionInput.runtime.containerName);
|
|
170
|
+
logDeployStep("run", `容器检查结果: existingContainerId=${existingContainerId ?? "none"}`);
|
|
171
|
+
if (existingContainerId) {
|
|
172
|
+
logDeployStep("run", `停止容器: ${existingContainerId}`);
|
|
173
|
+
await client.stopContainer(existingContainerId);
|
|
174
|
+
logDeployStep("run", `删除容器: ${existingContainerId}`);
|
|
175
|
+
await client.removeContainer(existingContainerId);
|
|
176
|
+
logDeployStep("run", `旧容器替换完成: ${existingContainerId}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][run] 替换容器失败: ${toErrorMessage(error)}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
181
|
+
}
|
|
182
|
+
let createContainerInput;
|
|
183
|
+
try {
|
|
184
|
+
createContainerInput = (0, dockerode_client_1.toRuntimeContainerInput)({
|
|
185
|
+
runtime: {
|
|
186
|
+
name: executionInput.runtime.containerName,
|
|
187
|
+
image: remoteImage,
|
|
188
|
+
projectRoot: input.projectRoot,
|
|
189
|
+
envFilePath: executionInput.runtime.envFilePath,
|
|
190
|
+
env: executionInput.runtime.env,
|
|
191
|
+
cliEnv: executionInput.runtime.cliEnv,
|
|
192
|
+
ports: executionInput.runtime.ports,
|
|
193
|
+
binds: executionInput.runtime.binds,
|
|
194
|
+
nanoCpus: executionInput.runtime.nanoCpus,
|
|
195
|
+
memory: executionInput.runtime.memory,
|
|
196
|
+
},
|
|
197
|
+
resolve: input.resolve,
|
|
198
|
+
readText: input.readText,
|
|
199
|
+
});
|
|
200
|
+
logDeployStep("run", `环境变量加载完成: count=${createContainerInput.env.length}`);
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][config] 环境变量文件无效: ${toErrorMessage(error)}`, "E_CONFIG_REQUIRED_FIELD_MISSING", 1);
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
logDeployStep("run", `创建容器参数: ${JSON.stringify(createContainerInput)}`);
|
|
207
|
+
logDeployStep("run", `创建容器: name=${executionInput.runtime.containerName}, image=${remoteImage}`);
|
|
208
|
+
const containerId = await client.createContainer(createContainerInput);
|
|
209
|
+
logDeployStep("run", `容器创建完成: containerId=${containerId}`);
|
|
210
|
+
logDeployStep("run", `启动容器: ${containerId}`);
|
|
211
|
+
await client.startContainer(containerId);
|
|
212
|
+
logDeployStep("run", `容器启动完成: ${containerId}`);
|
|
213
|
+
const containerState = await client.inspectContainer(containerId);
|
|
214
|
+
logDeployStep("run", `容器状态: running=${containerState.running ?? "unknown"}, ` +
|
|
215
|
+
`status=${containerState.status ?? "unknown"}, ` +
|
|
216
|
+
`exitCode=${containerState.exitCode ?? "unknown"}, ` +
|
|
217
|
+
`health=${containerState.health ?? "unknown"}`);
|
|
218
|
+
logDeployStep("run", `容器端口映射: ${containerState.portMappings && containerState.portMappings.length > 0
|
|
219
|
+
? containerState.portMappings.join(", ")
|
|
220
|
+
: "<none>"}`);
|
|
221
|
+
if (containerState.running === false) {
|
|
222
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][run] 容器启动后已退出: ` +
|
|
223
|
+
`status=${containerState.status ?? "unknown"}, ` +
|
|
224
|
+
`exitCode=${containerState.exitCode ?? "unknown"}, ` +
|
|
225
|
+
`error=${containerState.error ?? "none"}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
226
|
+
}
|
|
227
|
+
logDeployStep("run", `拉取容器启动日志: containerId=${containerId}, tail=100`);
|
|
228
|
+
try {
|
|
229
|
+
const startupLogs = await client.getContainerLogs(containerId, 100);
|
|
230
|
+
const trimmed = startupLogs.trim();
|
|
231
|
+
if (trimmed === "") {
|
|
232
|
+
logDeployStep("run", "容器启动日志为空");
|
|
170
233
|
}
|
|
234
|
+
else {
|
|
235
|
+
console.log(colorizeMultiline(`[${DEPLOY_STAGE}][container-logs]\n${trimmed}`));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
logDeployStep("run", `拉取容器启动日志失败(不影响部署结果): ${toErrorMessage(error)}`);
|
|
171
240
|
}
|
|
172
241
|
}
|
|
173
242
|
catch (error) {
|
|
174
|
-
|
|
175
|
-
|
|
243
|
+
return (0, command_result_1.commandError)(`[${DEPLOY_STAGE}][run] 创建或启动容器失败: ${toErrorMessage(error)}`, command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
244
|
+
}
|
|
245
|
+
return (0, command_result_1.commandOk)(`${colorizeDeployLine(`[${DEPLOY_STAGE}] 部署完成: ${remoteImage}`)}\n` +
|
|
246
|
+
`${colorizeDeployLine(`[${DEPLOY_STAGE}] 容器名称: ${executionInput.runtime.containerName}`)}`);
|
|
247
|
+
}
|
|
248
|
+
function buildPullAuth(docker) {
|
|
249
|
+
if (!docker.registryUsername || !docker.registryPassword) {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
username: docker.registryUsername,
|
|
254
|
+
password: docker.registryPassword,
|
|
255
|
+
serveraddress: docker.registryUrl,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function asOptionalNumber(value) {
|
|
259
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
263
|
+
const parsed = Number(value);
|
|
264
|
+
if (Number.isFinite(parsed)) {
|
|
265
|
+
return parsed;
|
|
176
266
|
}
|
|
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
267
|
}
|
|
180
|
-
return
|
|
181
|
-
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
function resolveProfile(profiles, profileName) {
|
|
271
|
+
const profile = profiles && typeof profiles === "object" && !Array.isArray(profiles)
|
|
272
|
+
? profiles[profileName]
|
|
273
|
+
: undefined;
|
|
274
|
+
if (!profile) {
|
|
275
|
+
throw new Error(`缺少字段: docker.releaseProfiles.${profileName}`);
|
|
276
|
+
}
|
|
277
|
+
return profile;
|
|
278
|
+
}
|
|
279
|
+
function resolveImageTag(cliTag, profileTag, fallbackTag) {
|
|
280
|
+
const normalizedCli = cliTag?.trim();
|
|
281
|
+
if (normalizedCli) {
|
|
282
|
+
return normalizedCli;
|
|
283
|
+
}
|
|
284
|
+
if (profileTag && profileTag.trim() !== "") {
|
|
285
|
+
return profileTag;
|
|
286
|
+
}
|
|
287
|
+
if (fallbackTag && fallbackTag.trim() !== "") {
|
|
288
|
+
return fallbackTag;
|
|
289
|
+
}
|
|
290
|
+
throw new Error("缺少字段: docker.releaseProfiles.<profile>.tag");
|
|
291
|
+
}
|
|
292
|
+
function resolveContainerConfig(baseContainer, profile, cliOverrides) {
|
|
293
|
+
const containerOverrides = ((0, config_value_utils_1.asOptionalObject)(profile.containerOverrides) ??
|
|
294
|
+
{});
|
|
295
|
+
const mergedEnv = {
|
|
296
|
+
...(baseContainer.env ?? {}),
|
|
297
|
+
...(containerOverrides.env ?? {}),
|
|
298
|
+
};
|
|
299
|
+
const merged = {
|
|
300
|
+
...baseContainer,
|
|
301
|
+
...containerOverrides,
|
|
302
|
+
...(Object.keys(mergedEnv).length > 0 ? { env: mergedEnv } : {}),
|
|
303
|
+
};
|
|
304
|
+
if (profile.envFile && profile.envFile.trim() !== "") {
|
|
305
|
+
merged.envFile = profile.envFile.trim();
|
|
306
|
+
}
|
|
307
|
+
if (cliOverrides?.env && Object.keys(cliOverrides.env).length > 0) {
|
|
308
|
+
merged.env = {
|
|
309
|
+
...(merged.env ?? {}),
|
|
310
|
+
...cliOverrides.env,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return merged;
|
|
314
|
+
}
|
|
315
|
+
function resolveRemoteConfig(remote) {
|
|
316
|
+
const remoteObj = ((0, config_value_utils_1.asOptionalObject)(remote) ?? {});
|
|
317
|
+
const protocol = (remoteObj.protocol ?? "https").toLowerCase();
|
|
318
|
+
if (!["http", "https"].includes(protocol)) {
|
|
319
|
+
throw new Error("docker.remote.protocol 仅支持 http 或 https");
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
socketPath: (0, config_value_utils_1.asOptionalString)(remoteObj.socketPath),
|
|
323
|
+
host: (0, config_value_utils_1.asOptionalString)(remoteObj.host) ?? "127.0.0.1",
|
|
324
|
+
port: asOptionalNumber(remoteObj.port) ?? 2376,
|
|
325
|
+
protocol,
|
|
326
|
+
ca: (0, config_value_utils_1.asOptionalString)(remoteObj.tls?.ca),
|
|
327
|
+
cert: (0, config_value_utils_1.asOptionalString)(remoteObj.tls?.cert),
|
|
328
|
+
key: (0, config_value_utils_1.asOptionalString)(remoteObj.tls?.key),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function toErrorMessage(error) {
|
|
332
|
+
if (error instanceof Error) {
|
|
333
|
+
return error.message;
|
|
334
|
+
}
|
|
335
|
+
return String(error ?? "unknown");
|
|
336
|
+
}
|
|
337
|
+
function normalizeContainerName(raw) {
|
|
338
|
+
return raw.replace(/[^a-zA-Z0-9_.-]/g, "-");
|
|
182
339
|
}
|
|
183
340
|
function normalizeDeployResult(result) {
|
|
184
341
|
if (result.ok) {
|
|
@@ -190,48 +347,44 @@ function normalizeDeployResult(result) {
|
|
|
190
347
|
if (result.exitCode !== 0) {
|
|
191
348
|
return result;
|
|
192
349
|
}
|
|
193
|
-
return (0, command_result_1.commandError)(result.message ??
|
|
194
|
-
`[${DEPLOY_STAGE}] 部署执行器返回失败但退出码为 0`, result.errorCode ?? command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
350
|
+
return (0, command_result_1.commandError)(result.message ?? `[${DEPLOY_STAGE}] 部署执行器返回失败但退出码为 0`, result.errorCode ?? command_result_1.COMMAND_ERROR_CODES.executionFailed, 1);
|
|
195
351
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
throw new Error(`缺少对象字段: ${fieldName}`);
|
|
199
|
-
}
|
|
200
|
-
return value;
|
|
352
|
+
function logDeployStep(stage, message) {
|
|
353
|
+
console.log(colorizeDeployLine(`[${DEPLOY_STAGE}][${stage}] ${message}`));
|
|
201
354
|
}
|
|
202
|
-
function
|
|
203
|
-
if (!
|
|
204
|
-
return
|
|
355
|
+
function colorizeDeployLine(line) {
|
|
356
|
+
if (!USE_COLOR || line.trim() === "") {
|
|
357
|
+
return line;
|
|
205
358
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
function asRequiredString(value, fieldName) {
|
|
209
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
210
|
-
return value.trim();
|
|
359
|
+
if (line.startsWith("[deploy][run]")) {
|
|
360
|
+
return wrapColor(line, ANSI.cyan);
|
|
211
361
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
function asOptionalString(value) {
|
|
215
|
-
if (typeof value !== "string") {
|
|
216
|
-
return undefined;
|
|
362
|
+
if (line.startsWith("[deploy][container-logs]")) {
|
|
363
|
+
return wrapColor(line, ANSI.yellow);
|
|
217
364
|
}
|
|
218
|
-
|
|
219
|
-
|
|
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;
|
|
365
|
+
if (line.startsWith("[deploy][config]")) {
|
|
366
|
+
return wrapColor(line, ANSI.blue);
|
|
228
367
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
return candidate.exitCode;
|
|
368
|
+
if (line.startsWith("[deploy][connect]")) {
|
|
369
|
+
return wrapColor(line, ANSI.magenta);
|
|
232
370
|
}
|
|
233
|
-
if (
|
|
234
|
-
return
|
|
371
|
+
if (line.startsWith("[deploy] 部署完成")) {
|
|
372
|
+
return wrapColor(line, ANSI.green);
|
|
235
373
|
}
|
|
236
|
-
|
|
374
|
+
if (line.startsWith("[deploy] 容器名称")) {
|
|
375
|
+
return wrapColor(line, ANSI.dim);
|
|
376
|
+
}
|
|
377
|
+
if (line.startsWith("[deploy]")) {
|
|
378
|
+
return wrapColor(line, ANSI.blue);
|
|
379
|
+
}
|
|
380
|
+
return line;
|
|
381
|
+
}
|
|
382
|
+
function colorizeMultiline(text) {
|
|
383
|
+
return text
|
|
384
|
+
.split("\n")
|
|
385
|
+
.map((line) => colorizeDeployLine(line))
|
|
386
|
+
.join("\n");
|
|
387
|
+
}
|
|
388
|
+
function wrapColor(text, color) {
|
|
389
|
+
return `${color}${text}${ANSI.reset}`;
|
|
237
390
|
}
|
|
@@ -45,11 +45,6 @@ function createFilesystemCapabilityService(fs, options) {
|
|
|
45
45
|
const source = getTemplateDir(fs, options.templateRootDir, "backend");
|
|
46
46
|
copyDir(fs, source, resolvedTargetDir, vars);
|
|
47
47
|
},
|
|
48
|
-
copyDeployScriptTemplate: (targetDir, vars) => {
|
|
49
|
-
const resolvedTargetDir = fs.join(targetDir, "common", "scripts", "package-app");
|
|
50
|
-
const source = getTemplateDir(fs, options.templateRootDir, "package-app");
|
|
51
|
-
copyDir(fs, source, resolvedTargetDir, vars);
|
|
52
|
-
},
|
|
53
48
|
copySkillDir: (sourceDir, targetDir) => {
|
|
54
49
|
syncDirWithRename(fs, sourceDir, targetDir);
|
|
55
50
|
},
|
|
@@ -34,13 +34,6 @@ async function runInitRushFlow(deps, targetDir, projectName) {
|
|
|
34
34
|
FRONTEND_NAME: frontendName,
|
|
35
35
|
};
|
|
36
36
|
deps.files.copyRushTemplate(targetDir, vars);
|
|
37
|
-
const installDeployScriptResult = await deps.prompts.askInstallDeployScript();
|
|
38
|
-
if (installDeployScriptResult.status !== "value") {
|
|
39
|
-
return mapInteractionToCommandResult(installDeployScriptResult, "部署脚本确认");
|
|
40
|
-
}
|
|
41
|
-
if (installDeployScriptResult.value) {
|
|
42
|
-
deps.files.copyDeployScriptTemplate(targetDir, vars);
|
|
43
|
-
}
|
|
44
37
|
deps.files.updateRushJsonProjects(deps.fs.join(targetDir, "rush.json"), frontendName, backendName);
|
|
45
38
|
if (frontendName) {
|
|
46
39
|
deps.files.copyFrontendTemplate(targetDir, vars);
|