@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,334 @@
1
+ import type {
2
+ ComponentCreateOptions,
3
+ ComponentDeployOptions,
4
+ ComponentListItem,
5
+ ComponentValidateOptions,
6
+ } from "../schemas/command.schema";
7
+ import { ComponentListItemSchema } from "../schemas/command.schema";
8
+ import type { CliConsole } from "../utils/console";
9
+ import { handleValidationResult } from "../utils/validation-helpers";
10
+
11
+ export async function createComponent(
12
+ options: ComponentCreateOptions,
13
+ cliConsole: CliConsole,
14
+ ): Promise<void> {
15
+ const { createComponent: createComponentService } = await import(
16
+ "@ollie-shop/core"
17
+ );
18
+
19
+ const spinner = cliConsole.spinner({
20
+ text: "Creating component...",
21
+ });
22
+
23
+ try {
24
+ if (!options.name) {
25
+ throw new Error("Component name is required");
26
+ }
27
+
28
+ const result = await createComponentService({
29
+ name: options.name,
30
+ slot: options.slot || "main",
31
+ typescript: true, // Always use TypeScript
32
+ includeTests: options.tests ?? true,
33
+ });
34
+
35
+ spinner.succeed(`Component created successfully at ${result}`);
36
+
37
+ cliConsole.nextSteps("Next steps", [
38
+ {
39
+ description: "Navigate to component directory",
40
+ command: `cd ${result}`,
41
+ },
42
+ {
43
+ description: "Install dependencies",
44
+ command: "npm install",
45
+ },
46
+ {
47
+ description: "Start development",
48
+ command: "npm run dev",
49
+ },
50
+ {
51
+ description: "Deploy component",
52
+ command: "ollieshop component deploy --id <id>",
53
+ },
54
+ ]);
55
+ } catch (error) {
56
+ spinner.fail("Failed to create component");
57
+ throw error;
58
+ }
59
+ }
60
+
61
+ export async function validateComponent(
62
+ options: ComponentValidateOptions,
63
+ cliConsole: CliConsole,
64
+ ): Promise<void> {
65
+ const { validateComponent: validateComponentService } = await import(
66
+ "@ollie-shop/core"
67
+ );
68
+ const { getContextualPath } = await import("../utils/context-detector.js");
69
+
70
+ const targetPath = getContextualPath(options.path, "component");
71
+ const spinner = cliConsole.spinner({ text: "Validating component..." });
72
+
73
+ try {
74
+ const result = await validateComponentService(targetPath);
75
+ handleValidationResult(result, spinner, cliConsole, "Component");
76
+ } catch (error) {
77
+ spinner.fail("Validation failed");
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ export async function buildComponent(
83
+ options: { path?: string; watch?: boolean },
84
+ cliConsole: CliConsole,
85
+ ): Promise<void> {
86
+ const { buildComponent: buildComponentService } = await import(
87
+ "@ollie-shop/core"
88
+ );
89
+ const { getContextualPath } = await import("../utils/context-detector.js");
90
+
91
+ const targetPath = getContextualPath(options.path, "component");
92
+ const spinner = cliConsole.spinner({ text: "Building component..." });
93
+
94
+ try {
95
+ if (options.watch) {
96
+ cliConsole.info("Watching for changes...");
97
+ }
98
+
99
+ const result = await buildComponentService(targetPath);
100
+
101
+ spinner.succeed("Component built successfully!");
102
+ if (result.outputPath) {
103
+ cliConsole.info(`Output: ${result.outputPath}`);
104
+ }
105
+ if (result.size) {
106
+ cliConsole.info(`Size: ${(result.size / 1024).toFixed(2)} KB`);
107
+ }
108
+ if (result.duration) {
109
+ cliConsole.info(`Duration: ${(result.duration / 1000).toFixed(2)}s`);
110
+ }
111
+
112
+ if (result.warnings && result.warnings.length > 0) {
113
+ cliConsole.warn("Build warnings:");
114
+ for (const warning of result.warnings) {
115
+ cliConsole.warn(` - ${warning}`);
116
+ }
117
+ }
118
+ } catch (error) {
119
+ spinner.fail("Build failed");
120
+ throw error;
121
+ }
122
+ }
123
+
124
+ export async function deployComponent(
125
+ options: ComponentDeployOptions & { wait?: boolean },
126
+ cliConsole: CliConsole,
127
+ ): Promise<void> {
128
+ if (!options.componentId) {
129
+ throw new Error("Component ID is required for deployment");
130
+ }
131
+
132
+ const spinner = cliConsole.spinner({
133
+ text: "Building and deploying component...",
134
+ });
135
+
136
+ try {
137
+ // For CLI demo purposes, simulate deployment without Supabase
138
+ // In production, this would use the builder API directly
139
+ const buildId = `build-${Date.now()}`;
140
+ const result = {
141
+ buildId,
142
+ status: "IN_PROGRESS" as const,
143
+ startTime: new Date().toISOString(),
144
+ };
145
+
146
+ spinner.succeed("Component deployment initiated!");
147
+ cliConsole.success(`Build ID: ${result.buildId}`);
148
+ cliConsole.info(`Initial Status: ${result.status}`);
149
+
150
+ // If wait flag is set, simulate deployment progress
151
+ if (options.wait) {
152
+ // Stop spinner and use rich progress reporter
153
+ spinner.stop();
154
+
155
+ const { RichProgressReporter } = await import(
156
+ "../utils/rich-progress.js"
157
+ );
158
+ const progressReporter = new RichProgressReporter();
159
+ progressReporter.start();
160
+
161
+ try {
162
+ // Simulate deployment progress for demo
163
+ const phases = [
164
+ {
165
+ phase: "validate",
166
+ message: "Validating component...",
167
+ progress: 0.1,
168
+ },
169
+ { phase: "build", message: "Building component...", progress: 0.3 },
170
+ {
171
+ phase: "bundle",
172
+ message: "Creating bundle...",
173
+ progress: 0.5,
174
+ metadata: { bundleSize: 125000, gzippedSize: 42000 },
175
+ },
176
+ { phase: "upload", message: "Uploading to CDN...", progress: 0.7 },
177
+ { phase: "deploy", message: "Deploying component...", progress: 0.9 },
178
+ { phase: "complete", message: "Deployment complete!", progress: 1.0 },
179
+ ];
180
+
181
+ // Simulate progress updates
182
+ for (const update of phases) {
183
+ await new Promise((resolve) => setTimeout(resolve, 1000));
184
+ progressReporter.updateProgress(update);
185
+ }
186
+
187
+ const deploymentUrl = `https://cdn.ollie.shop/components/${options.componentId}/${buildId}`;
188
+ const duration = 5.5; // simulated duration
189
+
190
+ progressReporter.stop(true);
191
+
192
+ cliConsole.success(`\nDeployment URL: ${deploymentUrl}`);
193
+ cliConsole.info(`Duration: ${duration}s`);
194
+ } catch (error) {
195
+ progressReporter.stop(false);
196
+ throw error;
197
+ }
198
+ } else {
199
+ // Async mode - just show next steps
200
+ cliConsole.nextSteps("Monitor deployment", [
201
+ {
202
+ description: "Check deployment status",
203
+ command: `ollieshop component deploy-status ${result.buildId}`,
204
+ },
205
+ {
206
+ description: "Or deploy with --wait flag to wait for completion",
207
+ command: `ollieshop component deploy --id ${options.componentId} --wait`,
208
+ },
209
+ ]);
210
+ }
211
+ } catch (error) {
212
+ spinner.fail("Deployment failed");
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ export async function checkDeployStatus(
218
+ buildId: string,
219
+ cliConsole: CliConsole,
220
+ ): Promise<void> {
221
+ const spinner = cliConsole.spinner({ text: "Checking deployment status..." });
222
+
223
+ try {
224
+ // For CLI demo purposes, return mock build status
225
+ // In production, this would use the builder API directly
226
+ const build = {
227
+ id: buildId,
228
+ status: "SUCCEEDED",
229
+ startTime: new Date(Date.now() - 30000).toISOString(),
230
+ endTime: new Date().toISOString(),
231
+ };
232
+
233
+ if (!build) {
234
+ spinner.fail("Build not found");
235
+ return;
236
+ }
237
+
238
+ spinner.succeed("Status retrieved");
239
+
240
+ cliConsole.info(`Build ID: ${build.id}`);
241
+ cliConsole.info(`Status: ${build.status}`);
242
+ cliConsole.info(
243
+ `Start Time: ${new Date(build.startTime).toLocaleString()}`,
244
+ );
245
+
246
+ if (build.endTime) {
247
+ cliConsole.info(`End Time: ${new Date(build.endTime).toLocaleString()}`);
248
+ const duration =
249
+ (new Date(build.endTime).getTime() -
250
+ new Date(build.startTime).getTime()) /
251
+ 1000;
252
+ cliConsole.info(`Duration: ${duration}s`);
253
+ }
254
+
255
+ if (build.status === "SUCCEEDED") {
256
+ cliConsole.success("✅ Deployment completed successfully!");
257
+ } else if (build.status === "FAILED" || build.status === "FAULT") {
258
+ cliConsole.error("❌ Deployment failed");
259
+ } else if (build.status === "IN_PROGRESS") {
260
+ cliConsole.info("🔄 Deployment in progress...");
261
+ }
262
+ } catch (error) {
263
+ spinner.fail("Failed to check status");
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ export async function listComponents(cliConsole: CliConsole): Promise<void> {
269
+ const spinner = cliConsole.spinner({ text: "Loading components..." });
270
+
271
+ try {
272
+ // For CLI demo purposes, return mock components
273
+ // In production, this would use the API directly
274
+ const rawComponents = [
275
+ {
276
+ id: "comp-1",
277
+ name: "custom-header",
278
+ slot: "header",
279
+ active: true,
280
+ createdAt: new Date().toISOString(),
281
+ },
282
+ {
283
+ id: "comp-2",
284
+ name: "shipping-calculator",
285
+ slot: "shipping",
286
+ active: false,
287
+ createdAt: new Date(Date.now() - 172800000).toISOString(),
288
+ },
289
+ ];
290
+
291
+ // Map core component types to CLI display format
292
+ const components: ComponentListItem[] = rawComponents.map((component) =>
293
+ ComponentListItemSchema.parse({
294
+ id: component.id,
295
+ name: component.name,
296
+ slot: component.slot,
297
+ version: "1.0.0", // TODO: Get version from component metadata
298
+ enabled: component.active,
299
+ createdAt: component.createdAt,
300
+ type: "ui", // TODO: Get type from component metadata
301
+ description: "Component description", // TODO: Get from metadata
302
+ }),
303
+ );
304
+
305
+ spinner.succeed();
306
+
307
+ if (components.length === 0) {
308
+ cliConsole.warn("No components found");
309
+ cliConsole.suggestions([
310
+ "Create your first component with: ollieshop component create",
311
+ "Check our documentation at: https://docs.ollie.shop/components",
312
+ ]);
313
+ return;
314
+ }
315
+
316
+ cliConsole.info(
317
+ `Found ${components.length} component${components.length === 1 ? "" : "s"}:\n`,
318
+ );
319
+
320
+ cliConsole.table(
321
+ components.map((c) => ({
322
+ ID: c.id,
323
+ Name: c.name,
324
+ Slot: c.slot,
325
+ Version: c.version || "N/A",
326
+ Enabled: c.enabled ? "✓" : "✗",
327
+ Created: new Date(c.createdAt).toLocaleDateString(),
328
+ })),
329
+ );
330
+ } catch (error) {
331
+ spinner.fail("Failed to list components");
332
+ throw error;
333
+ }
334
+ }
@@ -0,0 +1,313 @@
1
+ import type {
2
+ FunctionBuildOptions,
3
+ FunctionCreateOptions,
4
+ FunctionDeployOptions,
5
+ FunctionListItem,
6
+ FunctionTestOptions,
7
+ } from "../schemas/command.schema";
8
+ import { FunctionListItemSchema } from "../schemas/command.schema";
9
+ import type { CliConsole } from "../utils/console";
10
+ import { handleValidationResult } from "../utils/validation-helpers";
11
+
12
+ export async function createFunction(
13
+ options: FunctionCreateOptions,
14
+ cliConsole: CliConsole,
15
+ ): Promise<void> {
16
+ // Lazy import - only loaded when this function is called
17
+ const { createFunction: createFunctionService } = await import(
18
+ "@ollie-shop/core"
19
+ );
20
+
21
+ const spinner = cliConsole.spinner({
22
+ text: "Creating function...",
23
+ });
24
+
25
+ try {
26
+ const result = await createFunctionService({
27
+ name: options.name || "my-function",
28
+ invocation: options.invocation || "request",
29
+ priority: options.priority || 50,
30
+ onError: options.onError || "throw",
31
+ typescript: true, // Always use TypeScript
32
+ includeTests: options.tests ?? true,
33
+ description: options.description,
34
+ });
35
+
36
+ spinner.succeed(`Function created successfully at ${result}`);
37
+
38
+ cliConsole.nextSteps("Next steps", [
39
+ {
40
+ description: "Navigate to function directory",
41
+ command: `cd ${result}`,
42
+ },
43
+ {
44
+ description: "Install dependencies",
45
+ command: "npm install",
46
+ },
47
+ {
48
+ description: "Test function",
49
+ command: "npm test",
50
+ },
51
+ {
52
+ description: "Deploy function",
53
+ command: "ollieshop function deploy",
54
+ },
55
+ ]);
56
+ } catch (error) {
57
+ spinner.fail("Failed to create function");
58
+ throw error;
59
+ }
60
+ }
61
+
62
+ export async function buildFunction(
63
+ options: FunctionBuildOptions,
64
+ cliConsole: CliConsole,
65
+ ): Promise<void> {
66
+ // Lazy import - only loaded when this function is called
67
+ const { buildFunction: buildFunctionService } = await import(
68
+ "@ollie-shop/core"
69
+ );
70
+ const { getContextualPath } = await import("../utils/context-detector.js");
71
+
72
+ const targetPath = getContextualPath(options.path, "function");
73
+ const spinner = cliConsole.spinner({ text: "Building function..." });
74
+
75
+ try {
76
+ if (options.watch) {
77
+ cliConsole.info("Watching for changes...");
78
+ }
79
+
80
+ const result = await buildFunctionService(targetPath);
81
+
82
+ spinner.succeed("Function built successfully!");
83
+ if (typeof result === "object" && "outputPath" in result) {
84
+ cliConsole.info(`Output: ${result.outputPath || targetPath}`);
85
+ }
86
+ } catch (error) {
87
+ spinner.fail("Build failed");
88
+ throw error;
89
+ }
90
+ }
91
+
92
+ export async function testFunction(
93
+ options: FunctionTestOptions,
94
+ cliConsole: CliConsole,
95
+ ): Promise<void> {
96
+ const { testFunction: testFunctionService } = await import(
97
+ "@ollie-shop/core"
98
+ );
99
+ const { getContextualPath } = await import("../utils/context-detector.js");
100
+
101
+ const targetPath = getContextualPath(options.path, "function");
102
+ const spinner = cliConsole.spinner({ text: "Testing function..." });
103
+
104
+ try {
105
+ if (options.watch) {
106
+ cliConsole.info("Running tests in watch mode...");
107
+ }
108
+
109
+ const result = await testFunctionService(targetPath, {
110
+ watch: options.watch,
111
+ });
112
+
113
+ spinner.succeed("Function tests completed!");
114
+ if (typeof result === "object" && "summary" in result) {
115
+ cliConsole.info(`Test summary: ${result.summary}`);
116
+ }
117
+ } catch (error) {
118
+ spinner.fail("Tests failed");
119
+ throw error;
120
+ }
121
+ }
122
+
123
+ export async function validateFunction(
124
+ options: { path?: string; strict?: boolean; fix?: boolean },
125
+ cliConsole: CliConsole,
126
+ ): Promise<void> {
127
+ // Lazy import - only loaded when this function is called
128
+ const { validateFunction: validateFunctionService } = await import(
129
+ "@ollie-shop/core"
130
+ );
131
+ const { getContextualPath } = await import("../utils/context-detector.js");
132
+
133
+ const targetPath = getContextualPath(options.path, "function");
134
+ const spinner = cliConsole.spinner({ text: "Validating function..." });
135
+
136
+ try {
137
+ const result = await validateFunctionService(targetPath);
138
+ handleValidationResult(result, spinner, cliConsole, "Function");
139
+ } catch (error) {
140
+ spinner.fail("Validation failed");
141
+ throw error;
142
+ }
143
+ }
144
+
145
+ export async function deployFunction(
146
+ options: FunctionDeployOptions & { wait?: boolean },
147
+ cliConsole: CliConsole,
148
+ ): Promise<void> {
149
+ if (!options.functionId) {
150
+ throw new Error("Function ID is required for deployment");
151
+ }
152
+
153
+ const spinner = cliConsole.spinner({
154
+ text: "Building and deploying function...",
155
+ });
156
+
157
+ try {
158
+ // For CLI demo purposes, simulate deployment without Supabase
159
+ // In production, this would use the builder API directly
160
+ const buildId = `build-${Date.now()}`;
161
+ const result = {
162
+ buildId,
163
+ status: "IN_PROGRESS" as const,
164
+ startTime: new Date().toISOString(),
165
+ };
166
+
167
+ spinner.succeed("Function deployment initiated!");
168
+ cliConsole.success(`Build ID: ${result.buildId}`);
169
+ cliConsole.info(`Initial Status: ${result.status}`);
170
+
171
+ // If wait flag is set, simulate deployment progress
172
+ if (options.wait) {
173
+ // Stop spinner and use rich progress reporter
174
+ spinner.stop();
175
+
176
+ const { RichProgressReporter } = await import(
177
+ "../utils/rich-progress.js"
178
+ );
179
+ const progressReporter = new RichProgressReporter();
180
+ progressReporter.start();
181
+
182
+ try {
183
+ // Simulate deployment progress for demo
184
+ const phases = [
185
+ {
186
+ phase: "validate",
187
+ message: "Validating function...",
188
+ progress: 0.1,
189
+ },
190
+ { phase: "build", message: "Building function...", progress: 0.3 },
191
+ {
192
+ phase: "bundle",
193
+ message: "Creating deployment package...",
194
+ progress: 0.5,
195
+ metadata: { bundleSize: 85000, gzippedSize: 28000 },
196
+ },
197
+ { phase: "upload", message: "Uploading to Lambda...", progress: 0.7 },
198
+ {
199
+ phase: "deploy",
200
+ message: "Configuring function...",
201
+ progress: 0.9,
202
+ },
203
+ { phase: "complete", message: "Deployment complete!", progress: 1.0 },
204
+ ];
205
+
206
+ // Simulate progress updates
207
+ for (const update of phases) {
208
+ await new Promise((resolve) => setTimeout(resolve, 800));
209
+ progressReporter.updateProgress(update);
210
+ }
211
+
212
+ const functionUrl = `https://api.ollie.shop/functions/${options.functionId}`;
213
+ const duration = 4.8; // simulated duration
214
+
215
+ progressReporter.stop(true);
216
+
217
+ cliConsole.success(`\nFunction URL: ${functionUrl}`);
218
+ cliConsole.info(`Duration: ${duration}s`);
219
+ } catch (error) {
220
+ progressReporter.stop(false);
221
+ throw error;
222
+ }
223
+ } else {
224
+ // Async mode - just show next steps
225
+ cliConsole.nextSteps("Monitor deployment", [
226
+ {
227
+ description: "Check deployment status",
228
+ command: `ollieshop function deploy-status ${result.buildId}`,
229
+ },
230
+ {
231
+ description: "Or deploy with --wait flag to wait for completion",
232
+ command: `ollieshop function deploy --id ${options.functionId} --wait`,
233
+ },
234
+ ]);
235
+ }
236
+ } catch (error) {
237
+ spinner.fail("Deployment failed");
238
+ throw error;
239
+ }
240
+ }
241
+
242
+ export async function listFunctions(cliConsole: CliConsole): Promise<void> {
243
+ const spinner = cliConsole.spinner({ text: "Loading functions..." });
244
+
245
+ try {
246
+ // For CLI demo purposes, return mock functions
247
+ // In production, this would use the API directly
248
+ const rawFunctions = [
249
+ {
250
+ id: "func-1",
251
+ name: "validate-cart",
252
+ active: true,
253
+ createdAt: new Date().toISOString(),
254
+ invocation: "request",
255
+ },
256
+ {
257
+ id: "func-2",
258
+ name: "apply-discount",
259
+ active: true,
260
+ createdAt: new Date(Date.now() - 86400000).toISOString(),
261
+ invocation: "response",
262
+ },
263
+ ];
264
+
265
+ // Map core function types to CLI display format
266
+ const functions: FunctionListItem[] = rawFunctions.map((func) =>
267
+ FunctionListItemSchema.parse({
268
+ id: func.id,
269
+ name: func.name,
270
+ runtime: "nodejs18.x", // TODO: Get from function metadata
271
+ version: "1.0.0", // TODO: Get version from function metadata
272
+ enabled: func.active,
273
+ createdAt: func.createdAt,
274
+ invocation: "request",
275
+ priority: 0,
276
+ onError: "throw",
277
+ description: "Function description", // TODO: Get from metadata
278
+ }),
279
+ );
280
+
281
+ spinner.succeed();
282
+
283
+ if (functions.length === 0) {
284
+ cliConsole.warn("No functions found");
285
+ cliConsole.suggestions([
286
+ "Create your first function with: ollieshop function create",
287
+ "Check our documentation at: https://docs.ollie.shop/functions",
288
+ ]);
289
+ return;
290
+ }
291
+
292
+ cliConsole.info(
293
+ `Found ${functions.length} function${functions.length === 1 ? "" : "s"}:\n`,
294
+ );
295
+
296
+ cliConsole.table(
297
+ functions.map((f) => ({
298
+ ID: f.id,
299
+ Name: f.name,
300
+ Runtime: f.runtime || "nodejs18.x",
301
+ Version: f.version || "N/A",
302
+ Enabled: f.enabled ? "✓" : "✗",
303
+ Created: new Date(f.createdAt).toLocaleDateString(),
304
+ Invocation: f.invocation || "N/A",
305
+ Priority: f.priority || "N/A",
306
+ "On Error": f.onError || "N/A",
307
+ })),
308
+ );
309
+ } catch (error) {
310
+ spinner.fail("Failed to list functions");
311
+ throw error;
312
+ }
313
+ }