@infly/libs 2.0.43 → 2.0.45
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/README.md +104 -67
- package/{module → adapters/vue2/module}/Permission.js +2 -2
- package/{module → adapters/vue2/module}/REST.js +32 -20
- package/{module → adapters/vue2/module}/TokenService.js +10 -1
- package/adapters/vue2/module/file-export.js +18 -0
- package/adapters/vue2/postinstall.js +46 -0
- package/adapters/vue2/project-preview.js +152 -0
- package/adapters/vue2/script-config.js +26 -0
- package/adapters/vue2/store/index.js +1 -0
- package/{store → adapters/vue2/store}/modules/user.js +1 -1
- package/adapters/vue2/vite/index.js +95 -0
- package/adapters/vue2/vite/jest.config.js +11 -0
- package/{build → adapters/vue2}/webpack5/webpack.base.js +26 -79
- package/bin/cli.js +71 -71
- package/build/build-dist/index.js +863 -863
- package/build/build-dist/script-management.js +5 -5
- package/{script/build → build/build-utils}/command.js +13 -4
- package/{script/build → build/build-utils}/deps.js +1 -1
- package/{script/build → build/build-utils}/git.js +6 -4
- package/{script/build → build/build-utils}/preview.js +4 -1
- package/{script/webhook/webhook.js → build/build-utils/webhook-single.js} +7 -2
- package/{script/build → build/build-utils}/webhook.js +3 -2
- package/build/docker/compose-command.js +74 -74
- package/build/docker/compose-release.js +91 -91
- package/compat/init.js +9 -0
- package/index.js +8 -20
- package/module/Uts.js +77 -7265
- package/module/cjs/{request-url-rules.cjs → request-url-rules.js} +6 -0
- package/module/cjs/rest-error-rules.js +33 -0
- package/package.json +124 -92
- package/script/git-automation/git-clone.js +3 -4
- package/script/git-automation/git-configure.js +29 -0
- package/script/git-automation/index.js +0 -14
- package/script/git-automation/submodule-status.js +113 -0
- package/script/git-automation/submodule-utils.js +18 -0
- package/script/index.js +1 -26
- package/tools/auto-export.js +1 -2
- package/tools/concurrency.js +30 -0
- package/tools/progress.js +63 -0
- package/tools/project-command.js +198 -194
- package/tools/project-preview.js +1 -148
- package/tools/select-option.js +60 -60
- package/tools/workspace-config.js +145 -0
- package/.prettierrc +0 -5
- package/build/build-dist/script-management.test.js +0 -16
- package/build/docker/compose-command.test.js +0 -148
- package/build/docker/compose-release.test.js +0 -101
- package/build/docker/docker-build-push.test.js +0 -124
- package/build/webpack5/remove-legacy-assets-plugin.js +0 -84
- package/build/webpack5/webpack.base.test.js +0 -59
- package/lib/index.js +0 -6
- package/lib/server/connect.js +0 -3011
- package/lib/server/serve-static.js +0 -4848
- package/module/cjs/deep-merge.cjs +0 -38
- package/script/pts/cloud-scenes.mjs +0 -65
- package/script/pts/cloud.js +0 -151
- package/script/pts/generate-cloud-params.mjs +0 -210
- package/script/pts/generate-cloud-params.test.mjs +0 -67
- package/store/index.js +0 -0
- package/tools/file-export.js +0 -165
- package/tools/project-command.test.js +0 -267
- package/tools/select-option.test.js +0 -52
- package/webpack.config.js +0 -38
- /package/{module → adapters/vue2/module}/Router.js +0 -0
- /package/{build → adapters/vue2}/webpack5/inline-runtime-plugin.js +0 -0
- /package/{script/build → build/build-utils}/env.js +0 -0
- /package/module/cjs/{page-config.cjs → page-config.js} +0 -0
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const YAML = require("yaml");
|
|
4
|
+
|
|
5
|
+
function readJson(filePath) {
|
|
6
|
+
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function readWorkspaceList(content, sectionName) {
|
|
10
|
+
let workspaceConfig;
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
workspaceConfig = YAML.parse(content);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
throw new Error(`无法解析 pnpm-workspace.yaml: ${error.message}`, { cause: error });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (workspaceConfig == null) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (typeof workspaceConfig !== "object" || Array.isArray(workspaceConfig)) {
|
|
23
|
+
throw new TypeError("pnpm-workspace.yaml 的根节点必须是对象");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const patterns = workspaceConfig[sectionName];
|
|
27
|
+
|
|
28
|
+
if (patterns == null) {
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (
|
|
33
|
+
!Array.isArray(patterns)
|
|
34
|
+
|| patterns.some((pattern) => typeof pattern !== "string" || pattern.trim().length === 0)
|
|
35
|
+
) {
|
|
36
|
+
throw new TypeError(`pnpm-workspace.yaml 的 ${sectionName} 必须是非空字符串数组`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return patterns;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readWorkspaceSection(workspaceFile, sectionName) {
|
|
43
|
+
return readWorkspaceList(fs.readFileSync(workspaceFile, "utf8"), sectionName);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readWorkspaceSectionWithFallback(workspaceFile, sectionName, fallbackSectionName) {
|
|
47
|
+
const patterns = readWorkspaceSection(workspaceFile, sectionName);
|
|
48
|
+
|
|
49
|
+
if (patterns.length > 0) {
|
|
50
|
+
return patterns;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return readWorkspaceSection(workspaceFile, fallbackSectionName);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function expandWorkspacePattern(rootDir, pattern) {
|
|
57
|
+
const normalizedPattern = pattern.replace(/\\/g, "/");
|
|
58
|
+
|
|
59
|
+
if (!normalizedPattern.includes("*")) {
|
|
60
|
+
return [path.join(rootDir, normalizedPattern)];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const starIndex = normalizedPattern.indexOf("*");
|
|
64
|
+
const basePart = normalizedPattern.slice(0, starIndex);
|
|
65
|
+
const suffixPart = normalizedPattern.slice(starIndex + 1);
|
|
66
|
+
const baseDir = path.join(rootDir, basePart);
|
|
67
|
+
|
|
68
|
+
if (!fs.existsSync(baseDir)) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const suffixIsNamePattern = suffixPart && !suffixPart.startsWith("/");
|
|
73
|
+
|
|
74
|
+
return fs
|
|
75
|
+
.readdirSync(baseDir, { withFileTypes: true })
|
|
76
|
+
.filter((entry) => entry.isDirectory())
|
|
77
|
+
.filter((entry) => !suffixIsNamePattern || entry.name.endsWith(suffixPart))
|
|
78
|
+
.map((entry) => (suffixIsNamePattern
|
|
79
|
+
? path.join(baseDir, entry.name)
|
|
80
|
+
: path.join(baseDir, entry.name, suffixPart)))
|
|
81
|
+
.filter((packageDir) => fs.existsSync(path.join(packageDir, "package.json")));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getWorkspacePackages(rootDir, workspaceFile, sectionName, fallbackSectionName, scriptName) {
|
|
85
|
+
const patterns = fallbackSectionName
|
|
86
|
+
? readWorkspaceSectionWithFallback(workspaceFile, sectionName, fallbackSectionName)
|
|
87
|
+
: readWorkspaceSection(workspaceFile, sectionName);
|
|
88
|
+
const included = new Set();
|
|
89
|
+
patterns.forEach((pattern) => {
|
|
90
|
+
const excluded = pattern.startsWith("!");
|
|
91
|
+
const resolvedPattern = excluded ? pattern.slice(1) : pattern;
|
|
92
|
+
expandWorkspacePattern(rootDir, resolvedPattern).forEach((packageDir) => {
|
|
93
|
+
const resolvedDir = path.resolve(packageDir);
|
|
94
|
+
if (excluded) included.delete(resolvedDir);
|
|
95
|
+
else included.add(resolvedDir);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
const packageDirs = [...included];
|
|
99
|
+
const seen = new Set();
|
|
100
|
+
|
|
101
|
+
return packageDirs
|
|
102
|
+
.map((packageDir) => {
|
|
103
|
+
const packageJsonPath = path.join(packageDir, "package.json");
|
|
104
|
+
|
|
105
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const packageJson = readJson(packageJsonPath);
|
|
110
|
+
const scripts = packageJson.scripts || {};
|
|
111
|
+
|
|
112
|
+
if (!packageJson.name || seen.has(packageJson.name) || (scriptName && !scripts[scriptName])) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
seen.add(packageJson.name);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
name: packageJson.name,
|
|
120
|
+
dir: path.relative(rootDir, packageDir),
|
|
121
|
+
packageJson,
|
|
122
|
+
};
|
|
123
|
+
})
|
|
124
|
+
.filter(Boolean);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function toTurboFilter(value) {
|
|
128
|
+
const normalized = value.replace(/\\/g, "/");
|
|
129
|
+
|
|
130
|
+
if (normalized.includes("/") && !normalized.startsWith("./") && !normalized.startsWith("../")) {
|
|
131
|
+
return `./${normalized}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return normalized;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
expandWorkspacePattern,
|
|
139
|
+
getWorkspacePackages,
|
|
140
|
+
readJson,
|
|
141
|
+
readWorkspaceList,
|
|
142
|
+
readWorkspaceSection,
|
|
143
|
+
readWorkspaceSectionWithFallback,
|
|
144
|
+
toTurboFilter,
|
|
145
|
+
};
|
package/.prettierrc
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,148 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
const assert = require("node:assert/strict");
|
|
2
|
-
const path = require("node:path");
|
|
3
|
-
const { spawnSync } = require("node:child_process");
|
|
4
|
-
const test = require("node:test");
|
|
5
|
-
|
|
6
|
-
const cliPath = path.resolve(__dirname, "../../bin/cli.js");
|
|
7
|
-
|
|
8
|
-
function parseArgsInChildProcess(argv) {
|
|
9
|
-
const source = `
|
|
10
|
-
const { parseArgs } = require(${JSON.stringify(cliPath)});
|
|
11
|
-
process.stdout.write(JSON.stringify(parseArgs(${JSON.stringify(argv)})));
|
|
12
|
-
`;
|
|
13
|
-
return spawnSync(process.execPath, ["-e", source], {
|
|
14
|
-
encoding: "utf-8",
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
test("parseArgs keeps existing defaults and options", () => {
|
|
19
|
-
const result = parseArgsInChildProcess(["--local", "--env", "dev", "--dry-run"]);
|
|
20
|
-
|
|
21
|
-
assert.equal(result.status, 0, result.stderr);
|
|
22
|
-
assert.deepEqual(JSON.parse(result.stdout), {
|
|
23
|
-
env: "dev",
|
|
24
|
-
local: true,
|
|
25
|
-
dryRun: true,
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
test("parseArgs accepts spaced and equals cicd URL forms", () => {
|
|
30
|
-
const url = "https://cicd.sutpay.com/view/积分商城/";
|
|
31
|
-
const spaced = parseArgsInChildProcess(["--cicd-url", url]);
|
|
32
|
-
const equals = parseArgsInChildProcess([`--cicd-url=${url}`]);
|
|
33
|
-
|
|
34
|
-
assert.equal(spaced.status, 0, spaced.stderr);
|
|
35
|
-
assert.equal(equals.status, 0, equals.stderr);
|
|
36
|
-
assert.equal(JSON.parse(spaced.stdout).cicdUrl, url);
|
|
37
|
-
assert.equal(JSON.parse(equals.stdout).cicdUrl, url);
|
|
38
|
-
});
|
|
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
|
-
});
|
|
83
|
-
test("parseArgs rejects cicd URL without a value", () => {
|
|
84
|
-
const result = parseArgsInChildProcess(["--cicd-url"]);
|
|
85
|
-
|
|
86
|
-
assert.notEqual(result.status, 0);
|
|
87
|
-
assert.match(result.stderr, /--cicd-url/);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("CLI keeps the existing unknown command output", () => {
|
|
91
|
-
const result = spawnSync(process.execPath, [cliPath, "unknown-command"], {
|
|
92
|
-
encoding: "utf-8",
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
assert.equal(result.status, 1);
|
|
96
|
-
assert.match(result.stderr, /未知命令: unknown-command/);
|
|
97
|
-
assert.match(result.stdout, /可用命令:/);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test("formatCicdLink returns no output when option is absent", () => {
|
|
101
|
-
const { formatCicdLink } = require("./docker-build-push");
|
|
102
|
-
|
|
103
|
-
assert.equal(formatCicdLink(), "");
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("formatCicdLink normalizes encoded Chinese URL and colors it blue", () => {
|
|
107
|
-
const { formatCicdLink } = require("./docker-build-push");
|
|
108
|
-
const result = formatCicdLink(
|
|
109
|
-
"https://cicd.sutpay.com/view/%E7%A7%AF%E5%88%86%E5%95%86%E5%9F%8E/"
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
assert.equal(
|
|
113
|
-
result,
|
|
114
|
-
"发布系统:\x1b[34mhttps://cicd.sutpay.com/view/积分商城/\x1b[0m"
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("formatCicdLink rejects unsafe URL values", () => {
|
|
119
|
-
const { formatCicdLink } = require("./docker-build-push");
|
|
120
|
-
|
|
121
|
-
assert.throws(() => formatCicdLink("ftp://cicd.sutpay.com/build"), /HTTP\/HTTPS/);
|
|
122
|
-
assert.throws(() => formatCicdLink("https://user:secret@cicd.sutpay.com/"), /凭据/);
|
|
123
|
-
assert.throws(() => formatCicdLink("https://cicd.sutpay.com/\nnext"), /控制字符/);
|
|
124
|
-
});
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RemoveLegacyAssetsPlugin
|
|
3
|
-
* 用于处理 Vue CLI Modern Mode 的 legacy 文件问题
|
|
4
|
-
*
|
|
5
|
-
* 功能:
|
|
6
|
-
* 1. 在构建前创建占位文件,防止 ModernModePlugin 读取时报错
|
|
7
|
-
* 2. 在构建时删除 legacy 相关的资源
|
|
8
|
-
* 3. 在构建完成后清理占位文件
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
"use strict";
|
|
12
|
-
|
|
13
|
-
const fs = require('fs');
|
|
14
|
-
const path = require('path');
|
|
15
|
-
|
|
16
|
-
class RemoveLegacyAssetsPlugin {
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.options = {
|
|
19
|
-
legacyFiles: [
|
|
20
|
-
'legacy-assets-index.html.json',
|
|
21
|
-
'modern-assets-index.html.json'
|
|
22
|
-
],
|
|
23
|
-
...options
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
apply(compiler) {
|
|
28
|
-
const outputPath = compiler.options.output.path;
|
|
29
|
-
const { legacyFiles } = this.options;
|
|
30
|
-
|
|
31
|
-
// 🔥 Hook 1: 构建开始前创建占位文件
|
|
32
|
-
compiler.hooks.beforeRun.tapAsync('RemoveLegacyAssetsPlugin', (compiler, callback) => {
|
|
33
|
-
// 确保输出目录存在
|
|
34
|
-
if (!fs.existsSync(outputPath)) {
|
|
35
|
-
fs.mkdirSync(outputPath, { recursive: true });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// 创建空数组的 JSON 文件(Vue CLI 期望数组格式)
|
|
39
|
-
legacyFiles.forEach(file => {
|
|
40
|
-
const filePath = path.join(outputPath, file);
|
|
41
|
-
try {
|
|
42
|
-
fs.writeFileSync(filePath, '[]');
|
|
43
|
-
} catch (err) {
|
|
44
|
-
// 忽略创建失败的错误
|
|
45
|
-
console.warn(`⚠ Failed to create placeholder: ${file}`, err.message);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
callback();
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
// 🔥 Hook 2: 构建时删除编译产物中的 legacy 资源
|
|
53
|
-
compiler.hooks.emit.tapAsync('RemoveLegacyAssetsPlugin', (compilation, callback) => {
|
|
54
|
-
Object.keys(compilation.assets).forEach(filename => {
|
|
55
|
-
if (filename.includes('-legacy') || filename.includes('legacy-assets')) {
|
|
56
|
-
delete compilation.assets[filename];
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
callback();
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
// 🔥 Hook 3: 构建完成后清理占位文件
|
|
63
|
-
compiler.hooks.done.tapAsync('RemoveLegacyAssetsPlugin', (stats, callback) => {
|
|
64
|
-
// 延迟执行,确保所有读取操作完成
|
|
65
|
-
setTimeout(() => {
|
|
66
|
-
legacyFiles.forEach(file => {
|
|
67
|
-
const filePath = path.join(outputPath, file);
|
|
68
|
-
try {
|
|
69
|
-
if (fs.existsSync(filePath)) {
|
|
70
|
-
fs.unlinkSync(filePath);
|
|
71
|
-
console.log(`\x1b[32m✓ Cleaned up: ${file}\x1b[0m`);
|
|
72
|
-
}
|
|
73
|
-
} catch (err) {
|
|
74
|
-
// 忽略删除失败的错误
|
|
75
|
-
console.warn(`⚠ Failed to cleanup: ${file}`, err.message);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
callback();
|
|
79
|
-
}, 100);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
module.exports = RemoveLegacyAssetsPlugin;
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
const assert = require("node:assert/strict");
|
|
4
|
-
const test = require("node:test");
|
|
5
|
-
const { chainWebpack } = require("./webpack.base");
|
|
6
|
-
|
|
7
|
-
function createChainConfig() {
|
|
8
|
-
let htmlPluginOptions;
|
|
9
|
-
const chain = new Proxy(
|
|
10
|
-
function () {},
|
|
11
|
-
{
|
|
12
|
-
get(_target, property) {
|
|
13
|
-
if (property === "plugin") {
|
|
14
|
-
return (name) => ({
|
|
15
|
-
tap(callback) {
|
|
16
|
-
if (name === "html") {
|
|
17
|
-
[htmlPluginOptions] = callback([htmlPluginOptions || {}]);
|
|
18
|
-
}
|
|
19
|
-
return this;
|
|
20
|
-
},
|
|
21
|
-
use() {
|
|
22
|
-
return this;
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return chain;
|
|
28
|
-
},
|
|
29
|
-
apply() {
|
|
30
|
-
return chain;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
return {
|
|
36
|
-
config: chain,
|
|
37
|
-
getHtmlPluginOptions: () => htmlPluginOptions
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
test("chainWebpack 默认不增加 HTML 插件参数", () => {
|
|
42
|
-
const { config, getHtmlPluginOptions } = createChainConfig();
|
|
43
|
-
|
|
44
|
-
chainWebpack(config);
|
|
45
|
-
|
|
46
|
-
assert.equal(getHtmlPluginOptions()?.faviconPath, undefined);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("chainWebpack 合并调用方传入的 HTML 插件参数", () => {
|
|
50
|
-
const { config, getHtmlPluginOptions } = createChainConfig();
|
|
51
|
-
|
|
52
|
-
chainWebpack(config, {
|
|
53
|
-
htmlPluginOptions: {
|
|
54
|
-
faviconPath: "favicon_xzya.ico"
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
assert.equal(getHtmlPluginOptions()?.faviconPath, "favicon_xzya.ico");
|
|
59
|
-
});
|