hbuilder-build-move 0.0.6 → 0.0.7

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 CHANGED
@@ -1,9 +1,88 @@
1
- # HBuilderX 工具打包 H5 资源后,将资源移动到指定目录
1
+ # hbuilder-build-move
2
+
3
+ HBuilderX 打包 H5 之后,一条命令把产物从 `unpackage/dist/build/web` 同步到项目里指定的目录,并顺手 `git add / commit / push`。
4
+
5
+ 省掉每次发版都要做的三件事:手动复制、手动粘贴、手动提交。
6
+
7
+ ## 安装
8
+
9
+ ```shell
10
+ npm i -g hbuilder-build-move
11
+ ```
12
+
13
+ 命令名是 `hbm`,装完直接可用(不想全局装也可以用 `npx hbm`)。
14
+
15
+ 环境要求:
16
+
17
+ - Node.js 18 及以上
18
+ - 项目已初始化 git,且命令行能正常 `git push`(SSH key 或凭据已配好)
19
+
20
+ ## 用法
21
+
22
+ 在 **HBuilderX 项目根目录** 下执行:
2
23
 
3
24
  ```shell
4
- hbm build_commit_info
5
- # 目标目录默认值是 build/h5,可以自定义,例如:
6
- hbm build_commit_info -t dist
25
+ # 打包完成后,提交并推送,commit 信息为「更新首页样式」
26
+ hbm 更新首页样式
27
+
28
+ # 目标目录默认是 build/h5,可以改成别的
29
+ hbm 更新首页样式 -t dist
30
+
31
+ # 目标目录可以是多级路径,不存在会自动创建
32
+ hbm 更新首页样式 -t static/h5
7
33
  ```
8
34
 
35
+ | 参数 | 说明 | 默认值 |
36
+ | --- | --- | --- |
37
+ | `[string]` | git commit 信息,最终会拼成 `build: <你的信息>` | `test` |
38
+ | `-t, --target <path>` | 产物输出目录,相对项目根目录 | `build/h5` |
39
+ | `-V, --version` | 查看版本 | - |
40
+ | `-h, --help` | 查看帮助 | - |
41
+
42
+ ## 执行流程
43
+
44
+ 1. 从当前工作目录向上查找,第一个含 `package.json` 或 `.git` 的目录认作项目根目录
45
+ 2. 检查项目根目录下有没有 `.git`,没有就报错退出
46
+ 3. 检查 `unpackage/dist/build/web/index.html` 是否存在,不存在说明还没打包,报错退出
47
+ 4. 目标目录:不存在则创建;已存在则整个删掉重建
48
+ 5. 递归把产物拷到目标目录(源产物保留)
49
+ 6. 依次执行 `git add .`、`git commit -m "build: <信息>"`、`git push`,任何一步失败立即中断
50
+
51
+ 跑起来的样子:
52
+
9
53
  ![image](assets/image.png)
54
+
55
+ ## 注意事项
56
+
57
+ - **在项目根目录或其子目录里运行。** 它会从当前目录往上找第一个含 `package.json` 或 `.git` 的目录当项目根,在项目外执行会认错根目录。
58
+ - **目标目录会被整体删除。** `-t` 不要指向源码目录,比如 `-t src` 会把 `src` 删掉再复制作物进去。
59
+ - **`git add .` 是全量提交。** 跑之前确认工作区没有别的半成品改动,否则会被一并提交。建议在 `.gitignore` 里忽略 `unpackage/`,否则打包产物源目录也会进版本库。
60
+ - **只处理 H5 产物。** 小程序、App 平台的构建目录结构不同,不适用。
61
+ - **名字叫 move,实际是 copy。** 源产物 `unpackage/dist/build/web` 不会删除。
62
+ - 不传 commit 信息时,提交记录是 `build: test`,翻历史时容易认不出来,建议养成带信息的习惯。
63
+
64
+ ## 作为模块调用
65
+
66
+ `main` 指向搬运函数本身,也可以不装命令行、直接引:
67
+
68
+ ```js
69
+ const move = require("hbuilder-build-move");
70
+
71
+ // 把当前项目的产物搬到 build/h5,并提交推送
72
+ await move("./unpackage/dist/build/web", "build/h5", "build: 更新首页");
73
+ ```
74
+
75
+ 定位项目根的方式和命令行一致,同样从 `process.cwd()` 往上找。
76
+
77
+ ## 开发
78
+
79
+ ```shell
80
+ npm install
81
+ npm test
82
+ ```
83
+
84
+ 测试用 Node 自带的 test runner,不需要额外依赖。每个用例都会在临时目录里建一个真实 git 仓库,并拿本地裸仓库充当 origin,完整跑一遍复制、`git add`、`commit`、`push` —— 全程离线,不碰你的仓库,也不碰 GitHub。
85
+
86
+ ## License
87
+
88
+ ISC
package/index.mjs CHANGED
@@ -17,7 +17,7 @@ const data = fs.readFileSync(filePath, "utf8");
17
17
  const packageJson = JSON.parse(data);
