@kb-labs/shared 1.1.0
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/.cursorrules +32 -0
- package/.github/workflows/ci.yml +13 -0
- package/.github/workflows/deploy.yml +28 -0
- package/.github/workflows/docker-build.yml +25 -0
- package/.github/workflows/drift-check.yml +10 -0
- package/.github/workflows/profiles-validate.yml +16 -0
- package/.github/workflows/release.yml +8 -0
- package/.kb/devkit/agents/devkit-maintainer/context.globs +15 -0
- package/.kb/devkit/agents/devkit-maintainer/permissions.yml +17 -0
- package/.kb/devkit/agents/devkit-maintainer/prompt.md +28 -0
- package/.kb/devkit/agents/devkit-maintainer/runbook.md +31 -0
- package/.kb/devkit/agents/docs-crafter/prompt.md +24 -0
- package/.kb/devkit/agents/docs-crafter/runbook.md +18 -0
- package/.kb/devkit/agents/release-manager/context.globs +7 -0
- package/.kb/devkit/agents/release-manager/prompt.md +27 -0
- package/.kb/devkit/agents/release-manager/runbook.md +17 -0
- package/.kb/devkit/agents/test-generator/context.globs +7 -0
- package/.kb/devkit/agents/test-generator/prompt.md +27 -0
- package/.kb/devkit/agents/test-generator/runbook.md +18 -0
- package/.vscode/settings.json +23 -0
- package/CHANGELOG.md +33 -0
- package/CONTRIBUTING.md +117 -0
- package/LICENSE +21 -0
- package/README.md +306 -0
- package/docs/DECLARATIVE-FLAGS-AND-ENV.md +622 -0
- package/docs/DOCUMENTATION.md +70 -0
- package/docs/adr/0000-template.md +52 -0
- package/docs/adr/0001-architecture-and-repository-layout.md +31 -0
- package/docs/adr/0002-plugins-and-extensibility.md +44 -0
- package/docs/adr/0003-package-and-module-boundaries.md +35 -0
- package/docs/adr/0004-versioning-and-release-policy.md +36 -0
- package/docs/adr/0005-reactive-loader-pattern.md +179 -0
- package/docs/adr/0006-declarative-flags-and-env-systems.md +376 -0
- package/eslint.config.js +27 -0
- package/kb-labs.config.json +5 -0
- package/package.json +88 -0
- package/package.json.bin +25 -0
- package/package.json.lib +30 -0
- package/packages/shared-cli-ui/CHANGELOG.md +20 -0
- package/packages/shared-cli-ui/README.md +342 -0
- package/packages/shared-cli-ui/docs/ARCHITECTURE.md +105 -0
- package/packages/shared-cli-ui/eslint.config.js +27 -0
- package/packages/shared-cli-ui/package.json +72 -0
- package/packages/shared-cli-ui/src/__tests__/artifacts-display.spec.ts +89 -0
- package/packages/shared-cli-ui/src/__tests__/format.spec.ts +44 -0
- package/packages/shared-cli-ui/src/__tests__/loader-json-mode.test.ts +119 -0
- package/packages/shared-cli-ui/src/artifacts-display.ts +266 -0
- package/packages/shared-cli-ui/src/cli-auto-discovery.ts +120 -0
- package/packages/shared-cli-ui/src/colors.ts +142 -0
- package/packages/shared-cli-ui/src/command-discovery.ts +72 -0
- package/packages/shared-cli-ui/src/command-output.ts +153 -0
- package/packages/shared-cli-ui/src/command-result.ts +267 -0
- package/packages/shared-cli-ui/src/command-runner.ts +310 -0
- package/packages/shared-cli-ui/src/command-suggestions.ts +204 -0
- package/packages/shared-cli-ui/src/debug/components/output.ts +141 -0
- package/packages/shared-cli-ui/src/debug/components/trace.ts +101 -0
- package/packages/shared-cli-ui/src/debug/components/tree.ts +88 -0
- package/packages/shared-cli-ui/src/debug/formatters/ai.ts +17 -0
- package/packages/shared-cli-ui/src/debug/formatters/human.ts +98 -0
- package/packages/shared-cli-ui/src/debug/formatters/timeline.ts +94 -0
- package/packages/shared-cli-ui/src/debug/index.ts +56 -0
- package/packages/shared-cli-ui/src/debug/types.ts +57 -0
- package/packages/shared-cli-ui/src/debug/utilities.ts +203 -0
- package/packages/shared-cli-ui/src/dynamic-command-discovery.ts +131 -0
- package/packages/shared-cli-ui/src/format.ts +412 -0
- package/packages/shared-cli-ui/src/index.ts +34 -0
- package/packages/shared-cli-ui/src/loader.ts +196 -0
- package/packages/shared-cli-ui/src/manifest-parser.ts +151 -0
- package/packages/shared-cli-ui/src/modern-format.ts +271 -0
- package/packages/shared-cli-ui/src/multi-cli-suggestions.ts +159 -0
- package/packages/shared-cli-ui/src/table.ts +134 -0
- package/packages/shared-cli-ui/src/timing-tracker.ts +68 -0
- package/packages/shared-cli-ui/src/utils/context.ts +12 -0
- package/packages/shared-cli-ui/src/utils/env.ts +164 -0
- package/packages/shared-cli-ui/src/utils/flags.ts +269 -0
- package/packages/shared-cli-ui/src/utils/path.ts +8 -0
- package/packages/shared-cli-ui/tsconfig.build.json +15 -0
- package/packages/shared-cli-ui/tsconfig.json +9 -0
- package/packages/shared-cli-ui/tsup.config.ts +11 -0
- package/packages/shared-cli-ui/vitest.config.ts +15 -0
- package/packages/shared-command-kit/CHANGELOG.md +20 -0
- package/packages/shared-command-kit/LICENSE +22 -0
- package/packages/shared-command-kit/README.md +1030 -0
- package/packages/shared-command-kit/docs/HIGH-LEVEL-API.md +89 -0
- package/packages/shared-command-kit/docs/LOW-LEVEL-API.md +105 -0
- package/packages/shared-command-kit/docs/MIGRATION-GUIDE.md +135 -0
- package/packages/shared-command-kit/eslint.config.js +27 -0
- package/packages/shared-command-kit/eslint.config.ts +14 -0
- package/packages/shared-command-kit/package.json +76 -0
- package/packages/shared-command-kit/prettierrc.json +5 -0
- package/packages/shared-command-kit/src/__tests__/define-command.spec.ts +294 -0
- package/packages/shared-command-kit/src/__tests__/define-route.test.ts +285 -0
- package/packages/shared-command-kit/src/__tests__/define-system-command.spec.ts +508 -0
- package/packages/shared-command-kit/src/__tests__/define-webhook.test.ts +156 -0
- package/packages/shared-command-kit/src/__tests__/define-websocket.test.ts +316 -0
- package/packages/shared-command-kit/src/__tests__/errors.spec.ts +45 -0
- package/packages/shared-command-kit/src/__tests__/flags.spec.ts +353 -0
- package/packages/shared-command-kit/src/__tests__/platform-api.test.ts +135 -0
- package/packages/shared-command-kit/src/__tests__/plugin-context-v3.snapshot.spec.ts +240 -0
- package/packages/shared-command-kit/src/__tests__/ws-types.test.ts +359 -0
- package/packages/shared-command-kit/src/analytics/index.ts +6 -0
- package/packages/shared-command-kit/src/analytics/with-analytics.ts +195 -0
- package/packages/shared-command-kit/src/define-action.ts +100 -0
- package/packages/shared-command-kit/src/define-command.ts +113 -0
- package/packages/shared-command-kit/src/define-route.ts +113 -0
- package/packages/shared-command-kit/src/define-system-command.ts +362 -0
- package/packages/shared-command-kit/src/define-webhook.ts +115 -0
- package/packages/shared-command-kit/src/define-websocket.ts +308 -0
- package/packages/shared-command-kit/src/errors/factory.ts +282 -0
- package/packages/shared-command-kit/src/errors/format-validation.ts +144 -0
- package/packages/shared-command-kit/src/errors/format.ts +92 -0
- package/packages/shared-command-kit/src/errors/index.ts +9 -0
- package/packages/shared-command-kit/src/errors/types.ts +32 -0
- package/packages/shared-command-kit/src/flags/define.ts +92 -0
- package/packages/shared-command-kit/src/flags/index.ts +9 -0
- package/packages/shared-command-kit/src/flags/types.ts +153 -0
- package/packages/shared-command-kit/src/flags/validate.ts +358 -0
- package/packages/shared-command-kit/src/helpers/context.ts +8 -0
- package/packages/shared-command-kit/src/helpers/flags.ts +84 -0
- package/packages/shared-command-kit/src/helpers/index.ts +42 -0
- package/packages/shared-command-kit/src/helpers/patterns.ts +464 -0
- package/packages/shared-command-kit/src/helpers/platform.ts +335 -0
- package/packages/shared-command-kit/src/helpers/use-analytics.ts +95 -0
- package/packages/shared-command-kit/src/helpers/use-cache.ts +97 -0
- package/packages/shared-command-kit/src/helpers/use-config.ts +99 -0
- package/packages/shared-command-kit/src/helpers/use-embeddings.ts +49 -0
- package/packages/shared-command-kit/src/helpers/use-llm.ts +316 -0
- package/packages/shared-command-kit/src/helpers/use-logger.ts +77 -0
- package/packages/shared-command-kit/src/helpers/use-platform.ts +111 -0
- package/packages/shared-command-kit/src/helpers/use-resource-broker.ts +106 -0
- package/packages/shared-command-kit/src/helpers/use-storage.ts +71 -0
- package/packages/shared-command-kit/src/helpers/use-vector-store.ts +49 -0
- package/packages/shared-command-kit/src/helpers/validation.ts +398 -0
- package/packages/shared-command-kit/src/index.ts +410 -0
- package/packages/shared-command-kit/src/jobs.ts +132 -0
- package/packages/shared-command-kit/src/lifecycle/define-handlers.ts +366 -0
- package/packages/shared-command-kit/src/lifecycle/index.ts +6 -0
- package/packages/shared-command-kit/src/manifest.ts +127 -0
- package/packages/shared-command-kit/src/rest/define-handler.ts +187 -0
- package/packages/shared-command-kit/src/rest/index.ts +11 -0
- package/packages/shared-command-kit/src/studio/index.ts +12 -0
- package/packages/shared-command-kit/src/validation/index.ts +6 -0
- package/packages/shared-command-kit/src/validation/schema-builders.ts +409 -0
- package/packages/shared-command-kit/src/ws-types.ts +106 -0
- package/packages/shared-command-kit/tsconfig.build.json +15 -0
- package/packages/shared-command-kit/tsconfig.json +9 -0
- package/packages/shared-command-kit/tsup.config.ts +30 -0
- package/packages/shared-command-kit/vitest.config.ts +4 -0
- package/packages/shared-http/package.json +67 -0
- package/packages/shared-http/src/__tests__/log-correlation.test.ts +81 -0
- package/packages/shared-http/src/__tests__/operation-metrics-tracker.test.ts +55 -0
- package/packages/shared-http/src/http-observability-collector.ts +363 -0
- package/packages/shared-http/src/index.ts +36 -0
- package/packages/shared-http/src/log-correlation.ts +89 -0
- package/packages/shared-http/src/operation-metrics-tracker.ts +107 -0
- package/packages/shared-http/src/register-openapi.ts +108 -0
- package/packages/shared-http/src/resolve-schema-ref.ts +75 -0
- package/packages/shared-http/src/schemas.ts +29 -0
- package/packages/shared-http/src/service-observability.ts +63 -0
- package/packages/shared-http/tsconfig.build.json +15 -0
- package/packages/shared-http/tsconfig.json +9 -0
- package/packages/shared-http/tsup.config.ts +23 -0
- package/packages/shared-http/vitest.config.ts +13 -0
- package/packages/shared-perm-presets/CHANGELOG.md +20 -0
- package/packages/shared-perm-presets/README.md +78 -0
- package/packages/shared-perm-presets/eslint.config.js +27 -0
- package/packages/shared-perm-presets/package.json +45 -0
- package/packages/shared-perm-presets/src/__tests__/combine.test.ts +403 -0
- package/packages/shared-perm-presets/src/__tests__/presets.test.ts +205 -0
- package/packages/shared-perm-presets/src/combine.ts +278 -0
- package/packages/shared-perm-presets/src/index.ts +18 -0
- package/packages/shared-perm-presets/src/presets/ci-environment.ts +34 -0
- package/packages/shared-perm-presets/src/presets/full-env.ts +16 -0
- package/packages/shared-perm-presets/src/presets/git-workflow.ts +40 -0
- package/packages/shared-perm-presets/src/presets/index.ts +8 -0
- package/packages/shared-perm-presets/src/presets/kb-platform.ts +30 -0
- package/packages/shared-perm-presets/src/presets/llm-access.ts +29 -0
- package/packages/shared-perm-presets/src/presets/minimal.ts +21 -0
- package/packages/shared-perm-presets/src/presets/npm-publish.ts +48 -0
- package/packages/shared-perm-presets/src/presets/vector-store.ts +40 -0
- package/packages/shared-perm-presets/src/types.ts +192 -0
- package/packages/shared-perm-presets/tsconfig.build.json +15 -0
- package/packages/shared-perm-presets/tsconfig.json +9 -0
- package/packages/shared-perm-presets/tsup.config.ts +8 -0
- package/packages/shared-perm-presets/vitest.config.ts +9 -0
- package/packages/shared-testing/CHANGELOG.md +20 -0
- package/packages/shared-testing/README.md +430 -0
- package/packages/shared-testing/package.json +51 -0
- package/packages/shared-testing/src/__tests__/create-test-context.test.ts +199 -0
- package/packages/shared-testing/src/__tests__/mock-cache.test.ts +174 -0
- package/packages/shared-testing/src/__tests__/mock-llm.test.ts +212 -0
- package/packages/shared-testing/src/__tests__/setup-platform.test.ts +90 -0
- package/packages/shared-testing/src/__tests__/test-command.test.ts +557 -0
- package/packages/shared-testing/src/create-test-context.ts +550 -0
- package/packages/shared-testing/src/index.ts +77 -0
- package/packages/shared-testing/src/mock-cache.ts +179 -0
- package/packages/shared-testing/src/mock-llm.ts +319 -0
- package/packages/shared-testing/src/mock-logger.ts +97 -0
- package/packages/shared-testing/src/mock-storage.ts +108 -0
- package/packages/shared-testing/src/setup-platform.ts +101 -0
- package/packages/shared-testing/src/test-command.ts +288 -0
- package/packages/shared-testing/tsconfig.build.json +15 -0
- package/packages/shared-testing/tsconfig.json +9 -0
- package/packages/shared-testing/tsup.config.ts +20 -0
- package/packages/shared-testing/vitest.config.ts +3 -0
- package/packages/shared-tool-kit/CHANGELOG.md +20 -0
- package/packages/shared-tool-kit/package.json +47 -0
- package/packages/shared-tool-kit/src/__tests__/factory.test.ts +103 -0
- package/packages/shared-tool-kit/src/__tests__/mock-tool.test.ts +95 -0
- package/packages/shared-tool-kit/src/factory.ts +126 -0
- package/packages/shared-tool-kit/src/index.ts +32 -0
- package/packages/shared-tool-kit/src/testing/index.ts +84 -0
- package/packages/shared-tool-kit/tsconfig.build.json +15 -0
- package/packages/shared-tool-kit/tsconfig.json +9 -0
- package/packages/shared-tool-kit/tsup.config.ts +21 -0
- package/pnpm-workspace.yaml +11070 -0
- package/prettierrc.json +1 -0
- package/scripts/devkit-sync.mjs +37 -0
- package/scripts/hooks/post-push +9 -0
- package/scripts/hooks/pre-commit +9 -0
- package/scripts/hooks/pre-push +9 -0
- package/tsconfig.base.json +9 -0
- package/tsconfig.build.json +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.paths.json +50 -0
- package/tsconfig.tools.json +18 -0
- package/tsup.config.bin.ts +34 -0
- package/tsup.config.cli.ts +41 -0
- package/tsup.config.dual.ts +46 -0
- package/tsup.config.ts +36 -0
- package/tsup.external.json +104 -0
- package/vitest.config.ts +48 -0
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @kb-labs/shared-command-kit/__tests__/define-system-command
|
|
3
|
+
*
|
|
4
|
+
* Tests for defineSystemCommand - system commands using PluginContextV3.
|
|
5
|
+
*
|
|
6
|
+
* CRITICAL: System commands receive EnhancedCliContext (PluginContextV3 + helpers)
|
|
7
|
+
* and run with SYSTEM_UNRESTRICTED_PERMISSIONS.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
11
|
+
import { defineSystemCommand, defineSystemCommandGroup } from '../define-system-command';
|
|
12
|
+
|
|
13
|
+
describe('defineSystemCommand', () => {
|
|
14
|
+
let mockCtx: any; // EnhancedCliContext (PluginContextV3 + helpers)
|
|
15
|
+
let mockUI: {
|
|
16
|
+
write: ReturnType<typeof vi.fn>;
|
|
17
|
+
json: ReturnType<typeof vi.fn>;
|
|
18
|
+
error: ReturnType<typeof vi.fn>;
|
|
19
|
+
info: ReturnType<typeof vi.fn>;
|
|
20
|
+
success: ReturnType<typeof vi.fn>;
|
|
21
|
+
warn: ReturnType<typeof vi.fn>;
|
|
22
|
+
};
|
|
23
|
+
let mockLogger: {
|
|
24
|
+
info: ReturnType<typeof vi.fn>;
|
|
25
|
+
error: ReturnType<typeof vi.fn>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
mockUI = {
|
|
30
|
+
write: vi.fn(),
|
|
31
|
+
json: vi.fn(),
|
|
32
|
+
error: vi.fn(),
|
|
33
|
+
info: vi.fn(),
|
|
34
|
+
success: vi.fn(),
|
|
35
|
+
warn: vi.fn(),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
mockLogger = {
|
|
39
|
+
info: vi.fn(),
|
|
40
|
+
error: vi.fn(),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// Pure PluginContextV3 (no EnhancedCliContext, no tracker, no output helpers)
|
|
44
|
+
mockCtx = {
|
|
45
|
+
host: 'cli',
|
|
46
|
+
requestId: 'test-request-id',
|
|
47
|
+
pluginId: '@kb-labs/system',
|
|
48
|
+
cwd: '/test',
|
|
49
|
+
ui: mockUI,
|
|
50
|
+
platform: {
|
|
51
|
+
logger: mockLogger as unknown as any,
|
|
52
|
+
llm: {} as any,
|
|
53
|
+
embeddings: {} as any,
|
|
54
|
+
vectorStore: {} as any,
|
|
55
|
+
cache: {} as any,
|
|
56
|
+
storage: {} as any,
|
|
57
|
+
analytics: {} as any,
|
|
58
|
+
},
|
|
59
|
+
runtime: {
|
|
60
|
+
fs: {} as any,
|
|
61
|
+
fetch: vi.fn(),
|
|
62
|
+
env: vi.fn(),
|
|
63
|
+
} as any,
|
|
64
|
+
api: {} as any,
|
|
65
|
+
trace: {
|
|
66
|
+
traceId: 'test-trace-id',
|
|
67
|
+
spanId: 'test-span-id',
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Basic Command Creation', () => {
|
|
73
|
+
it('should create a command with correct metadata', () => {
|
|
74
|
+
const command = defineSystemCommand({
|
|
75
|
+
name: 'test-cmd',
|
|
76
|
+
description: 'Test command',
|
|
77
|
+
longDescription: 'Long description',
|
|
78
|
+
category: 'testing',
|
|
79
|
+
aliases: ['tc', 'test'],
|
|
80
|
+
examples: ['kb test-cmd --flag value'],
|
|
81
|
+
flags: {
|
|
82
|
+
flag: { type: 'string', description: 'A test flag' },
|
|
83
|
+
},
|
|
84
|
+
handler: async () => ({ ok: true }),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(command.name).toBe('test-cmd');
|
|
88
|
+
expect(command.describe).toBe('Test command');
|
|
89
|
+
expect(command.longDescription).toBe('Long description');
|
|
90
|
+
expect(command.category).toBe('testing');
|
|
91
|
+
expect(command.aliases).toEqual(['tc', 'test']);
|
|
92
|
+
expect(command.examples).toEqual(['kb test-cmd --flag value']);
|
|
93
|
+
expect(command.flags).toHaveLength(1);
|
|
94
|
+
const firstFlag = command.flags![0]!;
|
|
95
|
+
expect(firstFlag.name).toBe('flag');
|
|
96
|
+
expect(firstFlag.type).toBe('string');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should default category to "system"', () => {
|
|
100
|
+
const command = defineSystemCommand({
|
|
101
|
+
name: 'test',
|
|
102
|
+
description: 'Test',
|
|
103
|
+
flags: {},
|
|
104
|
+
handler: async () => ({ ok: true }),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(command.category).toBe('system');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('should default aliases to empty array', () => {
|
|
111
|
+
const command = defineSystemCommand({
|
|
112
|
+
name: 'test',
|
|
113
|
+
description: 'Test',
|
|
114
|
+
flags: {},
|
|
115
|
+
handler: async () => ({ ok: true }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
expect(command.aliases).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('should default examples to empty array', () => {
|
|
122
|
+
const command = defineSystemCommand({
|
|
123
|
+
name: 'test',
|
|
124
|
+
description: 'Test',
|
|
125
|
+
flags: {},
|
|
126
|
+
handler: async () => ({ ok: true }),
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(command.examples).toEqual([]);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('Flag Schema Conversion', () => {
|
|
134
|
+
it('should convert flag schema to FlagDefinition[]', () => {
|
|
135
|
+
const command = defineSystemCommand({
|
|
136
|
+
name: 'test',
|
|
137
|
+
description: 'Test',
|
|
138
|
+
flags: {
|
|
139
|
+
name: { type: 'string', description: 'Name', required: true },
|
|
140
|
+
count: { type: 'number', default: 10 },
|
|
141
|
+
verbose: { type: 'boolean', alias: 'v' },
|
|
142
|
+
},
|
|
143
|
+
handler: async () => ({ ok: true }),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(command.flags).toHaveLength(3);
|
|
147
|
+
|
|
148
|
+
const nameFlag = command.flags!.find(f => f.name === 'name');
|
|
149
|
+
expect(nameFlag).toEqual({
|
|
150
|
+
name: 'name',
|
|
151
|
+
type: 'string',
|
|
152
|
+
description: 'Name',
|
|
153
|
+
required: true,
|
|
154
|
+
alias: undefined,
|
|
155
|
+
default: undefined,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const countFlag = command.flags!.find(f => f.name === 'count');
|
|
159
|
+
expect(countFlag).toEqual({
|
|
160
|
+
name: 'count',
|
|
161
|
+
type: 'number',
|
|
162
|
+
description: undefined,
|
|
163
|
+
default: 10,
|
|
164
|
+
required: undefined,
|
|
165
|
+
alias: undefined,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const verboseFlag = command.flags!.find(f => f.name === 'verbose');
|
|
169
|
+
expect(verboseFlag).toEqual({
|
|
170
|
+
name: 'verbose',
|
|
171
|
+
type: 'boolean',
|
|
172
|
+
alias: 'v',
|
|
173
|
+
description: undefined,
|
|
174
|
+
default: undefined,
|
|
175
|
+
required: undefined,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('should handle choices for string flags', () => {
|
|
180
|
+
const command = defineSystemCommand({
|
|
181
|
+
name: 'test',
|
|
182
|
+
description: 'Test',
|
|
183
|
+
flags: {
|
|
184
|
+
mode: { type: 'string', choices: ['dev', 'prod'] as const },
|
|
185
|
+
},
|
|
186
|
+
handler: async () => ({ ok: true }),
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const modeFlag = command.flags!.find(f => f.name === 'mode');
|
|
190
|
+
expect(modeFlag!.choices).toEqual(['dev', 'prod']);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('Handler Execution', () => {
|
|
195
|
+
it('should call handler with EnhancedCliContext', async () => {
|
|
196
|
+
const handler = vi.fn().mockResolvedValue({ ok: true });
|
|
197
|
+
|
|
198
|
+
const command = defineSystemCommand({
|
|
199
|
+
name: 'test',
|
|
200
|
+
description: 'Test',
|
|
201
|
+
flags: {},
|
|
202
|
+
handler,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
await command.run(mockCtx, [], {});
|
|
206
|
+
|
|
207
|
+
expect(handler).toHaveBeenCalledWith(
|
|
208
|
+
expect.objectContaining({
|
|
209
|
+
host: 'cli',
|
|
210
|
+
pluginId: '@kb-labs/system',
|
|
211
|
+
ui: mockUI,
|
|
212
|
+
platform: expect.objectContaining({ logger: mockLogger }),
|
|
213
|
+
runtime: expect.any(Object),
|
|
214
|
+
trace: expect.objectContaining({ traceId: 'test-trace-id' }),
|
|
215
|
+
// Removed: tracker is no longer added (pure PluginContextV3)
|
|
216
|
+
}),
|
|
217
|
+
[],
|
|
218
|
+
{}
|
|
219
|
+
);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it('should return 0 for successful CommandResult', async () => {
|
|
223
|
+
const command = defineSystemCommand({
|
|
224
|
+
name: 'test',
|
|
225
|
+
description: 'Test',
|
|
226
|
+
flags: {},
|
|
227
|
+
handler: async () => ({ ok: true, data: 'success' }),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const exitCode = await command.run(mockCtx, [], {});
|
|
231
|
+
expect(exitCode).toBe(0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it('should return 1 for failed CommandResult', async () => {
|
|
235
|
+
const command = defineSystemCommand({
|
|
236
|
+
name: 'test',
|
|
237
|
+
description: 'Test',
|
|
238
|
+
flags: {},
|
|
239
|
+
handler: async () => ({ ok: false, error: 'failed' }),
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const exitCode = await command.run(mockCtx, [], {});
|
|
243
|
+
expect(exitCode).toBe(1);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should return numeric exit code directly', async () => {
|
|
247
|
+
const command = defineSystemCommand({
|
|
248
|
+
name: 'test',
|
|
249
|
+
description: 'Test',
|
|
250
|
+
flags: {},
|
|
251
|
+
handler: async () => 42,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const exitCode = await command.run(mockCtx, [], {});
|
|
255
|
+
expect(exitCode).toBe(42);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should handle synchronous handlers', async () => {
|
|
259
|
+
const command = defineSystemCommand({
|
|
260
|
+
name: 'test',
|
|
261
|
+
description: 'Test',
|
|
262
|
+
flags: {},
|
|
263
|
+
handler: () => ({ ok: true }),
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const exitCode = await command.run(mockCtx, [], {});
|
|
267
|
+
expect(exitCode).toBe(0);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('Flag Validation', () => {
|
|
272
|
+
it('should validate required flags', async () => {
|
|
273
|
+
const handler = vi.fn().mockResolvedValue({ ok: true });
|
|
274
|
+
|
|
275
|
+
const command = defineSystemCommand({
|
|
276
|
+
name: 'test',
|
|
277
|
+
description: 'Test',
|
|
278
|
+
flags: {
|
|
279
|
+
name: { type: 'string', required: true },
|
|
280
|
+
},
|
|
281
|
+
handler,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const exitCode = await command.run(mockCtx, [], {});
|
|
285
|
+
|
|
286
|
+
expect(exitCode).toBe(3); // EXIT_CODES.INVALID_FLAGS
|
|
287
|
+
expect(handler).not.toHaveBeenCalled();
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('should pass validation when required flags provided', async () => {
|
|
291
|
+
const handler = vi.fn().mockResolvedValue({ ok: true });
|
|
292
|
+
|
|
293
|
+
const command = defineSystemCommand({
|
|
294
|
+
name: 'test',
|
|
295
|
+
description: 'Test',
|
|
296
|
+
flags: {
|
|
297
|
+
name: { type: 'string', required: true },
|
|
298
|
+
},
|
|
299
|
+
handler,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
const exitCode = await command.run(mockCtx, [], { name: 'test-value' });
|
|
303
|
+
|
|
304
|
+
expect(exitCode).toBe(0);
|
|
305
|
+
expect(handler).toHaveBeenCalledWith(
|
|
306
|
+
expect.anything(),
|
|
307
|
+
[],
|
|
308
|
+
expect.objectContaining({ name: 'test-value' })
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it('should apply default values', async () => {
|
|
313
|
+
const handler = vi.fn().mockResolvedValue({ ok: true });
|
|
314
|
+
|
|
315
|
+
const command = defineSystemCommand({
|
|
316
|
+
name: 'test',
|
|
317
|
+
description: 'Test',
|
|
318
|
+
flags: {
|
|
319
|
+
count: { type: 'number', default: 10 },
|
|
320
|
+
verbose: { type: 'boolean', default: false },
|
|
321
|
+
},
|
|
322
|
+
handler,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
await command.run(mockCtx, [], {});
|
|
326
|
+
|
|
327
|
+
expect(handler).toHaveBeenCalledWith(
|
|
328
|
+
expect.anything(),
|
|
329
|
+
[],
|
|
330
|
+
expect.objectContaining({ count: 10, verbose: false })
|
|
331
|
+
);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe('Error Handling', () => {
|
|
336
|
+
it('should handle handler errors', async () => {
|
|
337
|
+
const command = defineSystemCommand({
|
|
338
|
+
name: 'test',
|
|
339
|
+
description: 'Test',
|
|
340
|
+
flags: {},
|
|
341
|
+
handler: async () => {
|
|
342
|
+
throw new Error('Handler error');
|
|
343
|
+
},
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
const exitCode = await command.run(mockCtx, [], {});
|
|
347
|
+
|
|
348
|
+
expect(exitCode).toBe(1);
|
|
349
|
+
expect(mockUI.error).toHaveBeenCalled();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should log errors to platform logger', async () => {
|
|
353
|
+
const command = defineSystemCommand({
|
|
354
|
+
name: 'test',
|
|
355
|
+
description: 'Test',
|
|
356
|
+
flags: {},
|
|
357
|
+
handler: async () => {
|
|
358
|
+
throw new Error('Handler error');
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
await command.run(mockCtx, [], {});
|
|
363
|
+
|
|
364
|
+
expect(mockLogger.error).toHaveBeenCalled();
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('should output error in JSON mode', async () => {
|
|
368
|
+
const command = defineSystemCommand({
|
|
369
|
+
name: 'test',
|
|
370
|
+
description: 'Test',
|
|
371
|
+
flags: {},
|
|
372
|
+
handler: async () => {
|
|
373
|
+
throw new Error('Handler error');
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
await command.run(mockCtx, [], { json: true });
|
|
378
|
+
|
|
379
|
+
expect(mockUI.json).toHaveBeenCalled();
|
|
380
|
+
const jsonCall = mockUI.json.mock.calls[0]?.[0];
|
|
381
|
+
expect(jsonCall.ok).toBe(false);
|
|
382
|
+
expect(jsonCall.error).toBe('Handler error');
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
describe('Analytics Integration', () => {
|
|
387
|
+
it('should accept analytics config (legacy)', async () => {
|
|
388
|
+
const command = defineSystemCommand({
|
|
389
|
+
name: 'test',
|
|
390
|
+
description: 'Test',
|
|
391
|
+
flags: {},
|
|
392
|
+
analytics: {
|
|
393
|
+
startEvent: 'TEST_STARTED',
|
|
394
|
+
finishEvent: 'TEST_FINISHED',
|
|
395
|
+
command: 'test-command',
|
|
396
|
+
},
|
|
397
|
+
handler: async () => ({ ok: true }),
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const exitCode = await command.run(mockCtx, [], {});
|
|
401
|
+
|
|
402
|
+
// Analytics is legacy - command should still work
|
|
403
|
+
expect(exitCode).toBe(0);
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe('Formatter', () => {
|
|
408
|
+
it('should call formatter with result', async () => {
|
|
409
|
+
const formatter = vi.fn();
|
|
410
|
+
|
|
411
|
+
const command = defineSystemCommand({
|
|
412
|
+
name: 'test',
|
|
413
|
+
description: 'Test',
|
|
414
|
+
flags: {
|
|
415
|
+
name: { type: 'string' },
|
|
416
|
+
},
|
|
417
|
+
handler: async () => ({ ok: true, message: 'success' }),
|
|
418
|
+
formatter,
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await command.run(mockCtx, [], { name: 'test' });
|
|
422
|
+
|
|
423
|
+
// Formatter should be called with result, ctx, flags, argv
|
|
424
|
+
expect(formatter).toHaveBeenCalled();
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Removed: Tracker Integration - tracker is no longer added (pure PluginContextV3)
|
|
429
|
+
describe('Tracker Integration (REMOVED)', () => {
|
|
430
|
+
it('tracker is no longer added - context is pure PluginContextV3', async () => {
|
|
431
|
+
const handler = vi.fn().mockImplementation((ctx) => {
|
|
432
|
+
// Pure PluginContextV3 - no tracker field
|
|
433
|
+
expect(ctx.tracker).toBeUndefined();
|
|
434
|
+
return { ok: true };
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const command = defineSystemCommand({
|
|
438
|
+
name: 'test',
|
|
439
|
+
description: 'Test',
|
|
440
|
+
flags: {},
|
|
441
|
+
handler,
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
await command.run(mockCtx, [], {});
|
|
445
|
+
|
|
446
|
+
expect(handler).toHaveBeenCalled();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe('Logging', () => {
|
|
451
|
+
it('should log command execution', async () => {
|
|
452
|
+
const command = defineSystemCommand({
|
|
453
|
+
name: 'test-cmd',
|
|
454
|
+
description: 'Test',
|
|
455
|
+
flags: {},
|
|
456
|
+
handler: async () => ({ ok: true }),
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
await command.run(mockCtx, [], {});
|
|
460
|
+
|
|
461
|
+
expect(mockLogger.info).toHaveBeenCalled();
|
|
462
|
+
const calls = mockLogger.info.mock.calls;
|
|
463
|
+
const hasStartLog = calls.some((call: any[]) => call[0]?.includes('started'));
|
|
464
|
+
const hasCompleteLog = calls.some((call: any[]) => call[0]?.includes('completed'));
|
|
465
|
+
expect(hasStartLog || hasCompleteLog).toBe(true);
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
describe('defineSystemCommandGroup', () => {
|
|
471
|
+
it('should create a command group', () => {
|
|
472
|
+
const cmd1 = defineSystemCommand({
|
|
473
|
+
name: 'cmd1',
|
|
474
|
+
description: 'Command 1',
|
|
475
|
+
flags: {},
|
|
476
|
+
handler: async () => ({ ok: true }),
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const cmd2 = defineSystemCommand({
|
|
480
|
+
name: 'cmd2',
|
|
481
|
+
description: 'Command 2',
|
|
482
|
+
flags: {},
|
|
483
|
+
handler: async () => ({ ok: true }),
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
const group = defineSystemCommandGroup(
|
|
487
|
+
'test-group',
|
|
488
|
+
'Test command group',
|
|
489
|
+
[cmd1, cmd2]
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
expect(group.name).toBe('test-group');
|
|
493
|
+
expect(group.describe).toBe('Test command group');
|
|
494
|
+
expect(group.commands).toHaveLength(2);
|
|
495
|
+
expect(group.commands[0]!.name).toBe('cmd1');
|
|
496
|
+
expect(group.commands[1]!.name).toBe('cmd2');
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('should handle empty command array', () => {
|
|
500
|
+
const group = defineSystemCommandGroup(
|
|
501
|
+
'empty-group',
|
|
502
|
+
'Empty group',
|
|
503
|
+
[]
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
expect(group.commands).toHaveLength(0);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Unit tests for defineWebhook
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
import { defineWebhook, isWebhookHost } from '../define-webhook.js';
|
|
7
|
+
import type { PluginContextV3, WebhookHostContext } from '@kb-labs/plugin-contracts';
|
|
8
|
+
|
|
9
|
+
describe('defineWebhook', () => {
|
|
10
|
+
let mockContext: PluginContextV3<unknown>;
|
|
11
|
+
let mockLogger: any;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mockLogger = {
|
|
15
|
+
info: vi.fn(),
|
|
16
|
+
error: vi.fn(),
|
|
17
|
+
warn: vi.fn(),
|
|
18
|
+
debug: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const webhookHostContext: WebhookHostContext = {
|
|
22
|
+
host: 'webhook',
|
|
23
|
+
event: 'github:push',
|
|
24
|
+
source: 'github',
|
|
25
|
+
payload: { ref: 'refs/heads/main' },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
mockContext = {
|
|
29
|
+
host: 'webhook',
|
|
30
|
+
hostContext: webhookHostContext,
|
|
31
|
+
ui: {} as any,
|
|
32
|
+
platform: {} as any,
|
|
33
|
+
config: {},
|
|
34
|
+
} as any;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe('host validation', () => {
|
|
38
|
+
it('should throw error if host is not webhook', async () => {
|
|
39
|
+
const handler = defineWebhook({
|
|
40
|
+
event: 'github:push',
|
|
41
|
+
handler: {
|
|
42
|
+
async execute() {
|
|
43
|
+
return { data: {}, exitCode: 0 };
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const invalidContext = {
|
|
49
|
+
...mockContext,
|
|
50
|
+
host: 'cli' as const,
|
|
51
|
+
hostContext: { host: 'cli' as const } as any,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
await expect(
|
|
55
|
+
handler.execute(invalidContext, {})
|
|
56
|
+
).rejects.toThrow('can only run in webhook host');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should throw error if event name mismatch', async () => {
|
|
60
|
+
const handler = defineWebhook({
|
|
61
|
+
event: 'github:push',
|
|
62
|
+
handler: {
|
|
63
|
+
async execute() {
|
|
64
|
+
return { data: {}, exitCode: 0 };
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const wrongEventContext = {
|
|
70
|
+
...mockContext,
|
|
71
|
+
hostContext: {
|
|
72
|
+
...mockContext.hostContext,
|
|
73
|
+
event: 'github:pull_request',
|
|
74
|
+
} as WebhookHostContext,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
await expect(
|
|
78
|
+
handler.execute(wrongEventContext, {})
|
|
79
|
+
).rejects.toThrow('expects event github:push but got github:pull_request');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should accept valid webhook host context', async () => {
|
|
83
|
+
const execute = vi.fn(async () => ({ data: {}, exitCode: 0 }));
|
|
84
|
+
|
|
85
|
+
const handler = defineWebhook({
|
|
86
|
+
event: 'github:push',
|
|
87
|
+
handler: { execute },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await handler.execute(mockContext, {});
|
|
91
|
+
|
|
92
|
+
expect(execute).toHaveBeenCalled();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('cleanup', () => {
|
|
97
|
+
it('should call cleanup after successful execution', async () => {
|
|
98
|
+
const cleanup = vi.fn();
|
|
99
|
+
|
|
100
|
+
const handler = defineWebhook({
|
|
101
|
+
event: 'github:push',
|
|
102
|
+
handler: {
|
|
103
|
+
async execute() {
|
|
104
|
+
return { data: {}, exitCode: 0 };
|
|
105
|
+
},
|
|
106
|
+
cleanup,
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
await handler.execute(mockContext, {});
|
|
111
|
+
|
|
112
|
+
expect(cleanup).toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should call cleanup even if handler throws', async () => {
|
|
116
|
+
const cleanup = vi.fn();
|
|
117
|
+
|
|
118
|
+
const handler = defineWebhook({
|
|
119
|
+
event: 'github:push',
|
|
120
|
+
handler: {
|
|
121
|
+
async execute() {
|
|
122
|
+
throw new Error('Handler error');
|
|
123
|
+
},
|
|
124
|
+
cleanup,
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
await expect(handler.execute(mockContext, {})).rejects.toThrow('Handler error');
|
|
129
|
+
|
|
130
|
+
expect(cleanup).toHaveBeenCalled();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('isWebhookHost', () => {
|
|
136
|
+
it('should return true for webhook host context', () => {
|
|
137
|
+
const webhookContext: WebhookHostContext = {
|
|
138
|
+
host: 'webhook',
|
|
139
|
+
event: 'github:push',
|
|
140
|
+
source: 'github',
|
|
141
|
+
payload: {},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
expect(isWebhookHost(webhookContext)).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return false for non-webhook host context', () => {
|
|
148
|
+
const cliContext = {
|
|
149
|
+
host: 'cli' as const,
|
|
150
|
+
argv: [],
|
|
151
|
+
flags: {},
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
expect(isWebhookHost(cliContext)).toBe(false);
|
|
155
|
+
});
|
|
156
|
+
});
|