gogcli-mcp 2.0.7 → 2.0.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/dist/index.js +33 -4
- package/dist/lib.js +33 -4
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/server.json +2 -2
- package/src/runner.ts +37 -1
- package/tests/runner.test.ts +88 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
},
|
|
8
8
|
"metadata": {
|
|
9
9
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
10
|
-
"version": "2.0.
|
|
10
|
+
"version": "2.0.8"
|
|
11
11
|
},
|
|
12
12
|
"plugins": [
|
|
13
13
|
{
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"displayName": "gogcli",
|
|
16
16
|
"source": "./",
|
|
17
17
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
18
|
-
"version": "2.0.
|
|
18
|
+
"version": "2.0.8",
|
|
19
19
|
"author": {
|
|
20
20
|
"name": "Chris Hall"
|
|
21
21
|
},
|
package/dist/index.js
CHANGED
|
@@ -8415,8 +8415,8 @@ function emoji() {
|
|
|
8415
8415
|
}
|
|
8416
8416
|
var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
8417
8417
|
var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
8418
|
-
var mac = (
|
|
8419
|
-
const escapedDelim = escapeRegex(
|
|
8418
|
+
var mac = (delimiter2) => {
|
|
8419
|
+
const escapedDelim = escapeRegex(delimiter2 ?? ":");
|
|
8420
8420
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
8421
8421
|
};
|
|
8422
8422
|
var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
|
@@ -30863,12 +30863,34 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
30863
30863
|
|
|
30864
30864
|
// src/runner.ts
|
|
30865
30865
|
import { spawn } from "node:child_process";
|
|
30866
|
+
import { delimiter } from "node:path";
|
|
30866
30867
|
var TIMEOUT_MS = 3e4;
|
|
30867
30868
|
function envOrUndefined(key) {
|
|
30868
30869
|
const value = process.env[key];
|
|
30869
30870
|
if (!value || value.startsWith("${")) return void 0;
|
|
30870
30871
|
return value;
|
|
30871
30872
|
}
|
|
30873
|
+
function augmentedPath() {
|
|
30874
|
+
const home = process.env.HOME;
|
|
30875
|
+
const candidates = [
|
|
30876
|
+
process.env.PATH ?? "",
|
|
30877
|
+
"/opt/homebrew/bin",
|
|
30878
|
+
"/usr/local/bin",
|
|
30879
|
+
home ? `${home}/.local/bin` : "",
|
|
30880
|
+
home ? `${home}/go/bin` : ""
|
|
30881
|
+
];
|
|
30882
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30883
|
+
const parts = [];
|
|
30884
|
+
for (const c of candidates) {
|
|
30885
|
+
if (!c) continue;
|
|
30886
|
+
for (const dir of c.split(delimiter)) {
|
|
30887
|
+
if (!dir || seen.has(dir)) continue;
|
|
30888
|
+
seen.add(dir);
|
|
30889
|
+
parts.push(dir);
|
|
30890
|
+
}
|
|
30891
|
+
}
|
|
30892
|
+
return parts.join(delimiter);
|
|
30893
|
+
}
|
|
30872
30894
|
function formatTimeout(ms) {
|
|
30873
30895
|
const seconds = Math.round(ms / 1e3);
|
|
30874
30896
|
if (seconds >= 60) {
|
|
@@ -30891,7 +30913,8 @@ async function run(args, options = {}) {
|
|
|
30891
30913
|
const effectiveTimeout = timeout ?? TIMEOUT_MS;
|
|
30892
30914
|
return new Promise((resolve, reject) => {
|
|
30893
30915
|
const { GOG_ACCESS_TOKEN: _, ...cleanEnv } = process.env;
|
|
30894
|
-
const
|
|
30916
|
+
const childEnv = { ...cleanEnv, PATH: augmentedPath() };
|
|
30917
|
+
const child = spawner(envOrUndefined("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
|
|
30895
30918
|
const stdoutChunks = [];
|
|
30896
30919
|
const stderrChunks = [];
|
|
30897
30920
|
let settled = false;
|
|
@@ -30926,6 +30949,12 @@ async function run(args, options = {}) {
|
|
|
30926
30949
|
clearTimeout(timer);
|
|
30927
30950
|
if (settled) return;
|
|
30928
30951
|
settled = true;
|
|
30952
|
+
if (err.code === "ENOENT") {
|
|
30953
|
+
reject(new Error(
|
|
30954
|
+
"gog executable not found. Install gogcli (https://github.com/steipete/gogcli) or set GOG_PATH in your MCP client config to the absolute binary path (run `which gog` in a terminal to find it)."
|
|
30955
|
+
));
|
|
30956
|
+
return;
|
|
30957
|
+
}
|
|
30929
30958
|
reject(err);
|
|
30930
30959
|
});
|
|
30931
30960
|
});
|
|
@@ -32123,7 +32152,7 @@ function registerTasksTools(server2) {
|
|
|
32123
32152
|
}
|
|
32124
32153
|
|
|
32125
32154
|
// src/server.ts
|
|
32126
|
-
var VERSION = true ? "2.0.
|
|
32155
|
+
var VERSION = true ? "2.0.8" : "0.0.0";
|
|
32127
32156
|
function createServer(options) {
|
|
32128
32157
|
return new McpServer({
|
|
32129
32158
|
name: options?.name ?? "gogcli",
|
package/dist/lib.js
CHANGED
|
@@ -12044,8 +12044,8 @@ function emoji() {
|
|
|
12044
12044
|
}
|
|
12045
12045
|
var ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;
|
|
12046
12046
|
var ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
|
|
12047
|
-
var mac = (
|
|
12048
|
-
const escapedDelim = escapeRegex(
|
|
12047
|
+
var mac = (delimiter2) => {
|
|
12048
|
+
const escapedDelim = escapeRegex(delimiter2 ?? ":");
|
|
12049
12049
|
return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
|
|
12050
12050
|
};
|
|
12051
12051
|
var cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
|
|
@@ -30770,12 +30770,34 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
30770
30770
|
|
|
30771
30771
|
// src/runner.ts
|
|
30772
30772
|
import { spawn } from "node:child_process";
|
|
30773
|
+
import { delimiter } from "node:path";
|
|
30773
30774
|
var TIMEOUT_MS = 3e4;
|
|
30774
30775
|
function envOrUndefined(key) {
|
|
30775
30776
|
const value = process.env[key];
|
|
30776
30777
|
if (!value || value.startsWith("${")) return void 0;
|
|
30777
30778
|
return value;
|
|
30778
30779
|
}
|
|
30780
|
+
function augmentedPath() {
|
|
30781
|
+
const home = process.env.HOME;
|
|
30782
|
+
const candidates = [
|
|
30783
|
+
process.env.PATH ?? "",
|
|
30784
|
+
"/opt/homebrew/bin",
|
|
30785
|
+
"/usr/local/bin",
|
|
30786
|
+
home ? `${home}/.local/bin` : "",
|
|
30787
|
+
home ? `${home}/go/bin` : ""
|
|
30788
|
+
];
|
|
30789
|
+
const seen = /* @__PURE__ */ new Set();
|
|
30790
|
+
const parts = [];
|
|
30791
|
+
for (const c of candidates) {
|
|
30792
|
+
if (!c) continue;
|
|
30793
|
+
for (const dir of c.split(delimiter)) {
|
|
30794
|
+
if (!dir || seen.has(dir)) continue;
|
|
30795
|
+
seen.add(dir);
|
|
30796
|
+
parts.push(dir);
|
|
30797
|
+
}
|
|
30798
|
+
}
|
|
30799
|
+
return parts.join(delimiter);
|
|
30800
|
+
}
|
|
30779
30801
|
function formatTimeout(ms) {
|
|
30780
30802
|
const seconds = Math.round(ms / 1e3);
|
|
30781
30803
|
if (seconds >= 60) {
|
|
@@ -30798,7 +30820,8 @@ async function run(args, options = {}) {
|
|
|
30798
30820
|
const effectiveTimeout = timeout ?? TIMEOUT_MS;
|
|
30799
30821
|
return new Promise((resolve, reject) => {
|
|
30800
30822
|
const { GOG_ACCESS_TOKEN: _, ...cleanEnv } = process.env;
|
|
30801
|
-
const
|
|
30823
|
+
const childEnv = { ...cleanEnv, PATH: augmentedPath() };
|
|
30824
|
+
const child = spawner(envOrUndefined("GOG_PATH") ?? "gog", fullArgs, { env: childEnv });
|
|
30802
30825
|
const stdoutChunks = [];
|
|
30803
30826
|
const stderrChunks = [];
|
|
30804
30827
|
let settled = false;
|
|
@@ -30833,6 +30856,12 @@ async function run(args, options = {}) {
|
|
|
30833
30856
|
clearTimeout(timer);
|
|
30834
30857
|
if (settled) return;
|
|
30835
30858
|
settled = true;
|
|
30859
|
+
if (err.code === "ENOENT") {
|
|
30860
|
+
reject(new Error(
|
|
30861
|
+
"gog executable not found. Install gogcli (https://github.com/steipete/gogcli) or set GOG_PATH in your MCP client config to the absolute binary path (run `which gog` in a terminal to find it)."
|
|
30862
|
+
));
|
|
30863
|
+
return;
|
|
30864
|
+
}
|
|
30836
30865
|
reject(err);
|
|
30837
30866
|
});
|
|
30838
30867
|
});
|
|
@@ -32030,7 +32059,7 @@ function registerTasksTools(server) {
|
|
|
32030
32059
|
}
|
|
32031
32060
|
|
|
32032
32061
|
// src/server.ts
|
|
32033
|
-
var VERSION = true ? "2.0.
|
|
32062
|
+
var VERSION = true ? "2.0.8" : "0.0.0";
|
|
32034
32063
|
function createServer(options) {
|
|
32035
32064
|
return new McpServer({
|
|
32036
32065
|
name: options?.name ?? "gogcli",
|
package/manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"manifest_version": "0.3",
|
|
4
4
|
"name": "gogcli-mcp",
|
|
5
5
|
"display_name": "gogcli",
|
|
6
|
-
"version": "2.0.
|
|
6
|
+
"version": "2.0.8",
|
|
7
7
|
"description": "Google Sheets (and more) for Claude via gogcli — read, write, and manage spreadsheets",
|
|
8
8
|
"author": {
|
|
9
9
|
"name": "Chris Hall",
|
package/package.json
CHANGED
package/server.json
CHANGED
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
"source": "github",
|
|
8
8
|
"subfolder": "packages/gogcli-mcp"
|
|
9
9
|
},
|
|
10
|
-
"version": "2.0.
|
|
10
|
+
"version": "2.0.8",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "gogcli-mcp",
|
|
15
|
-
"version": "2.0.
|
|
15
|
+
"version": "2.0.8",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
},
|
package/src/runner.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import type { ChildProcess } from 'node:child_process';
|
|
3
|
+
import { delimiter } from 'node:path';
|
|
3
4
|
|
|
4
5
|
export type Spawner = (
|
|
5
6
|
command: string,
|
|
@@ -26,6 +27,32 @@ function envOrUndefined(key: string): string | undefined {
|
|
|
26
27
|
return value;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// MCP desktop clients often spawn servers with a stripped PATH that excludes
|
|
31
|
+
// Homebrew, user-local, and Go's default install dirs — so even when gog is
|
|
32
|
+
// installed, the spawned server can't find it. Augment the child's PATH with
|
|
33
|
+
// the locations where gogcli is commonly installed.
|
|
34
|
+
function augmentedPath(): string {
|
|
35
|
+
const home = process.env.HOME;
|
|
36
|
+
const candidates = [
|
|
37
|
+
process.env.PATH ?? '',
|
|
38
|
+
'/opt/homebrew/bin',
|
|
39
|
+
'/usr/local/bin',
|
|
40
|
+
home ? `${home}/.local/bin` : '',
|
|
41
|
+
home ? `${home}/go/bin` : '',
|
|
42
|
+
];
|
|
43
|
+
const seen = new Set<string>();
|
|
44
|
+
const parts: string[] = [];
|
|
45
|
+
for (const c of candidates) {
|
|
46
|
+
if (!c) continue;
|
|
47
|
+
for (const dir of c.split(delimiter)) {
|
|
48
|
+
if (!dir || seen.has(dir)) continue;
|
|
49
|
+
seen.add(dir);
|
|
50
|
+
parts.push(dir);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return parts.join(delimiter);
|
|
54
|
+
}
|
|
55
|
+
|
|
29
56
|
function formatTimeout(ms: number): string {
|
|
30
57
|
const seconds = Math.round(ms / 1000);
|
|
31
58
|
if (seconds >= 60) {
|
|
@@ -55,7 +82,8 @@ export async function run(args: string[], options: RunOptions = {}): Promise<str
|
|
|
55
82
|
// Strip GOG_ACCESS_TOKEN so gogcli uses stored refresh tokens instead of
|
|
56
83
|
// a potentially stale direct access token passed through MCP env config.
|
|
57
84
|
const { GOG_ACCESS_TOKEN: _, ...cleanEnv } = process.env;
|
|
58
|
-
const
|
|
85
|
+
const childEnv = { ...cleanEnv, PATH: augmentedPath() };
|
|
86
|
+
const child = spawner(envOrUndefined('GOG_PATH') ?? 'gog', fullArgs, { env: childEnv });
|
|
59
87
|
const stdoutChunks: Buffer[] = [];
|
|
60
88
|
const stderrChunks: Buffer[] = [];
|
|
61
89
|
let settled = false;
|
|
@@ -90,6 +118,14 @@ export async function run(args: string[], options: RunOptions = {}): Promise<str
|
|
|
90
118
|
clearTimeout(timer);
|
|
91
119
|
if (settled) return;
|
|
92
120
|
settled = true;
|
|
121
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
122
|
+
reject(new Error(
|
|
123
|
+
'gog executable not found. Install gogcli (https://github.com/steipete/gogcli) ' +
|
|
124
|
+
'or set GOG_PATH in your MCP client config to the absolute binary path ' +
|
|
125
|
+
'(run `which gog` in a terminal to find it).',
|
|
126
|
+
));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
93
129
|
reject(err);
|
|
94
130
|
});
|
|
95
131
|
});
|
package/tests/runner.test.ts
CHANGED
|
@@ -24,7 +24,7 @@ describe('run', () => {
|
|
|
24
24
|
expect(spawner).toHaveBeenCalledWith(
|
|
25
25
|
'gog',
|
|
26
26
|
['--json', '--color=never', '--no-input', 'sheets', 'get', 'id1', 'A1'],
|
|
27
|
-
expect.objectContaining({ env:
|
|
27
|
+
expect.objectContaining({ env: expect.any(Object) }),
|
|
28
28
|
);
|
|
29
29
|
});
|
|
30
30
|
|
|
@@ -206,6 +206,93 @@ describe('run', () => {
|
|
|
206
206
|
.rejects.toThrow('gog not found');
|
|
207
207
|
});
|
|
208
208
|
|
|
209
|
+
it('wraps ENOENT spawn errors with an install-or-set-GOG_PATH hint', async () => {
|
|
210
|
+
const spawner = vi.fn(() => {
|
|
211
|
+
const proc = new EventEmitter() as ReturnType<Spawner>;
|
|
212
|
+
(proc as unknown as { stdout: EventEmitter; stderr: EventEmitter }).stdout = new EventEmitter();
|
|
213
|
+
(proc as unknown as { stdout: EventEmitter; stderr: EventEmitter }).stderr = new EventEmitter();
|
|
214
|
+
setTimeout(() => {
|
|
215
|
+
const err = new Error('spawn gog ENOENT') as NodeJS.ErrnoException;
|
|
216
|
+
err.code = 'ENOENT';
|
|
217
|
+
proc.emit('error', err);
|
|
218
|
+
}, 0);
|
|
219
|
+
return proc;
|
|
220
|
+
}) as unknown as Spawner;
|
|
221
|
+
await expect(run(['sheets', 'get', 'id', 'A1'], { spawner }))
|
|
222
|
+
.rejects.toThrow(/gog executable not found.*Install gogcli.*GOG_PATH/s);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it('augments child PATH with common gogcli install dirs', async () => {
|
|
226
|
+
const spawner = makeSpawner(0, '{}');
|
|
227
|
+
const originalHome = process.env.HOME;
|
|
228
|
+
const originalPath = process.env.PATH;
|
|
229
|
+
process.env.HOME = '/Users/test';
|
|
230
|
+
process.env.PATH = '/usr/bin:/bin';
|
|
231
|
+
try {
|
|
232
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
233
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
234
|
+
const passedPath = passedEnv.PATH!;
|
|
235
|
+
expect(passedPath).toContain('/usr/bin');
|
|
236
|
+
expect(passedPath).toContain('/opt/homebrew/bin');
|
|
237
|
+
expect(passedPath).toContain('/usr/local/bin');
|
|
238
|
+
expect(passedPath).toContain('/Users/test/.local/bin');
|
|
239
|
+
expect(passedPath).toContain('/Users/test/go/bin');
|
|
240
|
+
} finally {
|
|
241
|
+
if (originalHome === undefined) delete process.env.HOME;
|
|
242
|
+
else process.env.HOME = originalHome;
|
|
243
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
244
|
+
else process.env.PATH = originalPath;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('does not duplicate dirs that are already on PATH', async () => {
|
|
249
|
+
const spawner = makeSpawner(0, '{}');
|
|
250
|
+
const originalPath = process.env.PATH;
|
|
251
|
+
process.env.PATH = '/opt/homebrew/bin:/usr/bin';
|
|
252
|
+
try {
|
|
253
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
254
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
255
|
+
const passedPath = passedEnv.PATH!;
|
|
256
|
+
const homebrewCount = passedPath.split(':').filter(d => d === '/opt/homebrew/bin').length;
|
|
257
|
+
expect(homebrewCount).toBe(1);
|
|
258
|
+
} finally {
|
|
259
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
260
|
+
else process.env.PATH = originalPath;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('augments PATH even when HOME is unset', async () => {
|
|
265
|
+
const spawner = makeSpawner(0, '{}');
|
|
266
|
+
const originalHome = process.env.HOME;
|
|
267
|
+
const originalPath = process.env.PATH;
|
|
268
|
+
delete process.env.HOME;
|
|
269
|
+
process.env.PATH = '/usr/bin';
|
|
270
|
+
try {
|
|
271
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
272
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
273
|
+
const passedPath = passedEnv.PATH!;
|
|
274
|
+
expect(passedPath).toContain('/opt/homebrew/bin');
|
|
275
|
+
expect(passedPath).not.toContain('.local/bin');
|
|
276
|
+
} finally {
|
|
277
|
+
if (originalHome !== undefined) process.env.HOME = originalHome;
|
|
278
|
+
if (originalPath === undefined) delete process.env.PATH;
|
|
279
|
+
else process.env.PATH = originalPath;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('handles empty PATH gracefully', async () => {
|
|
284
|
+
const spawner = makeSpawner(0, '{}');
|
|
285
|
+
const originalPath = process.env.PATH;
|
|
286
|
+
delete process.env.PATH;
|
|
287
|
+
try {
|
|
288
|
+
await run(['sheets', 'metadata', 'id1'], { spawner });
|
|
289
|
+
const passedEnv = (spawner as ReturnType<typeof vi.fn>).mock.calls[0][2].env as NodeJS.ProcessEnv;
|
|
290
|
+
expect(passedEnv.PATH).toContain('/opt/homebrew/bin');
|
|
291
|
+
} finally {
|
|
292
|
+
if (originalPath !== undefined) process.env.PATH = originalPath;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
209
296
|
it('ignores close event if error event already settled the promise', async () => {
|
|
210
297
|
const spawner = vi.fn(() => {
|
|
211
298
|
const proc = new EventEmitter() as ReturnType<Spawner>;
|