bluera-knowledge 0.11.18 → 0.11.20
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/plugin.json +1 -1
- package/CHANGELOG.md +55 -0
- package/dist/{chunk-6FHWC36B.js → chunk-HRQD3MPH.js} +8 -6
- package/dist/chunk-HRQD3MPH.js.map +1 -0
- package/dist/{chunk-ZZNABJMQ.js → chunk-MQGRQ2EG.js} +99 -34
- package/dist/chunk-MQGRQ2EG.js.map +1 -0
- package/dist/{chunk-ZDEO4WJT.js → chunk-Q2ZGPJ66.js} +22 -70
- package/dist/chunk-Q2ZGPJ66.js.map +1 -0
- package/dist/{chunk-5NUI6JL6.js → chunk-ZSKQIMD7.js} +5 -2
- package/dist/chunk-ZSKQIMD7.js.map +1 -0
- package/dist/index.js +36 -18
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +3 -3
- package/dist/watch.service-OPLKIDFQ.js +7 -0
- package/dist/workers/background-worker-cli.js +3 -3
- package/package.json +1 -1
- package/src/cli/commands/crawl.ts +1 -1
- package/src/cli/commands/index-cmd.test.ts +14 -4
- package/src/cli/commands/index-cmd.ts +11 -4
- package/src/cli/commands/store.test.ts +211 -18
- package/src/cli/commands/store.ts +26 -8
- package/src/crawl/article-converter.test.ts +30 -61
- package/src/crawl/article-converter.ts +2 -8
- package/src/crawl/bridge.test.ts +14 -0
- package/src/crawl/bridge.ts +17 -5
- package/src/crawl/intelligent-crawler.test.ts +65 -76
- package/src/crawl/intelligent-crawler.ts +33 -69
- package/src/db/lance.test.ts +3 -4
- package/src/db/lance.ts +14 -19
- package/src/mcp/server.test.ts +56 -1
- package/src/mcp/server.ts +5 -1
- package/src/plugin/git-clone.test.ts +44 -0
- package/src/plugin/git-clone.ts +4 -0
- package/src/services/code-unit.service.test.ts +59 -6
- package/src/services/code-unit.service.ts +47 -2
- package/src/services/index.ts +19 -3
- package/src/services/job.service.test.ts +10 -7
- package/src/services/job.service.ts +12 -6
- package/src/services/search.service.ts +15 -9
- package/src/services/services.test.ts +19 -6
- package/src/services/watch.service.test.ts +80 -56
- package/src/services/watch.service.ts +9 -6
- package/dist/chunk-5NUI6JL6.js.map +0 -1
- package/dist/chunk-6FHWC36B.js.map +0 -1
- package/dist/chunk-ZDEO4WJT.js.map +0 -1
- package/dist/chunk-ZZNABJMQ.js.map +0 -1
- package/dist/watch.service-BJV3TI3F.js +0 -7
- /package/dist/{watch.service-BJV3TI3F.js.map → watch.service-OPLKIDFQ.js.map} +0 -0
package/dist/mcp/server.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMCPServer,
|
|
3
3
|
runMCPServer
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
6
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-ZSKQIMD7.js";
|
|
5
|
+
import "../chunk-MQGRQ2EG.js";
|
|
6
|
+
import "../chunk-HRQD3MPH.js";
|
|
7
7
|
export {
|
|
8
8
|
createMCPServer,
|
|
9
9
|
runMCPServer
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
IntelligentCrawler
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-Q2ZGPJ66.js";
|
|
5
5
|
import {
|
|
6
6
|
JobService,
|
|
7
7
|
createDocumentId,
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
createServices,
|
|
10
10
|
createStoreId,
|
|
11
11
|
shutdownLogger
|
|
12
|
-
} from "../chunk-
|
|
13
|
-
import "../chunk-
|
|
12
|
+
} from "../chunk-MQGRQ2EG.js";
|
|
13
|
+
import "../chunk-HRQD3MPH.js";
|
|
14
14
|
|
|
15
15
|
// src/workers/background-worker.ts
|
|
16
16
|
import { createHash } from "crypto";
|
package/package.json
CHANGED
|
@@ -77,7 +77,7 @@ export function createCrawlCommand(getOptions: () => GlobalOptions): Command {
|
|
|
77
77
|
store = existingStore;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
const maxPages = cmdOptions.maxPages !== undefined ? parseInt(cmdOptions.maxPages) : 50;
|
|
80
|
+
const maxPages = cmdOptions.maxPages !== undefined ? parseInt(cmdOptions.maxPages, 10) : 50;
|
|
81
81
|
|
|
82
82
|
// Use spinner in interactive mode
|
|
83
83
|
const isInteractive =
|
|
@@ -407,7 +407,12 @@ describe('createIndexCommand - Execution Tests', () => {
|
|
|
407
407
|
|
|
408
408
|
expect(mockServices.store.getByIdOrName).toHaveBeenCalledWith('test-store');
|
|
409
409
|
expect(WatchService).toHaveBeenCalledWith(mockServices.index, mockServices.lance);
|
|
410
|
-
expect(mockWatchService.watch).toHaveBeenCalledWith(
|
|
410
|
+
expect(mockWatchService.watch).toHaveBeenCalledWith(
|
|
411
|
+
mockStore,
|
|
412
|
+
1000,
|
|
413
|
+
expect.any(Function),
|
|
414
|
+
expect.any(Function)
|
|
415
|
+
);
|
|
411
416
|
});
|
|
412
417
|
|
|
413
418
|
it('watches a repo store successfully', async () => {
|
|
@@ -467,7 +472,12 @@ describe('createIndexCommand - Execution Tests', () => {
|
|
|
467
472
|
const action = watchCmd!._actionHandler;
|
|
468
473
|
await action(['test']);
|
|
469
474
|
|
|
470
|
-
expect(mockWatchService.watch).toHaveBeenCalledWith(
|
|
475
|
+
expect(mockWatchService.watch).toHaveBeenCalledWith(
|
|
476
|
+
mockStore,
|
|
477
|
+
2500,
|
|
478
|
+
expect.any(Function),
|
|
479
|
+
expect.any(Function)
|
|
480
|
+
);
|
|
471
481
|
});
|
|
472
482
|
|
|
473
483
|
it('outputs watching message in normal mode', async () => {
|
|
@@ -550,8 +560,8 @@ describe('createIndexCommand - Execution Tests', () => {
|
|
|
550
560
|
|
|
551
561
|
let capturedCallback: (() => void) | undefined;
|
|
552
562
|
const mockWatchService = {
|
|
553
|
-
watch: vi.fn((store, debounce,
|
|
554
|
-
capturedCallback =
|
|
563
|
+
watch: vi.fn((store, debounce, onReindex, onError) => {
|
|
564
|
+
capturedCallback = onReindex;
|
|
555
565
|
}),
|
|
556
566
|
unwatchAll: vi.fn(),
|
|
557
567
|
};
|
|
@@ -100,11 +100,18 @@ export function createIndexCommand(getOptions: () => GlobalOptions): Command {
|
|
|
100
100
|
if (globalOpts.quiet !== true) {
|
|
101
101
|
console.log(`Watching ${store.name} for changes...`);
|
|
102
102
|
}
|
|
103
|
-
await watchService.watch(
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
await watchService.watch(
|
|
104
|
+
store,
|
|
105
|
+
parseInt(options.debounce ?? '1000', 10),
|
|
106
|
+
() => {
|
|
107
|
+
if (globalOpts.quiet !== true) {
|
|
108
|
+
console.log(`Re-indexed ${store.name}`);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
(error: Error) => {
|
|
112
|
+
console.error(`Watch error: ${error.message}`);
|
|
106
113
|
}
|
|
107
|
-
|
|
114
|
+
);
|
|
108
115
|
|
|
109
116
|
// Keep process alive
|
|
110
117
|
process.on('SIGINT', () => {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach, type MockInstance } from 'vitest';
|
|
2
2
|
import { createStoreCommand } from './store.js';
|
|
3
|
-
import { createServices } from '../../services/index.js';
|
|
3
|
+
import { createServices, destroyServices } from '../../services/index.js';
|
|
4
4
|
import type { GlobalOptions } from '../program.js';
|
|
5
5
|
import type { FileStore, RepoStore, WebStore } from '../../types/store.js';
|
|
6
6
|
import { createStoreId } from '../../types/brands.js';
|
|
@@ -10,11 +10,22 @@ vi.mock('../../services/index.js', () => ({
|
|
|
10
10
|
destroyServices: vi.fn().mockResolvedValue(undefined),
|
|
11
11
|
}));
|
|
12
12
|
|
|
13
|
+
interface MockStoreService {
|
|
14
|
+
list: MockInstance;
|
|
15
|
+
getByIdOrName: MockInstance;
|
|
16
|
+
create: MockInstance;
|
|
17
|
+
delete: MockInstance;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface MockServices {
|
|
21
|
+
store: MockStoreService;
|
|
22
|
+
}
|
|
23
|
+
|
|
13
24
|
describe('store command execution', () => {
|
|
14
|
-
let mockServices:
|
|
15
|
-
let consoleLogSpy:
|
|
16
|
-
let consoleErrorSpy:
|
|
17
|
-
let processExitSpy:
|
|
25
|
+
let mockServices: MockServices;
|
|
26
|
+
let consoleLogSpy: MockInstance;
|
|
27
|
+
let consoleErrorSpy: MockInstance;
|
|
28
|
+
let processExitSpy: MockInstance;
|
|
18
29
|
let getOptions: () => GlobalOptions;
|
|
19
30
|
|
|
20
31
|
beforeEach(() => {
|
|
@@ -402,10 +413,12 @@ describe('store command execution', () => {
|
|
|
402
413
|
const actionHandler = createCommand?._actionHandler;
|
|
403
414
|
|
|
404
415
|
createCommand.parseOptions(['--type', 'file', '--source', '/path/to/files']);
|
|
405
|
-
await
|
|
416
|
+
await actionHandler!(['duplicate-store']);
|
|
406
417
|
|
|
407
418
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store already exists');
|
|
408
|
-
expect(
|
|
419
|
+
expect(process.exitCode).toBe(1);
|
|
420
|
+
// Reset for other tests
|
|
421
|
+
process.exitCode = undefined;
|
|
409
422
|
});
|
|
410
423
|
});
|
|
411
424
|
|
|
@@ -536,10 +549,12 @@ describe('store command execution', () => {
|
|
|
536
549
|
const infoCommand = command.commands.find((c) => c.name() === 'info');
|
|
537
550
|
const actionHandler = infoCommand?._actionHandler;
|
|
538
551
|
|
|
539
|
-
await
|
|
552
|
+
await actionHandler!(['nonexistent']);
|
|
540
553
|
|
|
541
554
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
|
|
542
|
-
expect(
|
|
555
|
+
expect(process.exitCode).toBe(3);
|
|
556
|
+
// Reset for other tests
|
|
557
|
+
process.exitCode = undefined;
|
|
543
558
|
});
|
|
544
559
|
|
|
545
560
|
it('can lookup store by ID', async () => {
|
|
@@ -629,11 +644,13 @@ describe('store command execution', () => {
|
|
|
629
644
|
const actionHandler = deleteCommand?._actionHandler;
|
|
630
645
|
|
|
631
646
|
deleteCommand.parseOptions(['--force']);
|
|
632
|
-
await
|
|
647
|
+
await actionHandler!(['nonexistent']);
|
|
633
648
|
|
|
634
649
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store not found: nonexistent');
|
|
635
|
-
expect(
|
|
650
|
+
expect(process.exitCode).toBe(3);
|
|
636
651
|
expect(mockServices.store.delete).not.toHaveBeenCalled();
|
|
652
|
+
// Reset for other tests
|
|
653
|
+
process.exitCode = undefined;
|
|
637
654
|
});
|
|
638
655
|
|
|
639
656
|
it('exits with code 1 when deletion fails', async () => {
|
|
@@ -659,10 +676,12 @@ describe('store command execution', () => {
|
|
|
659
676
|
const actionHandler = deleteCommand?._actionHandler;
|
|
660
677
|
|
|
661
678
|
deleteCommand.parseOptions(['--force']);
|
|
662
|
-
await
|
|
679
|
+
await actionHandler!(['locked-store']);
|
|
663
680
|
|
|
664
681
|
expect(consoleErrorSpy).toHaveBeenCalledWith('Error: Store is locked');
|
|
665
|
-
expect(
|
|
682
|
+
expect(process.exitCode).toBe(1);
|
|
683
|
+
// Reset for other tests
|
|
684
|
+
process.exitCode = undefined;
|
|
666
685
|
});
|
|
667
686
|
|
|
668
687
|
it('exits with code 1 when force not provided in non-TTY mode', async () => {
|
|
@@ -688,12 +707,12 @@ describe('store command execution', () => {
|
|
|
688
707
|
const deleteCommand = command.commands.find((c) => c.name() === 'delete');
|
|
689
708
|
const actionHandler = deleteCommand?._actionHandler;
|
|
690
709
|
|
|
691
|
-
await
|
|
710
|
+
await actionHandler!(['my-store']);
|
|
692
711
|
|
|
693
712
|
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
694
713
|
'Error: Use --force or -y to delete without confirmation in non-interactive mode'
|
|
695
714
|
);
|
|
696
|
-
expect(
|
|
715
|
+
expect(process.exitCode).toBe(1);
|
|
697
716
|
expect(mockServices.store.delete).not.toHaveBeenCalled();
|
|
698
717
|
|
|
699
718
|
// Restore original value
|
|
@@ -701,6 +720,8 @@ describe('store command execution', () => {
|
|
|
701
720
|
value: originalIsTTY,
|
|
702
721
|
configurable: true,
|
|
703
722
|
});
|
|
723
|
+
// Reset for other tests
|
|
724
|
+
process.exitCode = undefined;
|
|
704
725
|
});
|
|
705
726
|
|
|
706
727
|
it('prompts for confirmation in TTY mode when user confirms', async () => {
|
|
@@ -788,10 +809,11 @@ describe('store command execution', () => {
|
|
|
788
809
|
const deleteCommand = command.commands.find((c) => c.name() === 'delete');
|
|
789
810
|
const actionHandler = deleteCommand?._actionHandler;
|
|
790
811
|
|
|
791
|
-
await
|
|
812
|
+
await actionHandler!(['my-store']);
|
|
792
813
|
|
|
793
814
|
expect(consoleLogSpy).toHaveBeenCalledWith('Cancelled.');
|
|
794
|
-
|
|
815
|
+
// exitCode stays 0 (undefined) for user-initiated cancellation - not an error
|
|
816
|
+
expect(process.exitCode).toBeUndefined();
|
|
795
817
|
expect(mockServices.store.delete).not.toHaveBeenCalled();
|
|
796
818
|
|
|
797
819
|
// Restore original value
|
|
@@ -1096,4 +1118,175 @@ describe('store command execution', () => {
|
|
|
1096
1118
|
});
|
|
1097
1119
|
});
|
|
1098
1120
|
});
|
|
1121
|
+
|
|
1122
|
+
describe('destroyServices cleanup on error paths', () => {
|
|
1123
|
+
it('calls destroyServices when store info returns store not found', async () => {
|
|
1124
|
+
mockServices.store.getByIdOrName.mockResolvedValue(undefined);
|
|
1125
|
+
|
|
1126
|
+
const command = createStoreCommand(getOptions);
|
|
1127
|
+
const infoCommand = command.commands.find((c) => c.name() === 'info');
|
|
1128
|
+
const actionHandler = infoCommand?._actionHandler;
|
|
1129
|
+
|
|
1130
|
+
await actionHandler!(['nonexistent']);
|
|
1131
|
+
|
|
1132
|
+
// destroyServices should be called before setting exitCode
|
|
1133
|
+
expect(destroyServices).toHaveBeenCalledWith(mockServices);
|
|
1134
|
+
expect(process.exitCode).toBe(3);
|
|
1135
|
+
process.exitCode = undefined;
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
it('calls destroyServices when store delete returns store not found', async () => {
|
|
1139
|
+
mockServices.store.getByIdOrName.mockResolvedValue(undefined);
|
|
1140
|
+
|
|
1141
|
+
const command = createStoreCommand(getOptions);
|
|
1142
|
+
const deleteCommand = command.commands.find((c) => c.name() === 'delete');
|
|
1143
|
+
const actionHandler = deleteCommand?._actionHandler;
|
|
1144
|
+
|
|
1145
|
+
deleteCommand.parseOptions(['--force']);
|
|
1146
|
+
await actionHandler!(['nonexistent']);
|
|
1147
|
+
|
|
1148
|
+
// destroyServices should be called before setting exitCode
|
|
1149
|
+
expect(destroyServices).toHaveBeenCalledWith(mockServices);
|
|
1150
|
+
expect(process.exitCode).toBe(3);
|
|
1151
|
+
process.exitCode = undefined;
|
|
1152
|
+
});
|
|
1153
|
+
|
|
1154
|
+
it('calls destroyServices when delete requires --force in non-TTY mode', async () => {
|
|
1155
|
+
const mockStore: FileStore = {
|
|
1156
|
+
id: createStoreId('store-1'),
|
|
1157
|
+
name: 'my-store',
|
|
1158
|
+
type: 'file',
|
|
1159
|
+
path: '/path/to/files',
|
|
1160
|
+
createdAt: new Date(),
|
|
1161
|
+
updatedAt: new Date(),
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
mockServices.store.getByIdOrName.mockResolvedValue(mockStore);
|
|
1165
|
+
|
|
1166
|
+
// Mock non-TTY mode
|
|
1167
|
+
const originalIsTTY = process.stdin.isTTY;
|
|
1168
|
+
Object.defineProperty(process.stdin, 'isTTY', {
|
|
1169
|
+
value: false,
|
|
1170
|
+
configurable: true,
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
const command = createStoreCommand(getOptions);
|
|
1174
|
+
const deleteCommand = command.commands.find((c) => c.name() === 'delete');
|
|
1175
|
+
const actionHandler = deleteCommand?._actionHandler;
|
|
1176
|
+
|
|
1177
|
+
await actionHandler!(['my-store']);
|
|
1178
|
+
|
|
1179
|
+
// destroyServices should be called before setting exitCode
|
|
1180
|
+
expect(destroyServices).toHaveBeenCalledWith(mockServices);
|
|
1181
|
+
expect(process.exitCode).toBe(1);
|
|
1182
|
+
|
|
1183
|
+
// Restore
|
|
1184
|
+
Object.defineProperty(process.stdin, 'isTTY', {
|
|
1185
|
+
value: originalIsTTY,
|
|
1186
|
+
configurable: true,
|
|
1187
|
+
});
|
|
1188
|
+
process.exitCode = undefined;
|
|
1189
|
+
});
|
|
1190
|
+
|
|
1191
|
+
it('calls destroyServices when deletion fails', async () => {
|
|
1192
|
+
const mockStore: FileStore = {
|
|
1193
|
+
id: createStoreId('store-1'),
|
|
1194
|
+
name: 'locked-store',
|
|
1195
|
+
type: 'file',
|
|
1196
|
+
path: '/path/to/files',
|
|
1197
|
+
createdAt: new Date(),
|
|
1198
|
+
updatedAt: new Date(),
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
mockServices.store.getByIdOrName.mockResolvedValue(mockStore);
|
|
1202
|
+
mockServices.store.delete.mockResolvedValue({
|
|
1203
|
+
success: false,
|
|
1204
|
+
error: {
|
|
1205
|
+
message: 'Store is locked',
|
|
1206
|
+
},
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
const command = createStoreCommand(getOptions);
|
|
1210
|
+
const deleteCommand = command.commands.find((c) => c.name() === 'delete');
|
|
1211
|
+
const actionHandler = deleteCommand?._actionHandler;
|
|
1212
|
+
|
|
1213
|
+
deleteCommand.parseOptions(['--force']);
|
|
1214
|
+
await actionHandler!(['locked-store']);
|
|
1215
|
+
|
|
1216
|
+
// destroyServices should be called before setting exitCode
|
|
1217
|
+
expect(destroyServices).toHaveBeenCalledWith(mockServices);
|
|
1218
|
+
expect(process.exitCode).toBe(1);
|
|
1219
|
+
process.exitCode = undefined;
|
|
1220
|
+
});
|
|
1221
|
+
|
|
1222
|
+
it('calls destroyServices when user cancels delete in TTY mode', async () => {
|
|
1223
|
+
const mockStore: FileStore = {
|
|
1224
|
+
id: createStoreId('store-1'),
|
|
1225
|
+
name: 'my-store',
|
|
1226
|
+
type: 'file',
|
|
1227
|
+
path: '/path/to/files',
|
|
1228
|
+
createdAt: new Date(),
|
|
1229
|
+
updatedAt: new Date(),
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
mockServices.store.getByIdOrName.mockResolvedValue(mockStore);
|
|
1233
|
+
|
|
1234
|
+
// Mock TTY mode
|
|
1235
|
+
const originalIsTTY = process.stdin.isTTY;
|
|
1236
|
+
Object.defineProperty(process.stdin, 'isTTY', {
|
|
1237
|
+
value: true,
|
|
1238
|
+
configurable: true,
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
// Mock readline to simulate user typing 'n'
|
|
1242
|
+
const mockReadline = {
|
|
1243
|
+
question: vi.fn((prompt: string, callback: (answer: string) => void) => {
|
|
1244
|
+
callback('n');
|
|
1245
|
+
}),
|
|
1246
|
+
close: vi.fn(),
|
|
1247
|
+
};
|
|
1248
|
+
|
|
1249
|
+
vi.doMock('node:readline', () => ({
|
|
1250
|
+
createInterface: vi.fn(() => mockReadline),
|
|
1251
|
+
}));
|
|
1252
|
+
|
|
1253
|
+
const command = createStoreCommand(getOptions);
|
|
1254
|
+
const deleteCommand = command.commands.find((c) => c.name() === 'delete');
|
|
1255
|
+
const actionHandler = deleteCommand?._actionHandler;
|
|
1256
|
+
|
|
1257
|
+
await actionHandler!(['my-store']);
|
|
1258
|
+
|
|
1259
|
+
// destroyServices should be called even for user cancellation
|
|
1260
|
+
expect(destroyServices).toHaveBeenCalledWith(mockServices);
|
|
1261
|
+
// exitCode stays undefined (0) for user-initiated cancellation
|
|
1262
|
+
expect(process.exitCode).toBeUndefined();
|
|
1263
|
+
|
|
1264
|
+
// Restore
|
|
1265
|
+
Object.defineProperty(process.stdin, 'isTTY', {
|
|
1266
|
+
value: originalIsTTY,
|
|
1267
|
+
configurable: true,
|
|
1268
|
+
});
|
|
1269
|
+
});
|
|
1270
|
+
|
|
1271
|
+
it('calls destroyServices when create fails', async () => {
|
|
1272
|
+
mockServices.store.create.mockResolvedValue({
|
|
1273
|
+
success: false,
|
|
1274
|
+
error: {
|
|
1275
|
+
message: 'Store already exists',
|
|
1276
|
+
},
|
|
1277
|
+
});
|
|
1278
|
+
|
|
1279
|
+
const command = createStoreCommand(getOptions);
|
|
1280
|
+
const createCommand = command.commands.find((c) => c.name() === 'create');
|
|
1281
|
+
const actionHandler = createCommand?._actionHandler;
|
|
1282
|
+
|
|
1283
|
+
createCommand.parseOptions(['--type', 'file', '--source', '/path/to/files']);
|
|
1284
|
+
await actionHandler!(['duplicate-store']);
|
|
1285
|
+
|
|
1286
|
+
// destroyServices should be called before setting exitCode
|
|
1287
|
+
expect(destroyServices).toHaveBeenCalledWith(mockServices);
|
|
1288
|
+
expect(process.exitCode).toBe(1);
|
|
1289
|
+
process.exitCode = undefined;
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1099
1292
|
});
|
|
@@ -97,7 +97,9 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
|
|
|
97
97
|
await destroyServices(services);
|
|
98
98
|
}
|
|
99
99
|
if (exitCode !== 0) {
|
|
100
|
-
|
|
100
|
+
// Set exit code and let Node.js exit naturally after event loop drains
|
|
101
|
+
// Using process.exit() causes mutex crashes from native code (LanceDB, tree-sitter)
|
|
102
|
+
process.exitCode = exitCode;
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
);
|
|
@@ -108,12 +110,14 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
|
|
|
108
110
|
.action(async (storeIdOrName: string) => {
|
|
109
111
|
const globalOpts = getOptions();
|
|
110
112
|
const services = await createServices(globalOpts.config, globalOpts.dataDir);
|
|
111
|
-
|
|
113
|
+
let exitCode = 0;
|
|
114
|
+
storeInfo: try {
|
|
112
115
|
const s = await services.store.getByIdOrName(storeIdOrName);
|
|
113
116
|
|
|
114
117
|
if (s === undefined) {
|
|
115
118
|
console.error(`Error: Store not found: ${storeIdOrName}`);
|
|
116
|
-
|
|
119
|
+
exitCode = 3;
|
|
120
|
+
break storeInfo;
|
|
117
121
|
}
|
|
118
122
|
|
|
119
123
|
if (globalOpts.format === 'json') {
|
|
@@ -132,6 +136,11 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
|
|
|
132
136
|
} finally {
|
|
133
137
|
await destroyServices(services);
|
|
134
138
|
}
|
|
139
|
+
if (exitCode !== 0) {
|
|
140
|
+
// Set exit code and let Node.js exit naturally after event loop drains
|
|
141
|
+
// Using process.exit() causes mutex crashes from native code (LanceDB, tree-sitter)
|
|
142
|
+
process.exitCode = exitCode;
|
|
143
|
+
}
|
|
135
144
|
});
|
|
136
145
|
|
|
137
146
|
store
|
|
@@ -142,12 +151,14 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
|
|
|
142
151
|
.action(async (storeIdOrName: string, options: { force?: boolean; yes?: boolean }) => {
|
|
143
152
|
const globalOpts = getOptions();
|
|
144
153
|
const services = await createServices(globalOpts.config, globalOpts.dataDir);
|
|
145
|
-
|
|
154
|
+
let exitCode = 0;
|
|
155
|
+
storeDelete: try {
|
|
146
156
|
const s = await services.store.getByIdOrName(storeIdOrName);
|
|
147
157
|
|
|
148
158
|
if (s === undefined) {
|
|
149
159
|
console.error(`Error: Store not found: ${storeIdOrName}`);
|
|
150
|
-
|
|
160
|
+
exitCode = 3;
|
|
161
|
+
break storeDelete;
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
// Require --force or -y in non-TTY mode, prompt in TTY mode
|
|
@@ -157,7 +168,8 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
|
|
|
157
168
|
console.error(
|
|
158
169
|
'Error: Use --force or -y to delete without confirmation in non-interactive mode'
|
|
159
170
|
);
|
|
160
|
-
|
|
171
|
+
exitCode = 1;
|
|
172
|
+
break storeDelete;
|
|
161
173
|
}
|
|
162
174
|
// Interactive confirmation
|
|
163
175
|
const readline = await import('node:readline');
|
|
@@ -171,7 +183,8 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
|
|
|
171
183
|
rl.close();
|
|
172
184
|
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
173
185
|
console.log('Cancelled.');
|
|
174
|
-
|
|
186
|
+
// exitCode stays 0 for user-initiated cancellation
|
|
187
|
+
break storeDelete;
|
|
175
188
|
}
|
|
176
189
|
}
|
|
177
190
|
|
|
@@ -181,11 +194,16 @@ export function createStoreCommand(getOptions: () => GlobalOptions): Command {
|
|
|
181
194
|
console.log(`Deleted store: ${s.name}`);
|
|
182
195
|
} else {
|
|
183
196
|
console.error(`Error: ${result.error.message}`);
|
|
184
|
-
|
|
197
|
+
exitCode = 1;
|
|
185
198
|
}
|
|
186
199
|
} finally {
|
|
187
200
|
await destroyServices(services);
|
|
188
201
|
}
|
|
202
|
+
if (exitCode !== 0) {
|
|
203
|
+
// Set exit code and let Node.js exit naturally after event loop drains
|
|
204
|
+
// Using process.exit() causes mutex crashes from native code (LanceDB, tree-sitter)
|
|
205
|
+
process.exitCode = exitCode;
|
|
206
|
+
}
|
|
189
207
|
});
|
|
190
208
|
|
|
191
209
|
return store;
|