@ollie-shop/cli 0.2.0 → 0.3.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.
Files changed (182) hide show
  1. package/.turbo/turbo-build.log +2 -11
  2. package/CHANGELOG.md +13 -7
  3. package/CLAUDE_CLI.md +265 -0
  4. package/README.md +704 -8
  5. package/__tests__/mocks/console.ts +22 -0
  6. package/__tests__/mocks/core.ts +137 -0
  7. package/__tests__/mocks/index.ts +4 -0
  8. package/__tests__/mocks/inquirer.ts +16 -0
  9. package/__tests__/mocks/progress.ts +19 -0
  10. package/dist/__tests__/helpers/cli-test-helper.d.ts +89 -0
  11. package/dist/__tests__/helpers/cli-test-helper.d.ts.map +1 -0
  12. package/dist/__tests__/helpers/cli-test-helper.js +220 -0
  13. package/dist/__tests__/mocks/index.d.ts +69 -0
  14. package/dist/__tests__/mocks/index.d.ts.map +1 -0
  15. package/dist/__tests__/mocks/index.js +77 -0
  16. package/dist/actions/component.actions.d.ts +14 -0
  17. package/dist/actions/component.actions.d.ts.map +1 -0
  18. package/dist/actions/component.actions.js +273 -0
  19. package/dist/actions/function.actions.d.ts +15 -0
  20. package/dist/actions/function.actions.d.ts.map +1 -0
  21. package/dist/actions/function.actions.js +254 -0
  22. package/dist/actions/project.actions.d.ts +17 -0
  23. package/dist/actions/project.actions.d.ts.map +1 -0
  24. package/dist/actions/project.actions.js +97 -0
  25. package/dist/actions/version.actions.d.ts +19 -0
  26. package/dist/actions/version.actions.d.ts.map +1 -0
  27. package/dist/actions/version.actions.js +216 -0
  28. package/dist/commands/component.d.ts +3 -0
  29. package/dist/commands/component.d.ts.map +1 -0
  30. package/dist/commands/component.js +192 -0
  31. package/dist/commands/docs.d.ts +3 -0
  32. package/dist/commands/docs.d.ts.map +1 -0
  33. package/dist/commands/docs.js +16 -0
  34. package/dist/commands/function.d.ts +3 -0
  35. package/dist/commands/function.d.ts.map +1 -0
  36. package/dist/commands/function.js +243 -0
  37. package/dist/commands/help.d.ts +3 -0
  38. package/dist/commands/help.d.ts.map +1 -0
  39. package/dist/commands/help.js +20 -0
  40. package/dist/commands/index.d.ts +3 -0
  41. package/dist/commands/index.d.ts.map +1 -0
  42. package/dist/commands/index.js +26 -0
  43. package/dist/commands/login.d.ts +3 -0
  44. package/dist/commands/login.d.ts.map +1 -0
  45. package/dist/commands/login.js +175 -0
  46. package/dist/commands/project.d.ts +3 -0
  47. package/dist/commands/project.d.ts.map +1 -0
  48. package/dist/commands/project.js +78 -0
  49. package/dist/commands/store-version.d.ts +3 -0
  50. package/dist/commands/store-version.d.ts.map +1 -0
  51. package/dist/commands/store-version.js +241 -0
  52. package/dist/commands/version.d.ts +3 -0
  53. package/dist/commands/version.d.ts.map +1 -0
  54. package/dist/commands/version.js +46 -0
  55. package/dist/commands/whoami.d.ts +3 -0
  56. package/dist/commands/whoami.d.ts.map +1 -0
  57. package/dist/commands/whoami.js +41 -0
  58. package/dist/index.d.ts +3 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +88 -478
  61. package/dist/prompts/component.prompts.d.ts +14 -0
  62. package/dist/prompts/component.prompts.d.ts.map +1 -0
  63. package/dist/prompts/component.prompts.js +75 -0
  64. package/dist/prompts/function.prompts.d.ts +21 -0
  65. package/dist/prompts/function.prompts.d.ts.map +1 -0
  66. package/dist/prompts/function.prompts.js +127 -0
  67. package/dist/schemas/command.schema.d.ts +516 -0
  68. package/dist/schemas/command.schema.d.ts.map +1 -0
  69. package/dist/schemas/command.schema.js +267 -0
  70. package/dist/types/index.d.ts +147 -0
  71. package/dist/types/index.d.ts.map +1 -0
  72. package/dist/types/index.js +18 -0
  73. package/dist/utils/auth.d.ts +4 -0
  74. package/dist/utils/auth.d.ts.map +1 -0
  75. package/dist/utils/auth.js +26 -0
  76. package/dist/utils/cli-progress-reporter.d.ts +12 -0
  77. package/dist/utils/cli-progress-reporter.d.ts.map +1 -0
  78. package/dist/utils/cli-progress-reporter.js +77 -0
  79. package/dist/utils/command-builder.d.ts +22 -0
  80. package/dist/utils/command-builder.d.ts.map +1 -0
  81. package/dist/utils/command-builder.js +268 -0
  82. package/dist/utils/command-helpers.d.ts +19 -0
  83. package/dist/utils/command-helpers.d.ts.map +1 -0
  84. package/dist/utils/command-helpers.js +79 -0
  85. package/dist/utils/command-parser.d.ts +146 -0
  86. package/dist/utils/command-parser.d.ts.map +1 -0
  87. package/dist/utils/command-parser.js +179 -0
  88. package/dist/utils/command-suggestions.d.ts +35 -0
  89. package/dist/utils/command-suggestions.d.ts.map +1 -0
  90. package/dist/utils/command-suggestions.js +152 -0
  91. package/dist/utils/console.d.ts +44 -0
  92. package/dist/utils/console.d.ts.map +1 -0
  93. package/dist/utils/console.js +233 -0
  94. package/dist/utils/constants.d.ts +8 -0
  95. package/dist/utils/constants.d.ts.map +1 -0
  96. package/dist/utils/constants.js +10 -0
  97. package/dist/utils/context-detector.d.ts +12 -0
  98. package/dist/utils/context-detector.d.ts.map +1 -0
  99. package/dist/utils/context-detector.js +155 -0
  100. package/dist/utils/enhanced-error-handler.d.ts +47 -0
  101. package/dist/utils/enhanced-error-handler.d.ts.map +1 -0
  102. package/dist/utils/enhanced-error-handler.js +221 -0
  103. package/dist/utils/error-handler.d.ts +3 -0
  104. package/dist/utils/error-handler.d.ts.map +1 -0
  105. package/dist/utils/error-handler.js +55 -0
  106. package/dist/utils/errors.d.ts +44 -0
  107. package/dist/utils/errors.d.ts.map +1 -0
  108. package/dist/utils/errors.js +76 -0
  109. package/dist/utils/interactive-builder.d.ts +22 -0
  110. package/dist/utils/interactive-builder.d.ts.map +1 -0
  111. package/dist/utils/interactive-builder.js +246 -0
  112. package/dist/utils/rich-progress.d.ts +59 -0
  113. package/dist/utils/rich-progress.d.ts.map +1 -0
  114. package/dist/utils/rich-progress.js +234 -0
  115. package/dist/utils/store.d.ts +11 -0
  116. package/dist/utils/store.d.ts.map +1 -0
  117. package/dist/utils/store.js +19 -0
  118. package/dist/utils/validation-error-formatter.d.ts +25 -0
  119. package/dist/utils/validation-error-formatter.d.ts.map +1 -0
  120. package/dist/utils/validation-error-formatter.js +258 -0
  121. package/dist/utils/validation-helpers.d.ts +60 -0
  122. package/dist/utils/validation-helpers.d.ts.map +1 -0
  123. package/dist/utils/validation-helpers.js +152 -0
  124. package/package.json +43 -11
  125. package/src/__tests__/helpers/cli-test-helper.ts +281 -0
  126. package/src/__tests__/mocks/index.ts +142 -0
  127. package/src/actions/component.actions.ts +334 -0
  128. package/src/actions/function.actions.ts +313 -0
  129. package/src/actions/project.actions.ts +126 -0
  130. package/src/actions/version.actions.ts +233 -0
  131. package/src/commands/__tests__/component-validation.test.ts +250 -0
  132. package/src/commands/__tests__/component.test.ts +321 -0
  133. package/src/commands/__tests__/function-validation.test.ts +220 -0
  134. package/src/commands/__tests__/function.test.ts +286 -0
  135. package/src/commands/__tests__/store-version-validation.test.ts +414 -0
  136. package/src/commands/__tests__/store-version.test.ts +405 -0
  137. package/src/commands/__tests__/version.test.ts +71 -0
  138. package/src/commands/component.ts +188 -0
  139. package/src/commands/docs.ts +11 -11
  140. package/src/commands/function.ts +252 -0
  141. package/src/commands/help.ts +8 -18
  142. package/src/commands/index.ts +14 -7
  143. package/src/commands/login.ts +19 -79
  144. package/src/commands/project.ts +107 -0
  145. package/src/commands/store-version.ts +242 -0
  146. package/src/commands/version.ts +45 -8
  147. package/src/commands/whoami.ts +8 -13
  148. package/src/index.ts +108 -34
  149. package/src/prompts/component.prompts.ts +94 -0
  150. package/src/prompts/function.prompts.ts +168 -0
  151. package/src/schemas/command.schema.ts +354 -0
  152. package/src/types/index.ts +183 -0
  153. package/src/utils/__tests__/command-parser.test.ts +159 -0
  154. package/src/utils/__tests__/command-suggestions.test.ts +185 -0
  155. package/src/utils/__tests__/console.test.ts +192 -0
  156. package/src/utils/__tests__/context-detector.test.ts +258 -0
  157. package/src/utils/__tests__/enhanced-error-handler.test.ts +137 -0
  158. package/src/utils/__tests__/error-handler.test.ts +107 -0
  159. package/src/utils/__tests__/rich-progress.test.ts +170 -0
  160. package/src/utils/__tests__/validation-error-formatter.test.ts +175 -0
  161. package/src/utils/__tests__/validation-helpers.test.ts +125 -0
  162. package/src/utils/auth.ts +0 -1
  163. package/src/utils/cli-progress-reporter.ts +84 -0
  164. package/src/utils/command-builder.ts +390 -0
  165. package/src/utils/command-helpers.ts +83 -0
  166. package/src/utils/command-parser.ts +250 -0
  167. package/src/utils/command-suggestions.ts +176 -0
  168. package/src/utils/console.ts +291 -0
  169. package/src/utils/context-detector.ts +177 -0
  170. package/src/utils/enhanced-error-handler.ts +264 -0
  171. package/src/utils/error-handler.ts +60 -0
  172. package/src/utils/errors.ts +125 -0
  173. package/src/utils/interactive-builder.ts +271 -0
  174. package/src/utils/rich-progress.ts +320 -0
  175. package/src/utils/validation-error-formatter.ts +337 -0
  176. package/src/utils/validation-helpers.ts +192 -0
  177. package/tsconfig.json +13 -7
  178. package/vitest.config.ts +28 -0
  179. package/vitest.setup.ts +29 -0
  180. package/src/commands/validate.ts +0 -62
  181. package/src/utils/core.ts +0 -105
  182. package/tsup.config.ts +0 -15
