nuwax-codex 0.17.5 → 0.17.8

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.
@@ -7,8 +7,6 @@ import { spawnSync } from "node:child_process";
7
7
  import { createWriteStream, existsSync, mkdirSync, chmodSync } from "node:fs";
8
8
  import { homedir } from "node:os";
9
9
  import { join, dirname } from "node:path";
10
- import { pipeline } from "node:stream/promises";
11
- import { createGunzip } from "node:zlib";
12
10
  import { familySync } from "detect-libc";
13
11
  import { createRequire } from "node:module";
14
12
 
@@ -88,6 +86,13 @@ async function downloadBinary(url, outPath) {
88
86
  clearInterval(logInterval);
89
87
  ws.end();
90
88
  }
89
+ // 等待写流的文件描述符真正释放、数据全部落盘后再返回。解压阶段(tar / yauzl)
90
+ // 要读取这个归档文件,若 Node 仍持有写句柄,数据未必已刷盘、在 Windows 上还可能
91
+ // 被独占打开拒绝。ws.end() 只是结束写入,fd 在 ‘close’ 事件时才异步关闭,必须 await。
92
+ await new Promise((resolve, reject) => {
93
+ ws.once("close", resolve);
94
+ ws.once("error", reject);
95
+ });
91
96
  process.stderr.write("\n");
92
97
 
