listpage_cli 0.0.294 → 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.
Files changed (32) hide show
  1. package/bin/{prompts.js → adapters/cli-interaction.js} +6 -15
  2. package/bin/adapters/dockerode-client.js +295 -0
  3. package/bin/app/parse-args.js +92 -1
  4. package/bin/cli.js +16 -7
  5. package/bin/commands/deploy-project-command.js +24 -0
  6. package/bin/commands/release-project-command.js +24 -0
  7. package/bin/domain/package-name.js +12 -0
  8. package/bin/ports/release-project-command.js +2 -0
  9. package/bin/services/build-artifact-validator.js +47 -0
  10. package/bin/services/command-runner.js +4 -3
  11. package/bin/services/config-value-utils.js +44 -0
  12. package/bin/services/deploy-project-service.js +390 -0
  13. package/bin/services/filesystem-capability-service.js +7 -11
  14. package/bin/services/init-service.js +0 -7
  15. package/bin/services/release-project-service.js +239 -0
  16. package/bin/shared/json-with-comments.js +9 -0
  17. package/bin/types/deploy-config.js +2 -0
  18. package/package.json +6 -4
  19. package/templates/backend-template/package.json.tmpl +1 -1
  20. package/templates/backend-template/tsconfig.build.json +12 -2
  21. package/templates/frontend-template/package.json.tmpl +2 -2
  22. package/templates/rush-template/listpage.config.json.tmpl +89 -0
  23. package/bin/copy.js +0 -40
  24. package/templates/package-app-template/.gitignore.tmpl +0 -28
  25. package/templates/package-app-template/README.md +0 -33
  26. package/templates/package-app-template/package.json +0 -27
  27. package/templates/package-app-template/src/build.ts +0 -6
  28. package/templates/package-app-template/src/config.ts.tmpl +0 -45
  29. package/templates/package-app-template/src/package.ts +0 -5
  30. package/templates/package-app-template/src/publish.ts +0 -6
  31. package/templates/package-app-template/tsconfig.json +0 -25
  32. /package/bin/{services/artifact-validator.js → ports/deploy-project-command.js} +0 -0
@@ -7,13 +7,12 @@ exports.runPrompt = runPrompt;
7
7
  exports.askRushQuestions = askRushQuestions;
8
8
  exports.askProjectPath = askProjectPath;
9
9
  exports.askOverwrite = askOverwrite;
10
- exports.askInstallDeployScript = askInstallDeployScript;
11
10
  exports.printHelp = printHelp;
12
11
  exports.printVersion = printVersion;
13
12
  const enquirer_1 = require("enquirer");
14
13
  const fs_1 = require("fs");
15
14
  const path_1 = __importDefault(require("path"));
16
- const interaction_result_1 = require("./domain/interaction-result");
15
+ const interaction_result_1 = require("../domain/interaction-result");
17
16
  let promptTestScript = null;
18
17
  let promptTestScriptIndex = 0;