@@ -0,0 +1,321 @@
1
+ import type { Command } from "@commander-js/extra-typings";
2
+ import {
3
+ createTestProgram,
4
+ executeCLI,
5
+ resetCLIMocks,
6
+ } from "@tests/helpers/cli-test-helper";
7
+ import { createMockCore } from "@tests/mocks";
8
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
9
+ import { registerComponentCommands } from "../component";
10
+
11
+ // Mock the component actions module
12
+ vi.mock("../../actions/component.actions", () => ({
13
+ createComponent: vi.fn(),
14
+ listComponents: vi.fn(),
15
+ deployComponent: vi.fn(),
16
+ validateComponent: vi.fn(),
17
+ checkDeployStatus: vi.fn(),
18
+ }));
19
+
20
+ // Import mocked actions
21
+ import * as componentActions from "../../actions/component.actions";
22
+
23
+ describe("Component Command", () => {
24
+ let program: Command;
25
+
26
+ beforeEach(() => {
27
+ const testSetup = createTestProgram();
28
+ program = testSetup.program;
29
+ createMockCore();
30
+
31
+ // Register the component commands
32
+ registerComponentCommands(program);
33
+
34
+ // Reset all mocks
35
+ vi.clearAllMocks();
36
+ });
37
+
38
+ afterEach(() => {
39
+ resetCLIMocks();
40
+ });
41
+
42
+ describe("list subcommand", () => {
43
+ it("should list components successfully", async () => {
44
+ // Arrange
45
+ const components = [
46
+ { id: "1", name: "Header", slot: "header", active: true },
47
+ { id: "2", name: "Footer", slot: "footer", active: false },
48
+ ];
49
+ vi.mocked(componentActions.listComponents).mockResolvedValue(components);
50
+
51
+ // Act
52
+ const result = await executeCLI(program, ["component", "list"]);
53
+
54
+ // Assert
55
+ expect(result.exitCode).toBe(0);
56
+ expect(componentActions.listComponents).toHaveBeenCalled();
57
+ });
58
+
59
+ it("should handle list errors gracefully", async () => {
60
+ // Arrange
61
+ const error = new Error("Failed to fetch components");
62
+ vi.mocked(componentActions.listComponents).mockRejectedValue(error);
63
+
64
+ // Act
65
+ const result = await executeCLI(program, ["component", "list"]);
66
+
67
+ // Assert
68
+ expect(result.exitCode).toBe(1);
69
+ });
70
+ });
71
+
72
+ describe("create subcommand", () => {
73
+ it("should create component with options", async () => {
74
+ // Arrange
75
+ vi.mocked(componentActions.createComponent).mockResolvedValue();
76
+
77
+ // Act
78
+ const result = await executeCLI(program, [
79
+ "component",
80
+ "create",
81
+ "--name",
82
+ "my-component",
83
+ "--slot",
84
+ "header",
85
+ ]);
86
+
87
+ // Assert
88
+ expect(result.exitCode).toBe(0);
89
+ expect(componentActions.createComponent).toHaveBeenCalledWith(
90
+ expect.objectContaining({
91
+ name: "my-component",
92
+ slot: "header",
93
+ tests: true,
94
+ interactive: false,
95
+ }),
96
+ expect.any(Object), // console object
97
+ );
98
+ });
99
+
100
+ it("should create component with default values", async () => {
101
+ // Arrange
102
+ vi.mocked(componentActions.createComponent).mockResolvedValue();
103
+
104
+ // Act
105
+ const result = await executeCLI(program, [
106
+ "component",
107
+ "create",
108
+ "--name",
109
+ "my-component",
110
+ ]);
111
+
112
+ // Assert
113
+ expect(result.exitCode).toBe(0);
114
+ expect(componentActions.createComponent).toHaveBeenCalledWith(
115
+ expect.objectContaining({
116
+ name: "my-component",
117
+ slot: "main",
118
+ tests: true,
119
+ interactive: false,
120
+ }),
121
+ expect.any(Object),
122
+ );
123
+ });
124
+
125
+ it("should validate component name format - reject uppercase", async () => {
126
+ // Act
127
+ const result = await executeCLI(program, [
128
+ "component",
129
+ "create",
130
+ "--name",
131
+ "Test",
132
+ "--slot",
133
+ "header",
134
+ ]);
135
+
136
+ // Assert
137
+ expect(result.exitCode).toBe(1);
138
+ expect(componentActions.createComponent).not.toHaveBeenCalled();
139
+ });
140
+
141
+ it("should validate component name format - reject spaces", async () => {
142
+ // Act
143
+ const result = await executeCLI(program, [
144
+ "component",
145
+ "create",
146
+ "--name",
147
+ "test component",
148
+ "--slot",
149
+ "header",
150
+ ]);
151
+
152
+ // Assert
153
+ expect(result.exitCode).toBe(1);
154
+ expect(componentActions.createComponent).not.toHaveBeenCalled();
155
+ });
156
+
157
+ it("should validate component name format - accept valid kebab-case", async () => {
158
+ // Arrange
159
+ vi.mocked(componentActions.createComponent).mockResolvedValue();
160
+
161
+ // Act
162
+ const result = await executeCLI(program, [
163
+ "component",
164
+ "create",
165
+ "--name",
166
+ "test-component",
167
+ "--slot",
168
+ "header",
169
+ ]);
170
+
171
+ // Assert
172
+ expect(result.exitCode).toBe(0);
173
+ expect(componentActions.createComponent).toHaveBeenCalledWith(
174
+ expect.objectContaining({
175
+ name: "test-component",
176
+ slot: "header",
177
+ }),
178
+ expect.any(Object),
179
+ );
180
+ });
181
+
182
+ it("should require component name", async () => {
183
+ // Act
184
+ const result = await executeCLI(program, [
185
+ "component",
186
+ "create",
187
+ "--slot",
188
+ "header",
189
+ ]);
190
+
191
+ // Assert
192
+ expect(result.exitCode).toBe(1);
193
+ expect(componentActions.createComponent).not.toHaveBeenCalled();
194
+ });
195
+
196
+ it("should handle action errors", async () => {
197
+ // Arrange
198
+ const error = new Error("Creation failed");
199
+ vi.mocked(componentActions.createComponent).mockRejectedValue(error);
200
+
201
+ // Act
202
+ const result = await executeCLI(program, [
203
+ "component",
204
+ "create",
205
+ "--name",
206
+ "test-component",
207
+ ]);
208
+
209
+ // Assert
210
+ expect(result.exitCode).toBe(1);
211
+ });
212
+ });
213
+
214
+ describe("deploy subcommand", () => {
215
+ it("should deploy a component by path", async () => {
216
+ // Arrange
217
+ vi.mocked(componentActions.deployComponent).mockResolvedValue();
218
+
219
+ // Act
220
+ const result = await executeCLI(program, [
221
+ "component",
222
+ "deploy",
223
+ "--path",
224
+ "./my-component",
225
+ "--id",
226
+ "comp-123",
227
+ ]);
228
+
229
+ // Assert
230
+ expect(result.exitCode).toBe(0);
231
+ expect(componentActions.deployComponent).toHaveBeenCalledWith(
232
+ expect.objectContaining({
233
+ path: "./my-component",
234
+ wait: false,
235
+ }),
236
+ expect.any(Object),
237
+ );
238
+ });
239
+
240
+ it("should deploy a component by ID", async () => {
241
+ // Arrange
242
+ vi.mocked(componentActions.deployComponent).mockResolvedValue();
243
+
244
+ // Act
245
+ const result = await executeCLI(program, [
246
+ "component",
247
+ "deploy",
248
+ "--id",
249
+ "comp-123",
250
+ "--wait",
251
+ ]);
252
+
253
+ // Assert
254
+ expect(result.exitCode).toBe(0);
255
+ expect(componentActions.deployComponent).toHaveBeenCalledWith(
256
+ expect.objectContaining({
257
+ componentId: "comp-123",
258
+ wait: true,
259
+ }),
260
+ expect.any(Object),
261
+ );
262
+ });
263
+ });
264
+
265
+ describe("validate subcommand", () => {
266
+ it("should validate a component at path", async () => {
267
+ // Arrange
268
+ vi.mocked(componentActions.validateComponent).mockResolvedValue();
269
+
270
+ // Act
271
+ const result = await executeCLI(program, [
272
+ "component",
273
+ "validate",
274
+ "--path",
275
+ "./my-component",
276
+ ]);
277
+
278
+ // Assert
279
+ expect(result.exitCode).toBe(0);
280
+ expect(componentActions.validateComponent).toHaveBeenCalledWith(
281
+ expect.objectContaining({
282
+ path: "./my-component",
283
+ }),
284
+ expect.any(Object),
285
+ );
286
+ });
287
+ });
288
+
289
+ describe("error handling", () => {
290
+ it("should handle validation errors gracefully", async () => {
291
+ // Arrange
292
+ const error = new Error("Validation failed");
293
+ vi.mocked(componentActions.validateComponent).mockRejectedValue(error);
294
+
295
+ // Act
296
+ const result = await executeCLI(program, [
297
+ "component",
298
+ "validate",
299
+ "--path",
300
+ "./my-component",
301
+ ]);
302
+
303
+ // Assert
304
+ expect(result.exitCode).toBe(1);
305
+ });
306
+ });
307
+
308
+ describe("alias support", () => {
309
+ it("should work with comp alias", async () => {
310
+ // Arrange
311
+ vi.mocked(componentActions.listComponents).mockResolvedValue();
312
+
313
+ // Act
314
+ const result = await executeCLI(program, ["comp", "list"]);
315
+
316
+ // Assert
317
+ expect(result.exitCode).toBe(0);
318
+ expect(componentActions.listComponents).toHaveBeenCalled();
319
+ });
320
+ });
321
+ });
@@ -0,0 +1,220 @@
1
+ import type { Command } from "@commander-js/extra-typings";
2
+ import {
3
+ createTestProgram,
4
+ executeCLI,
5
+ resetCLIMocks,
6
+ } from "@tests/helpers/cli-test-helper";
7
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
8
+ import { registerFunctionCommands } from "../function";
9
+
10
+ // Mock the function actions module
11
+ vi.mock("../../actions/function.actions", () => ({
12
+ createFunction: vi.fn(),
13
+ listFunctions: vi.fn(),
14
+ deployFunction: vi.fn(),
15
+ validateFunction: vi.fn(),
16
+ testFunction: vi.fn(),
17
+ }));
18
+
19
+ import * as functionActions from "../../actions/function.actions";
20
+
21
+ describe("Function Command Validation and DX", () => {
22
+ let program: Command;
23
+ let testSetup: ReturnType<typeof createTestProgram>;
24
+
25
+ beforeEach(() => {
26
+ testSetup = createTestProgram();
27
+ program = testSetup.program;
28
+ registerFunctionCommands(program);
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ afterEach(() => {
33
+ resetCLIMocks();
34
+ });
35
+
36
+ describe("name validation", () => {
37
+ it("should reject uppercase function names", async () => {
38
+ const result = await executeCLI(program, [
39
+ "function",
40
+ "create",
41
+ "--name",
42
+ "ValidateOrder",
43
+ ]);
44
+
45
+ const consoleOutput = testSetup.mocks.console.error.mock.calls
46
+ .map((call) => call.join(" "))
47
+ .join("\n");
48
+
49
+ expect(result.exitCode).toBe(1);
50
+ expect(consoleOutput).toContain("Validation failed");
51
+ expect(consoleOutput).toContain("Must be lowercase with hyphens only");
52
+ expect(functionActions.createFunction).not.toHaveBeenCalled();
53
+ });
54
+
55
+ it("should reject function names with spaces", async () => {
56
+ const result = await executeCLI(program, [
57
+ "function",
58
+ "create",
59
+ "--name",
60
+ "validate order",
61
+ ]);
62
+
63
+ const consoleOutput = testSetup.mocks.console.error.mock.calls
64
+ .map((call) => call.join(" "))
65
+ .join("\n");
66
+
67
+ expect(result.exitCode).toBe(1);
68
+ expect(consoleOutput).toContain("Validation failed");
69
+ expect(functionActions.createFunction).not.toHaveBeenCalled();
70
+ });
71
+
72
+ it("should reject function names with underscores", async () => {
73
+ const result = await executeCLI(program, [
74
+ "function",
75
+ "create",
76
+ "--name",
77
+ "validate_order",
78
+ ]);
79
+
80
+ const consoleOutput = testSetup.mocks.console.error.mock.calls
81
+ .map((call) => call.join(" "))
82
+ .join("\n");
83
+
84
+ expect(result.exitCode).toBe(1);
85
+ expect(consoleOutput).toContain("Validation failed");
86
+ expect(functionActions.createFunction).not.toHaveBeenCalled();
87
+ });
88
+
89
+ it("should require function name", async () => {
90
+ const result = await executeCLI(program, [
91
+ "function",
92
+ "create",
93
+ "--event",
94
+ "cart",
95
+ ]);
96
+
97
+ expect(result.exitCode).toBe(1);
98
+ expect(functionActions.createFunction).not.toHaveBeenCalled();
99
+ });
100
+
101
+ it("should accept valid kebab-case names", async () => {
102
+ vi.mocked(functionActions.createFunction).mockResolvedValue();
103
+
104
+ const result = await executeCLI(program, [
105
+ "function",
106
+ "create",
107
+ "--name",
108
+ "validate-order",
109
+ ]);
110
+
111
+ expect(result.exitCode).toBe(0);
112
+ expect(functionActions.createFunction).toHaveBeenCalledWith(
113
+ expect.objectContaining({
114
+ name: "validate-order",
115
+ }),
116
+ expect.any(Object),
117
+ );
118
+ });
119
+
120
+ it("should accept names with numbers", async () => {
121
+ vi.mocked(functionActions.createFunction).mockResolvedValue();
122
+
123
+ const result = await executeCLI(program, [
124
+ "function",
125
+ "create",
126
+ "--name",
127
+ "validate-order-v2",
128
+ ]);
129
+
130
+ expect(result.exitCode).toBe(0);
131
+ expect(functionActions.createFunction).toHaveBeenCalledWith(
132
+ expect.objectContaining({
133
+ name: "validate-order-v2",
134
+ }),
135
+ expect.any(Object),
136
+ );
137
+ });
138
+ });
139
+
140
+ describe("invocation validation", () => {
141
+ it("should reject invalid invocation types", async () => {
142
+ const result = await executeCLI(program, [
143
+ "function",
144
+ "create",
145
+ "--name",
146
+ "test-func",
147
+ "--invocation",
148
+ "invalid",
149
+ ]);
150
+
151
+ expect(result.exitCode).toBe(1);
152
+ expect(result.stderr.join("")).toContain("Invalid function invocation");
153
+ expect(result.stderr.join("")).toContain(
154
+ "Valid invocations: request, response",
155
+ );
156
+ expect(functionActions.createFunction).not.toHaveBeenCalled();
157
+ });
158
+
159
+ it("should accept all valid invocation types", async () => {
160
+ vi.mocked(functionActions.createFunction).mockResolvedValue();
161
+
162
+ const validInvocations = ["request", "response"];
163
+
164
+ for (const invocation of validInvocations) {
165
+ vi.clearAllMocks();
166
+
167
+ const result = await executeCLI(program, [
168
+ "function",
169
+ "create",
170
+ "--name",
171
+ "test-func",
172
+ "--invocation",
173
+ invocation,
174
+ ]);
175
+
176
+ expect(result.exitCode).toBe(0);
177
+ expect(functionActions.createFunction).toHaveBeenCalledWith(
178
+ expect.objectContaining({
179
+ invocation,
180
+ }),
181
+ expect.any(Object),
182
+ );
183
+ }
184
+ });
185
+ });
186
+
187
+ describe("help command", () => {
188
+ it("should show examples in help output", async () => {
189
+ const result = await executeCLI(program, [
190
+ "function",
191
+ "create",
192
+ "--help",
193
+ ]);
194
+
195
+ const output = result.stdout.join("") + result.stderr.join("");
196
+ expect(output).toContain("Examples:");
197
+ expect(output).toContain("Create a function to validate orders:");
198
+ expect(output).toContain(
199
+ "$ ollieshop function create --name validate-order --event order --timing before",
200
+ );
201
+ expect(output).toContain("Create a discount function for cart:");
202
+ expect(output).toContain("Create a JavaScript function without tests:");
203
+ });
204
+ });
205
+
206
+ describe("unknown options", () => {
207
+ it("should provide helpful error for unknown options", async () => {
208
+ const result = await executeCLI(program, [
209
+ "function",
210
+ "create",
211
+ "--name",
212
+ "test",
213
+ "--unknown",
214
+ ]);
215
+
216
+ expect(result.exitCode).toBe(1);
217
+ expect(result.stderr.join("")).toContain("unknown option '--unknown'");
218
+ });
219
+ });
220
+ });