@bash0816/claude-code 2.1.161 → 2.1.177
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
|
@@ -37,7 +37,7 @@ npm が `claude` bin link を管理する状態になれば、通常の `npm ins
|
|
|
37
37
|
Latest audited version / 最新監査済み版:
|
|
38
38
|
|
|
39
39
|
```sh
|
|
40
|
-
npm install -g @bash0816/claude-code@2.1.161
|
|
40
|
+
npm install -g @bash0816/claude-code@2.1.161-2
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
## Update / 更新
|
|
@@ -54,6 +54,14 @@ claude update
|
|
|
54
54
|
npm install -g @bash0816/claude-code@<latest_audited_version>
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
+
If you already have the repo's `2.1.161` release or the official upstream
|
|
58
|
+
`2.1.165` installed, install `2.1.159-13` explicitly. `claude update` will not
|
|
59
|
+
roll a newer installed terminal back to this repo's current audited release.
|
|
60
|
+
|
|
61
|
+
この repo の `2.1.161` release または official upstream の `2.1.165` を
|
|
62
|
+
すでに入れている端末では、`2.1.159-13` を明示 install してください。
|
|
63
|
+
`claude update` だけでは、より新しい端末をこの repo の現在の audited release に戻しません。
|
|
64
|
+
|
|
57
65
|
Normal launch also checks the same manifest with a short timeout and prints a notice when a newer audited version is available.
|
|
58
66
|
|
|
59
67
|
通常起動時も、同じ manifest を短い timeout で確認し、新しい監査済み version があれば通知します。
|
|
@@ -229,6 +229,26 @@
|
|
|
229
229
|
"status": "termux_verified"
|
|
230
230
|
},
|
|
231
231
|
"2.1.161": {
|
|
232
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.161",
|
|
233
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.161",
|
|
234
|
+
"entry_js_offset": 225551924,
|
|
235
|
+
"entry_end_offset": 241503454,
|
|
236
|
+
"tarball_integrity": "sha512-rLEzd8/q4UnTLDSAd3eYgog+G0iZ8y6K8yh0gSxf82d7uOkypYaBUkbmFENuHuzIzCIf9Yy/utCJfp6kQFjUow==",
|
|
237
|
+
"tarball_sha256": "e87d973bbe58593e06c101196f6417f7d950bf777cb9f1a8ff002ebf987ea066",
|
|
238
|
+
"tarball_size": 74314760,
|
|
239
|
+
"status": "offset_discovered"
|
|
240
|
+
},
|
|
241
|
+
"2.1.161-1": {
|
|
242
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.161",
|
|
243
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.161",
|
|
244
|
+
"entry_js_offset": 225551924,
|
|
245
|
+
"entry_end_offset": 241503454,
|
|
246
|
+
"tarball_integrity": "sha512-rLEzd8/q4UnTLDSAd3eYgog+G0iZ8y6K8yh0gSxf82d7uOkypYaBUkbmFENuHuzIzCIf9Yy/utCJfp6kQFjUow==",
|
|
247
|
+
"tarball_sha256": "e87d973bbe58593e06c101196f6417f7d950bf777cb9f1a8ff002ebf987ea066",
|
|
248
|
+
"tarball_size": 74314760,
|
|
249
|
+
"status": "reverted"
|
|
250
|
+
},
|
|
251
|
+
"2.1.161-2": {
|
|
232
252
|
"wrapper_spec": "@anthropic-ai/claude-code@2.1.161",
|
|
233
253
|
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.161",
|
|
234
254
|
"entry_js_offset": 225551924,
|
|
@@ -237,6 +257,24 @@
|
|
|
237
257
|
"tarball_sha256": "e87d973bbe58593e06c101196f6417f7d950bf777cb9f1a8ff002ebf987ea066",
|
|
238
258
|
"tarball_size": 74314760,
|
|
239
259
|
"status": "termux_verified"
|
|
260
|
+
},
|
|
261
|
+
"2.1.165": {
|
|
262
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.165",
|
|
263
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.165",
|
|
264
|
+
"entry_js_offset": 226798932,
|
|
265
|
+
"entry_end_offset": 242980856,
|
|
266
|
+
"tarball_integrity": "sha512-uGwKxRQGTxh3BZGnX096CLf/2us5lUFnk4KerWhG4WCpRiEUSu4TUDojp/S3SUzA//FpM57fViUW07XL3T19lw==",
|
|
267
|
+
"tarball_sha256": "6dc6d84624bedaf5ff0948b178fdfc9c2724d8051cb00e70559e292055f7155e",
|
|
268
|
+
"status": "reverted"
|
|
269
|
+
},
|
|
270
|
+
"2.1.177": {
|
|
271
|
+
"wrapper_spec": "@anthropic-ai/claude-code@2.1.177",
|
|
272
|
+
"native_spec": "@anthropic-ai/claude-code-linux-arm64@2.1.177",
|
|
273
|
+
"entry_js_offset": 231509988,
|
|
274
|
+
"entry_end_offset": 248542413,
|
|
275
|
+
"tarball_integrity": "sha512-qb0K+/xwaZFz9spBUcDX3vNgEFTSNdbDze8AOpU4R9weVrwBY9zAO/OPqxC9qNhb5ZIRnJAZLKiICEeEUHowzA==",
|
|
276
|
+
"tarball_sha256": "dc6f28ab95d8748528a92e207d0f2d1dcd173855d18f11b8f82c5827bbdcb234",
|
|
277
|
+
"status": "offset_discovered"
|
|
240
278
|
}
|
|
241
279
|
}
|
|
242
280
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifest_version": 1,
|
|
3
3
|
"package_name": "@bash0816/claude-code",
|
|
4
|
-
"latest_audited_version": "2.1.161",
|
|
5
|
-
"latest_candidate_version": "2.1.
|
|
4
|
+
"latest_audited_version": "2.1.161-2",
|
|
5
|
+
"latest_candidate_version": "2.1.177",
|
|
6
6
|
"previous_stable_version": "2.1.159-13",
|
|
7
7
|
"manifest_url": "https://raw.githubusercontent.com/bash0816/ClaudeCode-Termux/main/config/claude-termux-release-manifest.json"
|
|
8
8
|
}
|
|
@@ -49,7 +49,7 @@ for _a in "$@"; do
|
|
|
49
49
|
done
|
|
50
50
|
|
|
51
51
|
if [ "$_pf" = "1" ] && [ "${CLAUDE_TERMUX_STDIN:-}" != "inherit" ]; then
|
|
52
|
-
_helper=$(mktemp "${
|
|
52
|
+
_helper=$(mktemp "${TERMUX_TMPDIR}/claude-helper.XXXXXX.js")
|
|
53
53
|
trap 'rm -f "$_helper"' EXIT HUP INT TERM
|
|
54
54
|
cat <<'NODE' > "$_helper"
|
|
55
55
|
const fs = require('fs');
|
|
@@ -73,8 +73,39 @@ class RequestedExit extends Error {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {
|
|
77
|
+
const prefix = `cli.${currentEntryJsOffset}.${currentEntryEndOffset}.`;
|
|
78
|
+
const suffix = '.bare-path.js';
|
|
79
|
+
const maxAgeMs = 24 * 60 * 60 * 1000;
|
|
80
|
+
let entries;
|
|
81
|
+
try {
|
|
82
|
+
entries = fs.readdirSync(currentWorkdir, { withFileTypes: true });
|
|
83
|
+
} catch {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
88
|
+
if (!entry.name.startsWith(prefix) || !entry.name.endsWith(suffix)) continue;
|
|
89
|
+
const filePath = path.join(currentWorkdir, entry.name);
|
|
90
|
+
let stats;
|
|
91
|
+
try {
|
|
92
|
+
stats = fs.statSync(filePath);
|
|
93
|
+
} catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (Number.isFinite(stats.mtimeMs) && now - stats.mtimeMs < maxAgeMs) continue;
|
|
97
|
+
try {
|
|
98
|
+
fs.rmSync(filePath, { force: true });
|
|
99
|
+
} catch {}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
76
103
|
function ensureEntryFile() {
|
|
77
|
-
|
|
104
|
+
cleanupStaleEntryFiles();
|
|
105
|
+
const extractedFile = path.join(
|
|
106
|
+
workdir,
|
|
107
|
+
`cli.${entryJsOffset}.${entryEndOffset}.${process.pid}.${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.bare-path.js`,
|
|
108
|
+
);
|
|
78
109
|
const len = entryEndOffset - entryJsOffset;
|
|
79
110
|
if (!(len > 0)) throw new Error('invalid replay offsets');
|
|
80
111
|
|
|
@@ -87,15 +118,165 @@ function ensureEntryFile() {
|
|
|
87
118
|
return extractedFile;
|
|
88
119
|
}
|
|
89
120
|
|
|
121
|
+
function isFullWidthCodePoint(codePoint) {
|
|
122
|
+
return Number.isFinite(codePoint) && (
|
|
123
|
+
codePoint >= 0x1100 && (
|
|
124
|
+
codePoint <= 0x115f ||
|
|
125
|
+
codePoint === 0x2329 ||
|
|
126
|
+
codePoint === 0x232a ||
|
|
127
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
128
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
129
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
130
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
131
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
132
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
133
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
134
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f6ff) ||
|
|
135
|
+
(codePoint >= 0x1fa70 && codePoint <= 0x1faff) ||
|
|
136
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
137
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function graphemeWidth(grapheme) {
|
|
143
|
+
let width = 0;
|
|
144
|
+
const symbols = Array.from(String(grapheme ?? ''));
|
|
145
|
+
const codePoints = symbols.map(symbol => symbol.codePointAt(0)).filter(codePoint => Number.isFinite(codePoint));
|
|
146
|
+
if (codePoints.length === 0) return 0;
|
|
147
|
+
if (codePoints.length > 1 && codePoints.every(codePoint => codePoint >= 0x1f1e6 && codePoint <= 0x1f1ff)) {
|
|
148
|
+
return 2;
|
|
149
|
+
}
|
|
150
|
+
if (codePoints.includes(0x20e3) || codePoints.includes(0x200d)) {
|
|
151
|
+
return 2;
|
|
152
|
+
}
|
|
153
|
+
if (codePoints.includes(0xfe0f) || codePoints.some(codePoint => codePoint >= 0x2600 && codePoint <= 0x27bf)) {
|
|
154
|
+
return 2;
|
|
155
|
+
}
|
|
156
|
+
for (const symbol of symbols) {
|
|
157
|
+
const codePoint = symbol.codePointAt(0);
|
|
158
|
+
if (codePoint === undefined || codePoint === 0) continue;
|
|
159
|
+
if (codePoint < 32 || (codePoint >= 0x7f && codePoint < 0xa0)) continue;
|
|
160
|
+
if (codePoint === 0x200d || codePoint === 0xfe0f) continue;
|
|
161
|
+
if (/\p{M}/u.test(symbol)) continue;
|
|
162
|
+
if (isFullWidthCodePoint(codePoint)) return 2;
|
|
163
|
+
width = 1;
|
|
164
|
+
}
|
|
165
|
+
return width;
|
|
166
|
+
}
|
|
167
|
+
|
|
90
168
|
function stringWidth(value) {
|
|
91
|
-
const text =
|
|
169
|
+
const text = stripANSI(value);
|
|
92
170
|
if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
|
|
93
171
|
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
|
94
172
|
let width = 0;
|
|
95
|
-
for (const
|
|
173
|
+
for (const segment of segmenter.segment(text)) width += graphemeWidth(segment.segment);
|
|
96
174
|
return width;
|
|
97
175
|
}
|
|
98
|
-
return Array.from(text).
|
|
176
|
+
return Array.from(text).reduce((width, symbol) => width + graphemeWidth(symbol), 0);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function stripANSI(value) {
|
|
180
|
+
return String(value ?? '')
|
|
181
|
+
.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
|
|
182
|
+
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function wrapAnsi(value, columns, options = {}) {
|
|
186
|
+
const text = String(value ?? '');
|
|
187
|
+
const width = Number(columns);
|
|
188
|
+
const hard = options.hard !== false;
|
|
189
|
+
const trim = options.trim === true;
|
|
190
|
+
const wordWrap = options.wordWrap !== false;
|
|
191
|
+
if (!Number.isFinite(width) || width <= 0) return trim ? text.trimEnd() : text;
|
|
192
|
+
|
|
193
|
+
const ansiPattern = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
|
|
194
|
+
const segmenter = typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function'
|
|
195
|
+
? new Intl.Segmenter('en', { granularity: 'grapheme' })
|
|
196
|
+
: null;
|
|
197
|
+
const splitVisible = (chunk) => {
|
|
198
|
+
if (chunk === '') return [];
|
|
199
|
+
if (!segmenter) return Array.from(chunk);
|
|
200
|
+
return Array.from(segmenter.segment(chunk), part => part.segment);
|
|
201
|
+
};
|
|
202
|
+
const splitByGrapheme = hard || !wordWrap;
|
|
203
|
+
let result = '';
|
|
204
|
+
let currentWidth = 0;
|
|
205
|
+
let lastIndex = 0;
|
|
206
|
+
const appendVisible = (chunk) => {
|
|
207
|
+
const tokens = splitByGrapheme ? splitVisible(chunk) : (chunk.match(/\s+|[^\s]+/gu) || []);
|
|
208
|
+
for (const token of tokens) {
|
|
209
|
+
if (token === '\n') {
|
|
210
|
+
if (trim) result = result.replace(/[ \t]+$/g, '');
|
|
211
|
+
result += token;
|
|
212
|
+
currentWidth = 0;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
const tokenWidth = stringWidth(token);
|
|
216
|
+
const isWhitespace = /^\s+$/u.test(token);
|
|
217
|
+
if (trim && isWhitespace && currentWidth === 0) continue;
|
|
218
|
+
if (currentWidth > 0 && currentWidth + tokenWidth > width) {
|
|
219
|
+
if (!splitByGrapheme && !isWhitespace) {
|
|
220
|
+
result = result.replace(/[ \t]+$/g, '');
|
|
221
|
+
result += '\n';
|
|
222
|
+
currentWidth = 0;
|
|
223
|
+
} else {
|
|
224
|
+
for (const piece of splitVisible(token)) {
|
|
225
|
+
const pieceWidth = stringWidth(piece);
|
|
226
|
+
if (currentWidth > 0 && currentWidth + pieceWidth > width) {
|
|
227
|
+
if (trim) result = result.replace(/[ \t]+$/g, '');
|
|
228
|
+
result += '\n';
|
|
229
|
+
currentWidth = 0;
|
|
230
|
+
}
|
|
231
|
+
if (trim && /^\s+$/u.test(piece) && currentWidth === 0) continue;
|
|
232
|
+
result += piece;
|
|
233
|
+
currentWidth += pieceWidth;
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
result += token;
|
|
239
|
+
currentWidth += tokenWidth;
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
for (const match of text.matchAll(ansiPattern)) {
|
|
243
|
+
appendVisible(text.slice(lastIndex, match.index ?? 0));
|
|
244
|
+
result += match[0];
|
|
245
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
246
|
+
}
|
|
247
|
+
appendVisible(text.slice(lastIndex));
|
|
248
|
+
return trim ? result.replace(/[ \t]+$/gm, '') : result;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function stableHash(value, seed) {
|
|
252
|
+
const text = String(value ?? '');
|
|
253
|
+
let hash = 2166136261;
|
|
254
|
+
if (seed !== undefined) {
|
|
255
|
+
const seedText = String(seed ?? '');
|
|
256
|
+
for (let i = 0; i < seedText.length; i += 1) {
|
|
257
|
+
hash ^= seedText.charCodeAt(i);
|
|
258
|
+
hash = Math.imul(hash, 16777619);
|
|
259
|
+
}
|
|
260
|
+
hash ^= 0x9e3779b9;
|
|
261
|
+
hash = Math.imul(hash, 16777619);
|
|
262
|
+
}
|
|
263
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
264
|
+
hash ^= text.charCodeAt(i);
|
|
265
|
+
hash = Math.imul(hash, 16777619);
|
|
266
|
+
}
|
|
267
|
+
return hash >>> 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function replaceRequired(source, pattern, replacement, label, expectedCount) {
|
|
271
|
+
const text = String(source);
|
|
272
|
+
const matches = text.match(pattern);
|
|
273
|
+
if (!matches || matches.length === 0) {
|
|
274
|
+
throw new Error(`rewriteNativeChunkSource: missing ${label}`);
|
|
275
|
+
}
|
|
276
|
+
if (expectedCount !== undefined && matches.length !== expectedCount) {
|
|
277
|
+
throw new Error(`rewriteNativeChunkSource: unexpected ${label} count ${matches.length}`);
|
|
278
|
+
}
|
|
279
|
+
return text.replace(pattern, replacement);
|
|
99
280
|
}
|
|
100
281
|
|
|
101
282
|
function parseScalar(value) {
|
|
@@ -244,6 +425,9 @@ function createFakeRequire(realRequire) {
|
|
|
244
425
|
writable: true,
|
|
245
426
|
});
|
|
246
427
|
}
|
|
428
|
+
if (context.Bun && globalThis.__claudeYaml) {
|
|
429
|
+
context.Bun.YAML = globalThis.__claudeYaml;
|
|
430
|
+
}
|
|
247
431
|
} catch {}
|
|
248
432
|
return context;
|
|
249
433
|
}
|
|
@@ -344,25 +528,53 @@ function createFakeRequire(realRequire) {
|
|
|
344
528
|
};
|
|
345
529
|
}
|
|
346
530
|
|
|
347
|
-
|
|
348
|
-
const
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
531
|
+
function rewriteNativeChunkSource(source) {
|
|
532
|
+
const rawPrefix = 'function(exports, require, module, __filename, __dirname) {';
|
|
533
|
+
const injectedPrefix = rawPrefix + 'var __claudeBun = globalThis.__claudeBunShim;';
|
|
534
|
+
let patched = String(source);
|
|
535
|
+
patched = replaceRequired(
|
|
536
|
+
patched,
|
|
537
|
+
/^function\(exports, require, module, __filename, __dirname\) \{(?:var __claudeBun = globalThis\.__claudeBunShim;)?/,
|
|
538
|
+
injectedPrefix,
|
|
539
|
+
'module wrapper prefix',
|
|
540
|
+
1,
|
|
541
|
+
);
|
|
542
|
+
patched = replaceRequired(
|
|
543
|
+
patched,
|
|
354
544
|
/\btypeof Bun\b/g,
|
|
355
545
|
'typeof __claudeBun',
|
|
356
|
-
|
|
546
|
+
'typeof Bun',
|
|
547
|
+
3,
|
|
548
|
+
);
|
|
549
|
+
patched = replaceRequired(
|
|
550
|
+
patched,
|
|
551
|
+
/\btypeof globalThis\.Bun\b/g,
|
|
552
|
+
'typeof globalThis.__claudeBun',
|
|
553
|
+
'typeof globalThis.Bun',
|
|
554
|
+
1,
|
|
555
|
+
);
|
|
556
|
+
patched = replaceRequired(
|
|
557
|
+
patched,
|
|
558
|
+
/\bglobalThis\.Bun\b/g,
|
|
559
|
+
'globalThis.__claudeBun',
|
|
560
|
+
'globalThis.Bun',
|
|
561
|
+
1,
|
|
562
|
+
);
|
|
563
|
+
patched = replaceRequired(
|
|
564
|
+
patched,
|
|
357
565
|
/\bBun\./g,
|
|
358
566
|
'__claudeBun.',
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
'function t5q(q){return globalThis.__claudeYaml.parse(q)}',
|
|
362
|
-
).replace(
|
|
363
|
-
/function VK6\(q\)\{return Bun\.YAML\.stringify\(q,null,2\)\+`/g,
|
|
364
|
-
'function VK6(q){return globalThis.__claudeYaml.stringify(q,null,2)+`',
|
|
567
|
+
'Bun property access',
|
|
568
|
+
33,
|
|
365
569
|
);
|
|
570
|
+
return patched;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function main() {
|
|
574
|
+
let extractedFile;
|
|
575
|
+
extractedFile = ensureEntryFile();
|
|
576
|
+
const code = fs.readFileSync(extractedFile, 'utf8');
|
|
577
|
+
const patchedCode = rewriteNativeChunkSource(code);
|
|
366
578
|
const fn = eval('(' + patchedCode.replace(/\)\s*$/, '') + ')');
|
|
367
579
|
|
|
368
580
|
const originalArgv = process.argv.slice();
|
|
@@ -389,6 +601,9 @@ async function main() {
|
|
|
389
601
|
globalThis.Bun = {
|
|
390
602
|
version: '1.1.8',
|
|
391
603
|
stringWidth,
|
|
604
|
+
wrapAnsi,
|
|
605
|
+
stripANSI,
|
|
606
|
+
hash: stableHash,
|
|
392
607
|
which: (cmd) => {
|
|
393
608
|
try {
|
|
394
609
|
return _realChild.execFileSync('which', [String(cmd)], { encoding: 'utf8' }).trim() || null;
|
|
@@ -424,9 +639,9 @@ async function main() {
|
|
|
424
639
|
gte: (a, b) => _cmp(a, b) >= 0,
|
|
425
640
|
lt: (a, b) => _cmp(a, b) < 0,
|
|
426
641
|
lte: (a, b) => _cmp(a, b) <= 0,
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
642
|
+
};
|
|
643
|
+
})(),
|
|
644
|
+
YAML: globalThis.__claudeYaml,
|
|
430
645
|
};
|
|
431
646
|
Object.assign(globalThis.__claudeBunShim, globalThis.Bun);
|
|
432
647
|
if (typeof globalThis.__claudeBunShim.gc !== 'function') {
|
|
@@ -439,10 +654,13 @@ async function main() {
|
|
|
439
654
|
throw new RequestedExit(code);
|
|
440
655
|
};
|
|
441
656
|
|
|
657
|
+
const printWaitMs = Number(process.env.CLAUDE_TERMUX_PRINT_WAIT_MS || 5000);
|
|
442
658
|
const moduleLike = { exports: {} };
|
|
443
659
|
const maybePromise = fn(moduleLike.exports, fakeRequire, moduleLike, extractedFile, workdir);
|
|
444
660
|
if (maybePromise && typeof maybePromise.then === 'function') await maybePromise;
|
|
445
|
-
|
|
661
|
+
if (Number.isFinite(printWaitMs) && printWaitMs > 0) {
|
|
662
|
+
await new Promise(resolve => setTimeout(resolve, printWaitMs));
|
|
663
|
+
}
|
|
446
664
|
if (asyncErrors.length > 0) throw asyncErrors[0];
|
|
447
665
|
} catch (error) {
|
|
448
666
|
if (error instanceof RequestedExit) {
|
|
@@ -453,6 +671,11 @@ async function main() {
|
|
|
453
671
|
} finally {
|
|
454
672
|
process.removeListener('uncaughtException', onAsyncError);
|
|
455
673
|
process.removeListener('unhandledRejection', onAsyncError);
|
|
674
|
+
if (extractedFile) {
|
|
675
|
+
try {
|
|
676
|
+
fs.rmSync(extractedFile, { force: true });
|
|
677
|
+
} catch {}
|
|
678
|
+
}
|
|
456
679
|
process.argv = originalArgv;
|
|
457
680
|
process.exit = originalExit;
|
|
458
681
|
try {
|
|
@@ -488,7 +711,7 @@ NODE
|
|
|
488
711
|
trap - EXIT HUP INT TERM
|
|
489
712
|
exit "$_status"
|
|
490
713
|
else
|
|
491
|
-
_bootstrap=$(mktemp "${
|
|
714
|
+
_bootstrap=$(mktemp "${TERMUX_TMPDIR}/claude-bootstrap.XXXXXX.js")
|
|
492
715
|
trap 'rm -f "$_bootstrap"' EXIT HUP INT TERM
|
|
493
716
|
cat <<'NODE' > "$_bootstrap"
|
|
494
717
|
const fs = require('fs');
|
|
@@ -512,8 +735,39 @@ class RequestedExit extends Error {
|
|
|
512
735
|
}
|
|
513
736
|
}
|
|
514
737
|
|
|
738
|
+
function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {
|
|
739
|
+
const prefix = `cli.${currentEntryJsOffset}.${currentEntryEndOffset}.`;
|
|
740
|
+
const suffix = '.bare-path.js';
|
|
741
|
+
const maxAgeMs = 24 * 60 * 60 * 1000;
|
|
742
|
+
let entries;
|
|
743
|
+
try {
|
|
744
|
+
entries = fs.readdirSync(currentWorkdir, { withFileTypes: true });
|
|
745
|
+
} catch {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
for (const entry of entries) {
|
|
749
|
+
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
750
|
+
if (!entry.name.startsWith(prefix) || !entry.name.endsWith(suffix)) continue;
|
|
751
|
+
const filePath = path.join(currentWorkdir, entry.name);
|
|
752
|
+
let stats;
|
|
753
|
+
try {
|
|
754
|
+
stats = fs.statSync(filePath);
|
|
755
|
+
} catch {
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (Number.isFinite(stats.mtimeMs) && now - stats.mtimeMs < maxAgeMs) continue;
|
|
759
|
+
try {
|
|
760
|
+
fs.rmSync(filePath, { force: true });
|
|
761
|
+
} catch {}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
515
765
|
function ensureEntryFile() {
|
|
516
|
-
|
|
766
|
+
cleanupStaleEntryFiles();
|
|
767
|
+
const extractedFile = path.join(
|
|
768
|
+
workdir,
|
|
769
|
+
`cli.${entryJsOffset}.${entryEndOffset}.${process.pid}.${Date.now().toString(36)}.${Math.random().toString(36).slice(2)}.bare-path.js`,
|
|
770
|
+
);
|
|
517
771
|
const len = entryEndOffset - entryJsOffset;
|
|
518
772
|
if (!(len > 0)) throw new Error('invalid replay offsets');
|
|
519
773
|
|
|
@@ -526,15 +780,165 @@ function ensureEntryFile() {
|
|
|
526
780
|
return extractedFile;
|
|
527
781
|
}
|
|
528
782
|
|
|
783
|
+
function isFullWidthCodePoint(codePoint) {
|
|
784
|
+
return Number.isFinite(codePoint) && (
|
|
785
|
+
codePoint >= 0x1100 && (
|
|
786
|
+
codePoint <= 0x115f ||
|
|
787
|
+
codePoint === 0x2329 ||
|
|
788
|
+
codePoint === 0x232a ||
|
|
789
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
790
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
791
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
792
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
793
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
794
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
795
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
796
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f6ff) ||
|
|
797
|
+
(codePoint >= 0x1fa70 && codePoint <= 0x1faff) ||
|
|
798
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
799
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd)
|
|
800
|
+
)
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
function graphemeWidth(grapheme) {
|
|
805
|
+
let width = 0;
|
|
806
|
+
const symbols = Array.from(String(grapheme ?? ''));
|
|
807
|
+
const codePoints = symbols.map(symbol => symbol.codePointAt(0)).filter(codePoint => Number.isFinite(codePoint));
|
|
808
|
+
if (codePoints.length === 0) return 0;
|
|
809
|
+
if (codePoints.length > 1 && codePoints.every(codePoint => codePoint >= 0x1f1e6 && codePoint <= 0x1f1ff)) {
|
|
810
|
+
return 2;
|
|
811
|
+
}
|
|
812
|
+
if (codePoints.includes(0x20e3) || codePoints.includes(0x200d)) {
|
|
813
|
+
return 2;
|
|
814
|
+
}
|
|
815
|
+
if (codePoints.includes(0xfe0f) || codePoints.some(codePoint => codePoint >= 0x2600 && codePoint <= 0x27bf)) {
|
|
816
|
+
return 2;
|
|
817
|
+
}
|
|
818
|
+
for (const symbol of symbols) {
|
|
819
|
+
const codePoint = symbol.codePointAt(0);
|
|
820
|
+
if (codePoint === undefined || codePoint === 0) continue;
|
|
821
|
+
if (codePoint < 32 || (codePoint >= 0x7f && codePoint < 0xa0)) continue;
|
|
822
|
+
if (codePoint === 0x200d || codePoint === 0xfe0f) continue;
|
|
823
|
+
if (/\p{M}/u.test(symbol)) continue;
|
|
824
|
+
if (isFullWidthCodePoint(codePoint)) return 2;
|
|
825
|
+
width = 1;
|
|
826
|
+
}
|
|
827
|
+
return width;
|
|
828
|
+
}
|
|
829
|
+
|
|
529
830
|
function stringWidth(value) {
|
|
530
|
-
const text =
|
|
831
|
+
const text = stripANSI(value);
|
|
531
832
|
if (typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function') {
|
|
532
833
|
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
|
533
834
|
let width = 0;
|
|
534
|
-
for (const
|
|
835
|
+
for (const segment of segmenter.segment(text)) width += graphemeWidth(segment.segment);
|
|
535
836
|
return width;
|
|
536
837
|
}
|
|
537
|
-
return Array.from(text).
|
|
838
|
+
return Array.from(text).reduce((width, symbol) => width + graphemeWidth(symbol), 0);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function stripANSI(value) {
|
|
842
|
+
return String(value ?? '')
|
|
843
|
+
.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
|
|
844
|
+
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '');
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function wrapAnsi(value, columns, options = {}) {
|
|
848
|
+
const text = String(value ?? '');
|
|
849
|
+
const width = Number(columns);
|
|
850
|
+
const hard = options.hard !== false;
|
|
851
|
+
const trim = options.trim === true;
|
|
852
|
+
const wordWrap = options.wordWrap !== false;
|
|
853
|
+
if (!Number.isFinite(width) || width <= 0) return trim ? text.trimEnd() : text;
|
|
854
|
+
|
|
855
|
+
const ansiPattern = /\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)/g;
|
|
856
|
+
const segmenter = typeof Intl !== 'undefined' && typeof Intl.Segmenter === 'function'
|
|
857
|
+
? new Intl.Segmenter('en', { granularity: 'grapheme' })
|
|
858
|
+
: null;
|
|
859
|
+
const splitVisible = (chunk) => {
|
|
860
|
+
if (chunk === '') return [];
|
|
861
|
+
if (!segmenter) return Array.from(chunk);
|
|
862
|
+
return Array.from(segmenter.segment(chunk), part => part.segment);
|
|
863
|
+
};
|
|
864
|
+
const splitByGrapheme = hard || !wordWrap;
|
|
865
|
+
let result = '';
|
|
866
|
+
let currentWidth = 0;
|
|
867
|
+
let lastIndex = 0;
|
|
868
|
+
const appendVisible = (chunk) => {
|
|
869
|
+
const tokens = splitByGrapheme ? splitVisible(chunk) : (chunk.match(/\s+|[^\s]+/gu) || []);
|
|
870
|
+
for (const token of tokens) {
|
|
871
|
+
if (token === '\n') {
|
|
872
|
+
if (trim) result = result.replace(/[ \t]+$/g, '');
|
|
873
|
+
result += token;
|
|
874
|
+
currentWidth = 0;
|
|
875
|
+
continue;
|
|
876
|
+
}
|
|
877
|
+
const tokenWidth = stringWidth(token);
|
|
878
|
+
const isWhitespace = /^\s+$/u.test(token);
|
|
879
|
+
if (trim && isWhitespace && currentWidth === 0) continue;
|
|
880
|
+
if (currentWidth > 0 && currentWidth + tokenWidth > width) {
|
|
881
|
+
if (!splitByGrapheme && !isWhitespace) {
|
|
882
|
+
result = result.replace(/[ \t]+$/g, '');
|
|
883
|
+
result += '\n';
|
|
884
|
+
currentWidth = 0;
|
|
885
|
+
} else {
|
|
886
|
+
for (const piece of splitVisible(token)) {
|
|
887
|
+
const pieceWidth = stringWidth(piece);
|
|
888
|
+
if (currentWidth > 0 && currentWidth + pieceWidth > width) {
|
|
889
|
+
if (trim) result = result.replace(/[ \t]+$/g, '');
|
|
890
|
+
result += '\n';
|
|
891
|
+
currentWidth = 0;
|
|
892
|
+
}
|
|
893
|
+
if (trim && /^\s+$/u.test(piece) && currentWidth === 0) continue;
|
|
894
|
+
result += piece;
|
|
895
|
+
currentWidth += pieceWidth;
|
|
896
|
+
}
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
result += token;
|
|
901
|
+
currentWidth += tokenWidth;
|
|
902
|
+
}
|
|
903
|
+
};
|
|
904
|
+
for (const match of text.matchAll(ansiPattern)) {
|
|
905
|
+
appendVisible(text.slice(lastIndex, match.index ?? 0));
|
|
906
|
+
result += match[0];
|
|
907
|
+
lastIndex = (match.index ?? 0) + match[0].length;
|
|
908
|
+
}
|
|
909
|
+
appendVisible(text.slice(lastIndex));
|
|
910
|
+
return trim ? result.replace(/[ \t]+$/gm, '') : result;
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
function stableHash(value, seed) {
|
|
914
|
+
const text = String(value ?? '');
|
|
915
|
+
let hash = 2166136261;
|
|
916
|
+
if (seed !== undefined) {
|
|
917
|
+
const seedText = String(seed ?? '');
|
|
918
|
+
for (let i = 0; i < seedText.length; i += 1) {
|
|
919
|
+
hash ^= seedText.charCodeAt(i);
|
|
920
|
+
hash = Math.imul(hash, 16777619);
|
|
921
|
+
}
|
|
922
|
+
hash ^= 0x9e3779b9;
|
|
923
|
+
hash = Math.imul(hash, 16777619);
|
|
924
|
+
}
|
|
925
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
926
|
+
hash ^= text.charCodeAt(i);
|
|
927
|
+
hash = Math.imul(hash, 16777619);
|
|
928
|
+
}
|
|
929
|
+
return hash >>> 0;
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
function replaceRequired(source, pattern, replacement, label, expectedCount) {
|
|
933
|
+
const text = String(source);
|
|
934
|
+
const matches = text.match(pattern);
|
|
935
|
+
if (!matches || matches.length === 0) {
|
|
936
|
+
throw new Error(`rewriteNativeChunkSource: missing ${label}`);
|
|
937
|
+
}
|
|
938
|
+
if (expectedCount !== undefined && matches.length !== expectedCount) {
|
|
939
|
+
throw new Error(`rewriteNativeChunkSource: unexpected ${label} count ${matches.length}`);
|
|
940
|
+
}
|
|
941
|
+
return text.replace(pattern, replacement);
|
|
538
942
|
}
|
|
539
943
|
|
|
540
944
|
function parseScalar(value) {
|
|
@@ -633,6 +1037,63 @@ function createFakeRequire(realRequire) {
|
|
|
633
1037
|
const realChild = realRequire('child_process');
|
|
634
1038
|
const realVm = realRequire('vm');
|
|
635
1039
|
|
|
1040
|
+
function injectBunIntoContext(context) {
|
|
1041
|
+
if (!context || typeof context !== 'object') return context;
|
|
1042
|
+
try {
|
|
1043
|
+
if (!Object.prototype.hasOwnProperty.call(context, '__claudeYaml')) {
|
|
1044
|
+
Object.defineProperty(context, '__claudeYaml', {
|
|
1045
|
+
value: globalThis.__claudeYaml,
|
|
1046
|
+
configurable: true,
|
|
1047
|
+
writable: true,
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
if (!Object.prototype.hasOwnProperty.call(context, '__claudeBunShim')) {
|
|
1051
|
+
Object.defineProperty(context, '__claudeBunShim', {
|
|
1052
|
+
value: globalThis.__claudeBunShim,
|
|
1053
|
+
configurable: true,
|
|
1054
|
+
writable: true,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
if (!Object.prototype.hasOwnProperty.call(context, '__claudeBun')) {
|
|
1058
|
+
Object.defineProperty(context, '__claudeBun', {
|
|
1059
|
+
value: globalThis.__claudeBunShim,
|
|
1060
|
+
configurable: true,
|
|
1061
|
+
writable: true,
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
if (Object.prototype.hasOwnProperty.call(context, 'Bun')) {
|
|
1065
|
+
if (context.Bun && typeof context.Bun === 'object' && context.Bun !== globalThis.Bun) {
|
|
1066
|
+
try {
|
|
1067
|
+
context.Bun = globalThis.Bun;
|
|
1068
|
+
} catch {
|
|
1069
|
+
Object.defineProperty(context, 'Bun', {
|
|
1070
|
+
value: globalThis.Bun,
|
|
1071
|
+
configurable: true,
|
|
1072
|
+
writable: true,
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (!context.Bun || typeof context.Bun !== 'object') {
|
|
1077
|
+
Object.defineProperty(context, 'Bun', {
|
|
1078
|
+
value: globalThis.Bun,
|
|
1079
|
+
configurable: true,
|
|
1080
|
+
writable: true,
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
} else {
|
|
1084
|
+
Object.defineProperty(context, 'Bun', {
|
|
1085
|
+
value: globalThis.Bun,
|
|
1086
|
+
configurable: true,
|
|
1087
|
+
writable: true,
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
if (context.Bun && globalThis.__claudeYaml) {
|
|
1091
|
+
context.Bun.YAML = globalThis.__claudeYaml;
|
|
1092
|
+
}
|
|
1093
|
+
} catch {}
|
|
1094
|
+
return context;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
636
1097
|
function rewriteArgs(args) {
|
|
637
1098
|
if (
|
|
638
1099
|
Array.isArray(args) &&
|
|
@@ -729,25 +1190,53 @@ function createFakeRequire(realRequire) {
|
|
|
729
1190
|
};
|
|
730
1191
|
}
|
|
731
1192
|
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
const
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
1193
|
+
function rewriteNativeChunkSource(source) {
|
|
1194
|
+
const rawPrefix = 'function(exports, require, module, __filename, __dirname) {';
|
|
1195
|
+
const injectedPrefix = rawPrefix + 'var __claudeBun = globalThis.__claudeBunShim;';
|
|
1196
|
+
let patched = String(source);
|
|
1197
|
+
patched = replaceRequired(
|
|
1198
|
+
patched,
|
|
1199
|
+
/^function\(exports, require, module, __filename, __dirname\) \{(?:var __claudeBun = globalThis\.__claudeBunShim;)?/,
|
|
1200
|
+
injectedPrefix,
|
|
1201
|
+
'module wrapper prefix',
|
|
1202
|
+
1,
|
|
1203
|
+
);
|
|
1204
|
+
patched = replaceRequired(
|
|
1205
|
+
patched,
|
|
739
1206
|
/\btypeof Bun\b/g,
|
|
740
1207
|
'typeof __claudeBun',
|
|
741
|
-
|
|
1208
|
+
'typeof Bun',
|
|
1209
|
+
3,
|
|
1210
|
+
);
|
|
1211
|
+
patched = replaceRequired(
|
|
1212
|
+
patched,
|
|
1213
|
+
/\btypeof globalThis\.Bun\b/g,
|
|
1214
|
+
'typeof globalThis.__claudeBun',
|
|
1215
|
+
'typeof globalThis.Bun',
|
|
1216
|
+
1,
|
|
1217
|
+
);
|
|
1218
|
+
patched = replaceRequired(
|
|
1219
|
+
patched,
|
|
1220
|
+
/\bglobalThis\.Bun\b/g,
|
|
1221
|
+
'globalThis.__claudeBun',
|
|
1222
|
+
'globalThis.Bun',
|
|
1223
|
+
1,
|
|
1224
|
+
);
|
|
1225
|
+
patched = replaceRequired(
|
|
1226
|
+
patched,
|
|
742
1227
|
/\bBun\./g,
|
|
743
1228
|
'__claudeBun.',
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
'function t5q(q){return globalThis.__claudeYaml.parse(q)}',
|
|
747
|
-
).replace(
|
|
748
|
-
/function VK6\(q\)\{return Bun\.YAML\.stringify\(q,null,2\)\+`/g,
|
|
749
|
-
'function VK6(q){return globalThis.__claudeYaml.stringify(q,null,2)+`',
|
|
1229
|
+
'Bun property access',
|
|
1230
|
+
33,
|
|
750
1231
|
);
|
|
1232
|
+
return patched;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
async function main() {
|
|
1236
|
+
let extractedFile;
|
|
1237
|
+
extractedFile = ensureEntryFile();
|
|
1238
|
+
const code = fs.readFileSync(extractedFile, 'utf8');
|
|
1239
|
+
const patchedCode = rewriteNativeChunkSource(code);
|
|
751
1240
|
const fn = eval('(' + patchedCode.replace(/\)\s*$/, '') + ')');
|
|
752
1241
|
|
|
753
1242
|
const originalArgv = process.argv.slice();
|
|
@@ -774,6 +1263,9 @@ async function main() {
|
|
|
774
1263
|
globalThis.Bun = {
|
|
775
1264
|
version: '1.1.8',
|
|
776
1265
|
stringWidth,
|
|
1266
|
+
wrapAnsi,
|
|
1267
|
+
stripANSI,
|
|
1268
|
+
hash: stableHash,
|
|
777
1269
|
which: (cmd) => {
|
|
778
1270
|
try {
|
|
779
1271
|
return _realChild.execFileSync('which', [String(cmd)], { encoding: 'utf8' }).trim() || null;
|
|
@@ -810,8 +1302,8 @@ async function main() {
|
|
|
810
1302
|
lt: (a, b) => _cmp(a, b) < 0,
|
|
811
1303
|
lte: (a, b) => _cmp(a, b) <= 0,
|
|
812
1304
|
};
|
|
813
|
-
|
|
814
|
-
|
|
1305
|
+
})(),
|
|
1306
|
+
YAML: globalThis.__claudeYaml,
|
|
815
1307
|
};
|
|
816
1308
|
Object.assign(globalThis.__claudeBunShim, globalThis.Bun);
|
|
817
1309
|
if (typeof globalThis.__claudeBunShim.gc !== 'function') {
|
|
@@ -827,8 +1319,6 @@ async function main() {
|
|
|
827
1319
|
const moduleLike = { exports: {} };
|
|
828
1320
|
const maybePromise = fn(moduleLike.exports, fakeRequire, moduleLike, extractedFile, workdir);
|
|
829
1321
|
if (maybePromise && typeof maybePromise.then === 'function') await maybePromise;
|
|
830
|
-
const _waitMs = Number(process.env.CLAUDE_TERMUX_PRINT_WAIT_MS || 200);
|
|
831
|
-
await new Promise(resolve => setTimeout(resolve, _waitMs));
|
|
832
1322
|
if (asyncErrors.length > 0) throw asyncErrors[0];
|
|
833
1323
|
} catch (error) {
|
|
834
1324
|
if (error instanceof RequestedExit) {
|
|
@@ -841,17 +1331,25 @@ async function main() {
|
|
|
841
1331
|
process.removeListener('unhandledRejection', onAsyncError);
|
|
842
1332
|
process.argv = originalArgv;
|
|
843
1333
|
process.exit = originalExit;
|
|
844
|
-
|
|
845
|
-
if (
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1334
|
+
process.once('exit', () => {
|
|
1335
|
+
if (extractedFile) {
|
|
1336
|
+
try {
|
|
1337
|
+
fs.rmSync(extractedFile, { force: true });
|
|
1338
|
+
} catch {}
|
|
849
1339
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1340
|
+
try {
|
|
1341
|
+
if (originalBun === undefined) {
|
|
1342
|
+
delete process.versions.bun;
|
|
1343
|
+
} else {
|
|
1344
|
+
Object.defineProperty(process.versions, 'bun', { value: originalBun, configurable: true });
|
|
1345
|
+
}
|
|
1346
|
+
} catch {}
|
|
1347
|
+
delete globalThis.__claudeYaml;
|
|
1348
|
+
delete globalThis.__claudeBunShim;
|
|
1349
|
+
delete globalThis.__claudeBun;
|
|
1350
|
+
if (hadGlobalBun) globalThis.Bun = originalGlobalBun;
|
|
1351
|
+
else delete globalThis.Bun;
|
|
1352
|
+
});
|
|
855
1353
|
}
|
|
856
1354
|
}
|
|
857
1355
|
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const test = require('node:test');
|
|
4
|
+
const assert = require('node:assert/strict');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const child_process = require('child_process');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const vm = require('vm');
|
|
11
|
+
|
|
12
|
+
const scriptPath = path.join(__dirname, 'termux-run-claude-native.sh');
|
|
13
|
+
const script = fs.readFileSync(scriptPath, 'utf8');
|
|
14
|
+
|
|
15
|
+
function extractBlock(marker, trailer) {
|
|
16
|
+
const start = script.indexOf(marker);
|
|
17
|
+
assert.notEqual(start, -1, `missing marker: ${marker}`);
|
|
18
|
+
const bodyStart = script.indexOf('\n', start);
|
|
19
|
+
assert.notEqual(bodyStart, -1, `missing body start: ${marker}`);
|
|
20
|
+
const end = script.indexOf(trailer, bodyStart + 1);
|
|
21
|
+
assert.notEqual(end, -1, `missing trailer: ${trailer}`);
|
|
22
|
+
return script.slice(bodyStart + 1, end);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function extractFunction(block, startName, endName) {
|
|
26
|
+
const start = block.indexOf(startName);
|
|
27
|
+
assert.notEqual(start, -1, `missing function: ${startName}`);
|
|
28
|
+
const end = block.indexOf(endName, start);
|
|
29
|
+
assert.notEqual(end, -1, `missing end marker: ${endName}`);
|
|
30
|
+
return block.slice(start, end).trimEnd();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function loadHelperApi() {
|
|
34
|
+
const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
|
|
35
|
+
const replaceSource = extractFunction(
|
|
36
|
+
helperBlock,
|
|
37
|
+
'function replaceRequired(source, pattern, replacement, label, expectedCount) {',
|
|
38
|
+
'\n\nfunction parseScalar(value) {',
|
|
39
|
+
);
|
|
40
|
+
const fullWidthSource = extractFunction(
|
|
41
|
+
helperBlock,
|
|
42
|
+
'function isFullWidthCodePoint(codePoint) {',
|
|
43
|
+
'\n\nfunction graphemeWidth(grapheme) {',
|
|
44
|
+
);
|
|
45
|
+
const graphemeWidthSource = extractFunction(
|
|
46
|
+
helperBlock,
|
|
47
|
+
'function graphemeWidth(grapheme) {',
|
|
48
|
+
'\n\nfunction stringWidth(value) {',
|
|
49
|
+
);
|
|
50
|
+
const stringWidthSource = extractFunction(
|
|
51
|
+
helperBlock,
|
|
52
|
+
'function stringWidth(value) {',
|
|
53
|
+
'\n\nfunction stripANSI(value) {',
|
|
54
|
+
);
|
|
55
|
+
const stripAnsiSource = extractFunction(
|
|
56
|
+
helperBlock,
|
|
57
|
+
'function stripANSI(value) {',
|
|
58
|
+
'\n\nfunction wrapAnsi(value, columns, options = {}) {',
|
|
59
|
+
);
|
|
60
|
+
const cleanupSource = extractFunction(
|
|
61
|
+
helperBlock,
|
|
62
|
+
'function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {',
|
|
63
|
+
'\n\nfunction ensureEntryFile() {',
|
|
64
|
+
);
|
|
65
|
+
const wrapAnsiSource = extractFunction(
|
|
66
|
+
helperBlock,
|
|
67
|
+
'function wrapAnsi(value, columns, options = {}) {',
|
|
68
|
+
'\n\nfunction stableHash(value, seed) {',
|
|
69
|
+
);
|
|
70
|
+
const stableHashSource = extractFunction(
|
|
71
|
+
helperBlock,
|
|
72
|
+
'function stableHash(value, seed) {',
|
|
73
|
+
'\n\nfunction replaceRequired(source, pattern, replacement, label, expectedCount) {',
|
|
74
|
+
);
|
|
75
|
+
const rewriteSource = extractFunction(
|
|
76
|
+
helperBlock,
|
|
77
|
+
'function rewriteNativeChunkSource(source) {',
|
|
78
|
+
'\n\nasync function main() {',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const context = vm.createContext({ module: { exports: {} }, exports: {}, fs, path });
|
|
82
|
+
vm.runInContext(
|
|
83
|
+
`${replaceSource}\n${cleanupSource}\n${fullWidthSource}\n${graphemeWidthSource}\n${stripAnsiSource}\n${stringWidthSource}\n${wrapAnsiSource}\n${stableHashSource}\n${rewriteSource}\nmodule.exports = { replaceRequired, cleanupStaleEntryFiles, isFullWidthCodePoint, graphemeWidth, stripANSI, stringWidth, wrapAnsi, stableHash, rewriteNativeChunkSource };`,
|
|
84
|
+
context,
|
|
85
|
+
);
|
|
86
|
+
return context.module.exports;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sha256(filePath) {
|
|
90
|
+
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
test('helper and bootstrap rewrite helpers stay identical', () => {
|
|
94
|
+
const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
|
|
95
|
+
const bootstrapBlock = extractBlock('cat <<\'NODE\' > "$_bootstrap"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
|
|
96
|
+
|
|
97
|
+
const helperReplace = extractFunction(
|
|
98
|
+
helperBlock,
|
|
99
|
+
'function replaceRequired(source, pattern, replacement, label, expectedCount) {',
|
|
100
|
+
'\n\nfunction parseScalar(value) {',
|
|
101
|
+
);
|
|
102
|
+
const bootstrapReplace = extractFunction(
|
|
103
|
+
bootstrapBlock,
|
|
104
|
+
'function replaceRequired(source, pattern, replacement, label, expectedCount) {',
|
|
105
|
+
'\n\nfunction parseScalar(value) {',
|
|
106
|
+
);
|
|
107
|
+
const helperRewrite = extractFunction(
|
|
108
|
+
helperBlock,
|
|
109
|
+
'function rewriteNativeChunkSource(source) {',
|
|
110
|
+
'\n\nasync function main() {',
|
|
111
|
+
);
|
|
112
|
+
const bootstrapRewrite = extractFunction(
|
|
113
|
+
bootstrapBlock,
|
|
114
|
+
'function rewriteNativeChunkSource(source) {',
|
|
115
|
+
'\n\nasync function main() {',
|
|
116
|
+
);
|
|
117
|
+
const helperWrapAnsi = extractFunction(
|
|
118
|
+
helperBlock,
|
|
119
|
+
'function wrapAnsi(value, columns, options = {}) {',
|
|
120
|
+
'\n\nfunction stableHash(value, seed) {',
|
|
121
|
+
);
|
|
122
|
+
const helperCleanup = extractFunction(
|
|
123
|
+
helperBlock,
|
|
124
|
+
'function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {',
|
|
125
|
+
'\n\nfunction ensureEntryFile() {',
|
|
126
|
+
);
|
|
127
|
+
const bootstrapWrapAnsi = extractFunction(
|
|
128
|
+
bootstrapBlock,
|
|
129
|
+
'function wrapAnsi(value, columns, options = {}) {',
|
|
130
|
+
'\n\nfunction stableHash(value, seed) {',
|
|
131
|
+
);
|
|
132
|
+
const bootstrapCleanup = extractFunction(
|
|
133
|
+
bootstrapBlock,
|
|
134
|
+
'function cleanupStaleEntryFiles(currentWorkdir = workdir, currentEntryJsOffset = entryJsOffset, currentEntryEndOffset = entryEndOffset, now = Date.now()) {',
|
|
135
|
+
'\n\nfunction ensureEntryFile() {',
|
|
136
|
+
);
|
|
137
|
+
const helperStableHash = extractFunction(
|
|
138
|
+
helperBlock,
|
|
139
|
+
'function stableHash(value, seed) {',
|
|
140
|
+
'\n\nfunction replaceRequired(source, pattern, replacement, label, expectedCount) {',
|
|
141
|
+
);
|
|
142
|
+
const bootstrapStableHash = extractFunction(
|
|
143
|
+
bootstrapBlock,
|
|
144
|
+
'function stableHash(value, seed) {',
|
|
145
|
+
'\n\nfunction replaceRequired(source, pattern, replacement, label, expectedCount) {',
|
|
146
|
+
);
|
|
147
|
+
const helperStringWidth = extractFunction(
|
|
148
|
+
helperBlock,
|
|
149
|
+
'function isFullWidthCodePoint(codePoint) {',
|
|
150
|
+
'\n\nfunction graphemeWidth(grapheme) {',
|
|
151
|
+
) + '\n\n' + extractFunction(
|
|
152
|
+
helperBlock,
|
|
153
|
+
'function graphemeWidth(grapheme) {',
|
|
154
|
+
'\n\nfunction stringWidth(value) {',
|
|
155
|
+
) + '\n\n' + extractFunction(
|
|
156
|
+
helperBlock,
|
|
157
|
+
'function stringWidth(value) {',
|
|
158
|
+
'\n\nfunction stripANSI(value) {',
|
|
159
|
+
);
|
|
160
|
+
const bootstrapStringWidth = extractFunction(
|
|
161
|
+
bootstrapBlock,
|
|
162
|
+
'function isFullWidthCodePoint(codePoint) {',
|
|
163
|
+
'\n\nfunction graphemeWidth(grapheme) {',
|
|
164
|
+
) + '\n\n' + extractFunction(
|
|
165
|
+
bootstrapBlock,
|
|
166
|
+
'function graphemeWidth(grapheme) {',
|
|
167
|
+
'\n\nfunction stringWidth(value) {',
|
|
168
|
+
) + '\n\n' + extractFunction(
|
|
169
|
+
bootstrapBlock,
|
|
170
|
+
'function stringWidth(value) {',
|
|
171
|
+
'\n\nfunction stripANSI(value) {',
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
assert.equal(helperReplace, bootstrapReplace);
|
|
175
|
+
assert.equal(helperRewrite, bootstrapRewrite);
|
|
176
|
+
assert.equal(helperWrapAnsi, bootstrapWrapAnsi);
|
|
177
|
+
assert.equal(helperCleanup, bootstrapCleanup);
|
|
178
|
+
assert.equal(helperStableHash, bootstrapStableHash);
|
|
179
|
+
assert.equal(helperStringWidth, bootstrapStringWidth);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test('print path does not defer cleanup to exit', () => {
|
|
183
|
+
const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
|
|
184
|
+
|
|
185
|
+
assert.equal(helperBlock.includes('CLAUDE_TERMUX_PRINT_WAIT_MS'), true);
|
|
186
|
+
assert.equal(helperBlock.includes('setTimeout(resolve, printWaitMs)'), true);
|
|
187
|
+
assert.equal(helperBlock.includes('process.once(\'exit\''), false);
|
|
188
|
+
assert.equal(helperBlock.includes('process.removeListener(\'uncaughtException\''), true);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('bootstrap path defers cleanup to exit', () => {
|
|
192
|
+
const bootstrapBlock = extractBlock('cat <<\'NODE\' > "$_bootstrap"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
|
|
193
|
+
|
|
194
|
+
assert.equal(bootstrapBlock.includes('CLAUDE_TERMUX_PRINT_WAIT_MS'), false);
|
|
195
|
+
assert.equal(bootstrapBlock.includes('setTimeout(resolve, printWaitMs)'), false);
|
|
196
|
+
assert.equal(bootstrapBlock.includes('process.once(\'exit\''), true);
|
|
197
|
+
assert.equal(bootstrapBlock.includes('process.removeListener(\'uncaughtException\''), true);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('entry extraction uses a process-unique filename', () => {
|
|
201
|
+
const helperBlock = extractBlock('cat <<\'NODE\' > "$_helper"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
|
|
202
|
+
const bootstrapBlock = extractBlock('cat <<\'NODE\' > "$_bootstrap"', '\n export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=');
|
|
203
|
+
|
|
204
|
+
assert.equal(helperBlock.includes('process.pid'), true);
|
|
205
|
+
assert.equal(helperBlock.includes('Math.random'), true);
|
|
206
|
+
assert.equal(bootstrapBlock.includes('process.pid'), true);
|
|
207
|
+
assert.equal(bootstrapBlock.includes('Math.random'), true);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test('replaceRequired enforces the expected replacement count', () => {
|
|
211
|
+
const { replaceRequired } = loadHelperApi();
|
|
212
|
+
|
|
213
|
+
assert.equal(replaceRequired('abc abc', /abc/g, 'x', 'abc', 2), 'x x');
|
|
214
|
+
assert.throws(
|
|
215
|
+
() => replaceRequired('abc abc', /abc/g, 'x', 'abc', 1),
|
|
216
|
+
/unexpected abc count 2/,
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('stringWidth treats full-width and combining text as expected', () => {
|
|
221
|
+
const { stringWidth } = loadHelperApi();
|
|
222
|
+
|
|
223
|
+
assert.equal(stringWidth('abc'), 3);
|
|
224
|
+
assert.equal(stringWidth('あ'), 2);
|
|
225
|
+
assert.equal(stringWidth('a\u0301'), 1);
|
|
226
|
+
assert.equal(stringWidth('🙂'), 2);
|
|
227
|
+
assert.equal(stringWidth('🚀'), 2);
|
|
228
|
+
assert.equal(stringWidth('🚗'), 2);
|
|
229
|
+
assert.equal(stringWidth('❤️'), 2);
|
|
230
|
+
assert.equal(stringWidth('☕️'), 2);
|
|
231
|
+
assert.equal(stringWidth('🇯🇵'), 2);
|
|
232
|
+
assert.equal(stringWidth('#️⃣'), 2);
|
|
233
|
+
assert.equal(stringWidth('👨👩👧👦'), 2);
|
|
234
|
+
assert.equal(stringWidth('\u001b[31mあ\u001b[0m'), 2);
|
|
235
|
+
assert.equal(stringWidth('\u001b[36m🇯🇵\u001b[0m'), 2);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('stableHash incorporates the optional seed', () => {
|
|
239
|
+
const { stableHash } = loadHelperApi();
|
|
240
|
+
|
|
241
|
+
assert.equal(stableHash('abc', 123), stableHash('abc', 123));
|
|
242
|
+
assert.notEqual(stableHash('abc', 123), stableHash('abc', 456));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('wrapAnsi respects soft wrap, trim, and no-wrap options', () => {
|
|
246
|
+
const { wrapAnsi } = loadHelperApi();
|
|
247
|
+
|
|
248
|
+
assert.equal(wrapAnsi('abc def', 4, { hard: false }), 'abc\ndef');
|
|
249
|
+
assert.equal(wrapAnsi(' abc', 10, { trim: true }), 'abc');
|
|
250
|
+
assert.equal(wrapAnsi('abc ', 10, { trim: true }), 'abc');
|
|
251
|
+
assert.equal(wrapAnsi('abc def', 3, { wordWrap: false }).includes('\n'), true);
|
|
252
|
+
assert.equal(wrapAnsi('ab\u001b[31mcd', 3).includes('\n'), true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test('cleanupStaleEntryFiles removes only stale extracted files for the same offsets', () => {
|
|
256
|
+
const { cleanupStaleEntryFiles } = loadHelperApi();
|
|
257
|
+
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-termux-'));
|
|
258
|
+
const tmpdir = fs.mkdtempSync(path.join(tmpRoot, 'work-'));
|
|
259
|
+
const stale = path.join(tmpdir, 'cli.11.22.123.old.bare-path.js');
|
|
260
|
+
const fresh = path.join(tmpdir, 'cli.11.22.456.new.bare-path.js');
|
|
261
|
+
const otherOffsets = path.join(tmpdir, 'cli.33.44.999.old.bare-path.js');
|
|
262
|
+
try {
|
|
263
|
+
fs.writeFileSync(stale, 'stale');
|
|
264
|
+
fs.writeFileSync(fresh, 'fresh');
|
|
265
|
+
fs.writeFileSync(otherOffsets, 'other');
|
|
266
|
+
const oldTime = new Date(Date.now() - (2 * 24 * 60 * 60 * 1000));
|
|
267
|
+
fs.utimesSync(stale, oldTime, oldTime);
|
|
268
|
+
fs.utimesSync(otherOffsets, oldTime, oldTime);
|
|
269
|
+
|
|
270
|
+
cleanupStaleEntryFiles(tmpdir, 11, 22, Date.now());
|
|
271
|
+
|
|
272
|
+
assert.equal(fs.existsSync(stale), false);
|
|
273
|
+
assert.equal(fs.existsSync(fresh), true);
|
|
274
|
+
assert.equal(fs.existsSync(otherOffsets), true);
|
|
275
|
+
} finally {
|
|
276
|
+
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
function buildSyntheticBundleSource() {
|
|
281
|
+
const typeofBun = Array.from({ length: 3 }, () => 'typeof Bun').join('; ');
|
|
282
|
+
const bunProps = Array.from({ length: 33 }, (_, index) => `Bun.p${index}`).join('; ');
|
|
283
|
+
return `function(exports, require, module, __filename, __dirname) { ${typeofBun}; typeof globalThis.Bun; globalThis.Bun; ${bunProps}; }`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
test('rewriteNativeChunkSource rewrites the synthetic bundle slice', () => {
|
|
287
|
+
const { rewriteNativeChunkSource } = loadHelperApi();
|
|
288
|
+
const source = buildSyntheticBundleSource();
|
|
289
|
+
const patched = rewriteNativeChunkSource(source);
|
|
290
|
+
|
|
291
|
+
assert.match(patched, /var __claudeBun = globalThis\.__claudeBunShim;/);
|
|
292
|
+
assert.match(patched, /typeof __claudeBun/);
|
|
293
|
+
assert.match(patched, /typeof globalThis\.__claudeBun/);
|
|
294
|
+
assert.match(patched, /globalThis\.__claudeBun/);
|
|
295
|
+
assert.match(patched, /__claudeBun\./);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test('rewriteNativeChunkSource rejects unexpected replacement counts', () => {
|
|
299
|
+
const { rewriteNativeChunkSource } = loadHelperApi();
|
|
300
|
+
const source = buildSyntheticBundleSource().replace('Bun.p30;', '');
|
|
301
|
+
|
|
302
|
+
assert.throws(
|
|
303
|
+
() => rewriteNativeChunkSource(source),
|
|
304
|
+
/unexpected Bun property access count 32/,
|
|
305
|
+
);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const tarballPath = path.join(__dirname, '..', 'bash0816-claude-code-2.1.177.tgz');
|
|
309
|
+
|
|
310
|
+
test('tarball contents match the workspace runner and test file', { skip: !fs.existsSync(tarballPath) }, () => {
|
|
311
|
+
const tarRunner = child_process.execFileSync('tar', ['-xOf', tarballPath, 'package/lib/termux-run-claude-native.sh']);
|
|
312
|
+
const tarTest = child_process.execFileSync('tar', ['-xOf', tarballPath, 'package/lib/termux-run-claude-native.test.js']);
|
|
313
|
+
|
|
314
|
+
const worktreeRunner = fs.readFileSync(path.join(__dirname, 'termux-run-claude-native.sh'));
|
|
315
|
+
const worktreeTest = fs.readFileSync(path.join(__dirname, 'termux-run-claude-native.test.js'));
|
|
316
|
+
|
|
317
|
+
assert.equal(crypto.createHash('sha256').update(tarRunner).digest('hex'), crypto.createHash('sha256').update(worktreeRunner).digest('hex'));
|
|
318
|
+
assert.equal(crypto.createHash('sha256').update(tarTest).digest('hex'), crypto.createHash('sha256').update(worktreeTest).digest('hex'));
|
|
319
|
+
assert.equal(tarRunner.length, worktreeRunner.length);
|
|
320
|
+
assert.equal(tarTest.length, worktreeTest.length);
|
|
321
|
+
});
|