@xx-in/tools 0.0.3 → 0.0.4

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 (3) hide show
  1. package/README.md +5 -5
  2. package/dist/cli.js +132 -50
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -65,13 +65,13 @@ npm run publish:npm:registry
65
65
  - **`open [path]`**:文件管理器目录打开工具。
66
66
  - 在系统内置文件管理器中打开指定目录。若未提供参数,则默认打开当前的工作目录。
67
67
 
68
- ### 2. macOS 本地工具安装管理
68
+ ### 2. 本地工具安装管理
69
69
 
70
- - **`install <path>`**:macOS 本地工具安装器。
71
- - 支持安装单个可执行文件,以及 `.tar.gz`、`.tgz`、`.tar.xz`、`.txz`、`.tar` 压缩包。
70
+ - **`install <path>`**:macOS / Windows 本地工具安装器。
71
+ - 支持安装单个可执行文件,以及 `.zip`、`.tar.gz`、`.tgz`、`.tar.xz`、`.txz`、`.tar` 压缩包。
72
72
  - 自动安装到 `~/.xx-tools/apps/`,并在 `~/.xx-tools/bin/` 创建终端命令入口。
73
- - 首次安装时会自动把 `~/.xx-tools/bin` 写入你的 Shell 配置,后续即可在终端中直接调用已安装工具。
74
- - **`uninstall <package>`**:macOS 本地工具卸载器。
73
+ - macOS 下会自动写入 Shell 配置;Windows 下会自动写入当前用户 PATH,后续即可在终端中直接调用已安装工具。
74
+ - **`uninstall <package>`**:macOS / Windows 本地工具卸载器。
75
75
  - 按安装名卸载工具,同时删除安装目录和终端命令入口。
76
76
 
77
77
  ### 3. 压缩与解压
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import process4 from "process";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@xx-in/tools",
10
- version: "0.0.3",
10
+ version: "0.0.4",
11
11
  description: "A command-line tool to run in any system",
12
12
  license: "MIT",
13
13
  type: "module",
