@yeseh/cortex-cli 0.6.7 → 0.6.9
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/dist/program.js +1538 -5
- package/dist/program.js.map +32 -3
- package/dist/run.d.ts +0 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -4
- package/dist/run.js.map +3 -3
- package/package.json +4 -6
- package/dist/chunk-tgrm2cc9.js +0 -1543
- package/dist/chunk-tgrm2cc9.js.map +0 -38
- package/src/category/commands/create.spec.ts +0 -139
- package/src/category/commands/create.ts +0 -119
- package/src/category/index.ts +0 -24
- package/src/commands/init.spec.ts +0 -203
- package/src/commands/init.ts +0 -301
- package/src/context.spec.ts +0 -60
- package/src/context.ts +0 -170
- package/src/errors.spec.ts +0 -264
- package/src/errors.ts +0 -105
- package/src/memory/commands/add.spec.ts +0 -169
- package/src/memory/commands/add.ts +0 -158
- package/src/memory/commands/definitions.spec.ts +0 -80
- package/src/memory/commands/list.spec.ts +0 -123
- package/src/memory/commands/list.ts +0 -269
- package/src/memory/commands/move.spec.ts +0 -85
- package/src/memory/commands/move.ts +0 -119
- package/src/memory/commands/remove.spec.ts +0 -79
- package/src/memory/commands/remove.ts +0 -108
- package/src/memory/commands/show.spec.ts +0 -71
- package/src/memory/commands/show.ts +0 -165
- package/src/memory/commands/test-helpers.spec.ts +0 -127
- package/src/memory/commands/update.spec.ts +0 -86
- package/src/memory/commands/update.ts +0 -230
- package/src/memory/index.spec.ts +0 -59
- package/src/memory/index.ts +0 -44
- package/src/memory/parsing.spec.ts +0 -105
- package/src/memory/parsing.ts +0 -22
- package/src/observability.spec.ts +0 -126
- package/src/observability.ts +0 -82
- package/src/output.spec.ts +0 -835
- package/src/output.ts +0 -119
- package/src/program.spec.ts +0 -46
- package/src/program.ts +0 -75
- package/src/run.spec.ts +0 -31
- package/src/run.ts +0 -9
- package/src/store/commands/add.spec.ts +0 -131
- package/src/store/commands/add.ts +0 -231
- package/src/store/commands/init.spec.ts +0 -220
- package/src/store/commands/init.ts +0 -272
- package/src/store/commands/list.spec.ts +0 -175
- package/src/store/commands/list.ts +0 -102
- package/src/store/commands/prune.spec.ts +0 -120
- package/src/store/commands/prune.ts +0 -152
- package/src/store/commands/reindexs.spec.ts +0 -94
- package/src/store/commands/reindexs.ts +0 -97
- package/src/store/commands/remove.spec.ts +0 -97
- package/src/store/commands/remove.ts +0 -189
- package/src/store/index.spec.ts +0 -60
- package/src/store/index.ts +0 -49
- package/src/store/utils/resolve-store-name.spec.ts +0 -62
- package/src/store/utils/resolve-store-name.ts +0 -79
- package/src/test-helpers.spec.ts +0 -430
- package/src/tests/cli.integration.spec.ts +0 -1306
- package/src/toon.spec.ts +0 -183
- package/src/toon.ts +0 -462
- package/src/utils/git.spec.ts +0 -95
- package/src/utils/git.ts +0 -51
- package/src/utils/input.spec.ts +0 -326
- package/src/utils/input.ts +0 -150
- package/src/utils/paths.spec.ts +0 -235
- package/src/utils/paths.ts +0 -75
- package/src/utils/prompts.spec.ts +0 -23
- package/src/utils/prompts.ts +0 -88
- package/src/utils/resolve-default-store.spec.ts +0 -135
- package/src/utils/resolve-default-store.ts +0 -74
package/src/context.spec.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for create-cli-command.ts — validateStorePath.
|
|
3
|
-
*
|
|
4
|
-
* Verifies that absolute paths pass validation and relative paths
|
|
5
|
-
* produce a typed INVALID_STORE_PATH error result.
|
|
6
|
-
*
|
|
7
|
-
* @module cli/create-cli-command.spec
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, it, expect } from 'bun:test';
|
|
11
|
-
|
|
12
|
-
import { validateStorePath } from './context.ts';
|
|
13
|
-
|
|
14
|
-
describe('validateStorePath', () => {
|
|
15
|
-
it('should return ok for a Unix absolute path', () => {
|
|
16
|
-
const result = validateStorePath('/home/user/.cortex/memory', 'my-store');
|
|
17
|
-
expect(result.ok()).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('should return ok for a Windows-style absolute path', () => {
|
|
21
|
-
// isAbsolute on Linux treats "C:\\..." as relative, but a UNC-style
|
|
22
|
-
// or drive-letter path on Windows would be absolute. We test with a
|
|
23
|
-
// typical Unix absolute path variant to keep cross-platform coverage.
|
|
24
|
-
const result = validateStorePath('/var/cortex/stores/global', 'global');
|
|
25
|
-
expect(result.ok()).toBe(true);
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should return INVALID_STORE_PATH for a relative path', () => {
|
|
29
|
-
const result = validateStorePath('relative/path/to/store', 'my-store');
|
|
30
|
-
expect(result.ok()).toBe(false);
|
|
31
|
-
if (!result.ok()) {
|
|
32
|
-
expect(result.error.code).toBe('INVALID_STORE_PATH');
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should return INVALID_STORE_PATH for a path starting with ./', () => {
|
|
37
|
-
const result = validateStorePath('./local/store', 'my-store');
|
|
38
|
-
expect(result.ok()).toBe(false);
|
|
39
|
-
if (!result.ok()) {
|
|
40
|
-
expect(result.error.code).toBe('INVALID_STORE_PATH');
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('should include the store name in the error message', () => {
|
|
45
|
-
const storeName = 'my-named-store';
|
|
46
|
-
const result = validateStorePath('not/absolute', storeName);
|
|
47
|
-
expect(result.ok()).toBe(false);
|
|
48
|
-
if (!result.ok()) {
|
|
49
|
-
expect(result.error.message).toContain(storeName);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('should return INVALID_STORE_PATH for empty string', () => {
|
|
54
|
-
const result = validateStorePath('', 'some-store');
|
|
55
|
-
expect(result.ok()).toBe(false);
|
|
56
|
-
if (!result.ok()) {
|
|
57
|
-
expect(result.error.code).toBe('INVALID_STORE_PATH');
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
});
|
package/src/context.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Cortex,
|
|
3
|
-
err,
|
|
4
|
-
getDefaultSettings,
|
|
5
|
-
ok,
|
|
6
|
-
type ConfigValidationError,
|
|
7
|
-
type CortexContext,
|
|
8
|
-
type Result,
|
|
9
|
-
} from '@yeseh/cortex-core';
|
|
10
|
-
import { homedir } from 'os';
|
|
11
|
-
import { isAbsolute, resolve } from 'path';
|
|
12
|
-
import { FilesystemStorageAdapter, FilesystemConfigAdapter } from '@yeseh/cortex-storage-fs';
|
|
13
|
-
import { stdin, stdout } from 'process';
|
|
14
|
-
import { createCliLogger } from './observability.ts';
|
|
15
|
-
|
|
16
|
-
// TODO: Much of this module should move to the FS adapter, since it's all about loading config from the filesystem. The CLI command handlers should just call into the core module to load config and create a context, rather than having all the logic here.
|
|
17
|
-
|
|
18
|
-
const makeAbsolute = (pathStr: string): string => {
|
|
19
|
-
if (pathStr.startsWith('~')) {
|
|
20
|
-
return resolve(homedir(), pathStr.slice(1).replace(/^[/\\]/, ''));
|
|
21
|
-
}
|
|
22
|
-
return isAbsolute(pathStr) ? pathStr : resolve(pathStr);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const validateStorePath = (
|
|
26
|
-
storePath: string,
|
|
27
|
-
storeName: string
|
|
28
|
-
): Result<void, ConfigValidationError> => {
|
|
29
|
-
if (!isAbsolute(storePath)) {
|
|
30
|
-
return err({
|
|
31
|
-
code: 'INVALID_STORE_PATH',
|
|
32
|
-
message:
|
|
33
|
-
`Store '${storeName}' path must be absolute. Got: ${storePath}. ` +
|
|
34
|
-
"Use an absolute path like '/home/user/.cortex/memory'.",
|
|
35
|
-
store: storeName,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return ok(undefined);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export interface ConfigLoadOptions {
|
|
42
|
-
cwd?: string;
|
|
43
|
-
globalConfigPath?: string;
|
|
44
|
-
localConfigPath?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface CliContextOptions {
|
|
48
|
-
configDir?: string;
|
|
49
|
-
configCwd?: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface CliConfigContext {
|
|
53
|
-
configAdapter: FilesystemConfigAdapter;
|
|
54
|
-
stores: Record<string, any>;
|
|
55
|
-
settings: ReturnType<typeof getDefaultSettings>;
|
|
56
|
-
effectiveCwd: string;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export const createCliConfigAdapter = (configPath: string): FilesystemConfigAdapter => {
|
|
60
|
-
return new FilesystemConfigAdapter(configPath);
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
export const createCliAdapterFactory = (configAdapter: FilesystemConfigAdapter) => {
|
|
64
|
-
return (storeName: string) => {
|
|
65
|
-
const stores = configAdapter.stores!;
|
|
66
|
-
const storeEntry = stores[storeName];
|
|
67
|
-
if (!storeEntry) {
|
|
68
|
-
const available = Object.keys(stores).join(', ') || 'none';
|
|
69
|
-
throw new Error(
|
|
70
|
-
`Store '${storeName}' is not registered. Available stores: ${available}. ` +
|
|
71
|
-
'Use --store to specify a registered store, or run `cortex store init` to create one.'
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const storePath = storeEntry.properties?.path as string | undefined;
|
|
76
|
-
if (!storePath) {
|
|
77
|
-
throw new Error(
|
|
78
|
-
`Store '${storeName}' has no path configured. ` +
|
|
79
|
-
'Check your cortex config or re-run `cortex store init`.'
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return new FilesystemStorageAdapter(configAdapter, {
|
|
84
|
-
rootDirectory: storePath,
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export const createCliConfigContext = async (
|
|
90
|
-
options: CliContextOptions = {}
|
|
91
|
-
): Promise<Result<CliConfigContext, any>> => {
|
|
92
|
-
const envConfigPath = process.env.CORTEX_CONFIG;
|
|
93
|
-
const envConfigDir = process.env.CORTEX_CONFIG_DIR;
|
|
94
|
-
const envConfigCwd = process.env.CORTEX_CONFIG_CWD;
|
|
95
|
-
|
|
96
|
-
const explicitConfigPath =
|
|
97
|
-
typeof envConfigPath === 'string' && envConfigPath.length > 0
|
|
98
|
-
? makeAbsolute(envConfigPath)
|
|
99
|
-
: undefined;
|
|
100
|
-
|
|
101
|
-
const dir = options.configDir ?? envConfigDir ?? resolve(homedir(), '.config', 'cortex');
|
|
102
|
-
|
|
103
|
-
const absoluteDir = makeAbsolute(dir);
|
|
104
|
-
const configPath = explicitConfigPath ?? resolve(absoluteDir, 'config.yaml');
|
|
105
|
-
|
|
106
|
-
const effectiveCwd =
|
|
107
|
-
options.configCwd ??
|
|
108
|
-
(typeof envConfigCwd === 'string' && envConfigCwd.length > 0
|
|
109
|
-
? envConfigCwd
|
|
110
|
-
: process.cwd());
|
|
111
|
-
|
|
112
|
-
const configAdapter = createCliConfigAdapter(configPath);
|
|
113
|
-
const initResult = await configAdapter.initializeConfig();
|
|
114
|
-
if (!initResult.ok()) {
|
|
115
|
-
return initResult;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return ok({
|
|
119
|
-
configAdapter,
|
|
120
|
-
settings: configAdapter.settings!,
|
|
121
|
-
stores: configAdapter.stores!,
|
|
122
|
-
effectiveCwd,
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/* Creates a CortexContext from the CLI environment, including loading configuration and setting up dependencies.
|
|
127
|
-
* This function is used to create a context object that can be injected into command handlers for consistent access to the Cortex client and other utilities.
|
|
128
|
-
*/
|
|
129
|
-
export const createCliCommandContext = async (
|
|
130
|
-
configDir?: string
|
|
131
|
-
): Promise<Result<CortexContext, any>> => {
|
|
132
|
-
try {
|
|
133
|
-
const configContextResult = await createCliConfigContext({
|
|
134
|
-
configDir,
|
|
135
|
-
});
|
|
136
|
-
if (!configContextResult.ok()) {
|
|
137
|
-
return configContextResult;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const { configAdapter, settings, stores } = configContextResult.value;
|
|
141
|
-
const adapterFactory = createCliAdapterFactory(configAdapter);
|
|
142
|
-
|
|
143
|
-
const now = () => new Date();
|
|
144
|
-
const cortex = Cortex.init({
|
|
145
|
-
settings,
|
|
146
|
-
stores,
|
|
147
|
-
adapterFactory,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const logger = createCliLogger();
|
|
151
|
-
|
|
152
|
-
const context: CortexContext = {
|
|
153
|
-
config: configAdapter,
|
|
154
|
-
settings: settings ?? getDefaultSettings(),
|
|
155
|
-
stores: stores ?? {},
|
|
156
|
-
cortex,
|
|
157
|
-
now,
|
|
158
|
-
stdin,
|
|
159
|
-
stdout,
|
|
160
|
-
logger,
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
return ok(context);
|
|
164
|
-
} catch (error) {
|
|
165
|
-
return err({
|
|
166
|
-
code: 'CONTEXT_CREATION_FAILED',
|
|
167
|
-
message: `Unexpected error creating CLI command context: ${error instanceof Error ? error.message : String(error)}`,
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
};
|
package/src/errors.spec.ts
DELETED
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'bun:test';
|
|
2
|
-
import { CommanderError, InvalidArgumentError } from '@commander-js/extra-typings';
|
|
3
|
-
|
|
4
|
-
import { throwCliError, type CoreError } from './errors.ts';
|
|
5
|
-
|
|
6
|
-
describe('throwCliError', () => {
|
|
7
|
-
describe('argument error codes', () => {
|
|
8
|
-
const argumentErrorCodes = [
|
|
9
|
-
'INVALID_PATH',
|
|
10
|
-
'INVALID_FILE_PATH',
|
|
11
|
-
'INVALID_SOURCE_PATH',
|
|
12
|
-
'INVALID_DESTINATION_PATH',
|
|
13
|
-
'INVALID_ARGUMENTS',
|
|
14
|
-
'INVALID_STORE_NAME',
|
|
15
|
-
'INVALID_STORE_PATH',
|
|
16
|
-
'MISSING_CONTENT',
|
|
17
|
-
'MULTIPLE_CONTENT_SOURCES',
|
|
18
|
-
'CONTENT_INPUT_FAILED',
|
|
19
|
-
'INVALID_COMMAND',
|
|
20
|
-
'GIT_REPO_REQUIRED',
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
it.each(argumentErrorCodes)('should throw InvalidArgumentError for %s', (code) => {
|
|
24
|
-
const error: CoreError = {
|
|
25
|
-
code,
|
|
26
|
-
message: `Test message for ${code}`,
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
expect(() => throwCliError(error)).toThrow(InvalidArgumentError);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should preserve error message in InvalidArgumentError', () => {
|
|
33
|
-
const error: CoreError = {
|
|
34
|
-
code: 'INVALID_PATH',
|
|
35
|
-
message: 'Path must not be empty',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
throwCliError(error);
|
|
40
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
41
|
-
}
|
|
42
|
-
catch (e) {
|
|
43
|
-
expect(e).toBeInstanceOf(InvalidArgumentError);
|
|
44
|
-
expect((e as InvalidArgumentError).message).toBe('Path must not be empty');
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('should handle empty message for argument errors', () => {
|
|
49
|
-
const error: CoreError = {
|
|
50
|
-
code: 'MISSING_CONTENT',
|
|
51
|
-
message: '',
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
try {
|
|
55
|
-
throwCliError(error);
|
|
56
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
57
|
-
}
|
|
58
|
-
catch (e) {
|
|
59
|
-
expect(e).toBeInstanceOf(InvalidArgumentError);
|
|
60
|
-
expect((e as InvalidArgumentError).message).toBe('');
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
describe('non-argument error codes', () => {
|
|
66
|
-
const nonArgumentErrorCodes = [
|
|
67
|
-
'MEMORY_NOT_FOUND',
|
|
68
|
-
'STORE_NOT_FOUND',
|
|
69
|
-
'FILE_READ_FAILED',
|
|
70
|
-
'CATEGORY_NOT_FOUND',
|
|
71
|
-
'INTERNAL_ERROR',
|
|
72
|
-
'UNKNOWN_ERROR',
|
|
73
|
-
'SERIALIZE_FAILED',
|
|
74
|
-
'STORE_INIT_FAILED',
|
|
75
|
-
'STORE_REGISTRY_FAILED',
|
|
76
|
-
'STORE_ALREADY_EXISTS',
|
|
77
|
-
'STORAGE_ERROR',
|
|
78
|
-
'IO_READ_ERROR',
|
|
79
|
-
'IO_WRITE_ERROR',
|
|
80
|
-
'PARSE_FAILED',
|
|
81
|
-
'MOVE_FAILED',
|
|
82
|
-
'REMOVE_FAILED',
|
|
83
|
-
'REINDEX_FAILED',
|
|
84
|
-
];
|
|
85
|
-
|
|
86
|
-
it.each(nonArgumentErrorCodes)('should throw CommanderError for %s', (code) => {
|
|
87
|
-
const error: CoreError = {
|
|
88
|
-
code,
|
|
89
|
-
message: `Test message for ${code}`,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
expect(() => throwCliError(error)).toThrow(CommanderError);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should set exitCode to 1 for CommanderError', () => {
|
|
96
|
-
const error: CoreError = {
|
|
97
|
-
code: 'MEMORY_NOT_FOUND',
|
|
98
|
-
message: 'Memory not found at path',
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
throwCliError(error);
|
|
103
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
104
|
-
}
|
|
105
|
-
catch (e) {
|
|
106
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
107
|
-
expect((e as CommanderError).exitCode).toBe(1);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it('should preserve error code in CommanderError', () => {
|
|
112
|
-
const error: CoreError = {
|
|
113
|
-
code: 'STORE_NOT_FOUND',
|
|
114
|
-
message: 'Store not found',
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
throwCliError(error);
|
|
119
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
120
|
-
}
|
|
121
|
-
catch (e) {
|
|
122
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
123
|
-
expect((e as CommanderError).code).toBe('STORE_NOT_FOUND');
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('should preserve error message in CommanderError', () => {
|
|
128
|
-
const error: CoreError = {
|
|
129
|
-
code: 'FILE_READ_FAILED',
|
|
130
|
-
message: 'Could not read file: /path/to/file.txt',
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
try {
|
|
134
|
-
throwCliError(error);
|
|
135
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
136
|
-
}
|
|
137
|
-
catch (e) {
|
|
138
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
139
|
-
expect((e as CommanderError).message).toBe(
|
|
140
|
-
'Could not read file: /path/to/file.txt',
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it('should handle empty message for non-argument errors', () => {
|
|
146
|
-
const error: CoreError = {
|
|
147
|
-
code: 'INTERNAL_ERROR',
|
|
148
|
-
message: '',
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
try {
|
|
152
|
-
throwCliError(error);
|
|
153
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
154
|
-
}
|
|
155
|
-
catch (e) {
|
|
156
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
157
|
-
expect((e as CommanderError).message).toBe('');
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
describe('edge cases', () => {
|
|
163
|
-
it('should handle unknown error codes as non-argument errors', () => {
|
|
164
|
-
const error: CoreError = {
|
|
165
|
-
code: 'COMPLETELY_UNKNOWN_CODE',
|
|
166
|
-
message: 'An unexpected error',
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
throwCliError(error);
|
|
171
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
172
|
-
}
|
|
173
|
-
catch (e) {
|
|
174
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
175
|
-
expect((e as CommanderError).code).toBe('COMPLETELY_UNKNOWN_CODE');
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it('should handle error codes that are similar to argument codes', () => {
|
|
180
|
-
const error: CoreError = {
|
|
181
|
-
code: 'INVALID_PATH_EXTRA', // Similar but not in the set
|
|
182
|
-
message: 'This should be a CommanderError',
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
try {
|
|
186
|
-
throwCliError(error);
|
|
187
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
188
|
-
}
|
|
189
|
-
catch (e) {
|
|
190
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it('should be case-sensitive for error codes', () => {
|
|
195
|
-
const error: CoreError = {
|
|
196
|
-
code: 'invalid_path', // lowercase version
|
|
197
|
-
message: 'This should be a CommanderError due to case sensitivity',
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
throwCliError(error);
|
|
202
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
203
|
-
}
|
|
204
|
-
catch (e) {
|
|
205
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('should handle message with special characters', () => {
|
|
210
|
-
const error: CoreError = {
|
|
211
|
-
code: 'INVALID_PATH',
|
|
212
|
-
message: 'Path contains invalid chars: <>&"\'\n\t',
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
try {
|
|
216
|
-
throwCliError(error);
|
|
217
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
218
|
-
}
|
|
219
|
-
catch (e) {
|
|
220
|
-
expect(e).toBeInstanceOf(InvalidArgumentError);
|
|
221
|
-
expect((e as InvalidArgumentError).message).toBe(
|
|
222
|
-
'Path contains invalid chars: <>&"\'\n\t',
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('should handle very long error messages', () => {
|
|
228
|
-
const longMessage = 'A'.repeat(10000);
|
|
229
|
-
const error: CoreError = {
|
|
230
|
-
code: 'MEMORY_NOT_FOUND',
|
|
231
|
-
message: longMessage,
|
|
232
|
-
};
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
throwCliError(error);
|
|
236
|
-
expect.unreachable('mapCoreError should have thrown');
|
|
237
|
-
}
|
|
238
|
-
catch (e) {
|
|
239
|
-
expect(e).toBeInstanceOf(CommanderError);
|
|
240
|
-
expect((e as CommanderError).message).toBe(longMessage);
|
|
241
|
-
}
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
describe('return type verification', () => {
|
|
246
|
-
it('should never return (always throws)', () => {
|
|
247
|
-
const error: CoreError = {
|
|
248
|
-
code: 'INVALID_PATH',
|
|
249
|
-
message: 'Test',
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
let didReturn = false;
|
|
253
|
-
try {
|
|
254
|
-
throwCliError(error);
|
|
255
|
-
didReturn = true;
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
// Expected
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
expect(didReturn).toBe(false);
|
|
262
|
-
});
|
|
263
|
-
});
|
|
264
|
-
});
|
package/src/errors.ts
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error mapping utilities for CLI commands.
|
|
3
|
-
*
|
|
4
|
-
* This module provides utilities to map core Result errors to Commander.js
|
|
5
|
-
* exceptions. Commander.js uses specific exception types to control error
|
|
6
|
-
* output and exit codes:
|
|
7
|
-
*
|
|
8
|
-
* - `InvalidArgumentError` - For user input validation errors (shows usage help)
|
|
9
|
-
* - `CommanderError` - For other errors (shows error message only)
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* const result = await runAddCommand(options);
|
|
14
|
-
* if (!result.ok()) {
|
|
15
|
-
* mapCoreError(result.error);
|
|
16
|
-
* }
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { InvalidArgumentError, CommanderError } from '@commander-js/extra-typings';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Represents a core error with a code and message.
|
|
24
|
-
*
|
|
25
|
-
* This interface matches the error shape used throughout the CLI commands,
|
|
26
|
-
* allowing consistent error handling across all command implementations.
|
|
27
|
-
*/
|
|
28
|
-
export interface CoreError {
|
|
29
|
-
code: string;
|
|
30
|
-
message: string;
|
|
31
|
-
cause?: unknown;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Error codes that indicate invalid user input or arguments.
|
|
36
|
-
*
|
|
37
|
-
* These errors result in `InvalidArgumentError` which causes Commander.js
|
|
38
|
-
* to display usage help alongside the error message.
|
|
39
|
-
*
|
|
40
|
-
* Includes:
|
|
41
|
-
* - Path validation errors (`INVALID_PATH`)
|
|
42
|
-
* - Argument parsing errors (`INVALID_ARGUMENTS`)
|
|
43
|
-
* - Store configuration errors (`INVALID_STORE_NAME`, `INVALID_STORE_PATH`)
|
|
44
|
-
* - Content input errors (`MISSING_CONTENT`, `MULTIPLE_CONTENT_SOURCES`, `CONTENT_INPUT_FAILED`)
|
|
45
|
-
* - Command syntax errors (`INVALID_COMMAND`)
|
|
46
|
-
*/
|
|
47
|
-
const ARGUMENT_ERROR_CODES = new Set([
|
|
48
|
-
// Path validation
|
|
49
|
-
'INVALID_PATH',
|
|
50
|
-
'INVALID_FILE_PATH',
|
|
51
|
-
'INVALID_SOURCE_PATH',
|
|
52
|
-
'INVALID_DESTINATION_PATH',
|
|
53
|
-
|
|
54
|
-
// Argument parsing
|
|
55
|
-
'INVALID_ARGUMENTS',
|
|
56
|
-
|
|
57
|
-
// Store configuration
|
|
58
|
-
'INVALID_STORE_NAME',
|
|
59
|
-
'INVALID_STORE_PATH',
|
|
60
|
-
|
|
61
|
-
// Content input
|
|
62
|
-
'MISSING_CONTENT',
|
|
63
|
-
'MULTIPLE_CONTENT_SOURCES',
|
|
64
|
-
'CONTENT_INPUT_FAILED',
|
|
65
|
-
|
|
66
|
-
// Command syntax
|
|
67
|
-
'INVALID_COMMAND',
|
|
68
|
-
|
|
69
|
-
// Git detection (user needs to provide --name)
|
|
70
|
-
'GIT_REPO_REQUIRED',
|
|
71
|
-
]);
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Maps a core error to a Commander.js exception and throws it.
|
|
75
|
-
*
|
|
76
|
-
* This function examines the error code to determine the appropriate
|
|
77
|
-
* Commander.js exception type:
|
|
78
|
-
*
|
|
79
|
-
* - **Argument errors** (`InvalidArgumentError`): For errors caused by invalid
|
|
80
|
-
* user input. Commander.js displays usage help for these errors.
|
|
81
|
-
*
|
|
82
|
-
* - **Other errors** (`CommanderError`): For system errors, missing resources,
|
|
83
|
-
* or internal failures. Commander.js displays only the error message.
|
|
84
|
-
*
|
|
85
|
-
* @param error - The core error to map
|
|
86
|
-
* @throws {InvalidArgumentError} When the error code indicates invalid input
|
|
87
|
-
* @throws {CommanderError} For all other error codes
|
|
88
|
-
*
|
|
89
|
-
* @example
|
|
90
|
-
* ```ts
|
|
91
|
-
* // In a command action handler
|
|
92
|
-
* const result = await runShowCommand(options);
|
|
93
|
-
* if (!result.ok()) {
|
|
94
|
-
* mapCoreError(result.error);
|
|
95
|
-
* // Never reaches here - mapCoreError always throws
|
|
96
|
-
* }
|
|
97
|
-
* ```
|
|
98
|
-
*/
|
|
99
|
-
export function throwCliError(error: CoreError): never {
|
|
100
|
-
if (ARGUMENT_ERROR_CODES.has(error.code)) {
|
|
101
|
-
throw new InvalidArgumentError(error.message);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
throw new CommanderError(1, error.code, error.message);
|
|
105
|
-
}
|