93
98
  if (process.platform !== "win32") {
@@ -96,62 +101,48 @@ async function downloadBinary(url, outPath) {
96
101
  }
97
102
 
98
103
  async function extractTarGz(archivePath, destDir) {
99
- // Simple tar.gz extraction: members are listed sequentially as
100
- // [header(512B)][content(padded to 512B)]...
101
- const { createReadStream } = await import("node:fs");
102
- const { createGunzip } = await import("node:zlib");
103
- const { pipeline } = await import("node:stream/promises");
104
- const { Transform } = await import("node:stream");
105
- const { writeFileSync } = await import("node:fs");
106
-
107
- const gunzip = createGunzip();
108
- const rs = createReadStream(archivePath);
109
- let buffer = Buffer.alloc(0);
110
-
111
- await pipeline(
112
- rs,
113
- gunzip,
114
- new Transform({
115
- transform(chunk, _enc, cb) {
116
- buffer = Buffer.concat([buffer, chunk]);
117
- while (buffer.length >= 512) {
118
- // Parse tar header
119
- const name = buffer.toString("utf8", 0, 100).replace(/\0.*$/, "");
120
- const sizeStr = buffer.toString("utf8", 124, 136).replace(/\0.*$/, "");
121
- const size = parseInt(sizeStr, 8);
122
- if (isNaN(size) || size < 0) break;
123
-
124
- const totalSize = Math.ceil((512 + size) / 512) * 512;
125
- if (buffer.length < totalSize) break;
126
-
127
- if (name && !name.endsWith("/") && size > 0) {
128
- const fileData = buffer.subarray(512, 512 + size);
129
- const outPath = join(destDir, name);
130
- mkdirSync(dirname(outPath), { recursive: true });
131
- writeFileSync(outPath, fileData);
132
- if (process.platform !== "win32") {
133
- chmodSync(outPath, 0o755);
134
- }
135
- }
136
-
137
- buffer = buffer.subarray(totalSize);
138
- }
139
- cb();
140
- },
141
- }),
142
- );
104
+ // 用成熟的 npm `tar` 库流式解压。仓库原先手写 tar 解析器,会把整个 entry 攒进
105
+ // 一个 Buffer 再落盘,对 codex 这种 ~280MB 的单文件归档是 O(n²) 内存拷贝
106
+ // (实测 15s 都写不出一个文件),在 macOS/Linux 上表现为“卡在 Extracting”。
107
+ // `tar` 走流式管道、内存恒定,并按归档内记录的 mode 还原可执行位(已验证 0o755)。
108
+ const tar = await import("tar");
109
+ await tar.x({ file: archivePath, cwd: destDir, gzip: true });
143
110
  }
144
111
 
145
112
  async function extractZip(archivePath, destDir) {
146
- // extractZip 仅在 Windows 上触发(getArchiveExt 对 win32 返回 zip)。
147
- // Windows 没有内置 `unzip` 命令,改用 PowerShell 的 Expand-Archive 解压。
148
- const { execSync } = await import("node:child_process");
149
- const psArchive = archivePath.replace(/'/g, "''");
150
- const psDest = destDir.replace(/'/g, "''");
151
- execSync(
152
- `powershell -NoProfile -NonInteractive -Command "Expand-Archive -LiteralPath '${psArchive}' -DestinationPath '${psDest}' -Force"`,
153
- { stdio: "inherit" },
154
- );
113
+ // Windows 上用纯 JS 的 yauzl 流式解压。原实现调用 PowerShell Expand-Archive,
114
+ // 两个硬伤:老版本 Windows PowerShell(5.0 前)没有该 cmdlet;它用独占 FileStream
115
+ // 打开 zip,极易撞“正由另一进程使用”(Node 写句柄未释放 / Defender 实时扫描)。
116
+ // yauzl 经 Node fs 以共享只读方式读取,绕开这两类问题,且逐条目流式落盘、内存恒定。
117
+ const yauzl = await import("yauzl");
118
+ const { createWriteStream, mkdirSync } = await import("node:fs");
119
+ const { dirname, join } = await import("node:path");
120
+
121
+ await new Promise((resolve, reject) => {
122
+ yauzl.open(archivePath, { lazyEntries: true, autoClose: true }, (err, zipfile) => {
123
+ if (err) return reject(err);
124
+ zipfile.on("error", reject);
125
+ zipfile.on("entry", (entry) => {
126
+ const outPath = join(destDir, entry.fileName);
127
+ if (/\/$/.test(entry.fileName)) {
128
+ mkdirSync(outPath, { recursive: true });
129
+ zipfile.readEntry();
130
+ return;
131
+ }
132
+ mkdirSync(dirname(outPath), { recursive: true });
133
+ zipfile.openReadStream(entry, (e, readStream) => {
134
+ if (e) return reject(e);
135
+ const ws = createWriteStream(outPath);
136
+ readStream.on("error", reject);
137
+ ws.on("error", reject);
138
+ ws.on("close", () => zipfile.readEntry());
139
+ readStream.pipe(ws);
140
+ });
141
+ });
142
+ zipfile.on("close", resolve);
143
+ zipfile.readEntry();
144
+ });
145
+ });
155
146
  }
156
147
 
157
148
  // -- main -------------------------------------------------------------------
@@ -2,13 +2,9 @@
2
2
  // Postinstall script: pre-downloads the native `nuwax-codex` binary from
3
3
  // Alibaba Cloud OSS so the first CLI invocation is instant.
4
4
 
5
- import { createWriteStream, existsSync, mkdirSync, chmodSync, writeFileSync } from "node:fs";
5
+ import { createWriteStream, existsSync, mkdirSync, chmodSync } from "node:fs";
6
6
  import { homedir } from "node:os";
7
7
  import { join, dirname } from "node:path";
8
- import { createGunzip } from "node:zlib";
9
- import { pipeline } from "node:stream/promises";
10
- import { Transform } from "node:stream";
11
- import { createReadStream } from "node:fs";
12
8
  import { createRequire } from "node:module";
13
9
 
14
10
  const require = createRequire(import.meta.url);
@@ -88,6 +84,13 @@ async function downloadBinary(url, outPath) {
88
84
  clearInterval(logInterval);
89
85
  ws.end();
90
86
  }
87
+ // 等待写流的文件描述符真正释放、数据全部落盘后再返回。解压阶段(tar / yauzl)
88
+ // 要读取这个归档文件,若 Node 仍持有写句柄,数据未必已刷盘、在 Windows 上还可能
89
+ // 被独占打开拒绝。ws.end() 只是结束写入,fd 在 ‘close’ 事件时才异步关闭,必须 await。
90
+ await new Promise((resolve, reject) => {
91
+ ws.once("close", resolve);
92
+ ws.once("error", reject);
93
+ });
91
94
  process.stderr.write("\n");
92
95
  if (process.platform !== "win32") {
93
96
  chmodSync(outPath, 0o755);
@@ -99,51 +102,48 @@ async function downloadBinary(url, outPath) {
99
102
  // ---------------------------------------------------------------------------
100
103
 
101
104
  async function extractTarGz(archivePath, destDir) {
102
- const gunzip = createGunzip();
103
- const rs = createReadStream(archivePath);
104
- let buffer = Buffer.alloc(0);
105
-
106
- await pipeline(
107
- rs,
108
- gunzip,
109
- new Transform({
110
- transform(chunk, _enc, cb) {
111
- buffer = Buffer.concat([buffer, chunk]);
112
- while (buffer.length >= 512) {
113
- const name = buffer.toString("utf8", 0, 100).replace(/\0.*$/, "");
114
- const sizeStr = buffer.toString("utf8", 124, 136).replace(/\0.*$/, "");
115
- const size = parseInt(sizeStr, 8);
116
- if (isNaN(size) || size < 0) break;
117
- const totalSize = Math.ceil((512 + size) / 512) * 512;
118
- if (buffer.length < totalSize) break;
119
- if (name && !name.endsWith("/") && size > 0) {
120
- const fileData = buffer.subarray(512, 512 + size);
121
- const outPath = join(destDir, name);
122
- mkdirSync(dirname(outPath), { recursive: true });
123
- writeFileSync(outPath, fileData);
124
- if (process.platform !== "win32") {
125
- chmodSync(outPath, 0o755);
126
- }
127
- }
128
- buffer = buffer.subarray(totalSize);
129
- }
130
- cb();
131
- },
132
- }),
133
- );
105
+ // 用成熟的 npm `tar` 库流式解压。仓库原先手写 tar 解析器,会把整个 entry 攒进
106
+ // 一个 Buffer 再落盘,对 codex 这种 ~280MB 的单文件归档是 O(n²) 内存拷贝
107
+ // (实测 15s 都写不出一个文件),在 macOS/Linux 上表现为“卡在 Extracting”。
108
+ // `tar` 走流式管道、内存恒定,并按归档内记录的 mode 还原可执行位(已验证 0o755)。
109
+ const tar = await import("tar");
110
+ await tar.x({ file: archivePath, cwd: destDir, gzip: true });
134
111
  }
135
112
 
136
113
  async function extractZip(archivePath, destDir) {
137
- // extractZip 仅在 Windows 上触发(getArchiveExt 对 win32 返回 zip)。
138
- // Windows 没有内置 `unzip` 命令,改用 PowerShell 的 Expand-Archive 解压,
139
- // 否则 Windows 用户安装后预下载会被静默吞掉、首次运行 CLI 直接报错退出。
140
- const { execSync } = await import("node:child_process");
141
- const psArchive = archivePath.replace(/'/g, "''");
142
- const psDest = destDir.replace(/'/g, "''");
143
- execSync(
144
- `powershell -NoProfile -NonInteractive -Command "Expand-Archive -LiteralPath '${psArchive}' -DestinationPath '${psDest}' -Force"`,
145
- { stdio: "inherit" },
146
- );
114
+ // Windows 上用纯 JS 的 yauzl 流式解压。原实现调用 PowerShell Expand-Archive,
115
+ // 两个硬伤:老版本 Windows PowerShell(5.0 前)没有该 cmdlet;它用独占 FileStream
116
+ // 打开 zip,极易撞“正由另一进程使用”(Node 写句柄未释放 / Defender 实时扫描)。
117
+ // yauzl 经 Node fs 以共享只读方式读取,绕开这两类问题,且逐条目流式落盘、内存恒定。
118
+ const yauzl = await import("yauzl");
119
+ const { createWriteStream, mkdirSync } = await import("node:fs");
120
+ const { dirname, join } = await import("node:path");
121
+
122
+ await new Promise((resolve, reject) => {
123
+ yauzl.open(archivePath, { lazyEntries: true, autoClose: true }, (err, zipfile) => {
124
+ if (err) return reject(err);
125
+ zipfile.on("error", reject);
126
+ zipfile.on("entry", (entry) => {
127
+ const outPath = join(destDir, entry.fileName);
128
+ if (/\/$/.test(entry.fileName)) {
129
+ mkdirSync(outPath, { recursive: true });
130
+ zipfile.readEntry();
131
+ return;
132
+ }
133
+ mkdirSync(dirname(outPath), { recursive: true });
134
+ zipfile.openReadStream(entry, (e, readStream) => {
135
+ if (e) return reject(e);
136
+ const ws = createWriteStream(outPath);
137
+ readStream.on("error", reject);
138
+ ws.on("error", reject);
139
+ ws.on("close", () => zipfile.readEntry());
140
+ readStream.pipe(ws);
141
+ });
142
+ });
143
+ zipfile.on("close", resolve);
144
+ zipfile.readEntry();
145
+ });
146
+ });
147
147
  }
148
148
 
149
149
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nuwax-codex",
3
- "version": "0.17.5",
3
+ "version": "0.17.8",
4
4
  "type": "module",
5
5
  "description": "Fork of OpenAI Codex CLI with domestic (Chinese) LLM support via genai",
6
6
  "license": "Apache-2.0",
@@ -22,6 +22,9 @@
22
22
  "llm",
23
23
  "genai"
24
24
  ],
25
+ "scripts": {
26
+ "postinstall": "node bin/postinstall.js"
27
+ },
25
28
  "bin": {
26
29
  "nuwax-codex": "bin/nuwax-codex.js"
27
30
  },
@@ -32,6 +35,8 @@
32
35
  "node": ">=20"
33
36
  },
34
37
  "dependencies": {
35
- "detect-libc": "^2.0.0"
38
+ "detect-libc": "^2.0.0",
39
+ "tar": "^7.0.0",
40
+ "yauzl": "^3.1.0"
36
41
  }
37
42
  }