@@ -72,7 +72,7 @@ _xx() {
72
72
  'create:\u9012\u5F52\u521B\u5EFA\u6587\u4EF6\u6216\u76EE\u5F55'
73
73
  'format:\u4F7F\u7528 Prettier \u683C\u5F0F\u5316\u4EE3\u7801'
74
74
  'help:\u663E\u793A\u6307\u5B9A\u547D\u4EE4\u7684\u5E2E\u52A9\u4FE1\u606F'
75
- 'install:macOS \u672C\u5730\u5DE5\u5177\u5B89\u88C5\u5668 (\u652F\u6301\u53EF\u6267\u884C\u6587\u4EF6\u4E0E tar \u5305)'
75
+ 'install:macOS/Windows \u672C\u5730\u5DE5\u5177\u5B89\u88C5\u5668 (\u652F\u6301\u53EF\u6267\u884C\u6587\u4EF6\u3001zip\u3001tar \u5305)'
76
76
  'ip:\u672C\u5730\u6D3B\u8DC3\u7F51\u5361\u53CA\u7F51\u5173\u4FA6\u6D4B'
77
77
  'list:\u5217\u51FA\u76EE\u5F55\u4E2D\u7684\u6240\u6709\u6587\u4EF6\u548C\u76EE\u5F55\uFF08\u542B\u9690\u85CF\u9879\uFF09'
78
78
  'move:\u79FB\u52A8\u6216\u91CD\u547D\u540D\u6587\u4EF6\u53CA\u76EE\u5F55'
@@ -82,7 +82,7 @@ _xx() {
82
82
  'remove:\u5B89\u5168\u5730\u5C06\u6307\u5B9A\u7684\u6587\u4EF6\u6216\u76EE\u5F55\u79FB\u81F3\u7CFB\u7EDF\u56DE\u6536\u7AD9\uFF08\u652F\u6301\u901A\u914D\u7B26\u53CA\u591A\u9009\uFF09'
83
83
  'search:\u9012\u5F52\u641C\u7D22\u76EE\u5F55\u4E0B\u5305\u542B\u6307\u5B9A\u5173\u952E\u8BCD\u7684\u6587\u4EF6\u6216\u76EE\u5F55'
84
84
  'translate:\u7EC8\u7AEF\u7FFB\u8BD1\u5DE5\u5177'
85
- 'uninstall:macOS \u672C\u5730\u5DE5\u5177\u5378\u8F7D\u5668\uFF08\u79FB\u9664\u5B89\u88C5\u76EE\u5F55\u548C\u7EC8\u7AEF\u547D\u4EE4\u5165\u53E3\uFF09'
85
+ 'uninstall:macOS/Windows \u672C\u5730\u5DE5\u5177\u5378\u8F7D\u5668\uFF08\u79FB\u9664\u5B89\u88C5\u76EE\u5F55\u548C\u7EC8\u7AEF\u547D\u4EE4\u5165\u53E3\uFF09'
86
86
  'unzip:\u89E3\u538B ZIP \u538B\u7F29\u5305'
87
87
  'upgrade:\u81EA\u52A8\u66F4\u65B0 xx \u547D\u4EE4\u884C\u5DE5\u5177\u81F3\u6700\u65B0\u7248\u672C'
88
88
  'zip:\u81EA\u52A8\u8FC7\u6EE4\u5E76\u6253\u5305\u4E3A ZIP'
@@ -111,7 +111,7 @@ _xx() {
111
111
  'copy:\u590D\u5236\u6587\u4EF6\u6216\u76EE\u5F55\uFF08\u652F\u6301\u9012\u5F52\u590D\u5236\uFF09'
112
112
  'create:\u9012\u5F52\u521B\u5EFA\u6587\u4EF6\u6216\u76EE\u5F55'
113
113
  'format:\u4F7F\u7528 Prettier \u683C\u5F0F\u5316\u4EE3\u7801'
114
- 'install:macOS \u672C\u5730\u5DE5\u5177\u5B89\u88C5\u5668 (\u652F\u6301\u53EF\u6267\u884C\u6587\u4EF6\u4E0E tar \u5305)'
114
+ 'install:macOS/Windows \u672C\u5730\u5DE5\u5177\u5B89\u88C5\u5668 (\u652F\u6301\u53EF\u6267\u884C\u6587\u4EF6\u3001zip\u3001tar \u5305)'
115
115
  'ip:\u672C\u5730\u6D3B\u8DC3\u7F51\u5361\u53CA\u7F51\u5173\u4FA6\u6D4B'
116
116
  'list:\u5217\u51FA\u76EE\u5F55\u4E2D\u7684\u6240\u6709\u6587\u4EF6\u548C\u76EE\u5F55\uFF08\u542B\u9690\u85CF\u9879\uFF09'
117
117
  'move:\u79FB\u52A8\u6216\u91CD\u547D\u540D\u6587\u4EF6\u53CA\u76EE\u5F55'
@@ -121,7 +121,7 @@ _xx() {
121
121
  'remove:\u5B89\u5168\u5730\u5C06\u6307\u5B9A\u7684\u6587\u4EF6\u6216\u76EE\u5F55\u79FB\u81F3\u7CFB\u7EDF\u56DE\u6536\u7AD9\uFF08\u652F\u6301\u901A\u914D\u7B26\u53CA\u591A\u9009\uFF09'
122
122
  'search:\u9012\u5F52\u641C\u7D22\u76EE\u5F55\u4E0B\u5305\u542B\u6307\u5B9A\u5173\u952E\u8BCD\u7684\u6587\u4EF6\u6216\u76EE\u5F55'
123
123
  'translate:\u7EC8\u7AEF\u7FFB\u8BD1\u5DE5\u5177'
124
- 'uninstall:macOS \u672C\u5730\u5DE5\u5177\u5378\u8F7D\u5668\uFF08\u79FB\u9664\u5B89\u88C5\u76EE\u5F55\u548C\u7EC8\u7AEF\u547D\u4EE4\u5165\u53E3\uFF09'
124
+ 'uninstall:macOS/Windows \u672C\u5730\u5DE5\u5177\u5378\u8F7D\u5668\uFF08\u79FB\u9664\u5B89\u88C5\u76EE\u5F55\u548C\u7EC8\u7AEF\u547D\u4EE4\u5165\u53E3\uFF09'
125
125
  'unzip:\u89E3\u538B ZIP \u538B\u7F29\u5305'
126
126
  'upgrade:\u81EA\u52A8\u66F4\u65B0 xx \u547D\u4EE4\u884C\u5DE5\u5177\u81F3\u6700\u65B0\u7248\u672C'
127
127
  'zip:\u81EA\u52A8\u8FC7\u6EE4\u5E76\u6253\u5305\u4E3A ZIP'
@@ -770,13 +770,38 @@ import { resolve as resolve4, join as join5, basename as basename3 } from "path"
770
770
 
771
771
  // utils/macos-apps.ts
772
772
  import fs5 from "fs/promises";
773
- import { basename as basename2, extname, join as join4 } from "path";
773
+ import { basename as basename2, delimiter, extname, join as join4 } from "path";
774
774
  import pc5 from "picocolors";
775
+
776
+ // utils/archive.ts
777
+ import AdmZip from "adm-zip";
778
+ import * as tar from "tar";
779
+ import { mkdir } from "fs/promises";
780
+ async function compressZip(sourceDir, outputPath) {
781
+ const zip = new AdmZip();
782
+ zip.addLocalFolder(sourceDir);
783
+ zip.writeZip(outputPath);
784
+ }
785
+ async function uncompressZip(src, destDir) {
786
+ await mkdir(destDir, { recursive: true });
787
+ const zip = new AdmZip(src);
788
+ zip.extractAllTo(destDir, true);
789
+ }
790
+ async function uncompressTar(src, destDir) {
791
+ await mkdir(destDir, { recursive: true });
792
+ await tar.x({ file: src, cwd: destDir });
793
+ }
794
+ async function uncompressTgz(src, destDir) {
795
+ await mkdir(destDir, { recursive: true });
796
+ await tar.x({ file: src, gzip: true, cwd: destDir });
797
+ }
798
+
799
+ // utils/macos-apps.ts
775
800
  var APP_DIR_NAME = ".xx-tools";
776
801
  var PATH_MARKER_BEGIN = "# >>> xx-tools path >>>";
777
802
  var PATH_MARKER_END = "# <<< xx-tools path <<<";
778
803
  function getHomeDir() {
779
- return process.env["HOME"] || process.cwd();
804
+ return process.env["HOME"] || process.env["USERPROFILE"] || process.cwd();
780
805
  }
781
806
  function getToolsRootDir() {
782
807
  return join4(getHomeDir(), APP_DIR_NAME);
@@ -790,9 +815,11 @@ function getBinDir() {
790
815
  function getManifestPath(appName) {
791
816
  return join4(getAppsDir(), appName, ".xx-manifest.json");
792
817
  }
793
- function ensureMacOS() {
794
- if (process.platform !== "darwin") {
795
- console.error(pc5.red("\u274C \u5F53\u524D install/uninstall \u4EC5\u652F\u6301 macOS\u3002"));
818
+ function ensureSupportedDesktopOS() {
819
+ if (process.platform !== "darwin" && process.platform !== "win32") {
820
+ console.error(
821
+ pc5.red("\u274C \u5F53\u524D install/uninstall \u4EC5\u652F\u6301 macOS \u548C Windows\u3002")
822
+ );
796
823
  return false;
797
824
  }
798
825
  return true;
@@ -815,6 +842,10 @@ function getPreferredProfiles() {
815
842
  return Array.from(new Set(profiles));
816
843
  }
817
844
  async function ensureBinDirOnPath() {
845
+ if (process.platform === "win32") {
846
+ await ensureWindowsBinDirOnPath();
847
+ return;
848
+ }
818
849
  const pathBlock = `${PATH_MARKER_BEGIN}
819
850
  export PATH="$HOME/${APP_DIR_NAME}/bin:$PATH"
820
851
  ${PATH_MARKER_END}`;
@@ -836,7 +867,43 @@ ${PATH_MARKER_END}`;
836
867
  }
837
868
  }
838
869
  }
870
+ async function ensureWindowsBinDirOnPath() {
871
+ const binDir = getBinDir();
872
+ const currentUserPath = process.env["Path"] || process.env["PATH"] || "";
873
+ const alreadyInCurrentEnv = currentUserPath.split(delimiter).some((item) => item.trim().toLowerCase() === binDir.toLowerCase());
874
+ const script = [
875
+ "$binDir = [System.IO.Path]::GetFullPath($args[0])",
876
+ "$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')",
877
+ "if ([string]::IsNullOrWhiteSpace($userPath)) { $userPath = '' }",
878
+ "$items = $userPath -split ';' | Where-Object { $_ -and $_.Trim() -ne '' }",
879
+ "$exists = $items | Where-Object { $_.Trim().ToLower() -eq $binDir.ToLower() }",
880
+ "if (-not $exists) {",
881
+ ' $newPath = if ($userPath) { "$binDir;$userPath" } else { $binDir }',
882
+ " [Environment]::SetEnvironmentVariable('Path', $newPath, 'User')",
883
+ " Write-Output 'UPDATED'",
884
+ "} else {",
885
+ " Write-Output 'UNCHANGED'",
886
+ "}"
887
+ ].join(" ");
888
+ const { success, stdout } = await runCommand("powershell", [
889
+ "-NoProfile",
890
+ "-Command",
891
+ script,
892
+ binDir
893
+ ]);
894
+ if (success) {
895
+ const output = decodeOutput(stdout).trim();
896
+ if (output.includes("UPDATED")) {
897
+ console.log(pc5.green(`\u2714 \u5DF2\u5199\u5165\u7528\u6237 PATH: ${binDir}`));
898
+ } else if (!alreadyInCurrentEnv) {
899
+ console.log(pc5.dim(`\u2796 \u7528\u6237 PATH \u4E2D\u5DF2\u5B58\u5728: ${binDir}`));
900
+ }
901
+ }
902
+ }
839
903
  function getShellReloadCommand() {
904
+ if (process.platform === "win32") {
905
+ return "\u91CD\u65B0\u6253\u5F00 PowerShell \u6216 CMD";
906
+ }
840
907
  const shell = process.env["SHELL"] || "";
841
908
  if (shell.includes("zsh")) {
842
909
  return "source ~/.zshrc";
@@ -878,10 +945,18 @@ async function findExecutablesRecursively(rootDir) {
878
945
  }
879
946
  if (!entry.isFile()) continue;
880
947
  const stat = await fs5.stat(fullPath);
881
- const isExecutable = stat.mode ? (stat.mode & 73) !== 0 : false;
882
- if (!isExecutable) continue;
883
- if (/\.(dylib|so|a|o|txt|md|json|yaml|yml|ts|js|c|h|sh)$/i.test(entry.name)) {
884
- continue;
948
+ if (process.platform === "win32") {
949
+ if (!/\.(exe|cmd|bat|ps1)$/i.test(entry.name)) {
950
+ continue;
951
+ }
952
+ } else {
953
+ const isExecutable = stat.mode ? (stat.mode & 73) !== 0 : false;
954
+ if (!isExecutable) continue;
955
+ if (/\.(dylib|so|a|o|txt|md|json|yaml|yml|ts|js|c|h|sh)$/i.test(
956
+ entry.name
957
+ )) {
958
+ continue;
959
+ }
885
960
  }
886
961
  matches.push(fullPath);
887
962
  }
@@ -893,11 +968,34 @@ async function createLinks(appName, executablePaths) {
893
968
  const binDir = getBinDir();
894
969
  const linkNames = [];
895
970
  for (const executablePath of executablePaths) {
896
- const linkName = basename2(executablePath);
897
- const linkPath = join4(binDir, linkName);
898
- await fs5.rm(linkPath, { force: true });
899
- await fs5.symlink(executablePath, linkPath);
900
- linkNames.push(linkName);
971
+ const executableBaseName = basename2(executablePath);
972
+ if (process.platform === "win32") {
973
+ const commandName = basename2(
974
+ executableBaseName,
975
+ extname(executableBaseName)
976
+ );
977
+ const cmdPath = join4(binDir, `${commandName}.cmd`);
978
+ const ps1Path = join4(binDir, `${commandName}.ps1`);
979
+ const quotedExecutable = executablePath.replace(/"/g, '""');
980
+ await fs5.rm(cmdPath, { force: true });
981
+ await fs5.rm(ps1Path, { force: true });
982
+ await fs5.writeFile(
983
+ cmdPath,
984
+ `@echo off\r
985
+ "${quotedExecutable}" %*\r
986
+ `,
987
+ "utf-8"
988
+ );
989
+ await fs5.writeFile(ps1Path, `& "${quotedExecutable}" @args
990
+ `, "utf-8");
991
+ linkNames.push(commandName);
992
+ } else {
993
+ const linkName = executableBaseName;
994
+ const linkPath = join4(binDir, linkName);
995
+ await fs5.rm(linkPath, { force: true });
996
+ await fs5.symlink(executablePath, linkPath);
997
+ linkNames.push(linkName);
998
+ }
901
999
  }
902
1000
  if (linkNames.length > 0) {
903
1001
  console.log(
@@ -927,7 +1025,12 @@ async function removeInstalledApp(appName) {
927
1025
  const manifest = await readManifest(appName);
928
1026
  if (!manifest) return false;
929
1027
  for (const linkName of manifest.links) {
930
- await fs5.rm(join4(getBinDir(), linkName), { force: true });
1028
+ if (process.platform === "win32") {
1029
+ await fs5.rm(join4(getBinDir(), `${linkName}.cmd`), { force: true });
1030
+ await fs5.rm(join4(getBinDir(), `${linkName}.ps1`), { force: true });
1031
+ } else {
1032
+ await fs5.rm(join4(getBinDir(), linkName), { force: true });
1033
+ }
931
1034
  console.log(pc5.green(`\u2714 \u5DF2\u79FB\u9664\u7EC8\u7AEF\u547D\u4EE4: ${linkName}`));
932
1035
  }
933
1036
  await fs5.rm(manifest.installDir, { recursive: true, force: true });
@@ -944,6 +1047,10 @@ async function listInstalledApps() {
944
1047
  }
945
1048
  async function extractArchiveToDir(archivePath, destDir) {
946
1049
  const lower = archivePath.toLowerCase();
1050
+ if (lower.endsWith(".zip")) {
1051
+ await uncompressZip(archivePath, destDir);
1052
+ return true;
1053
+ }
947
1054
  let args;
948
1055
  if (lower.endsWith(".tar.gz") || lower.endsWith(".tgz")) {
949
1056
  args = ["-xzf", archivePath, "-C", destDir];
@@ -1016,9 +1123,9 @@ async function installArchive(absPath) {
1016
1123
  }
1017
1124
  function registerInstallCommand(program2) {
1018
1125
  program2.command("install [path]").description(
1019
- "macOS \u672C\u5730\u5DE5\u5177\u5B89\u88C5\u5668\uFF08\u652F\u6301\u53EF\u6267\u884C\u6587\u4EF6\u3001.tar.gz\u3001.tgz\u3001.tar.xz\u3001.txz\u3001.tar\uFF09"
1126
+ "macOS/Windows \u672C\u5730\u5DE5\u5177\u5B89\u88C5\u5668\uFF08\u652F\u6301\u53EF\u6267\u884C\u6587\u4EF6\u3001.zip\u3001.tar.gz\u3001.tgz\u3001.tar.xz\u3001.txz\u3001.tar\uFF09"
1020
1127
  ).action(async (target) => {
1021
- if (!ensureMacOS()) return;
1128
+ if (!ensureSupportedDesktopOS()) return;
1022
1129
  if (!target) {
1023
1130
  console.error(pc6.red("\u274C \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u672C\u5730\u6587\u4EF6\u8DEF\u5F84\u3002"));
1024
1131
  return;
@@ -1037,7 +1144,7 @@ function registerInstallCommand(program2) {
1037
1144
  await ensureToolDirectories();
1038
1145
  await ensureBinDirOnPath();
1039
1146
  const lowerPath = target.toLowerCase();
1040
- if (lowerPath.endsWith(".tar.gz") || lowerPath.endsWith(".tgz") || lowerPath.endsWith(".tar.xz") || lowerPath.endsWith(".txz") || lowerPath.endsWith(".tar")) {
1147
+ if (lowerPath.endsWith(".zip") || lowerPath.endsWith(".tar.gz") || lowerPath.endsWith(".tgz") || lowerPath.endsWith(".tar.xz") || lowerPath.endsWith(".txz") || lowerPath.endsWith(".tar")) {
1041
1148
  await installArchive(absPath);
1042
1149
  } else {
1043
1150
  await installBinary(absPath);
@@ -1928,8 +2035,8 @@ function registerTanslateCommand(program2) {
1928
2035
  // commands/uninstall.ts
1929
2036
  import pc16 from "picocolors";
1930
2037
  function registerUninstallCommand(program2) {
1931
- program2.command("uninstall <package>").description("macOS \u672C\u5730\u5DE5\u5177\u5378\u8F7D\u5668\uFF08\u79FB\u9664\u5B89\u88C5\u76EE\u5F55\u548C\u7EC8\u7AEF\u547D\u4EE4\u5165\u53E3\uFF09").action(async (pkg) => {
1932
- if (!ensureMacOS()) return;
2038
+ program2.command("uninstall <package>").description("macOS/Windows \u672C\u5730\u5DE5\u5177\u5378\u8F7D\u5668\uFF08\u79FB\u9664\u5B89\u88C5\u76EE\u5F55\u548C\u7EC8\u7AEF\u547D\u4EE4\u5165\u53E3\uFF09").action(async (pkg) => {
2039
+ if (!ensureSupportedDesktopOS()) return;
1933
2040
  if (!pkg) {
1934
2041
  console.error(pc16.red("\u274C \u8BF7\u63D0\u4F9B\u8981\u5378\u8F7D\u7684\u5E94\u7528\u540D\u3002"));
1935
2042
  return;
@@ -1962,31 +2069,6 @@ function registerUninstallCommand(program2) {
1962
2069
  import fs13 from "fs/promises";
1963
2070
  import pc17 from "picocolors";
1964
2071
  import { join as join8, basename as basename6, extname as extname2, resolve as resolve11 } from "path";
1965
-
1966
- // utils/archive.ts
1967
- import AdmZip from "adm-zip";
1968
- import * as tar from "tar";
1969
- import { mkdir } from "fs/promises";
1970
- async function compressZip(sourceDir, outputPath) {
1971
- const zip = new AdmZip();
1972
- zip.addLocalFolder(sourceDir);
1973
- zip.writeZip(outputPath);
1974
- }
1975
- async function uncompressZip(src, destDir) {
1976
- await mkdir(destDir, { recursive: true });
1977
- const zip = new AdmZip(src);
1978
- zip.extractAllTo(destDir, true);
1979
- }
1980
- async function uncompressTar(src, destDir) {
1981
- await mkdir(destDir, { recursive: true });
1982
- await tar.x({ file: src, cwd: destDir });
1983
- }
1984
- async function uncompressTgz(src, destDir) {
1985
- await mkdir(destDir, { recursive: true });
1986
- await tar.x({ file: src, gzip: true, cwd: destDir });
1987
- }
1988
-
1989
- // commands/unzip.ts
1990
2072
  async function uncompressRar(src, dest) {
1991
2073
  const hasUnrar = isCommandAvailable("unrar");
1992
2074
  if (hasUnrar) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xx-in/tools",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "A command-line tool to run in any system",
5
5
  "license": "MIT",
6
6
  "type": "module",