# 给几天前的打包器改了改，现在更好了（高级玩法：制作安装程序）

**URL:** <https://www.nodeloc.com/t/topic/33306>\
**Category:** 编程开发\
**Created:** [2025 年1 月 12 日 20:27 UTC](https://www.nodeloc.com/t/topic/33306 "2025-01-12T20:27:12Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

作者： ![Str](https://www.nodeloc.com/user_avatar/www.nodeloc.com/str/32/1108_2.png) [@Str](https://www.nodeloc.com/u/Str)\
发布日期： [2025 年1 月 12 日 20:27 UTC](https://www.nodeloc.com/t/topic/33306/1 "2025-01-12T20:27:12Z")

</div>

````js
/**`

`

Nodejs File Pack Help by cmd1152

开源许可证：MIT

node pack /files some_file.txt /output some_file.bat

/files [file1] <file2> … 传入要被嵌入的文件

/dir <dir> 传入目录结构，不常用，现在会自动检测叶子目录

/output [file_name] 要输出的批处理文件

/run [cmd] 附加命令到文件，一般用于释放后执行程序

/del 批处理执行完毕后删除自己

一些高级玩法

安装程序

node pack /files A /output A.bat /run A\setup

这是 A 文件夹的目录结构

A\

```
   APP_NAME\

```

```
       setup.bat // 调用 setup_ink 释放快捷方式，下一级是文件内容
            @echo off
            cd "%~dp0"

            start /w "" "setup_ink.vbs"
            start mshta vbscript:msgbox("MY APP installation completed successfully",64,"MY APP")(window.close)
            del setup_ink.vbs
            del "%~0" &amp; exit

```

```
       shell.bat // 调用入口

```

```
       ink.ico // 快捷方式图标

```

```
       setup_ink.vbs //释放快捷方式，下一级是文件内容
            Set objShell = CreateObject("WScript.Shell")
            currentDir = objShell.CurrentDirectory

            targetPath = currentDir &amp; "\shell.bat"
            iconPath = currentDir &amp; "\ink.ico"

            desktopPath = objShell.SpecialFolders("Desktop") &amp; "\My APP.lnk"

            Set shortcut = objShell.CreateShortcut(desktopPath)

            shortcut.TargetPath = targetPath
            shortcut.WorkingDirectory = currentDir
            shortcut.IconLocation = iconPath
            shortcut.Save

```

```
       ... // 其他文件

```

```
   setup.bat // 用于复制文件到安装目录下，然后运行里面的 setup.bat 进一步安装，下一级是文件内容
       @echo off
       cd "%~dp0"
       echo;D|xcopy /Y /E APP_NAME "%HOMEDRIVE%%HOMEPATH%\APP_NAME"

       set "a=%cd%"

       cd /d "%HOMEDRIVE%%HOMEPATH%\APP_NAME"
       start "" "setup.bat"

       cd "%a%"
       cd ..
       rd /s /q "A" &amp; exit

```

然后按照命令构建，得到一个bat文件
```
构建开始
  树形扫描展开中
    已展开：
      叶子目录：
        A\APP_NAME
      文件：
        A\APP_NAME\ink.ico
        A\APP_NAME\shell.bat
        A\APP_NAME\setup.bat
        A\APP_NAME\setup_ink.vbs
        A\APP_NAME\setup.bat
        ...
  构建文件中
    Packing - A\APP_NAME\ink.ico
      转换cab - A\APP_NAME\ink.ico
    ...
  正在保存 A.bat
构建完成

```

在一个新环境双击，可以发现桌面多了个快捷方式，这就是安装成功了
*/

const fs = require(‘fs’);
const path = require(‘path’);
const { exec } = require(‘child_process’);
const argv = process.argv.slice(2);
const args = {};
argv.forEach((arg, index) => {
if (arg.startsWith(“/”)) {
let endIndex = argv.slice(index + 1).findIndex((arg) => arg.startsWith(‘/’)) + 1;
endIndex = endIndex == 0 ? undefined : index + endIndex;
args[arg.substring(1)] = argv.slice(index + 1, endIndex);
}
});
if (!args.files || args.files.length == 0) {
console.error(缺少 /files 指定需要嵌入的文件);
process.exit(1);
}
if (!args.output) {
console.error(缺少 /output 指定需要输出的文件);
process.exit(1);
}
if (!args.output[0].toLowerCase().endsWith(‘.bat’) && !args.output[0].toLowerCase().endsWith(‘.cmd’)) {
console.error(/output 文件必须是批处理格式);
process.exit(1);
}
if (args.files && args.files.some((filepath) => filepath.includes(“/”))) {
console.error(你似乎在使用 / 作为路径分割符号，请修改为 \\);
process.exit(1);
}
if (!args.dir && (args.files && args.files.some((filepath) => filepath.includes(“\”)))) {
console.error(你没有使用 /dir 创建目录树，但是在文件路径使用了路径符号，如果你希望打包目录，请直接写进去目录名\n也可能是你使用的路径不是相对路径导致的错误);
process.exit(1);
}
function readDirectory(dir, parentPath = ‘’, result = ) {
const fullPath = path.join(parentPath, dir);
const items = fs.readdirSync(fullPath);
let isLeaf = true;
items.forEach(item => {
const itemPath = path.join(fullPath, item);
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
isLeaf = false;
readDirectory(item, fullPath, result);
}
});
if (isLeaf) {
result.push(fullPath.replace(///g, ‘\’));
}
return result;
}
async function readDirectoryRecursively(dir) {
const filePaths = ;
try {
const files = fs.readdirSync(dir, { withFileTypes: true });
```
for (const file of files) {
  const filePath = path.join(dir, file.name);
  if (file.isDirectory()) {
    const subDirPaths = await readDirectoryRecursively(filePath);
    filePaths.push(...subDirPaths);
  } else {
    filePaths.push(filePath.replace(/\//g, '\\'));
  }
}

```
} catch (err) {
console.error( 无法读取目录 ${dir}: ${err});
}
return filePaths;
}
async function makeBase64(file) {
console.log( Packing - ${file});
const cabFilePath = path.join(path.dirname(file), ${path.basename(file)}.cab);
await createCab(file, cabFilePath);
let data = fs.readFileSync(cabFilePath);
let b64 = data.toString(‘base64’);
fs.unlinkSync(cabFilePath);
return makeUnzip(b64, file);
}
async function createCab(filePath, cabFilePath) {
return new Promise((resolve, reject) => {
console.log( 转换cab - ${filePath});
const command = makecab "${filePath}" "${cabFilePath}";
```
exec(command, (error, stdout, stderr) =&gt; {
  if (error) {
    reject(new Error(`makecab failed: ${error.message}\n${stderr}`));
  } else {
    resolve(stdout);
  }
});

```
});
}
function makeUnzip(b64, filepath) {
return [
(,
b64.split(/(\n|.{1,3000})/).filter(d => d).map((line) => echo;${line}).join(“\r\n”),
)&gt;"${filepath.replace(/"/g, '\\"')}.b64",
IF EXIST "${filepath.replace(/"/g, '\\"')}" del "${filepath.replace(/"/g, '\\"')}",
certutil -decode "${filepath.replace(/"/g, '\\"')}.b64" "${filepath.replace(/"/g, '\\"')}.tmp" &gt; nul,
IF NOT EXIST "${filepath.replace(/"/g, '\\"')}.tmp" echo;Failed to Create ${filepath},
expand "${filepath.replace(/"/g, '\\"')}.tmp" "${filepath.replace(/"/g, '\\"')}" &gt;nul,
del "${filepath.replace(/"/g, '\\"')}.b64",
del "${filepath.replace(/"/g, '\\"')}.tmp",
].join(“\r\n”);
}
async function main() {
console.log(构建开始);
let packed = [
‘@echo off’,
];
console.log( 树形扫描展开中);
let file = ‘’;
let tf = ;
while (file = args.files.shift()) {
if (fs.statSync(file).isDirectory()) {
let n_files = await readDirectoryRecursively(file) || ;
let n_dirs = await readDirectory(file) || ;
console.log([
    已展开：,
      叶子目录：,
        ${n_dirs.join('\n ')},
      文件：,
        ${n_files.join('\n ')},
].join(“\n”));
```
  tf = [...tf, ...n_files];
  args.dir = [...(args.dir || []), ...n_dirs];
} else tf.push(file);

```
};
if (args.dir) packed.push(md ${args.dir.map((d) =&gt; “${d.replace(/”/g, ‘\"’)}").join(" ")});
console.log( 构建文件中);
while (file = tf.shift()) {
let data = await makeBase64(file);
packed.push(data);
};
if (args.run) {
args.run.forEach((r) => {
packed.push(start "" "${r.replace(/"/g, '\\"')}");
});
}
if (args.del) packed.push(del "%~0");
console.log( 正在保存 ${args.output[0]});
fs.writeFileSync(args.output[0], packed.join(“\r\n”), { encoding: ‘latin1’ });
console.log(构建完成);
}
`

`main();
````

是的，现在用了微软Windows自带的 cap 工具压缩了打包后文件的大小，而且是windows自带  
也是不需要再安装什么新东西

然后帮助我写js代码里了，这样子方便看

---

<div class="post-metadata">

作者： ![James](https://www.nodeloc.com/user_avatar/www.nodeloc.com/james/32/1745_2.png) [@James](https://www.nodeloc.com/u/James)\
发布日期： [2025 年1 月 12 日 20:34 UTC](https://www.nodeloc.com/t/topic/33306/2 "2025-01-12T20:34:06Z")

</div>

打包成 GUI 的 exe，才能造福人类

---

<div class="post-metadata">

作者： ![Str](https://www.nodeloc.com/user_avatar/www.nodeloc.com/str/32/1108_2.png) [@Str](https://www.nodeloc.com/u/Str)\
发布日期： [2025 年1 月 12 日 20:51 UTC](https://www.nodeloc.com/t/topic/33306/3 "2025-01-12T20:51:31Z")

</div>

@“James”#p209652 只需要直接写个exe扔 /run 参数里就可以

---

<div class="post-metadata">

作者： ![Str](https://www.nodeloc.com/user_avatar/www.nodeloc.com/str/32/1108_2.png) [@Str](https://www.nodeloc.com/u/Str)\
发布日期： [2025 年1 月 12 日 20:55 UTC](https://www.nodeloc.com/t/topic/33306/4 "2025-01-12T20:55:16Z")

</div>

@“James”#p209652

 ~~[upl-file uuid=3cc29efc-193e-4e10-8d82-a330fc9ba784 size=27kB]~~www.zip[/upl-file]

---

<div class="post-metadata">

作者： ![Str](https://www.nodeloc.com/user_avatar/www.nodeloc.com/str/32/1108_2.png) [@Str](https://www.nodeloc.com/u/Str)\
发布日期： [2025 年1 月 12 日 23:07 UTC](https://www.nodeloc.com/t/topic/33306/5 "2025-01-12T23:07:26Z")

</div>

@“James”#p209652 草，理解错了，我以为你说的是这个安装程序，如果写gui的话……

可以不试试 elecrtron ，然后转成 exe

* * *
\*\*\*

4楼的压缩包看了吗，评价一下
