pi-odd-worktree 1.0.0 → 1.1.1
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 +69 -3
- package/extensions/odd-worktree-tools.ts +213 -5
- package/lib/codegraph-commands.js +165 -0
- package/lib/codegraph-session.js +438 -0
- package/lib/odd-worktrees-prompt.js +109 -0
- package/lib/worktree-hooks.js +23 -4
- package/lib/worktree-warp-sessions.js +28 -0
- package/package.json +1 -1
- package/tools/codegraph-commands.test.js +371 -0
- package/tools/codegraph-session.test.js +457 -0
- package/tools/odd-worktrees-prompt.test.js +170 -0
- package/tools/package-surface.test.js +13 -4
- package/tools/warp-empty-session.test.js +495 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// S9/T5: the three session commands — `/codegraph-hook`, `/codegraph-init` and
|
|
3
|
+
// `/codegraph-sync`.
|
|
4
|
+
//
|
|
5
|
+
// Two halves. The REGISTRATION half goes through the real extension loader with
|
|
6
|
+
// a fake Pi host: the commands exist only when the session gate answers that the
|
|
7
|
+
// `codegraph` CLI is present, so a session without it never lists them. The
|
|
8
|
+
// BODIES half drives `lib/codegraph-commands.js` directly with injected seams:
|
|
9
|
+
// no real `codegraph` child is ever started, and the hook toggle is observed
|
|
10
|
+
// through the scheduler's own decision instead of internal state.
|
|
11
|
+
const { test, describe, beforeEach, after } = require('node:test');
|
|
12
|
+
const assert = require('node:assert/strict');
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const os = require('node:os');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const { EventEmitter } = require('node:events');
|
|
17
|
+
|
|
18
|
+
const session = require('../lib/codegraph-session.js');
|
|
19
|
+
const commands = require('../lib/codegraph-commands.js');
|
|
20
|
+
const { loadExtension } = require('./fixtures/load-extension.js');
|
|
21
|
+
|
|
22
|
+
const scratch = [];
|
|
23
|
+
function tempDir(prefix = 'cg-commands-') {
|
|
24
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
25
|
+
scratch.push(dir);
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const savedEnv = { ...process.env };
|
|
30
|
+
function restoreEnv() {
|
|
31
|
+
for (const key of Object.keys(process.env)) {
|
|
32
|
+
if (!(key in savedEnv)) delete process.env[key];
|
|
33
|
+
}
|
|
34
|
+
for (const [key, value] of Object.entries(savedEnv)) process.env[key] = value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
after(() => {
|
|
38
|
+
restoreEnv();
|
|
39
|
+
for (const dir of scratch) {
|
|
40
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* teardown is best effort */ }
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
restoreEnv();
|
|
46
|
+
session.resetCodegraphSession();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// --- fixtures -----------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
// A directory PATH with no `codegraph` in it, provably: the probe only looks at
|
|
52
|
+
// the directories of this PATH string.
|
|
53
|
+
function emptyPath() {
|
|
54
|
+
return tempDir('cg-empty-bin-');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function binDirWithCodegraph() {
|
|
58
|
+
const dir = tempDir('cg-bin-');
|
|
59
|
+
fs.writeFileSync(path.join(dir, 'codegraph'), '#!/bin/sh\nexit 0\n', { mode: 0o755 });
|
|
60
|
+
return dir;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function makeCtx(cwd) {
|
|
64
|
+
const notifications = [];
|
|
65
|
+
return { notifications, ctx: { cwd, ui: { notify: (text, level) => { notifications.push([level, text]); } } } };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const FORCED_ENV = { PATH: '', CODEGRAPH_FORCE: '1' };
|
|
69
|
+
|
|
70
|
+
function indexedRoot() {
|
|
71
|
+
const root = tempDir('cg-session-root-');
|
|
72
|
+
fs.mkdirSync(path.join(root, '.codegraph'));
|
|
73
|
+
return root;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// One scheduler decision needs a git seam and a spawn seam; the spawn double
|
|
77
|
+
// closes immediately so `whenIdle()` settles without sleeping.
|
|
78
|
+
function schedulerDeps(root, overrides = {}) {
|
|
79
|
+
const spawns = [];
|
|
80
|
+
const deps = {
|
|
81
|
+
env: FORCED_ENV,
|
|
82
|
+
execFile: (bin, args) => {
|
|
83
|
+
assert.equal(bin, 'git');
|
|
84
|
+
assert.deepEqual(args, ['-C', path.join(root, 'src'), 'rev-parse', '--show-toplevel']);
|
|
85
|
+
return `${root}\n`;
|
|
86
|
+
},
|
|
87
|
+
debounceMs: 5,
|
|
88
|
+
spawn: (bin, args, opts) => {
|
|
89
|
+
spawns.push({ bin, args, opts });
|
|
90
|
+
const child = new EventEmitter();
|
|
91
|
+
child.unref = () => {};
|
|
92
|
+
setImmediate(() => child.emit('close'));
|
|
93
|
+
return child;
|
|
94
|
+
},
|
|
95
|
+
...overrides,
|
|
96
|
+
};
|
|
97
|
+
return { deps, spawns };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- registration gate --------------------------------------------------
|
|
101
|
+
|
|
102
|
+
describe('registration gate (S9)', () => {
|
|
103
|
+
test('no codegraph command is registered when the gate answers false', async () => {
|
|
104
|
+
process.env.PATH = emptyPath();
|
|
105
|
+
delete process.env.CODEGRAPH_FORCE;
|
|
106
|
+
session.resetCodegraphGate();
|
|
107
|
+
const { commands: registered } = await loadExtension({});
|
|
108
|
+
assert.deepEqual([...registered.keys()].sort(), ['issues', 'odd-list', 'warp']);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('all three commands are registered once the gate answers true', async () => {
|
|
112
|
+
process.env.PATH = `${binDirWithCodegraph()}${path.delimiter}${emptyPath()}`;
|
|
113
|
+
delete process.env.CODEGRAPH_FORCE;
|
|
114
|
+
session.resetCodegraphGate();
|
|
115
|
+
const { commands: registered } = await loadExtension({});
|
|
116
|
+
assert.deepEqual([...registered.keys()].sort(), [
|
|
117
|
+
'codegraph-hook', 'codegraph-init', 'codegraph-sync', 'issues', 'odd-list', 'warp',
|
|
118
|
+
]);
|
|
119
|
+
for (const name of ['codegraph-hook', 'codegraph-init', 'codegraph-sync']) {
|
|
120
|
+
const definition = registered.get(name);
|
|
121
|
+
assert.equal(typeof definition.handler, 'function', `${name} has a handler`);
|
|
122
|
+
assert.equal(typeof definition.description, 'string');
|
|
123
|
+
assert.ok(definition.description.length > 0, `${name} describes itself`);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('CODEGRAPH_FORCE=1 exposes the triple without a CLI probe', async () => {
|
|
128
|
+
process.env.PATH = emptyPath();
|
|
129
|
+
process.env.CODEGRAPH_FORCE = '1';
|
|
130
|
+
session.resetCodegraphGate();
|
|
131
|
+
const { commands: registered } = await loadExtension({});
|
|
132
|
+
assert.equal(registered.has('codegraph-hook'), true);
|
|
133
|
+
assert.equal(registered.has('codegraph-init'), true);
|
|
134
|
+
assert.equal(registered.has('codegraph-sync'), true);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('the registered hook handler reports the state through the session UI', async () => {
|
|
138
|
+
process.env.PATH = `${binDirWithCodegraph()}${path.delimiter}${emptyPath()}`;
|
|
139
|
+
delete process.env.CODEGRAPH_FORCE;
|
|
140
|
+
session.resetCodegraphGate();
|
|
141
|
+
const { commands: registered } = await loadExtension({});
|
|
142
|
+
const { notifications, ctx } = makeCtx(tempDir('cg-session-cwd-'));
|
|
143
|
+
await registered.get('codegraph-hook').handler('off', ctx);
|
|
144
|
+
await registered.get('codegraph-hook').handler(undefined, ctx);
|
|
145
|
+
await registered.get('codegraph-hook').handler('status', ctx);
|
|
146
|
+
assert.deepEqual(notifications, [
|
|
147
|
+
['info', 'codegraph hook: disabled'],
|
|
148
|
+
['info', 'codegraph hook: enabled'],
|
|
149
|
+
['info', 'codegraph hook: enabled'],
|
|
150
|
+
]);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// --- /codegraph-hook ----------------------------------------------------
|
|
155
|
+
|
|
156
|
+
describe('/codegraph-hook (S9)', () => {
|
|
157
|
+
test('the hook starts enabled', () => {
|
|
158
|
+
assert.equal(session.isHookEnabled(), true);
|
|
159
|
+
assert.deepEqual(commands.codegraphHook('status'), { ok: true, level: 'info', text: 'codegraph hook: enabled' });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('no argument toggles and status only reports', () => {
|
|
163
|
+
assert.equal(commands.codegraphHook('').text, 'codegraph hook: disabled');
|
|
164
|
+
assert.equal(commands.codegraphHook('status').text, 'codegraph hook: disabled');
|
|
165
|
+
assert.equal(session.isHookEnabled(), false);
|
|
166
|
+
assert.equal(commands.codegraphHook(undefined).text, 'codegraph hook: enabled');
|
|
167
|
+
assert.equal(commands.codegraphHook('status').text, 'codegraph hook: enabled');
|
|
168
|
+
assert.equal(session.isHookEnabled(), true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('on and off set the state explicitly', () => {
|
|
172
|
+
assert.deepEqual(commands.codegraphHook('on'), { ok: true, level: 'info', text: 'codegraph hook: enabled' });
|
|
173
|
+
assert.equal(session.isHookEnabled(), true);
|
|
174
|
+
assert.deepEqual(commands.codegraphHook('off'), { ok: true, level: 'info', text: 'codegraph hook: disabled' });
|
|
175
|
+
assert.equal(session.isHookEnabled(), false);
|
|
176
|
+
assert.deepEqual(commands.codegraphHook('off'), { ok: true, level: 'info', text: 'codegraph hook: disabled' });
|
|
177
|
+
assert.equal(session.isHookEnabled(), false);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('an unknown option is refused and changes nothing', () => {
|
|
181
|
+
assert.equal(session.isHookEnabled(), true);
|
|
182
|
+
assert.deepEqual(commands.codegraphHook('maybe'), {
|
|
183
|
+
ok: false,
|
|
184
|
+
level: 'error',
|
|
185
|
+
text: 'codegraph hook: unknown option "maybe". Use on, off or status.',
|
|
186
|
+
});
|
|
187
|
+
assert.equal(session.isHookEnabled(), true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('a disabled hook stops scheduling and does not touch the lock', async () => {
|
|
191
|
+
const root = indexedRoot();
|
|
192
|
+
const { deps, spawns } = schedulerDeps(root);
|
|
193
|
+
commands.codegraphHook('off');
|
|
194
|
+
const decision = session.scheduleSync(path.join(root, 'src', 'a.js'), deps);
|
|
195
|
+
assert.deepEqual(decision, { scheduled: false, reason: 'hook-off' });
|
|
196
|
+
await session.whenIdle();
|
|
197
|
+
assert.equal(spawns.length, 0, 'nothing is spawned while the hook is off');
|
|
198
|
+
assert.equal(fs.existsSync(path.join(root, '.codegraph', 'pi-sync.lock')), false, 'the lock is never taken');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test('turning the hook back on resumes the sync', async () => {
|
|
202
|
+
const root = indexedRoot();
|
|
203
|
+
const first = schedulerDeps(root);
|
|
204
|
+
commands.codegraphHook('off');
|
|
205
|
+
assert.equal(session.scheduleSync(path.join(root, 'src', 'a.js'), first.deps).reason, 'hook-off');
|
|
206
|
+
await session.whenIdle();
|
|
207
|
+
assert.equal(first.spawns.length, 0);
|
|
208
|
+
commands.codegraphHook('on');
|
|
209
|
+
assert.equal(session.scheduleSync(path.join(root, 'src', 'b.js'), first.deps).scheduled, true);
|
|
210
|
+
await session.whenIdle();
|
|
211
|
+
assert.equal(first.spawns.length, 1);
|
|
212
|
+
assert.equal(first.spawns[0].bin, 'codegraph');
|
|
213
|
+
assert.deepEqual(first.spawns[0].args, ['sync', root]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('CODEGRAPH_HOOK=0 starts the session with the hook already off', async () => {
|
|
217
|
+
process.env.CODEGRAPH_HOOK = '0';
|
|
218
|
+
session.resetCodegraphSession();
|
|
219
|
+
assert.equal(session.isHookEnabled(), false);
|
|
220
|
+
assert.equal(commands.codegraphHook('status').text, 'codegraph hook: disabled');
|
|
221
|
+
const root = indexedRoot();
|
|
222
|
+
const { deps, spawns } = schedulerDeps(root, { env: { ...FORCED_ENV, CODEGRAPH_HOOK: '0' } });
|
|
223
|
+
assert.deepEqual(session.scheduleSync(path.join(root, 'src', 'a.js'), deps), { scheduled: false, reason: 'hook-off' });
|
|
224
|
+
await session.whenIdle();
|
|
225
|
+
assert.equal(spawns.length, 0);
|
|
226
|
+
// `on` is explicit and still wins over the environment.
|
|
227
|
+
commands.codegraphHook('on');
|
|
228
|
+
assert.equal(session.scheduleSync(path.join(root, 'src', 'b.js'), deps).scheduled, true);
|
|
229
|
+
await session.whenIdle();
|
|
230
|
+
assert.equal(spawns.length, 1);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// --- /codegraph-init and /codegraph-sync --------------------------------
|
|
235
|
+
|
|
236
|
+
const OK = { status: 0, stdout: 'indexed\n', stderr: '', error: null };
|
|
237
|
+
// Real captures (glyphs included, ANSI stripped). The lines the summarizer reads
|
|
238
|
+
// are the plain-text ones; the box glyphs are decoration and never matched.
|
|
239
|
+
const CAPTURES = {
|
|
240
|
+
initFresh: (dir) => ({ status: 0, error: null, stderr: '', stdout: [
|
|
241
|
+
'┌ Initializing CodeGraph', '│', `◇ Initialized in ${dir}`, '│',
|
|
242
|
+
'Scanning files...', 'Parsing code...', 'Resolving refs...', '│',
|
|
243
|
+
'◇ Indexed 1 files', '│', '◇ 3 nodes, 3 edges in 141ms', '│', '└ Done', '',
|
|
244
|
+
].join('\n') }),
|
|
245
|
+
initAgain: (dir) => ({ status: 0, error: null, stderr: '', stdout: [
|
|
246
|
+
'┌ Initializing CodeGraph', '│', `▲ Already initialized in ${dir}`, '│',
|
|
247
|
+
'● Use "codegraph index" to re-index or "codegraph sync" to update', '│', '└ ', '',
|
|
248
|
+
].join('\n') }),
|
|
249
|
+
syncUpToDate: { status: 0, error: null, stderr: '', stdout: [
|
|
250
|
+
'┌ Syncing CodeGraph', '│', 'Scanning files...', '│', '● Already up to date', '│', '└ Done', '',
|
|
251
|
+
].join('\n') },
|
|
252
|
+
syncChanged: { status: 0, error: null, stderr: '', stdout: [
|
|
253
|
+
'┌ Syncing CodeGraph', '│', 'Scanning files...', '◆ Synced 1 changed files', '│',
|
|
254
|
+
'● Modified: 1 — 4 nodes in 31ms', '│', '└ Done', '',
|
|
255
|
+
].join('\n') },
|
|
256
|
+
syncNoIndex: (dir) => ({ status: 1, error: null, stdout: '', stderr: `✗ CodeGraph not initialized in ${dir}\n` }),
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
describe('init and sync report what happened, not just the exit code (S9)', () => {
|
|
260
|
+
const runOf = (capture) => ({ run: async () => (typeof capture === 'function' ? capture() : capture) });
|
|
261
|
+
|
|
262
|
+
test('a fresh init names the checkout, the files and the graph it built', async () => {
|
|
263
|
+
const cwd = tempDir('cg-report-init-');
|
|
264
|
+
const out = await commands.codegraphInit('', { cwd }, runOf(CAPTURES.initFresh(cwd)));
|
|
265
|
+
assert.deepEqual(out, { ok: true, level: 'info', text: `codegraph init: initialized ${cwd} — 1 file, 3 nodes, 3 edges` });
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test('an init over an existing index says so and points at sync', async () => {
|
|
269
|
+
const cwd = tempDir('cg-report-init2-');
|
|
270
|
+
const out = await commands.codegraphInit('', { cwd }, runOf(CAPTURES.initAgain(cwd)));
|
|
271
|
+
assert.deepEqual(out, { ok: true, level: 'info', text: `codegraph init: already initialized at ${cwd} — nothing to do, use /codegraph-sync to update` });
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
test('a sync with nothing to do says exactly that', async () => {
|
|
275
|
+
const cwd = tempDir('cg-report-sync0-');
|
|
276
|
+
const out = await commands.codegraphSync('', { cwd }, runOf(CAPTURES.syncUpToDate));
|
|
277
|
+
assert.deepEqual(out, { ok: true, level: 'info', text: 'codegraph sync: already up to date — nothing to sync' });
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
test('a sync that changed files names the count and the nodes', async () => {
|
|
281
|
+
const cwd = tempDir('cg-report-sync1-');
|
|
282
|
+
const out = await commands.codegraphSync('', { cwd }, runOf(CAPTURES.syncChanged));
|
|
283
|
+
assert.deepEqual(out, { ok: true, level: 'info', text: 'codegraph sync: synced 1 changed file — 4 nodes' });
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test('a sync with no index is a failure that names the fix', async () => {
|
|
287
|
+
const cwd = tempDir('cg-report-sync2-');
|
|
288
|
+
const out = await commands.codegraphSync('', { cwd }, runOf(CAPTURES.syncNoIndex(cwd)));
|
|
289
|
+
assert.deepEqual(out, { ok: false, level: 'error', text: 'codegraph sync: no index in this checkout — run /codegraph-init first' });
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
test('successful output the summarizer does not recognise still reports success', async () => {
|
|
293
|
+
const cwd = tempDir('cg-report-unknown-');
|
|
294
|
+
const out = await commands.codegraphSync('', { cwd }, runOf({ status: 0, stdout: 'something new\n', stderr: '', error: null }));
|
|
295
|
+
assert.deepEqual(out, { ok: true, level: 'info', text: 'codegraph sync: done' });
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe('/codegraph-init and /codegraph-sync (S9)', () => {
|
|
300
|
+
test('init runs `codegraph init .` in the session directory', async () => {
|
|
301
|
+
const cwd = tempDir('cg-init-cwd-');
|
|
302
|
+
const calls = [];
|
|
303
|
+
const out = await commands.codegraphInit('', { cwd }, {
|
|
304
|
+
run: async (bin, args, runCwd) => { calls.push([bin, args, runCwd]); return OK; },
|
|
305
|
+
});
|
|
306
|
+
assert.deepEqual(calls, [['codegraph', ['init', '.'], cwd]]);
|
|
307
|
+
assert.deepEqual(out, { ok: true, level: 'info', text: 'codegraph init: done' });
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test('sync runs `codegraph sync .` in the session directory', async () => {
|
|
311
|
+
const cwd = tempDir('cg-sync-cwd-');
|
|
312
|
+
const calls = [];
|
|
313
|
+
const out = await commands.codegraphSync('', { cwd }, {
|
|
314
|
+
run: async (bin, args, runCwd) => { calls.push([bin, args, runCwd]); return OK; },
|
|
315
|
+
});
|
|
316
|
+
assert.deepEqual(calls, [['codegraph', ['sync', '.'], cwd]]);
|
|
317
|
+
assert.deepEqual(out, { ok: true, level: 'info', text: 'codegraph sync: done' });
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test('the run happens in the process directory when the session has none', async () => {
|
|
321
|
+
const calls = [];
|
|
322
|
+
await commands.codegraphInit('', {}, {
|
|
323
|
+
run: async (bin, args, runCwd) => { calls.push([bin, args, runCwd]); return OK; },
|
|
324
|
+
});
|
|
325
|
+
assert.deepEqual(calls, [['codegraph', ['init', '.'], process.cwd()]]);
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test('a non-zero exit is reported as a failure, never as success', async () => {
|
|
329
|
+
const cwd = tempDir('cg-fail-cwd-');
|
|
330
|
+
const out = await commands.codegraphInit('', { cwd }, {
|
|
331
|
+
run: async () => ({ status: 1, stdout: '', stderr: 'CodeGraph: cannot write index\nat line 2\n', error: null }),
|
|
332
|
+
});
|
|
333
|
+
assert.deepEqual(out, {
|
|
334
|
+
ok: false,
|
|
335
|
+
level: 'error',
|
|
336
|
+
text: 'codegraph init: exit 1 — CodeGraph: cannot write index',
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test('a failed sync reports its own exit status and stderr line', async () => {
|
|
341
|
+
const cwd = tempDir('cg-fail-sync-cwd-');
|
|
342
|
+
const out = await commands.codegraphSync('', { cwd }, {
|
|
343
|
+
run: async () => ({ status: 2, stdout: '', stderr: 'CodeGraph not initialized\n', error: null }),
|
|
344
|
+
});
|
|
345
|
+
assert.deepEqual(out, {
|
|
346
|
+
ok: false,
|
|
347
|
+
level: 'error',
|
|
348
|
+
text: 'codegraph sync: exit 2 — CodeGraph not initialized',
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test('a child that could not be started is reported, not thrown', async () => {
|
|
353
|
+
const cwd = tempDir('cg-nospawn-cwd-');
|
|
354
|
+
const out = await commands.codegraphInit('', { cwd }, {
|
|
355
|
+
run: async () => ({ status: null, stdout: '', stderr: '', error: 'spawn codegraph ENOENT' }),
|
|
356
|
+
});
|
|
357
|
+
assert.deepEqual(out, {
|
|
358
|
+
ok: false,
|
|
359
|
+
level: 'error',
|
|
360
|
+
text: 'codegraph init: failed — spawn codegraph ENOENT',
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
test('a thrown child error is reported, not thrown', async () => {
|
|
365
|
+
const cwd = tempDir('cg-throw-cwd-');
|
|
366
|
+
const out = await commands.codegraphSync('', { cwd }, {
|
|
367
|
+
run: async () => { throw new Error('boom'); },
|
|
368
|
+
});
|
|
369
|
+
assert.deepEqual(out, { ok: false, level: 'error', text: 'codegraph sync: failed — boom' });
|
|
370
|
+
});
|
|
371
|
+
});
|