18
18
 
19
19
  program
20
- .name(packageJson.name)
20
+ .name("hbm")
21
21
  .description(packageJson.description)
22
22
  .version(packageJson.version);
23
23
 
package/move.js CHANGED
@@ -37,21 +37,13 @@ async function main(src, dest, commitInfo) {
37
37
  handleError("未找打包完成的H5资源");
38
38
  }
39
39
  const targetDir = path.join(projectRoot, dest);
40
- if (!fs.existsSync(targetDir)) {
41
- fs.mkdirSync(targetDir, { recursive: true });
42
- console.log(dest + " 目录创建完成");
43
- } else {
44
- // 删除目标目录下的文件
45
- const result = spawnSync("rm", ["-rf", targetDir]);
46
- console.log("result: ", result);
47
-
48
- if (result.status !== 0) {
49
- handleError("❌ 删除旧文件失败");
50
- process.exit(1); // 终止后续命令
51
- } else {
52
- console.log("🎉 删除旧文件成功");
53
- }
40
+ if (fs.existsSync(targetDir)) {
41
+ // 整体删除重建,保证目录内容与本次打包结果完全一致
42
+ fs.rmSync(targetDir, { recursive: true, force: true });
43
+ console.log("🎉 删除旧文件成功");
54
44
  }
45
+ fs.mkdirSync(targetDir, { recursive: true });
46
+ console.log(dest + " 目录创建完成");
55
47
  console.log("源目录 :>> ", path.join(projectRoot, src));
56
48
  console.log("目标目录 :>> ", targetDir);
57
49
 
@@ -82,7 +74,6 @@ async function main(src, dest, commitInfo) {
82
74
  }
83
75
 
84
76
  console.log("🎉 所有文件复制完成 ");
85
- spawnSync("cd", [projectRoot]);
86
77
  const cwd = process.cwd();
87
78
  console.log("当前执行路径:", cwd);
88
79
 
@@ -93,7 +84,10 @@ async function main(src, dest, commitInfo) {
93
84
  ];
94
85
 
95
86
  for (const command of gitCommands) {
96
- const result = spawnSync(command.cmd, command.args, { stdio: "inherit" });
87
+ const result = spawnSync(command.cmd, command.args, {
88
+ stdio: "inherit",
89
+ cwd: projectRoot,
90
+ });
97
91
  if (result.status !== 0) {
98
92
  handleError(`❌ 命令 ${command.cmd} 执行失败`);
99
93
  process.exit(1); // 终止后续命令
package/package.json CHANGED
@@ -1,18 +1,22 @@
1
1
  {
2
2
  "name": "hbuilder-build-move",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "将打包好的资源移动到项目指定的目录下",
5
5
  "keywords": [
6
6
  "Hbuilder",
7
7
  "h5",
8
8
  "build"
9
9
  ],
10
- "main": "index.js",
10
+ "main": "move.js",
11
11
  "bin": {
12
12
  "hbm": "index.mjs"
13
13
  },
14
14
  "scripts": {
15
- "test": "echo \"Error: no test specified\" && exit 1"
15
+ "test": "node --test",
16
+ "prepublishOnly": "npm test"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
16
20
  },
17
21
  "author": "hxf",
18
22
  "license": "ISC",
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+
3
+ const { test, after } = require("node:test");
4
+ const assert = require("node:assert");
5
+ const fs = require("node:fs");
6
+ const os = require("node:os");
7
+ const path = require("node:path");
8
+ const { spawnSync } = require("node:child_process");
9
+
10
+ const PKG_DIR = path.resolve(__dirname, "..");
11
+ const MOVE_JS = path.join(PKG_DIR, "move.js");
12
+ const move = require(MOVE_JS);
13
+
14
+ const sandboxes = [];
15
+
16
+ after(() => {
17
+ for (const dir of sandboxes) fs.rmSync(dir, { recursive: true, force: true });
18
+ });
19
+
20
+ function git(args, cwd) {
21
+ const result = spawnSync("git", args, { cwd, encoding: "utf8" });
22
+ if (result.status !== 0) {
23
+ throw new Error(
24
+ `git ${args.join(" ")} 失败 (status=${result.status})\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`
25
+ );
26
+ }
27
+ return result.stdout.trim();
28
+ }
29
+
30
+ function newSandboxDir() {
31
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "hbm-test-"));
32
+ sandboxes.push(dir);
33
+ return dir;
34
+ }
35
+
36
+ /**
37
+ * 建一个和真实使用一致的仓库:本地仓库已 init、已有首次提交、已关联 origin。
38
+ * origin 用本地裸仓库代替 GitHub,push 走文件系统,不依赖网络和凭据。
39
+ */
40
+ function makeRepo({ withBuild = true } = {}) {
41
+ const root = newSandboxDir();
42
+ const remote = path.join(root, "origin.git");
43
+ const repo = path.join(root, "repo");
44
+ fs.mkdirSync(repo);
45
+
46
+ git(["init", "--bare", "-b", "main", remote], root);
47
+ git(["init", "-b", "main"], repo);
48
+ git(["config", "user.name", "hbm-test"], repo);
49
+ git(["config", "user.email", "hbm-test@example.com"], repo);
50
+ git(["config", "commit.gpgsign", "false"], repo);
51
+ git(["remote", "add", "origin", remote], repo);
52
+ fs.writeFileSync(path.join(repo, "README.md"), "# sandbox\n");
53
+ git(["add", "."], repo);
54
+ git(["commit", "-m", "init"], repo);
55
+ git(["push", "-u", "origin", "main"], repo);
56
+
57
+ if (withBuild) {
58
+ const web = path.join(repo, "unpackage", "dist", "build", "web");
59
+ fs.mkdirSync(path.join(web, "static"), { recursive: true });
60
+ fs.writeFileSync(path.join(web, "index.html"), "<!DOCTYPE html><h1>built</h1>\n");
61
+ fs.writeFileSync(path.join(web, "static", "app.js"), "console.log('built');\n");
62
+ }
63
+
64
+ return { root, repo, remote };
65
+ }
66
+
67
+ /** 失败路径走子进程:move 里的 handleError 会直接 process.exit */
68
+ function runMoveInChild(cwd, src, dest, message) {
69
+ const code =
70
+ `require(${JSON.stringify(MOVE_JS)})` +
71
+ `(${JSON.stringify(src)}, ${JSON.stringify(dest)}, ${JSON.stringify(message)});`;
72
+ return spawnSync(process.execPath, ["-e", code], { cwd, encoding: "utf8" });
73
+ }
74
+
75
+ /** move 靠 process.cwd() 找项目根,测试期间临时切目录,结束后务必恢复 */
76
+ async function inDir(dir, fn) {
77
+ const prev = process.cwd();
78
+ process.chdir(dir);
79
+ try {
80
+ return await fn();
81
+ } finally {
82
+ process.chdir(prev);
83
+ }
84
+ }
85
+
86
+ test("package.json 的 main 指向真实存在的入口,require 包根目录能拿到函数", () => {
87
+ const entry = require.resolve(PKG_DIR);
88
+ assert.ok(fs.existsSync(entry), `main 指向的文件不存在: ${entry}`);
89
+ assert.strictEqual(path.resolve(entry), MOVE_JS);
90
+ assert.strictEqual(typeof require(PKG_DIR), "function");
91
+ });
92
+
93
+ test("项目没有初始化 git 时报错并以 1 退出", () => {
94
+ const dir = newSandboxDir();
95
+ fs.writeFileSync(path.join(dir, "package.json"), "{}\n");
96
+
97
+ const result = runMoveInChild(dir, "./unpackage/dist/build/web", "build/h5", "build: t");
98
+
99
+ assert.strictEqual(result.status, 1);
100
+ assert.match(result.stderr, /没有配置Git/);
101
+ });
102
+
103
+ test("没有打包产物时报错并以 1 退出", () => {
104
+ const { repo } = makeRepo({ withBuild: false });
105
+
106
+ const result = runMoveInChild(repo, "./unpackage/dist/build/web", "build/h5", "build: t");
107
+
108
+ assert.strictEqual(result.status, 1);
109
+ assert.match(result.stderr, /未找打包完成的H5资源/);
110
+ });
111
+
112
+ test("正常流程:清空旧目标目录、递归复制、提交并推送到远端", async () => {
113
+ const { root, repo, remote } = makeRepo();
114
+
115
+ // 目标目录里预置旧文件,验证会被整体清掉
116
+ fs.mkdirSync(path.join(repo, "build", "h5"), { recursive: true });
117
+ fs.writeFileSync(path.join(repo, "build", "h5", "stale.txt"), "old file\n");
118
+
119
+ await inDir(repo, () => move("./unpackage/dist/build/web", "build/h5", "build: v1"));
120
+
121
+ const target = path.join(repo, "build", "h5");
122
+ assert.ok(!fs.existsSync(path.join(target, "stale.txt")), "旧文件应被删除");
123
+ assert.strictEqual(
124
+ fs.readFileSync(path.join(target, "index.html"), "utf8"),
125
+ "<!DOCTYPE html><h1>built</h1>\n"
126
+ );
127
+ assert.strictEqual(
128
+ fs.readFileSync(path.join(target, "static", "app.js"), "utf8"),
129
+ "console.log('built');\n"
130
+ );
131
+
132
+ // 名字叫 move,实际是 copy,源产物必须还在
133
+ assert.ok(fs.existsSync(path.join(repo, "unpackage", "dist", "build", "web", "index.html")));
134
+
135
+ // 已提交、已推送
136
+ assert.strictEqual(git(["log", "-1", "--format=%s"], repo), "build: v1");
137
+ assert.strictEqual(git(["status", "--porcelain"], repo), "");
138
+ assert.strictEqual(git(["--git-dir", remote, "log", "-1", "--format=%s"], root), "build: v1");
139
+ });
140
+
141
+ test("目标目录不存在时自动创建,支持多级路径", async () => {
142
+ const { repo } = makeRepo();
143
+
144
+ await inDir(repo, () => move("./unpackage/dist/build/web", "static/h5/v1", "build: nested"));
145
+
146
+ const target = path.join(repo, "static", "h5", "v1");
147
+ assert.ok(fs.existsSync(path.join(target, "index.html")));
148
+ assert.ok(fs.existsSync(path.join(target, "static", "app.js")));
149
+ assert.strictEqual(git(["log", "-1", "--format=%s"], repo), "build: nested");
150
+ });
151
+
152
+ test("在子目录里运行也能定位到项目根,并把仓库根的全部改动一起提交", async () => {
153
+ const { repo } = makeRepo();
154
+
155
+ // 在产物目录里执行,模拟「在编辑器终端里从当前文件所在目录运行」
156
+ const sub = path.join(repo, "unpackage", "dist", "build", "web");
157
+ await inDir(sub, () => move("./unpackage/dist/build/web", "build/h5", "build: from subdir"));
158
+
159
+ // 产物落在仓库根下的 build/h5,而不是子目录里再套一层
160
+ assert.ok(fs.existsSync(path.join(repo, "build", "h5", "index.html")));
161
+ assert.ok(!fs.existsSync(path.join(sub, "build")));
162
+ // 工作区干净:说明 git add 是在仓库根执行的,没有漏掉其他改动
163
+ assert.strictEqual(git(["status", "--porcelain"], repo), "");
164
+ assert.strictEqual(git(["log", "-1", "--format=%s"], repo), "build: from subdir");
165
+ });