@ollie-shop/cli 0.1.3 → 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 (181) hide show
  1. package/.turbo/turbo-build.log +2 -11
  2. package/CHANGELOG.md +17 -5
  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 +93 -226
  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 +44 -9
  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 +24 -0
  140. package/src/commands/function.ts +252 -0
  141. package/src/commands/help.ts +18 -0
  142. package/src/commands/index.ts +21 -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 +51 -0
  147. package/src/commands/whoami.ts +46 -0
  148. package/src/index.ts +110 -15
  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 +41 -0
  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/store.ts +23 -0
  176. package/src/utils/validation-error-formatter.ts +337 -0
  177. package/src/utils/validation-helpers.ts +192 -0
  178. package/tsconfig.json +13 -7
  179. package/vitest.config.ts +28 -0
  180. package/vitest.setup.ts +29 -0
  181. package/tsup.config.ts +0 -15
@@ -0,0 +1,159 @@
1
+ import { InvalidArgumentError } from "@commander-js/extra-typings";
2
+ import { describe, expect, it } from "vitest";
3
+ import {
4
+ parseBooleanOption,
5
+ parseComponentName,
6
+ parseComponentSlot,
7
+ parseFunctionInvocation,
8
+ parseFunctionName,
9
+ parsePath,
10
+ } from "../command-parser";
11
+
12
+ describe("Command Parsers", () => {
13
+ describe("parseComponentName", () => {
14
+ it("should accept valid component names", () => {
15
+ expect(parseComponentName("header-nav")).toBe("header-nav");
16
+ expect(parseComponentName("shopping-cart")).toBe("shopping-cart");
17
+ expect(parseComponentName("product-list-2")).toBe("product-list-2");
18
+ });
19
+
20
+ it("should reject invalid component names", () => {
21
+ expect(() => parseComponentName("")).toThrow(InvalidArgumentError);
22
+ expect(() => parseComponentName("Test")).toThrow(
23
+ /lowercase with hyphens/,
24
+ );
25
+ expect(() => parseComponentName("test component")).toThrow(
26
+ /lowercase with hyphens/,
27
+ );
28
+ expect(() => parseComponentName("test_component")).toThrow(
29
+ /lowercase with hyphens/,
30
+ );
31
+ expect(() => parseComponentName("Test-Component")).toThrow(
32
+ /lowercase with hyphens/,
33
+ );
34
+ });
35
+
36
+ it("should provide helpful error messages", () => {
37
+ try {
38
+ parseComponentName("TestComponent");
39
+ } catch (error) {
40
+ expect(error).toBeInstanceOf(InvalidArgumentError);
41
+ expect(error.message).toContain("Invalid component name");
42
+ expect(error.message).toContain(
43
+ "Component names must be lowercase with hyphens only",
44
+ );
45
+ expect(error.message).toContain("Examples:");
46
+ }
47
+ });
48
+ });
49
+
50
+ describe("parseFunctionName", () => {
51
+ it("should accept valid function names", () => {
52
+ expect(parseFunctionName("validate-order")).toBe("validate-order");
53
+ expect(parseFunctionName("apply-discount")).toBe("apply-discount");
54
+ expect(parseFunctionName("check-inventory-v2")).toBe(
55
+ "check-inventory-v2",
56
+ );
57
+ });
58
+
59
+ it("should reject invalid function names", () => {
60
+ expect(() => parseFunctionName("")).toThrow(InvalidArgumentError);
61
+ expect(() => parseFunctionName("ValidateOrder")).toThrow(
62
+ /lowercase with hyphens/,
63
+ );
64
+ expect(() => parseFunctionName("validate order")).toThrow(
65
+ /lowercase with hyphens/,
66
+ );
67
+ });
68
+ });
69
+
70
+ describe("parseComponentSlot", () => {
71
+ it("should accept valid slots", () => {
72
+ expect(parseComponentSlot("header")).toBe("header");
73
+ expect(parseComponentSlot("main")).toBe("main");
74
+ expect(parseComponentSlot("sidebar")).toBe("sidebar");
75
+ expect(parseComponentSlot("footer")).toBe("footer");
76
+ });
77
+
78
+ it("should reject invalid slots", () => {
79
+ expect(() => parseComponentSlot("invalid")).toThrow(InvalidArgumentError);
80
+ expect(() => parseComponentSlot("")).toThrow(InvalidArgumentError);
81
+ });
82
+
83
+ it("should provide list of valid slots in error", () => {
84
+ try {
85
+ parseComponentSlot("invalid");
86
+ } catch (error) {
87
+ expect(error.message).toContain(
88
+ "Valid slots: header, footer, sidebar, main, checkout-summary, payment-method, shipping-method, customer-info, order-confirmation",
89
+ );
90
+ }
91
+ });
92
+ });
93
+
94
+ describe("parseFunctionInvocation", () => {
95
+ it("should accept valid invocations", () => {
96
+ expect(parseFunctionInvocation("request")).toBe("request");
97
+ expect(parseFunctionInvocation("response")).toBe("response");
98
+ });
99
+
100
+ it("should reject invalid invocations", () => {
101
+ expect(() => parseFunctionInvocation("invalid")).toThrow(
102
+ InvalidArgumentError,
103
+ );
104
+ });
105
+
106
+ it("should provide list of valid invocations in error", () => {
107
+ try {
108
+ parseFunctionInvocation("invalid");
109
+ } catch (error) {
110
+ expect(error.message).toContain("request");
111
+ expect(error.message).toContain("response");
112
+ }
113
+ });
114
+ });
115
+
116
+ describe("parseBooleanOption", () => {
117
+ it("should parse truthy values", () => {
118
+ expect(parseBooleanOption("true")).toBe(true);
119
+ expect(parseBooleanOption("TRUE")).toBe(true);
120
+ expect(parseBooleanOption("yes")).toBe(true);
121
+ expect(parseBooleanOption("y")).toBe(true);
122
+ expect(parseBooleanOption("1")).toBe(true);
123
+ });
124
+
125
+ it("should parse falsy values", () => {
126
+ expect(parseBooleanOption("false")).toBe(false);
127
+ expect(parseBooleanOption("FALSE")).toBe(false);
128
+ expect(parseBooleanOption("no")).toBe(false);
129
+ expect(parseBooleanOption("n")).toBe(false);
130
+ expect(parseBooleanOption("0")).toBe(false);
131
+ });
132
+
133
+ it("should reject invalid values", () => {
134
+ expect(() => parseBooleanOption("maybe")).toThrow(InvalidArgumentError);
135
+ expect(() => parseBooleanOption("")).toThrow(InvalidArgumentError);
136
+ });
137
+
138
+ it("should provide helpful error message", () => {
139
+ try {
140
+ parseBooleanOption("invalid");
141
+ } catch (error) {
142
+ expect(error.message).toContain("Use true/false, yes/no, or 1/0");
143
+ }
144
+ });
145
+ });
146
+
147
+ describe("parsePath", () => {
148
+ it("should accept valid paths", () => {
149
+ expect(parsePath("./components/header")).toBe("./components/header");
150
+ expect(parsePath("/absolute/path")).toBe("/absolute/path");
151
+ expect(parsePath("relative/path")).toBe("relative/path");
152
+ });
153
+
154
+ it("should reject empty paths", () => {
155
+ expect(() => parsePath("")).toThrow(InvalidArgumentError);
156
+ expect(() => parsePath("")).toThrow(/cannot be empty/);
157
+ });
158
+ });
159
+ });
@@ -0,0 +1,185 @@
1
+ import * as fs from "node:fs";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { CommandSuggestions } from "../command-suggestions";
4
+ import type { CliConsole } from "../console";
5
+
6
+ // Mock the fs module
7
+ vi.mock("fs");
8
+
9
+ describe("CommandSuggestions", () => {
10
+ let mockConsole: CliConsole;
11
+ let suggestions: CommandSuggestions;
12
+
13
+ beforeEach(() => {
14
+ // Create a mock console
15
+ mockConsole = {
16
+ log: vi.fn(),
17
+ error: vi.fn(),
18
+ warn: vi.fn(),
19
+ info: vi.fn(),
20
+ success: vi.fn(),
21
+ dim: vi.fn(),
22
+ bold: vi.fn(),
23
+ list: vi.fn(),
24
+ table: vi.fn(),
25
+ json: vi.fn(),
26
+ spinner: vi.fn(),
27
+ confirm: vi.fn(),
28
+ prompt: vi.fn(),
29
+ setOptions: vi.fn(),
30
+ group: vi.fn(),
31
+ groupEnd: vi.fn(),
32
+ separator: vi.fn(),
33
+ suggestions: vi.fn(),
34
+ debug: vi.fn(),
35
+ nextSteps: vi.fn(),
36
+ };
37
+ suggestions = new CommandSuggestions(mockConsole);
38
+ vi.clearAllMocks();
39
+
40
+ // Mock process.cwd
41
+ vi.spyOn(process, "cwd").mockReturnValue("/test/project");
42
+ });
43
+
44
+ describe("show", () => {
45
+ it("should show suggestions for component directory", () => {
46
+ // Mock component detection
47
+ vi.mocked(fs.existsSync).mockImplementation((path) => {
48
+ if (path.toString().includes("package.json")) return true;
49
+ if (path.toString().includes("meta.json")) return true;
50
+ if (path.toString().includes("index.tsx")) return true;
51
+ return false;
52
+ });
53
+
54
+ vi.mocked(fs.readFileSync).mockReturnValue(
55
+ JSON.stringify({ type: "component", name: "header-nav" }),
56
+ );
57
+
58
+ suggestions.show();
59
+
60
+ // Check context display
61
+ expect(mockConsole.log).toHaveBeenCalledWith(
62
+ expect.stringContaining("You're in a component directory: header-nav"),
63
+ );
64
+
65
+ // Check component commands
66
+ expect(mockConsole.log).toHaveBeenCalledWith(
67
+ expect.stringContaining("Common commands for this component:"),
68
+ );
69
+ expect(mockConsole.log).toHaveBeenCalledWith(
70
+ expect.stringContaining("validate"),
71
+ );
72
+ expect(mockConsole.log).toHaveBeenCalledWith(
73
+ expect.stringContaining("build"),
74
+ );
75
+ expect(mockConsole.log).toHaveBeenCalledWith(
76
+ expect.stringContaining("deploy"),
77
+ );
78
+
79
+ // Check tip
80
+ expect(mockConsole.log).toHaveBeenCalledWith(
81
+ expect.stringContaining("Run 'ollieshop dev' to start developing"),
82
+ );
83
+ });
84
+
85
+ it("should show suggestions for function directory", () => {
86
+ // Mock function detection - ensure we ONLY return true for function files
87
+ vi.mocked(fs.existsSync).mockImplementation((path) => {
88
+ const pathStr = path.toString();
89
+ // Check for function-specific files
90
+ const isFunctionFile =
91
+ pathStr.includes("package.json") ||
92
+ pathStr.includes("meta.json") ||
93
+ (pathStr.includes("index.ts") && !pathStr.includes("index.tsx"));
94
+ return isFunctionFile;
95
+ });
96
+
97
+ vi.mocked(fs.readFileSync).mockReturnValue(
98
+ JSON.stringify({ type: "function", name: "validate-order" }),
99
+ );
100
+
101
+ suggestions.show();
102
+
103
+ // Check context display
104
+ expect(mockConsole.log).toHaveBeenCalledWith(
105
+ expect.stringContaining(
106
+ "You're in a function directory: validate-order",
107
+ ),
108
+ );
109
+
110
+ // Check function commands
111
+ expect(mockConsole.log).toHaveBeenCalledWith(
112
+ expect.stringContaining("Common commands for this function:"),
113
+ );
114
+ expect(mockConsole.log).toHaveBeenCalledWith(
115
+ expect.stringContaining("test"),
116
+ );
117
+
118
+ // Check tip
119
+ expect(mockConsole.log).toHaveBeenCalledWith(
120
+ expect.stringContaining("Run 'ollieshop test' to test this function"),
121
+ );
122
+ });
123
+
124
+ it("should show suggestions for project root", () => {
125
+ // Mock project root detection
126
+ vi.mocked(fs.existsSync).mockImplementation((path) => {
127
+ if (path.toString().includes("components")) return true;
128
+ return false;
129
+ });
130
+
131
+ vi.mocked(fs.statSync).mockReturnValue({
132
+ isDirectory: () => true,
133
+ } as fs.Stats);
134
+
135
+ suggestions.show();
136
+
137
+ // Check context display
138
+ expect(mockConsole.log).toHaveBeenCalledWith(
139
+ expect.stringContaining("You're in the project root"),
140
+ );
141
+
142
+ // Check general commands
143
+ expect(mockConsole.log).toHaveBeenCalledWith(
144
+ expect.stringContaining("Common commands:"),
145
+ );
146
+ expect(mockConsole.log).toHaveBeenCalledWith(
147
+ expect.stringContaining("component create"),
148
+ );
149
+ expect(mockConsole.log).toHaveBeenCalledWith(
150
+ expect.stringContaining("function create"),
151
+ );
152
+
153
+ // Check tip
154
+ expect(mockConsole.log).toHaveBeenCalledWith(
155
+ expect.stringContaining(
156
+ "Navigate to a component with 'cd components/<name>'",
157
+ ),
158
+ );
159
+ });
160
+
161
+ it("should show recent commands for component", () => {
162
+ // Mock component detection
163
+ vi.mocked(fs.existsSync).mockImplementation((path) => {
164
+ if (path.toString().includes("package.json")) return true;
165
+ if (path.toString().includes("meta.json")) return true;
166
+ if (path.toString().includes("index.tsx")) return true;
167
+ return false;
168
+ });
169
+
170
+ vi.mocked(fs.readFileSync).mockReturnValue(
171
+ JSON.stringify({ type: "component" }),
172
+ );
173
+
174
+ suggestions.show();
175
+
176
+ // Check recent commands section
177
+ expect(mockConsole.log).toHaveBeenCalledWith(
178
+ expect.stringContaining("Recent commands:"),
179
+ );
180
+ expect(mockConsole.log).toHaveBeenCalledWith(
181
+ expect.stringContaining("validate --fix"),
182
+ );
183
+ });
184
+ });
185
+ });
@@ -0,0 +1,192 @@
1
+ import chalk from "chalk";
2
+ import { beforeEach, describe, expect, it, vi } from "vitest";
3
+ import { Console } from "../console";
4
+
5
+ // Mock chalk
6
+ vi.mock("chalk", () => ({
7
+ default: {
8
+ green: vi.fn((str: string) => `[GREEN]${str}[/GREEN]`),
9
+ red: Object.assign(
10
+ vi.fn((str: string) => `[RED]${str}[/RED]`),
11
+ {
12
+ bold: vi.fn((str: string) => `[RED-BOLD]${str}[/RED-BOLD]`),
13
+ },
14
+ ),
15
+ yellow: vi.fn((str: string) => `[YELLOW]${str}[/YELLOW]`),
16
+ blue: Object.assign(
17
+ vi.fn((str: string) => `[BLUE]${str}[/BLUE]`),
18
+ {
19
+ bold: vi.fn((str: string) => `[BLUE-BOLD]${str}[/BLUE-BOLD]`),
20
+ },
21
+ ),
22
+ gray: vi.fn((str: string) => `[GRAY]${str}[/GRAY]`),
23
+ cyan: vi.fn((str: string) => `[CYAN]${str}[/CYAN]`),
24
+ level: 0,
25
+ },
26
+ }));
27
+
28
+ // Mock ora
29
+ vi.mock("ora", () => ({
30
+ default: vi.fn(() => ({
31
+ start: vi.fn().mockReturnThis(),
32
+ succeed: vi.fn().mockReturnThis(),
33
+ fail: vi.fn().mockReturnThis(),
34
+ warn: vi.fn().mockReturnThis(),
35
+ stop: vi.fn().mockReturnThis(),
36
+ })),
37
+ }));
38
+
39
+ describe("Console Class", () => {
40
+ let consoleInstance: Console;
41
+
42
+ beforeEach(() => {
43
+ vi.clearAllMocks();
44
+ vi.spyOn(global.console, "log").mockImplementation(() => {
45
+ // Intentionally empty for test isolation
46
+ });
47
+ vi.spyOn(global.console, "error").mockImplementation(() => {
48
+ // Intentionally empty for test isolation
49
+ });
50
+ consoleInstance = new Console();
51
+ });
52
+
53
+ afterEach(() => {
54
+ vi.restoreAllMocks();
55
+ });
56
+
57
+ describe("setOptions", () => {
58
+ it("should set noColor option", () => {
59
+ // Act
60
+ consoleInstance.setOptions({ noColor: true });
61
+
62
+ // Assert
63
+ expect(chalk.level).toBe(0);
64
+ });
65
+ });
66
+
67
+ describe("success", () => {
68
+ it("should log success message in green", () => {
69
+ // Act
70
+ consoleInstance.success("Operation completed");
71
+
72
+ // Assert
73
+ expect(global.console.log).toHaveBeenCalledWith(
74
+ expect.stringContaining(
75
+ "[BLUE-BOLD][ollie][/BLUE-BOLD] [GREEN]✓[/GREEN] [GREEN]Operation completed[/GREEN]",
76
+ ),
77
+ );
78
+ });
79
+ });
80
+
81
+ describe("error", () => {
82
+ it("should log error message in red", () => {
83
+ // Act
84
+ consoleInstance.error("Operation failed");
85
+
86
+ // Assert
87
+ expect(global.console.error).toHaveBeenCalledWith(
88
+ expect.stringContaining(
89
+ "[BLUE-BOLD][ollie][/BLUE-BOLD] [RED]✗[/RED] [RED-BOLD]Operation failed[/RED-BOLD]",
90
+ ),
91
+ );
92
+ });
93
+ });
94
+
95
+ describe("info", () => {
96
+ it("should log info message", () => {
97
+ // Act
98
+ consoleInstance.info("Information message");
99
+
100
+ // Assert
101
+ expect(global.console.log).toHaveBeenCalledWith(
102
+ expect.stringContaining("Information message"),
103
+ );
104
+ });
105
+ });
106
+
107
+ describe("warn", () => {
108
+ it("should log warning message in yellow", () => {
109
+ // Act
110
+ consoleInstance.warn("Warning message");
111
+
112
+ // Assert
113
+ expect(global.console.warn).toHaveBeenCalledWith(
114
+ expect.stringContaining(
115
+ "[BLUE-BOLD][ollie][/BLUE-BOLD] [YELLOW]⚠[/YELLOW] [YELLOW]Warning message[/YELLOW]",
116
+ ),
117
+ );
118
+ });
119
+ });
120
+
121
+ describe("hint", () => {
122
+ it("should log hint message in gray", () => {
123
+ // Act
124
+ consoleInstance.hint("Hint message");
125
+
126
+ // Assert
127
+ expect(global.console.log).toHaveBeenCalledWith(
128
+ expect.stringContaining("[GRAY]Hint message[/GRAY]"),
129
+ );
130
+ });
131
+ });
132
+
133
+ describe("spinner", () => {
134
+ it("should create and return a spinner", () => {
135
+ // Act
136
+ const spinner = consoleInstance.spinner("Loading...");
137
+
138
+ // Assert
139
+ expect(spinner).toBeDefined();
140
+ expect(spinner.start).toBeDefined();
141
+ });
142
+ });
143
+
144
+ describe("quiet mode", () => {
145
+ it("should not log messages when quiet mode is enabled", () => {
146
+ // Arrange
147
+ consoleInstance.setOptions({ quiet: true });
148
+
149
+ // Act
150
+ consoleInstance.info("Should not appear");
151
+ consoleInstance.success("Should not appear");
152
+ consoleInstance.warn("Should not appear");
153
+
154
+ // Assert
155
+ expect(global.console.log).not.toHaveBeenCalled();
156
+ });
157
+
158
+ it("should still log errors in quiet mode", () => {
159
+ // Arrange
160
+ consoleInstance.setOptions({ quiet: true });
161
+
162
+ // Act
163
+ consoleInstance.error("Error should appear");
164
+
165
+ // Assert
166
+ expect(global.console.error).toHaveBeenCalled();
167
+ });
168
+ });
169
+
170
+ describe("verbose mode", () => {
171
+ it("should log debug messages when verbose mode is enabled", () => {
172
+ // Arrange
173
+ consoleInstance.setOptions({ verbose: true });
174
+
175
+ // Act
176
+ consoleInstance.debug("Debug message");
177
+
178
+ // Assert
179
+ expect(global.console.log).toHaveBeenCalledWith(
180
+ expect.stringContaining("[GRAY]Debug message[/GRAY]"),
181
+ );
182
+ });
183
+
184
+ it("should not log debug messages when verbose mode is disabled", () => {
185
+ // Act
186
+ consoleInstance.debug("Debug message");
187
+
188
+ // Assert
189
+ expect(global.console.log).not.toHaveBeenCalled();
190
+ });
191
+ });
192
+ });