@infly/libs 2.0.40 → 2.0.42

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.
@@ -0,0 +1,5 @@
1
+ function shouldManageBuildScripts(inflyConfig) {
2
+ return inflyConfig?.buildConfigs?.manageScripts !== false;
3
+ }
4
+
5
+ module.exports = { shouldManageBuildScripts };
@@ -0,0 +1,16 @@
1
+ const assert = require("node:assert/strict");
2
+ const test = require("node:test");
3
+
4
+ const { shouldManageBuildScripts } = require("./script-management");
5
+
6
+ test("shouldManageBuildScripts defaults to existing automatic script management", () => {
7
+ assert.equal(shouldManageBuildScripts(), true);
8
+ assert.equal(shouldManageBuildScripts({ buildConfigs: {} }), true);
9
+ });
10
+
11
+ test("shouldManageBuildScripts allows a project to own its build scripts", () => {
12
+ assert.equal(
13
+ shouldManageBuildScripts({ buildConfigs: { manageScripts: false } }),
14
+ false,
15
+ );
16
+ });
@@ -0,0 +1,74 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+
4
+ const {
5
+ EXIT_SELECTION,
6
+ selectOption: defaultSelectOption,
7
+ } = require("../../tools/select-option");
8
+ const { composeRelease: defaultComposeRelease } = require("./compose-release");
9
+
10
+ function loadTargetConfig(configPath) {
11
+ const resolvedPath = path.resolve(configPath);
12
+ delete require.cache[require.resolve(resolvedPath)];
13
+ return {
14
+ config: require(resolvedPath),
15
+ configDir: path.dirname(resolvedPath),
16
+ };
17
+ }
18
+
19
+ function loadPackageTargetConfig(cwd = process.cwd()) {
20
+ const packagePath = path.resolve(cwd, "package.json");
21
+ if (!fs.existsSync(packagePath)) {
22
+ throw new Error(`Package config not found: ${packagePath}`);
23
+ }
24
+ delete require.cache[require.resolve(packagePath)];
25
+ const packageJson = require(packagePath);
26
+ return {
27
+ config: packageJson.infly?.docker,
28
+ configDir: path.dirname(packagePath),
29
+ };
30
+ }
31
+
32
+ async function runComposeCommand(options = {}, dependencies = {}) {
33
+ const composeRelease = dependencies.composeRelease || defaultComposeRelease;
34
+ const usesDirectConfig = !options.config
35
+ && !options.configObject
36
+ && (options.projectDir || options.file);
37
+ if (usesDirectConfig) return composeRelease(options);
38
+
39
+ const loaded = options.config
40
+ ? loadTargetConfig(options.config)
41
+ : options.configObject
42
+ ? { config: options.configObject, configDir: options.configDir || process.cwd() }
43
+ : loadPackageTargetConfig(options.cwd || process.cwd());
44
+ const targets = loaded.config?.targets;
45
+ if (!targets || Object.keys(targets).length === 0) {
46
+ throw new Error("Docker target config must define at least one target.");
47
+ }
48
+
49
+ const selectOption = dependencies.selectOption || defaultSelectOption;
50
+ const log = dependencies.log || console.log;
51
+ const targetName = options.target || await selectOption(
52
+ "请选择 Docker 发布目标",
53
+ Object.entries(targets).map(([value, target]) => ({
54
+ value,
55
+ label: target.label || value,
56
+ })),
57
+ );
58
+ if (targetName === EXIT_SELECTION) {
59
+ log("已退出,未执行任何操作。");
60
+ return { cancelled: true };
61
+ }
62
+ const target = targets[targetName];
63
+ if (!target) throw new Error(`Unknown Docker target: ${targetName}`);
64
+
65
+ return composeRelease({
66
+ projectDir: loaded.configDir,
67
+ file: target.file,
68
+ bump: options.bump || "patch",
69
+ push: Boolean(options.push),
70
+ dryRun: Boolean(options.dryRun),
71
+ });
72
+ }
73
+
74
+ module.exports = { loadPackageTargetConfig, loadTargetConfig, runComposeCommand };
@@ -0,0 +1,148 @@
1
+ const assert = require("node:assert/strict");
2
+ const fs = require("node:fs");
3
+ const os = require("node:os");
4
+ const path = require("node:path");
5
+ const test = require("node:test");
6
+
7
+ const { runComposeCommand } = require("./compose-command");
8
+ const { EXIT_SELECTION } = require("../../tools/select-option");
9
+
10
+ test("runComposeCommand resolves a configured target relative to its config file", async () => {
11
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "compose-command-"));
12
+ const configPath = path.join(dir, "docker.targets.js");
13
+ fs.writeFileSync(
14
+ configPath,
15
+ `module.exports = { targets: { qdxy: { label: "青岛信跃", file: "docker-compose.qdxy.yaml" } } };`,
16
+ );
17
+ const calls = [];
18
+
19
+ try {
20
+ await runComposeCommand(
21
+ { config: configPath, target: "qdxy", push: true, dryRun: true },
22
+ { composeRelease: (options) => calls.push(options) },
23
+ );
24
+
25
+ assert.deepEqual(calls, [{
26
+ projectDir: dir,
27
+ file: "docker-compose.qdxy.yaml",
28
+ bump: "patch",
29
+ push: true,
30
+ dryRun: true,
31
+ }]);
32
+ } finally {
33
+ fs.rmSync(dir, { recursive: true, force: true });
34
+ }
35
+ });
36
+
37
+ test("runComposeCommand discovers targets from package.json in the working directory", async () => {
38
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "compose-package-config-"));
39
+ fs.writeFileSync(
40
+ path.join(dir, "package.json"),
41
+ JSON.stringify({
42
+ infly: {
43
+ docker: {
44
+ targets: {
45
+ qdxy: { label: "青岛信跃", file: "docker-compose.qdxy.yaml" },
46
+ },
47
+ },
48
+ },
49
+ }),
50
+ );
51
+ const calls = [];
52
+
53
+ try {
54
+ await runComposeCommand(
55
+ { cwd: dir, target: "qdxy", push: true, dryRun: true },
56
+ { composeRelease: (options) => calls.push(options) },
57
+ );
58
+
59
+ assert.deepEqual(calls, [{
60
+ projectDir: dir,
61
+ file: "docker-compose.qdxy.yaml",
62
+ bump: "patch",
63
+ push: true,
64
+ dryRun: true,
65
+ }]);
66
+ } finally {
67
+ fs.rmSync(dir, { recursive: true, force: true });
68
+ }
69
+ });
70
+
71
+ test("runComposeCommand prompts for a target when omitted", async () => {
72
+ const calls = [];
73
+
74
+ await runComposeCommand(
75
+ {
76
+ configObject: {
77
+ targets: {
78
+ yhqy: { label: "邮惠权益", file: "docker-compose.yaml" },
79
+ qdxy: { label: "青岛信跃", file: "docker-compose.qdxy.yaml" },
80
+ },
81
+ },
82
+ configDir: "C:/dist",
83
+ dryRun: true,
84
+ },
85
+ {
86
+ selectOption: async (message, choices) => {
87
+ assert.equal(message, "请选择 Docker 发布目标");
88
+ assert.deepEqual(choices.map(({ value }) => value), ["yhqy", "qdxy"]);
89
+ return "qdxy";
90
+ },
91
+ composeRelease: (options) => calls.push(options),
92
+ },
93
+ );
94
+
95
+ assert.equal(calls[0].file, "docker-compose.qdxy.yaml");
96
+ });
97
+
98
+ test("runComposeCommand exits without publishing when the user chooses exit", async () => {
99
+ const logs = [];
100
+ const result = await runComposeCommand(
101
+ {
102
+ configObject: {
103
+ targets: {
104
+ qdxy: { label: "青岛信跃", file: "docker-compose.qdxy.yaml" },
105
+ },
106
+ },
107
+ configDir: "C:/dist",
108
+ },
109
+ {
110
+ selectOption: async () => EXIT_SELECTION,
111
+ log: (message) => logs.push(message),
112
+ composeRelease: () => assert.fail("release should not run after exit"),
113
+ },
114
+ );
115
+
116
+ assert.deepEqual(result, { cancelled: true });
117
+ assert.deepEqual(logs, ["已退出,未执行任何操作。"]);
118
+ });
119
+
120
+ test("runComposeCommand preserves direct project-dir and file usage", async () => {
121
+ const calls = [];
122
+
123
+ await runComposeCommand(
124
+ { projectDir: "apps/dist", file: "docker-compose.yaml", dryRun: true },
125
+ { composeRelease: (options) => calls.push(options) },
126
+ );
127
+
128
+ assert.deepEqual(calls, [{
129
+ projectDir: "apps/dist",
130
+ file: "docker-compose.yaml",
131
+ dryRun: true,
132
+ }]);
133
+ });
134
+
135
+ test("postal dist package owns its Docker target mapping", () => {
136
+ const projectDir = path.resolve(__dirname, "../../../../apps/postal-benefits-platform-dist");
137
+ const packageJson = require(path.join(projectDir, "package.json"));
138
+
139
+ assert.equal(packageJson.scripts["docker:release"], "infly-libs docker:compose --push");
140
+ assert.deepEqual(packageJson.infly.docker.targets, {
141
+ yhqy: { label: "邮惠权益默认前端", file: "docker-compose.frontend.yaml" },
142
+ sq223: { label: "宿迁贰贰叁", file: "docker-compose.frontend.sq223.yaml" },
143
+ qdxy: { label: "青岛信跃", file: "docker-compose.frontend.qdxy.yaml" },
144
+ xzya: { label: "徐州沂埃", file: "docker-compose.frontend.xzya.yaml" },
145
+ next: { label: "邮惠权益 Next", file: "docker-compose.frontend.next.yaml" },
146
+ });
147
+ assert.equal(fs.existsSync(path.join(projectDir, "docker.targets.js")), false);
148
+ });
@@ -0,0 +1,91 @@
1
+ const { execFileSync } = require("node:child_process");
2
+ const fs = require("node:fs");
3
+ const path = require("node:path");
4
+
5
+ function bumpComposeImageVersion(content, bump = "patch") {
6
+ if (bump !== "patch") {
7
+ throw new Error(`Unsupported version bump: ${bump}. Only patch is supported.`);
8
+ }
9
+
10
+ const imageRegex = /^(\s*image:\s*)([^\s#]+):(\d+)\.(\d+)\.(\d+)(\s*(?:#.*)?)$/gm;
11
+ const matches = [...content.matchAll(imageRegex)];
12
+
13
+ if (matches.length !== 1) {
14
+ throw new Error(`Compose file must contain exactly one versioned image; found ${matches.length}.`);
15
+ }
16
+
17
+ const [, prefix, image, major, minor, patchVersion, suffix] = matches[0];
18
+ const from = `${image}:${major}.${minor}.${patchVersion}`;
19
+ const to = `${image}:${major}.${minor}.${Number(patchVersion) + 1}`;
20
+ const replacement = `${prefix}${to}${suffix}`;
21
+
22
+ return {
23
+ content: content.replace(imageRegex, replacement),
24
+ from,
25
+ to,
26
+ };
27
+ }
28
+
29
+ function resolveComposePath(projectDir, file) {
30
+ if (!projectDir) throw new Error("--project-dir is required.");
31
+ if (!file) throw new Error("--file is required.");
32
+
33
+ const resolvedProjectDir = path.resolve(projectDir);
34
+ const composePath = path.resolve(resolvedProjectDir, file);
35
+ const relative = path.relative(resolvedProjectDir, composePath);
36
+
37
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
38
+ throw new Error("Compose file must be inside project-dir.");
39
+ }
40
+ if (!fs.existsSync(composePath)) {
41
+ throw new Error(`Compose file not found: ${composePath}`);
42
+ }
43
+
44
+ return { projectDir: resolvedProjectDir, composePath };
45
+ }
46
+
47
+ function defaultRunDocker(args, options) {
48
+ execFileSync("docker", args, { cwd: options.cwd, stdio: "inherit" });
49
+ }
50
+
51
+ function composeRelease(options = {}, dependencies = {}) {
52
+ const {
53
+ projectDir,
54
+ file,
55
+ bump = "patch",
56
+ push = false,
57
+ dryRun = false,
58
+ } = options;
59
+ const runDocker = dependencies.runDocker || defaultRunDocker;
60
+ const resolved = resolveComposePath(projectDir, file);
61
+ const original = fs.readFileSync(resolved.composePath, "utf8");
62
+ const updated = bumpComposeImageVersion(original, bump);
63
+ const baseArgs = ["compose", "-f", resolved.composePath];
64
+
65
+ console.log(`Version update: ${updated.from} -> ${updated.to}`);
66
+
67
+ if (dryRun) {
68
+ console.log(`[dry-run] docker ${[...baseArgs, "config"].join(" ")}`);
69
+ console.log(`[dry-run] docker ${[...baseArgs, "build"].join(" ")}`);
70
+ if (push) console.log(`[dry-run] docker ${[...baseArgs, "push"].join(" ")}`);
71
+ return { ...updated, projectDir: resolved.projectDir, composePath: resolved.composePath };
72
+ }
73
+
74
+ fs.writeFileSync(resolved.composePath, updated.content, "utf8");
75
+ try {
76
+ runDocker([...baseArgs, "config"], { cwd: resolved.projectDir });
77
+ runDocker([...baseArgs, "build"], { cwd: resolved.projectDir });
78
+ if (push) runDocker([...baseArgs, "push"], { cwd: resolved.projectDir });
79
+ } catch (error) {
80
+ fs.writeFileSync(resolved.composePath, original, "utf8");
81
+ throw error;
82
+ }
83
+
84
+ return { ...updated, projectDir: resolved.projectDir, composePath: resolved.composePath };
85
+ }
86
+
87
+ module.exports = {
88
+ bumpComposeImageVersion,
89
+ composeRelease,
90
+ resolveComposePath,
91
+ };
@@ -0,0 +1,101 @@
1
+ const assert = require("node:assert/strict");
2
+ const fs = require("node:fs");
3
+ const os = require("node:os");
4
+ const path = require("node:path");
5
+ const test = require("node:test");
6
+
7
+ const {
8
+ bumpComposeImageVersion,
9
+ composeRelease,
10
+ } = require("./compose-release");
11
+
12
+ const composeSource = `services:\n frontend:\n image: registry.example.com/team/app:1.2.9\n`;
13
+
14
+ test("bumpComposeImageVersion bumps exactly one image patch version", () => {
15
+ const result = bumpComposeImageVersion(composeSource, "patch");
16
+
17
+ assert.equal(
18
+ result.content,
19
+ `services:\n frontend:\n image: registry.example.com/team/app:1.2.10\n`,
20
+ );
21
+ assert.equal(result.from, "registry.example.com/team/app:1.2.9");
22
+ assert.equal(result.to, "registry.example.com/team/app:1.2.10");
23
+ });
24
+
25
+ test("bumpComposeImageVersion rejects zero or multiple versioned images", () => {
26
+ assert.throws(() => bumpComposeImageVersion("services: {}\n", "patch"), /exactly one versioned image/);
27
+ assert.throws(
28
+ () => bumpComposeImageVersion(`${composeSource} worker:\n image: team/worker:2.0.0\n`, "patch"),
29
+ /exactly one versioned image/,
30
+ );
31
+ });
32
+
33
+ test("composeRelease dry-run does not write or invoke Docker", () => {
34
+ const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), "compose-release-"));
35
+ const composeFile = "docker-compose.yaml";
36
+ const composePath = path.join(projectDir, composeFile);
37
+ fs.writeFileSync(composePath, composeSource);
38
+ const calls = [];
39
+
40
+ try {
41
+ const result = composeRelease(
42
+ { projectDir, file: composeFile, dryRun: true, push: true },
43
+ { runDocker: (args) => calls.push(args) },
44
+ );
45
+
46
+ assert.equal(fs.readFileSync(composePath, "utf8"), composeSource);
47
+ assert.deepEqual(calls, []);
48
+ assert.equal(result.to, "registry.example.com/team/app:1.2.10");
49
+ } finally {
50
+ fs.rmSync(projectDir, { recursive: true, force: true });
51
+ }
52
+ });
53
+
54
+ test("composeRelease builds without pushing unless push is explicit", () => {
55
+ const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), "compose-release-"));
56
+ const composeFile = "docker-compose.yaml";
57
+ const composePath = path.join(projectDir, composeFile);
58
+ fs.writeFileSync(composePath, composeSource);
59
+ const calls = [];
60
+
61
+ try {
62
+ composeRelease(
63
+ { projectDir, file: composeFile },
64
+ { runDocker: (args) => calls.push(args) },
65
+ );
66
+
67
+ assert.deepEqual(calls, [
68
+ ["compose", "-f", composePath, "config"],
69
+ ["compose", "-f", composePath, "build"],
70
+ ]);
71
+ } finally {
72
+ fs.rmSync(projectDir, { recursive: true, force: true });
73
+ }
74
+ });
75
+
76
+ test("composeRelease restores the compose file when Docker fails", () => {
77
+ const projectDir = fs.mkdtempSync(path.join(os.tmpdir(), "compose-release-"));
78
+ const composeFile = "docker-compose.yaml";
79
+ const composePath = path.join(projectDir, composeFile);
80
+ fs.writeFileSync(composePath, composeSource);
81
+
82
+ try {
83
+ assert.throws(
84
+ () => composeRelease(
85
+ { projectDir, file: composeFile, push: true },
86
+ { runDocker: () => { throw new Error("docker failed"); } },
87
+ ),
88
+ /docker failed/,
89
+ );
90
+ assert.equal(fs.readFileSync(composePath, "utf8"), composeSource);
91
+ } finally {
92
+ fs.rmSync(projectDir, { recursive: true, force: true });
93
+ }
94
+ });
95
+
96
+ test("composeRelease rejects files outside projectDir", () => {
97
+ assert.throws(
98
+ () => composeRelease({ projectDir: ".", file: "../docker-compose.yaml", dryRun: true }),
99
+ /inside project-dir/,
100
+ );
101
+ });
@@ -37,6 +37,49 @@ test("parseArgs accepts spaced and equals cicd URL forms", () => {
37
37
  assert.equal(JSON.parse(equals.stdout).cicdUrl, url);
38
38
  });