19
18
  function getTestPromptMode() {
@@ -135,18 +134,6 @@ async function askOverwrite() {
135
134
  }
136
135
  return (0, interaction_result_1.interactionValue)(result.value.ok);
137
136
  }
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
137
  function printHelp() {
151
138
  const h = [
152
139
  "用法: listpage_cli init",
@@ -155,12 +142,16 @@ function printHelp() {
155
142
  "说明: 安装技能到 Cursor。默认 skillName 为 test,安装到当前命令执行目录的 .cursor/skills/",
156
143
  "用法: listpage_cli build-project",
157
144
  "说明: 非交互校验当前目录是否为有效项目根(需存在 listpage.config.json)",
145
+ "用法: listpage_cli release-project [tag] [--profile dev] [--platform linux/amd64] [--env KEY=VALUE]",
146
+ "说明: 先校验 .listpage/output 产物,再按 login/build/tag/push 执行 Docker 发布;参数优先级: CLI > profile > base",
147
+ "用法: listpage_cli deploy-project [tag] [--profile dev] [--platform linux/amd64] [--env KEY=VALUE]",
148
+ "说明: 使用 docker.remote + docker.container 执行部署,支持 ports[] 与 envFile/env 合并;参数优先级: CLI > profile > base",
158
149
  ].join("\n");
159
150
  console.log(h);
160
151
  }
161
152
  function printVersion() {
162
153
  try {
163
- const p = path_1.default.join(__dirname, "..", "package.json");
154
+ const p = path_1.default.join(__dirname, "..", "..", "package.json");
164
155
  const pkg = JSON.parse((0, fs_1.readFileSync)(p, "utf8"));
165
156
  console.log(pkg.version || "");
166
157
  }
@@ -0,0 +1,295 @@
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.createDockerodeClient = void 0;
7
+ exports.toDockerConnectionOptions = toDockerConnectionOptions;
8
+ exports.parsePorts = parsePorts;
9
+ exports.loadEnvFromFile = loadEnvFromFile;
10
+ exports.mergeEnv = mergeEnv;
11
+ exports.toRuntimeContainerInput = toRuntimeContainerInput;
12
+ const dockerode_1 = __importDefault(require("dockerode"));
13
+ const node_fs_1 = require("node:fs");
14
+ const createDockerodeClient = (config) => {
15
+ const options = toDockerConnectionOptions(config);
16
+ const client = new dockerode_1.default(options);
17
+ return {
18
+ ping: async () => {
19
+ await client.ping();
20
+ },
21
+ imageExists: async (image) => {
22
+ try {
23
+ await client.getImage(image).inspect();
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ },
30
+ removeImage: async (image) => {
31
+ await client.getImage(image).remove({ force: true });
32
+ },
33
+ pullImage: async (image, auth) => {
34
+ const stream = await new Promise((resolve, reject) => {
35
+ const pullOptions = auth ? { authconfig: auth } : undefined;
36
+ client.pull(image, pullOptions, (err, output) => {
37
+ if (err || !output) {
38
+ reject(err ?? new Error("docker pull 返回空输出"));
39
+ return;
40
+ }
41
+ resolve(output);
42
+ });
43
+ });
44
+ await new Promise((resolve, reject) => {
45
+ client.modem.followProgress(stream, (err) => {
46
+ if (err) {
47
+ reject(err);
48
+ return;
49
+ }
50
+ resolve();
51
+ }, () => undefined);
52
+ });
53
+ },
54
+ findContainerIdByName: async (name) => {
55
+ const containers = await client.listContainers({ all: true });
56
+ const matched = containers.find((item) => (item.Names ?? []).some((n) => n === `/${name}`));
57
+ return matched?.Id;
58
+ },
59
+ stopContainer: async (id) => {
60
+ const container = client.getContainer(id);
61
+ try {
62
+ await container.stop();
63
+ }
64
+ catch {
65
+ // Container may already be stopped.
66
+ }
67
+ },
68
+ removeContainer: async (id) => {
69
+ const container = client.getContainer(id);
70
+ await container.remove({ force: true });
71
+ },
72
+ createContainer: async (input) => {
73
+ const payload = toContainerCreateOptions(input);
74
+ const container = await client.createContainer(payload);
75
+ return container.id;
76
+ },
77
+ startContainer: async (id) => {
78
+ const container = client.getContainer(id);
79
+ await container.start();
80
+ },
81
+ inspectContainer: async (id) => {
82
+ const container = client.getContainer(id);
83
+ const inspectInfo = await container.inspect();
84
+ const state = inspectInfo?.State;
85
+ const portMappings = formatPortMappings(inspectInfo);
86
+ return {
87
+ running: state?.Running,
88
+ status: state?.Status,
89
+ exitCode: state?.ExitCode,
90
+ error: state?.Error,
91
+ startedAt: state?.StartedAt,
92
+ finishedAt: state?.FinishedAt,
93
+ health: state?.Health?.Status,
94
+ portMappings,
95
+ };
96
+ },
97
+ getContainerLogs: async (id, tail = 100) => {
98
+ const container = client.getContainer(id);
99
+ const logs = await container.logs({
100
+ stdout: true,
101
+ stderr: true,
102
+ timestamps: true,
103
+ follow: false,
104
+ tail,
105
+ });
106
+ if (typeof logs === "string") {
107
+ return logs;
108
+ }
109
+ return logs.toString("utf8");
110
+ },
111
+ };
112
+ };
113
+ exports.createDockerodeClient = createDockerodeClient;
114
+ function toDockerConnectionOptions(config) {
115
+ const protocol = (config.protocol ?? "https").toLowerCase();
116
+ const isHttps = protocol === "https";
117
+ const options = {
118
+ host: config.host ?? "127.0.0.1",
119
+ port: config.port ?? 2376,
120
+ protocol,
121
+ };
122
+ const socketPath = (config.socketPath ?? "").trim();
123
+ if (socketPath !== "") {
124
+ options.socketPath = socketPath;
125
+ }
126
+ if (isHttps) {
127
+ const tlsOptions = {
128
+ ca: asOptionalTlsBuffer(config.ca),
129
+ cert: asOptionalTlsBuffer(config.cert),
130
+ key: asOptionalTlsBuffer(config.key),
131
+ };
132
+ if (tlsOptions.ca) {
133
+ options.ca = tlsOptions.ca;
134
+ }
135
+ if (tlsOptions.cert) {
136
+ options.cert = tlsOptions.cert;
137
+ }
138
+ if (tlsOptions.key) {
139
+ options.key = tlsOptions.key;
140
+ }
141
+ options.checkServerIdentity = () => undefined;
142
+ }
143
+ return options;
144
+ }
145
+ function parsePorts(ports) {
146
+ if (!ports || ports.length === 0) {
147
+ return {};
148
+ }
149
+ const exposedPorts = {};
150
+ const portBindings = {};
151
+ for (const rawPort of ports) {
152
+ const normalized = rawPort.trim();
153
+ if (normalized === "") {
154
+ continue;
155
+ }
156
+ const [hostAndContainer, protocolPart] = normalized.split("/");
157
+ const protocol = protocolPart?.trim() === "udp" ? "udp" : "tcp";
158
+ const segments = hostAndContainer.split(":").map((part) => part.trim());
159
+ if (segments.length !== 2 || !segments[0] || !segments[1]) {
160
+ throw new Error(`container.ports 配置无效: ${rawPort}`);
161
+ }
162
+ const hostPort = segments[0];
163
+ const containerPort = segments[1];
164
+ const key = `${containerPort}/${protocol}`;
165
+ exposedPorts[key] = {};
166
+ portBindings[key] = [{ HostPort: hostPort }];
167
+ }
168
+ return {
169
+ exposedPorts: Object.keys(exposedPorts).length > 0 ? exposedPorts : undefined,
170
+ portBindings: Object.keys(portBindings).length > 0 ? portBindings : undefined,
171
+ };
172
+ }
173
+ function loadEnvFromFile(input) {
174
+ if (!input.envFilePath) {
175
+ return {};
176
+ }
177
+ const targetPath = input.resolve(input.projectRoot, input.envFilePath);
178
+ const raw = input.readText(targetPath);
179
+ const result = {};
180
+ for (const line of raw.split(/\r?\n/)) {
181
+ const normalized = line.trim();
182
+ if (normalized === "" || normalized.startsWith("#")) {
183
+ continue;
184
+ }
185
+ const eqIndex = normalized.indexOf("=");
186
+ if (eqIndex <= 0) {
187
+ continue;
188
+ }
189
+ const key = normalized.slice(0, eqIndex).trim();
190
+ const value = normalized.slice(eqIndex + 1);
191
+ if (key !== "") {
192
+ result[key] = value;
193
+ }
194
+ }
195
+ return result;
196
+ }
197
+ function mergeEnv(...inputs) {
198
+ const merged = {};
199
+ for (const input of inputs) {
200
+ if (!input) {
201
+ continue;
202
+ }
203
+ for (const [key, value] of Object.entries(input)) {
204
+ if (key.trim() === "") {
205
+ continue;
206
+ }
207
+ merged[key] = value;
208
+ }
209
+ }
210
+ return Object.entries(merged).map(([key, value]) => `${key}=${value}`);
211
+ }
212
+ function toRuntimeContainerInput(input) {
213
+ const envFromFile = loadEnvFromFile({
214
+ projectRoot: input.runtime.projectRoot,
215
+ envFilePath: input.runtime.envFilePath,
216
+ resolve: input.resolve,
217
+ readText: input.readText,
218
+ });
219
+ const env = mergeEnv(envFromFile, input.runtime.env, input.runtime.cliEnv);
220
+ const ports = parsePorts(input.runtime.ports);
221
+ return {
222
+ name: input.runtime.name,
223
+ image: input.runtime.image,
224
+ env,
225
+ exposedPorts: ports.exposedPorts,
226
+ portBindings: ports.portBindings,
227
+ binds: input.runtime.binds,
228
+ nanoCpus: input.runtime.nanoCpus,
229
+ memory: input.runtime.memory,
230
+ };
231
+ }
232
+ function toContainerCreateOptions(input) {
233
+ const hostConfig = {};
234
+ let hasHostConfig = false;
235
+ if (input.portBindings) {
236
+ hostConfig.PortBindings = input.portBindings;
237
+ hasHostConfig = true;
238
+ }
239
+ if (input.binds) {
240
+ hostConfig.Binds = input.binds;
241
+ hasHostConfig = true;
242
+ }
243
+ if (typeof input.nanoCpus === "number") {
244
+ hostConfig.NanoCpus = input.nanoCpus;
245
+ hasHostConfig = true;
246
+ }
247
+ if (typeof input.memory === "number") {
248
+ hostConfig.Memory = Number(input.memory) * 1024 * 1024;
249
+ hasHostConfig = true;
250
+ }
251
+ const payload = {
252
+ name: input.name,
253
+ Image: input.image,
254
+ Env: input.env,
255
+ };
256
+ if (input.exposedPorts) {
257
+ payload.ExposedPorts = input.exposedPorts;
258
+ }
259
+ if (hasHostConfig) {
260
+ payload.HostConfig = hostConfig;
261
+ }
262
+ return payload;
263
+ }
264
+ function asOptionalTlsBuffer(value) {
265
+ if (typeof value !== "string") {
266
+ return undefined;
267
+ }
268
+ const normalized = value.trim();
269
+ if (normalized === "") {
270
+ return undefined;
271
+ }
272
+ if ((0, node_fs_1.existsSync)(normalized)) {
273
+ return (0, node_fs_1.readFileSync)(normalized);
274
+ }
275
+ return Buffer.from(normalized);
276
+ }
277
+ function formatPortMappings(inspectInfo) {
278
+ const ports = inspectInfo?.NetworkSettings?.Ports;
279
+ if (!ports || typeof ports !== "object") {
280
+ return undefined;
281
+ }
282
+ const mappings = [];
283
+ for (const [containerPort, hostBindings] of Object.entries(ports)) {
284
+ if (!hostBindings || hostBindings.length === 0) {
285
+ mappings.push(`${containerPort} -> <not-published>`);
286
+ continue;
287
+ }
288
+ for (const binding of hostBindings) {
289
+ const hostIp = binding.HostIp || "0.0.0.0";
290
+ const hostPort = binding.HostPort || "<unknown>";
291
+ mappings.push(`${containerPort} -> ${hostIp}:${hostPort}`);
292
+ }
293
+ }
294
+ return mappings.length > 0 ? mappings : undefined;
295
+ }
@@ -1,7 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseArgs = parseArgs;
4
- const KNOWN_COMMANDS = new Set(["init", "install-skill", "build-project"]);
4
+ exports.parseCommandOptions = parseCommandOptions;
5
+ exports.parseCommandPositionals = parseCommandPositionals;
6
+ const KNOWN_COMMANDS = new Set([
7
+ "init",
8
+ "install-skill",
9
+ "build-project",
10
+ "release-project",
11
+ "deploy-project",
12
+ ]);
5
13
  function parseArgs(argv) {
6
14
  const rawArgs = [...argv];
7
15
  const commandToken = rawArgs[0];
@@ -32,3 +40,86 @@ function parseArgs(argv) {
32
40
  positionals,
33
41
  };
34
42
  }
43
+ function parseCommandOptions(rawArgs) {
44
+ const args = rawArgs.slice(1);
45
+ const profile = readSingleOption(args, "profile");
46
+ const platform = readSingleOption(args, "platform");
47
+ const envEntries = readMultiOption(args, "env");
48
+ const env = parseEnvEntries(envEntries);
49
+ return {
50
+ profile,
51
+ platform,
52
+ env: Object.keys(env).length > 0 ? env : undefined,
53
+ };
54
+ }
55
+ function parseCommandPositionals(rawArgs) {
56
+ const args = rawArgs.slice(1);
57
+ const positionals = [];
58
+ const optionsWithValue = new Set(["--profile", "--platform", "--env"]);
59
+ for (let index = 0; index < args.length; index += 1) {
60
+ const token = args[index];
61
+ if (token.startsWith("--")) {
62
+ if (token.includes("=")) {
63
+ continue;
64
+ }
65
+ if (optionsWithValue.has(token)) {
66
+ index += 1;
67
+ }
68
+ continue;
69
+ }
70
+ if (token.startsWith("-")) {
71
+ continue;
72
+ }
73
+ positionals.push(token);
74
+ }
75
+ return positionals;
76
+ }
77
+ function readSingleOption(args, optionName) {
78
+ const values = readMultiOption(args, optionName);
79
+ const last = values.at(-1);
80
+ if (typeof last !== "string") {
81
+ return undefined;
82
+ }
83
+ const normalized = last.trim();
84
+ return normalized === "" ? undefined : normalized;
85
+ }
86
+ function readMultiOption(args, optionName) {
87
+ const values = [];
88
+ const longFlag = `--${optionName}`;
89
+ const prefix = `${longFlag}=`;
90
+ for (let index = 0; index < args.length; index += 1) {
91
+ const token = args[index];
92
+ if (token === longFlag) {
93
+ const next = args[index + 1];
94
+ if (typeof next === "string" && !next.startsWith("-")) {
95
+ values.push(next);
96
+ index += 1;
97
+ }
98
+ continue;
99
+ }
100
+ if (token.startsWith(prefix)) {
101
+ values.push(token.slice(prefix.length));
102
+ }
103
+ }
104
+ return values;
105
+ }
106
+ function parseEnvEntries(entries) {
107
+ const result = {};
108
+ for (const entry of entries) {
109
+ const normalized = entry.trim();
110
+ if (normalized === "") {
111
+ continue;
112
+ }
113
+ const eqIndex = normalized.indexOf("=");
114
+ if (eqIndex <= 0) {
115
+ continue;
116
+ }
117
+ const key = normalized.slice(0, eqIndex).trim();
118
+ const value = normalized.slice(eqIndex + 1);
119
+ if (key === "") {
120
+ continue;
121
+ }
122
+ result[key] = value;
123
+ }
124
+ return result;
125
+ }
package/bin/cli.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- const prompts_1 = require("./prompts");
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 release_project_command_1 = require("./commands/release-project-command");
11
+ const deploy_project_command_1 = require("./commands/deploy-project-command");
10
12
  const node_fs_adapter_1 = require("./adapters/node-fs-adapter");
11
13
  const filesystem_capability_service_1 = require("./services/filesystem-capability-service");
12
14
  const fsAdapter = (0, node_fs_adapter_1.createNodeFsAdapter)();
@@ -15,10 +17,9 @@ const filesystemCapability = (0, filesystem_capability_service_1.createFilesyste
15
17
  });
16
18
  const initCommandHandler = (0, init_command_1.createInitCommandHandler)({
17
19
  prompts: {
18
- askProjectPath: prompts_1.askProjectPath,
19
- askOverwrite: prompts_1.askOverwrite,
20
- askRushQuestions: prompts_1.askRushQuestions,
21
- askInstallDeployScript: prompts_1.askInstallDeployScript,
20
+ askProjectPath: cli_interaction_1.askProjectPath,
21
+ askOverwrite: cli_interaction_1.askOverwrite,
22
+ askRushQuestions: cli_interaction_1.askRushQuestions,
22
23
  },
23
24
  fs: fsAdapter,
24
25
  files: filesystemCapability,
@@ -34,14 +35,22 @@ 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 releaseProjectCommandHandler = (0, release_project_command_1.createReleaseProjectCommandHandler)({
39
+ fs: fsAdapter,
40
+ });
41
+ const deployProjectCommandHandler = (0, deploy_project_command_1.createDeployProjectCommandHandler)({
42
+ fs: fsAdapter,
43
+ });
37
44
  async function main() {
38
45
  const result = await (0, execute_1.executeCommand)(process.argv.slice(2), {
39
- printHelp: prompts_1.printHelp,
40
- printVersion: prompts_1.printVersion,
46
+ printHelp: cli_interaction_1.printHelp,
47
+ printVersion: cli_interaction_1.printVersion,
41
48
  handlers: {
42
49
  init: initCommandHandler,
43
50
  "install-skill": installSkillCommandHandler,
44
51
  "build-project": buildProjectCommandHandler,
52
+ "release-project": releaseProjectCommandHandler,
53
+ "deploy-project": deployProjectCommandHandler,
45
54
  },
46
55
  });
47
56
  applyCommandResult(result);
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDeployProjectCommandHandler = createDeployProjectCommandHandler;
4
+ const parse_args_1 = require("../app/parse-args");
5
+ const deploy_project_service_1 = require("../services/deploy-project-service");
6
+ function createDeployProjectCommandHandler(deps) {
7
+ return async (input) => {
8
+ const firstPositional = (0, parse_args_1.parseCommandPositionals)(input.rawArgs)[0];
9
+ const tag = typeof firstPositional === "string" && firstPositional.trim() !== ""
10
+ ? firstPositional.trim()
11
+ : undefined;
12
+ const options = (0, parse_args_1.parseCommandOptions)(input.rawArgs);
13
+ const cliOverrides = {
14
+ tag,
15
+ profile: options.profile,
16
+ platform: options.platform,
17
+ env: options.env,
18
+ };
19
+ return (0, deploy_project_service_1.runDeployProjectFlow)({
20
+ ...deps,
21
+ cliOverrides,
22
+ });
23
+ };
24
+ }
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createReleaseProjectCommandHandler = createReleaseProjectCommandHandler;
4
+ const parse_args_1 = require("../app/parse-args");
5
+ const release_project_service_1 = require("../services/release-project-service");
6
+ function createReleaseProjectCommandHandler(deps) {
7
+ return async (input) => {
8
+ const firstPositional = (0, parse_args_1.parseCommandPositionals)(input.rawArgs)[0];
9
+ const tag = typeof firstPositional === "string" && firstPositional.trim() !== ""
10
+ ? firstPositional.trim()
11
+ : undefined;
12
+ const options = (0, parse_args_1.parseCommandOptions)(input.rawArgs);
13
+ const cliOverrides = {
14
+ tag,
15
+ profile: options.profile,
16
+ platform: options.platform,
17
+ env: options.env,
18
+ };
19
+ return (0, release_project_service_1.runReleaseProjectFlow)({
20
+ ...deps,
21
+ cliOverrides,
22
+ });
23
+ };
24
+ }
@@ -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,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createBuildArtifactValidator = createBuildArtifactValidator;
4
+ const ARTIFACT_STAGE = "artifact";
5
+ function createBuildArtifactValidator(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, `[${exports.COMMAND_RUNNER_STAGE}][${code}] ${messagePrefix}: ${input.command} (${output.error.message})`);
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,