@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,170 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { RichProgressReporter, SimpleProgressReporter } from "../rich-progress";
3
+
4
+ // Mock cli-progress
5
+ vi.mock("cli-progress", () => {
6
+ const mockBar = {
7
+ update: vi.fn(),
8
+ };
9
+
10
+ const mockMultiBar = {
11
+ create: vi.fn().mockReturnValue(mockBar),
12
+ stop: vi.fn(),
13
+ };
14
+
15
+ return {
16
+ default: {
17
+ MultiBar: vi.fn(() => mockMultiBar),
18
+ Presets: {
19
+ shades_classic: {},
20
+ },
21
+ },
22
+ };
23
+ });
24
+
25
+ // Mock ora
26
+ vi.mock("ora", () => ({
27
+ default: vi.fn(() => ({
28
+ start: vi.fn().mockReturnThis(),
29
+ succeed: vi.fn(),
30
+ fail: vi.fn(),
31
+ text: "",
32
+ })),
33
+ }));
34
+
35
+ describe("RichProgressReporter", () => {
36
+ let reporter: RichProgressReporter;
37
+ let consoleLogSpy: ReturnType<typeof vi.spyOn>;
38
+
39
+ beforeEach(() => {
40
+ // Skip creating the actual reporter to avoid mocking issues
41
+ consoleLogSpy = vi.spyOn(console, "log").mockImplementation(() => {
42
+ // Intentionally empty for test isolation
43
+ });
44
+ vi.clearAllMocks();
45
+ });
46
+
47
+ afterEach(() => {
48
+ consoleLogSpy.mockRestore();
49
+ });
50
+
51
+ it("should start progress tracking", () => {
52
+ reporter = new RichProgressReporter();
53
+ reporter.start();
54
+
55
+ expect(consoleLogSpy).toHaveBeenCalledWith(
56
+ expect.stringContaining("🚀 Starting deployment..."),
57
+ );
58
+ });
59
+
60
+ it("should update progress with build status", () => {
61
+ reporter = new RichProgressReporter();
62
+
63
+ const progress = {
64
+ phase: "build",
65
+ message: "Bundling files...",
66
+ progress: 0.5,
67
+ };
68
+
69
+ // Just verify no errors
70
+ expect(() => reporter.updateProgress(progress)).not.toThrow();
71
+ });
72
+
73
+ it("should show success summary", () => {
74
+ // Test just the summary method
75
+ const reporter = new RichProgressReporter();
76
+ // Access private method for testing
77
+ const reporterWithPrivate = reporter as unknown as {
78
+ showSuccessSummary(): void;
79
+ };
80
+ reporterWithPrivate.showSuccessSummary();
81
+
82
+ expect(consoleLogSpy).toHaveBeenCalledWith(
83
+ expect.stringContaining("✅ Deployment completed successfully!"),
84
+ );
85
+ });
86
+
87
+ it("should show failure message", () => {
88
+ // Create a minimal test that doesn't rely on bars
89
+ const reporter = new RichProgressReporter();
90
+ // Access private properties for testing
91
+ const reporterWithPrivate = reporter as unknown as {
92
+ multibar: { stop: () => void };
93
+ bars: Map<string, unknown>;
94
+ };
95
+ reporterWithPrivate.multibar = { stop: vi.fn() };
96
+ reporterWithPrivate.bars = new Map(); // Empty map
97
+
98
+ reporter.stop(false);
99
+
100
+ expect(consoleLogSpy).toHaveBeenCalledWith(
101
+ expect.stringContaining("❌ Deployment failed"),
102
+ );
103
+ });
104
+
105
+ it("should display build stats when available", () => {
106
+ const reporter = new RichProgressReporter();
107
+
108
+ // Set stats directly for testing
109
+ const reporterWithPrivate = reporter as unknown as {
110
+ stats: { bundleSize?: number; gzippedSize?: number };
111
+ };
112
+ reporterWithPrivate.stats = {
113
+ bundleSize: 150000,
114
+ gzippedSize: 50000,
115
+ };
116
+
117
+ // Call the summary method
118
+ const reporterWithSummary = reporter as unknown as {
119
+ showSuccessSummary(): void;
120
+ };
121
+ reporterWithSummary.showSuccessSummary();
122
+
123
+ expect(consoleLogSpy).toHaveBeenCalledWith(
124
+ expect.stringContaining("📊 Build Stats:"),
125
+ );
126
+ expect(consoleLogSpy).toHaveBeenCalledWith(
127
+ expect.stringContaining("Bundle size: 146.5KB"),
128
+ );
129
+ });
130
+ });
131
+
132
+ describe("SimpleProgressReporter", () => {
133
+ let reporter: SimpleProgressReporter;
134
+
135
+ beforeEach(() => {
136
+ reporter = new SimpleProgressReporter("Testing...");
137
+ vi.clearAllMocks();
138
+ });
139
+
140
+ it("should start spinner", () => {
141
+ reporter.start();
142
+
143
+ // Spinner is mocked, verify no errors
144
+ expect(() => reporter.start()).not.toThrow();
145
+ });
146
+
147
+ it("should update message", () => {
148
+ reporter.start();
149
+ reporter.update("New message");
150
+
151
+ // Update is mocked, verify no errors
152
+ expect(() => reporter.update("test")).not.toThrow();
153
+ });
154
+
155
+ it("should succeed with message", () => {
156
+ reporter.start();
157
+ reporter.succeed("Success!");
158
+
159
+ // Success is mocked, verify no errors
160
+ expect(() => reporter.succeed()).not.toThrow();
161
+ });
162
+
163
+ it("should fail with message", () => {
164
+ reporter.start();
165
+ reporter.fail("Failed!");
166
+
167
+ // Fail is mocked, verify no errors
168
+ expect(() => reporter.fail()).not.toThrow();
169
+ });
170
+ });
@@ -0,0 +1,175 @@
1
+ import { createMockConsole } from "@tests/mocks";
2
+ import { beforeEach, describe, expect, it } from "vitest";
3
+ import { z } from "zod";
4
+ import {
5
+ displayErrors,
6
+ formatZodError,
7
+ suggestCommand,
8
+ } from "../validation-error-formatter";
9
+
10
+ describe("validation-error-formatter", () => {
11
+ let mockConsole: ReturnType<typeof createMockConsole>;
12
+
13
+ beforeEach(() => {
14
+ mockConsole = createMockConsole();
15
+ });
16
+
17
+ describe("formatZodError", () => {
18
+ it("should format required field errors", () => {
19
+ const schema = z.object({
20
+ name: z.string(),
21
+ });
22
+
23
+ const result = schema.safeParse({});
24
+ if (!result.success) {
25
+ const errors = formatZodError(result.error);
26
+ expect(errors).toHaveLength(1);
27
+ expect(errors[0]).toEqual({
28
+ field: "name",
29
+ message: "Name is required",
30
+ });
31
+ } else {
32
+ throw new Error("Expected validation to fail");
33
+ }
34
+ });
35
+
36
+ it("should format regex validation errors", () => {
37
+ const schema = z.object({
38
+ name: z.string().regex(/^[a-z-]+$/),
39
+ });
40
+
41
+ const result = schema.safeParse({ name: "TestComponent" });
42
+ if (!result.success) {
43
+ const errors = formatZodError(result.error);
44
+ expect(errors).toHaveLength(1);
45
+ expect(errors[0]).toEqual({
46
+ field: "name",
47
+ message:
48
+ "Must be lowercase with hyphens only (e.g., 'my-component', 'header-nav')",
49
+ });
50
+ } else {
51
+ throw new Error("Expected validation to fail");
52
+ }
53
+ });
54
+
55
+ it("should format enum errors with suggestions", () => {
56
+ const schema = z.object({
57
+ slot: z.enum(["header", "main", "footer"]),
58
+ });
59
+
60
+ const result = schema.safeParse({ slot: "invalid" });
61
+ if (!result.success) {
62
+ const errors = formatZodError(result.error);
63
+ expect(errors).toHaveLength(1);
64
+ expect(errors[0]).toEqual({
65
+ field: "slot",
66
+ message: "Invalid Slot",
67
+ suggestedValue: "Valid options: header, main, footer",
68
+ });
69
+ } else {
70
+ throw new Error("Expected validation to fail");
71
+ }
72
+ });
73
+
74
+ it("should include examples when context is provided", () => {
75
+ const schema = z.object({
76
+ name: z.string(),
77
+ });
78
+
79
+ const result = schema.safeParse({});
80
+ if (!result.success) {
81
+ const errors = formatZodError(result.error, "component");
82
+ expect(errors[0].examples).toBeDefined();
83
+ expect(errors[0].examples?.[0]).toEqual({
84
+ description: "Create a header component",
85
+ command: "ollieshop component create --name header-nav --slot header",
86
+ });
87
+ } else {
88
+ throw new Error("Expected validation to fail");
89
+ }
90
+ });
91
+ });
92
+
93
+ describe("displayErrors", () => {
94
+ it("should display formatted errors", () => {
95
+ const errors = [
96
+ {
97
+ field: "name",
98
+ message: "Must be lowercase with hyphens",
99
+ suggestedValue: "Valid format: my-component",
100
+ },
101
+ ];
102
+
103
+ displayErrors(mockConsole, errors);
104
+
105
+ expect(mockConsole.error).toHaveBeenCalledWith("❌ Validation failed:");
106
+ expect(mockConsole.error).toHaveBeenCalledWith(
107
+ " • name: Must be lowercase with hyphens",
108
+ );
109
+ expect(mockConsole.log).toHaveBeenCalledWith(
110
+ " 💡 Valid format: my-component",
111
+ );
112
+ });
113
+
114
+ it("should display examples when provided", () => {
115
+ const errors = [
116
+ {
117
+ field: "name",
118
+ message: "Invalid format",
119
+ examples: [
120
+ {
121
+ description: "Create header",
122
+ command: "ollieshop component create --name header",
123
+ },
124
+ ],
125
+ },
126
+ ];
127
+
128
+ displayErrors(mockConsole, errors);
129
+
130
+ expect(mockConsole.log).toHaveBeenCalledWith(" 📝 Examples:");
131
+ expect(mockConsole.log).toHaveBeenCalledWith(" Create header:");
132
+ expect(mockConsole.log).toHaveBeenCalledWith(
133
+ " $ ollieshop component create --name header",
134
+ );
135
+ });
136
+
137
+ it("should show help command when provided", () => {
138
+ const errors = [{ field: "name", message: "Required" }];
139
+
140
+ displayErrors(mockConsole, errors, "component create");
141
+
142
+ expect(mockConsole.info).toHaveBeenCalledWith(
143
+ "💡 For more help, run: ollieshop component create --help",
144
+ );
145
+ });
146
+ });
147
+
148
+ describe("suggestCommand", () => {
149
+ const commands = ["create", "list", "deploy", "validate", "build"];
150
+
151
+ it("should return exact matches", () => {
152
+ expect(suggestCommand("create", commands)).toBe("create");
153
+ expect(suggestCommand("CREATE", commands)).toBe("create");
154
+ });
155
+
156
+ it("should return commands that start with input", () => {
157
+ expect(suggestCommand("cr", commands)).toBe("create");
158
+ expect(suggestCommand("val", commands)).toBe("validate");
159
+ });
160
+
161
+ it("should return commands that contain input", () => {
162
+ expect(suggestCommand("ploy", commands)).toBe("deploy");
163
+ });
164
+
165
+ it("should return close matches with small typos", () => {
166
+ expect(suggestCommand("craete", commands)).toBe("create");
167
+ expect(suggestCommand("buidl", commands)).toBe("build");
168
+ });
169
+
170
+ it("should return undefined for no matches", () => {
171
+ expect(suggestCommand("xyz", commands)).toBeUndefined();
172
+ expect(suggestCommand("random", commands)).toBeUndefined();
173
+ });
174
+ });
175
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ validateComponentName,
4
+ validateFunctionName,
5
+ validatePriority,
6
+ validateSemver,
7
+ validateUrl,
8
+ validateVersionName,
9
+ } from "../validation-helpers";
10
+
11
+ describe("Validation Helpers", () => {
12
+ describe("validateComponentName", () => {
13
+ it("should accept valid component names", () => {
14
+ expect(validateComponentName("my-component")).toBe(true);
15
+ expect(validateComponentName("header-123")).toBe(true);
16
+ expect(validateComponentName("a")).toBe(true);
17
+ expect(validateComponentName("component-with-many-parts")).toBe(true);
18
+ });
19
+
20
+ it("should reject invalid component names", () => {
21
+ expect(validateComponentName("My Component")).toContain(
22
+ "Component names must be lowercase with hyphens only",
23
+ );
24
+ expect(validateComponentName("UPPERCASE")).toContain(
25
+ "Component names must be lowercase with hyphens only",
26
+ );
27
+ expect(validateComponentName("special@char")).toContain(
28
+ "Component names must be lowercase with hyphens only",
29
+ );
30
+ expect(validateComponentName("")).toContain("required");
31
+ expect(validateComponentName("_underscore")).toContain(
32
+ "Component names must be lowercase with hyphens only",
33
+ );
34
+ });
35
+ });
36
+
37
+ describe("validateFunctionName", () => {
38
+ it("should accept valid function names", () => {
39
+ expect(validateFunctionName("my-function")).toBe(true);
40
+ expect(validateFunctionName("auth-handler")).toBe(true);
41
+ expect(validateFunctionName("process-order-v2")).toBe(true);
42
+ });
43
+
44
+ it("should reject invalid function names", () => {
45
+ expect(validateFunctionName("My Function")).toContain(
46
+ "Function names must be lowercase with hyphens only",
47
+ );
48
+ expect(validateFunctionName("function!")).toContain(
49
+ "Function names must be lowercase with hyphens only",
50
+ );
51
+ expect(validateFunctionName("")).toContain("required");
52
+ });
53
+ });
54
+
55
+ describe("validateVersionName", () => {
56
+ it("should accept valid version names", () => {
57
+ expect(validateVersionName("Production")).toBe(true);
58
+ expect(validateVersionName("v1.0")).toBe(true);
59
+ expect(validateVersionName("Development Branch")).toBe(true);
60
+ });
61
+
62
+ it("should reject invalid version names", () => {
63
+ expect(validateVersionName("")).toContain("required");
64
+ expect(validateVersionName("a".repeat(101))).toContain("too long");
65
+ });
66
+ });
67
+
68
+ describe("validatePriority", () => {
69
+ it("should accept valid priorities", () => {
70
+ expect(validatePriority(0)).toBe(true);
71
+ expect(validatePriority(50)).toBe(true);
72
+ expect(validatePriority(100)).toBe(true);
73
+ });
74
+
75
+ it("should reject invalid priorities", () => {
76
+ expect(validatePriority(-1)).toContain("between 0 and 100");
77
+ expect(validatePriority(101)).toContain("between 0 and 100");
78
+ expect(validatePriority(50.5)).toContain("whole number");
79
+ expect(validatePriority(Number.NaN)).toContain(
80
+ "Expected number, received nan",
81
+ );
82
+ });
83
+ });
84
+
85
+ describe("validateUrl", () => {
86
+ it("should accept valid URLs", () => {
87
+ expect(validateUrl("https://example.com")).toBe(true);
88
+ expect(validateUrl("http://localhost:3000")).toBe(true);
89
+ expect(validateUrl("https://api.example.com/v1/endpoint")).toBe(true);
90
+ });
91
+
92
+ it("should accept valid URL patterns", () => {
93
+ expect(validateUrl("/api/v1/*")).toBe(true);
94
+ expect(validateUrl("/*")).toBe(true);
95
+ expect(validateUrl("/users/:id")).toBe(true);
96
+ });
97
+
98
+ it("should reject invalid URLs", () => {
99
+ expect(validateUrl("")).toContain("required");
100
+ expect(validateUrl("not a url")).toContain(
101
+ "URL must be a valid URL or path pattern",
102
+ );
103
+ expect(validateUrl("ftp://example.com")).toContain(
104
+ "URL must be a valid URL or path pattern",
105
+ );
106
+ });
107
+ });
108
+
109
+ describe("validateSemver", () => {
110
+ it("should accept valid semantic versions", () => {
111
+ expect(validateSemver("1.0.0")).toBe(true);
112
+ expect(validateSemver("2.1.3")).toBe(true);
113
+ expect(validateSemver("10.20.30")).toBe(true);
114
+ });
115
+
116
+ it("should reject invalid versions", () => {
117
+ expect(validateSemver("1.0")).toContain("semantic versioning");
118
+ expect(validateSemver("v1.0.0")).toContain("semantic versioning");
119
+ expect(validateSemver("1.0.0-alpha")).toContain("semantic versioning");
120
+ expect(validateSemver("")).toContain(
121
+ "Version must follow semantic versioning format",
122
+ );
123
+ });
124
+ });
125
+ });
package/src/utils/auth.ts CHANGED
@@ -1,4 +1,3 @@
1
- // lib/auth.ts
2
1
  import fs from "node:fs/promises";
3
2
  import { homedir } from "node:os";
4
3
  import path from "node:path";
@@ -0,0 +1,84 @@
1
+ import type { BuildProgress } from "@ollie-shop/core";
2
+ import chalk from "chalk";
3
+ import cliProgress from "cli-progress";
4
+
5
+ const PHASE_DISPLAY_NAMES: Record<string, string> = {
6
+ SUBMITTED: "Queued",
7
+ PROVISIONING: "Setting up",
8
+ DOWNLOAD_SOURCE: "Downloading",
9
+ INSTALL: "Installing deps",
10
+ PRE_BUILD: "Preparing",
11
+ BUILD: "Building",
12
+ POST_BUILD: "Finalizing",
13
+ UPLOAD_ARTIFACTS: "Uploading",
14
+ FINALIZING: "Completing",
15
+ COMPLETED: "Done",
16
+ };
17
+
18
+ export class CLIProgressReporter {
19
+ private bar: cliProgress.SingleBar;
20
+
21
+ constructor() {
22
+ this.bar = new cliProgress.SingleBar({
23
+ format: `Building |${chalk.cyan("{bar}")}| {percentage}% | {phase} | ETA: {eta_formatted}`,
24
+ barCompleteChar: "\u2588",
25
+ barIncompleteChar: "\u2591",
26
+ hideCursor: true,
27
+ stopOnComplete: false,
28
+ clearOnComplete: false,
29
+ });
30
+ }
31
+
32
+ start(): void {
33
+ this.bar.start(100, 0, {
34
+ phase: "Initializing",
35
+ eta_formatted: "calculating...",
36
+ });
37
+ }
38
+
39
+ updateProgress(progress: BuildProgress): void {
40
+ const phaseDisplay = this.formatPhase(progress.currentPhase);
41
+ const etaFormatted = this.formatETA(progress.estimatedRemainingSeconds);
42
+
43
+ this.bar.update(progress.percentComplete, {
44
+ phase: phaseDisplay,
45
+ eta_formatted: etaFormatted,
46
+ });
47
+ }
48
+
49
+ stop(success: boolean): void {
50
+ this.bar.stop();
51
+
52
+ // Clear the line and show final status
53
+ process.stdout.clearLine(0);
54
+ process.stdout.cursorTo(0);
55
+
56
+ if (success) {
57
+ console.log(chalk.green("✓ Build completed successfully!"));
58
+ } else {
59
+ console.log(chalk.red("✗ Build failed"));
60
+ }
61
+ }
62
+
63
+ private formatPhase(phase: string): string {
64
+ return PHASE_DISPLAY_NAMES[phase] || phase;
65
+ }
66
+
67
+ private formatETA(seconds: number): string {
68
+ if (seconds <= 0) {
69
+ return "completing...";
70
+ }
71
+
72
+ if (seconds < 60) {
73
+ return `${Math.ceil(seconds)}s`;
74
+ }
75
+
76
+ const minutes = Math.floor(seconds / 60);
77
+ const remainingSeconds = Math.ceil(seconds % 60);
78
+ return `${minutes}m ${remainingSeconds}s`;
79
+ }
80
+ }
81
+
82
+ export function createCLIProgressReporter(): CLIProgressReporter {
83
+ return new CLIProgressReporter();
84
+ }