39
39
 
40
+ test("parseArgs accepts Docker Compose release options", () => {
41
+ const result = parseArgsInChildProcess([
42
+ "--project-dir",
43
+ "apps/deploy",
44
+ "--file=docker-compose.yaml",
45
+ "--bump",
46
+ "patch",
47
+ "--push",
48
+ "--config",
49
+ "docker.targets.js",
50
+ "--target=qdxy",
51
+ "--dry-run",
52
+ ]);
53
+
54
+ assert.equal(result.status, 0, result.stderr);
55
+ assert.deepEqual(JSON.parse(result.stdout), {
56
+ env: "prod",
57
+ local: false,
58
+ dryRun: true,
59
+ projectDir: "apps/deploy",
60
+ file: "docker-compose.yaml",
61
+ bump: "patch",
62
+ push: true,
63
+ config: "docker.targets.js",
64
+ target: "qdxy",
65
+ });
66
+ });
67
+ test("parseProjectArgs leaves environment unset for interactive selection", () => {
68
+ const source = `
69
+ const { parseProjectArgs } = require(${JSON.stringify(cliPath)});
70
+ process.stdout.write(JSON.stringify({
71
+ interactive: parseProjectArgs([]),
72
+ explicit: parseProjectArgs(["--env", "stage", "--target", "qdxy"]),
73
+ }));
74
+ `;
75
+ const result = spawnSync(process.execPath, ["-e", source], { encoding: "utf-8" });
76
+
77
+ assert.equal(result.status, 0, result.stderr);
78
+ assert.deepEqual(JSON.parse(result.stdout), {
79
+ interactive: { dryRun: false },
80
+ explicit: { dryRun: false, env: "stage", target: "qdxy" },
81
+ });
82
+ });
40
83
  test("parseArgs rejects cicd URL without a value", () => {
41
84
  const result = parseArgsInChildProcess(["--cicd-url"]);
42
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infly/libs",
3
- "version": "2.0.40",
3
+ "version": "2.0.42",
4
4
  "description": "工具组件库",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -20,7 +20,8 @@
20
20
  "./dataInit/*": "./dataInit/*",
21
21
  "./tools/*": "./tools/*",
22
22
  "./store/*": "./store/*",
23
- "./build/*": "./build/*"
23
+ "./build/*": "./build/*",
24
+ "./script/build/*": "./script/build/*"
24
25
  },
25
26
  "repository": {
26
27
  "type": "git",
@@ -34,6 +35,7 @@
34
35
  "author": "Kahal",
35
36
  "license": "ISC",
36
37
  "dependencies": {
38
+ "enquirer": "^2.4.1",
37
39
  "path-browserify": "^1.0.1"
38
40
  },
39
41
  "devDependencies": {
@@ -65,7 +67,7 @@
65
67
  "enablePreview": "[是否启用项目预览] eg: true",
66
68
  "enableClipboard": "[是否启用粘贴板粘贴git信息] eg: true",
67
69
  "openExplorer": "[是否自动打开资源管理器] eg: true",
68
- "webhookUrl": "[消息推送机器人链接配置] eg: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=2417edd3-8f74-4c30-a7a8-5a72826d40d2",
70
+ "webhookUrl": "[消息推送机器人链接配置] eg: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=",
69
71
  "webhookAtUser": [
70
72
  "@人手机号"
71
73
  ],
@@ -521,8 +521,8 @@ function checkAndUpdateRootPackages(packagesDir, targetBranch = "master") {
521
521
  stdio: "inherit"
522
522
  });
523
523
 
524
- // 只同步共享内容,保留当前分支配置和子模块集合
525
- const mergeScriptPath = path.join(rootDir, "script", "sync-from-branch.sh");
524
+ // 只同步共享内容,保留当前分支配置和子模块集合
525
+ const mergeScriptPath = path.join(rootDir, "script", "sync-from-branch.sh");
526
526
  if (fs.existsSync(mergeScriptPath)) {
527
527
  execSync(`bash "${mergeScriptPath}" ${targetBranch}`, {
528
528
  cwd: rootDir,
@@ -68,7 +68,7 @@ function postVersionFileAndMsg(publishText, extraParams = {}) {
68
68
  const { branch, commitMsg, author } = gitInfo || {};
69
69
  const webhookMessage = getWebhookMessage();
70
70
  const GITPATH = `https://gitee.com/gdinfly_1/${projectName}/blob/${branch}/${newFileName}`;
71
- const UPLOADURL = `https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=08ea37af-c6f5-47dc-8fab-0833707c70b8&type=file`;
71
+ const UPLOADURL = `https://qyapi.weixin.qq.com/cgi-bin/webhook/upload_media?key=&type=file`;
72
72
  /* const data = {
73
73
  msgtype: "markdown",
74
74
  markdown: {