@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.
- package/.turbo/turbo-build.log +2 -11
- package/CHANGELOG.md +13 -7
- package/CLAUDE_CLI.md +265 -0
- package/README.md +704 -8
- package/__tests__/mocks/console.ts +22 -0
- package/__tests__/mocks/core.ts +137 -0
- package/__tests__/mocks/index.ts +4 -0
- package/__tests__/mocks/inquirer.ts +16 -0
- package/__tests__/mocks/progress.ts +19 -0
- package/dist/__tests__/helpers/cli-test-helper.d.ts +89 -0
- package/dist/__tests__/helpers/cli-test-helper.d.ts.map +1 -0
- package/dist/__tests__/helpers/cli-test-helper.js +220 -0
- package/dist/__tests__/mocks/index.d.ts +69 -0
- package/dist/__tests__/mocks/index.d.ts.map +1 -0
- package/dist/__tests__/mocks/index.js +77 -0
- package/dist/actions/component.actions.d.ts +14 -0
- package/dist/actions/component.actions.d.ts.map +1 -0
- package/dist/actions/component.actions.js +273 -0
- package/dist/actions/function.actions.d.ts +15 -0
- package/dist/actions/function.actions.d.ts.map +1 -0
- package/dist/actions/function.actions.js +254 -0
- package/dist/actions/project.actions.d.ts +17 -0
- package/dist/actions/project.actions.d.ts.map +1 -0
- package/dist/actions/project.actions.js +97 -0
- package/dist/actions/version.actions.d.ts +19 -0
- package/dist/actions/version.actions.d.ts.map +1 -0
- package/dist/actions/version.actions.js +216 -0
- package/dist/commands/component.d.ts +3 -0
- package/dist/commands/component.d.ts.map +1 -0
- package/dist/commands/component.js +192 -0
- package/dist/commands/docs.d.ts +3 -0
- package/dist/commands/docs.d.ts.map +1 -0
- package/dist/commands/docs.js +16 -0
- package/dist/commands/function.d.ts +3 -0
- package/dist/commands/function.d.ts.map +1 -0
- package/dist/commands/function.js +243 -0
- package/dist/commands/help.d.ts +3 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +20 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +26 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +175 -0
- package/dist/commands/project.d.ts +3 -0
- package/dist/commands/project.d.ts.map +1 -0
- package/dist/commands/project.js +78 -0
- package/dist/commands/store-version.d.ts +3 -0
- package/dist/commands/store-version.d.ts.map +1 -0
- package/dist/commands/store-version.js +241 -0
- package/dist/commands/version.d.ts +3 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +46 -0
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +41 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -478
- package/dist/prompts/component.prompts.d.ts +14 -0
- package/dist/prompts/component.prompts.d.ts.map +1 -0
- package/dist/prompts/component.prompts.js +75 -0
- package/dist/prompts/function.prompts.d.ts +21 -0
- package/dist/prompts/function.prompts.d.ts.map +1 -0
- package/dist/prompts/function.prompts.js +127 -0
- package/dist/schemas/command.schema.d.ts +516 -0
- package/dist/schemas/command.schema.d.ts.map +1 -0
- package/dist/schemas/command.schema.js +267 -0
- package/dist/types/index.d.ts +147 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +18 -0
- package/dist/utils/auth.d.ts +4 -0
- package/dist/utils/auth.d.ts.map +1 -0
- package/dist/utils/auth.js +26 -0
- package/dist/utils/cli-progress-reporter.d.ts +12 -0
- package/dist/utils/cli-progress-reporter.d.ts.map +1 -0
- package/dist/utils/cli-progress-reporter.js +77 -0
- package/dist/utils/command-builder.d.ts +22 -0
- package/dist/utils/command-builder.d.ts.map +1 -0
- package/dist/utils/command-builder.js +268 -0
- package/dist/utils/command-helpers.d.ts +19 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/command-helpers.js +79 -0
- package/dist/utils/command-parser.d.ts +146 -0
- package/dist/utils/command-parser.d.ts.map +1 -0
- package/dist/utils/command-parser.js +179 -0
- package/dist/utils/command-suggestions.d.ts +35 -0
- package/dist/utils/command-suggestions.d.ts.map +1 -0
- package/dist/utils/command-suggestions.js +152 -0
- package/dist/utils/console.d.ts +44 -0
- package/dist/utils/console.d.ts.map +1 -0
- package/dist/utils/console.js +233 -0
- package/dist/utils/constants.d.ts +8 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +10 -0
- package/dist/utils/context-detector.d.ts +12 -0
- package/dist/utils/context-detector.d.ts.map +1 -0
- package/dist/utils/context-detector.js +155 -0
- package/dist/utils/enhanced-error-handler.d.ts +47 -0
- package/dist/utils/enhanced-error-handler.d.ts.map +1 -0
- package/dist/utils/enhanced-error-handler.js +221 -0
- package/dist/utils/error-handler.d.ts +3 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +55 -0
- package/dist/utils/errors.d.ts +44 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +76 -0
- package/dist/utils/interactive-builder.d.ts +22 -0
- package/dist/utils/interactive-builder.d.ts.map +1 -0
- package/dist/utils/interactive-builder.js +246 -0
- package/dist/utils/rich-progress.d.ts +59 -0
- package/dist/utils/rich-progress.d.ts.map +1 -0
- package/dist/utils/rich-progress.js +234 -0
- package/dist/utils/store.d.ts +11 -0
- package/dist/utils/store.d.ts.map +1 -0
- package/dist/utils/store.js +19 -0
- package/dist/utils/validation-error-formatter.d.ts +25 -0
- package/dist/utils/validation-error-formatter.d.ts.map +1 -0
- package/dist/utils/validation-error-formatter.js +258 -0
- package/dist/utils/validation-helpers.d.ts +60 -0
- package/dist/utils/validation-helpers.d.ts.map +1 -0
- package/dist/utils/validation-helpers.js +152 -0
- package/package.json +43 -11
- package/src/__tests__/helpers/cli-test-helper.ts +281 -0
- package/src/__tests__/mocks/index.ts +142 -0
- package/src/actions/component.actions.ts +334 -0
- package/src/actions/function.actions.ts +313 -0
- package/src/actions/project.actions.ts +126 -0
- package/src/actions/version.actions.ts +233 -0
- package/src/commands/__tests__/component-validation.test.ts +250 -0
- package/src/commands/__tests__/component.test.ts +321 -0
- package/src/commands/__tests__/function-validation.test.ts +220 -0
- package/src/commands/__tests__/function.test.ts +286 -0
- package/src/commands/__tests__/store-version-validation.test.ts +414 -0
- package/src/commands/__tests__/store-version.test.ts +405 -0
- package/src/commands/__tests__/version.test.ts +71 -0
- package/src/commands/component.ts +188 -0
- package/src/commands/docs.ts +11 -11
- package/src/commands/function.ts +252 -0
- package/src/commands/help.ts +8 -18
- package/src/commands/index.ts +14 -7
- package/src/commands/login.ts +19 -79
- package/src/commands/project.ts +107 -0
- package/src/commands/store-version.ts +242 -0
- package/src/commands/version.ts +45 -8
- package/src/commands/whoami.ts +8 -13
- package/src/index.ts +108 -34
- package/src/prompts/component.prompts.ts +94 -0
- package/src/prompts/function.prompts.ts +168 -0
- package/src/schemas/command.schema.ts +354 -0
- package/src/types/index.ts +183 -0
- package/src/utils/__tests__/command-parser.test.ts +159 -0
- package/src/utils/__tests__/command-suggestions.test.ts +185 -0
- package/src/utils/__tests__/console.test.ts +192 -0
- package/src/utils/__tests__/context-detector.test.ts +258 -0
- package/src/utils/__tests__/enhanced-error-handler.test.ts +137 -0
- package/src/utils/__tests__/error-handler.test.ts +107 -0
- package/src/utils/__tests__/rich-progress.test.ts +170 -0
- package/src/utils/__tests__/validation-error-formatter.test.ts +175 -0
- package/src/utils/__tests__/validation-helpers.test.ts +125 -0
- package/src/utils/auth.ts +0 -1
- package/src/utils/cli-progress-reporter.ts +84 -0
- package/src/utils/command-builder.ts +390 -0
- package/src/utils/command-helpers.ts +83 -0
- package/src/utils/command-parser.ts +250 -0
- package/src/utils/command-suggestions.ts +176 -0
- package/src/utils/console.ts +291 -0
- package/src/utils/context-detector.ts +177 -0
- package/src/utils/enhanced-error-handler.ts +264 -0
- package/src/utils/error-handler.ts +60 -0
- package/src/utils/errors.ts +125 -0
- package/src/utils/interactive-builder.ts +271 -0
- package/src/utils/rich-progress.ts +320 -0
- package/src/utils/validation-error-formatter.ts +337 -0
- package/src/utils/validation-helpers.ts +192 -0
- package/tsconfig.json +13 -7
- package/vitest.config.ts +28 -0
- package/vitest.setup.ts +29 -0
- package/src/commands/validate.ts +0 -62
- package/src/utils/core.ts +0 -105
- package/tsup.config.ts +0 -15
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { CliConsole } from "./console.js";
|
|
3
|
+
import { detectProjectContext } from "./context-detector.js";
|
|
4
|
+
|
|
5
|
+
interface CommandSuggestion {
|
|
6
|
+
command: string;
|
|
7
|
+
description: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class CommandSuggestions {
|
|
11
|
+
constructor(private console: CliConsole) {}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Show context-aware command suggestions
|
|
15
|
+
*/
|
|
16
|
+
show(): void {
|
|
17
|
+
const context = detectProjectContext();
|
|
18
|
+
|
|
19
|
+
// Show header
|
|
20
|
+
this.console.log("");
|
|
21
|
+
|
|
22
|
+
// Show current context
|
|
23
|
+
this.showCurrentContext(context);
|
|
24
|
+
|
|
25
|
+
// Show relevant commands
|
|
26
|
+
this.showContextCommands(context);
|
|
27
|
+
|
|
28
|
+
// Show recent commands if available
|
|
29
|
+
this.showRecentCommands();
|
|
30
|
+
|
|
31
|
+
// Show helpful tip
|
|
32
|
+
this.showTip(context);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Show current directory context
|
|
37
|
+
*/
|
|
38
|
+
private showCurrentContext(
|
|
39
|
+
context: ReturnType<typeof detectProjectContext>,
|
|
40
|
+
): void {
|
|
41
|
+
this.console.log(chalk.blue("📍 Current location:"));
|
|
42
|
+
|
|
43
|
+
if (context.type === "component") {
|
|
44
|
+
this.console.log(
|
|
45
|
+
` You're in a component directory: ${chalk.cyan(context.name || "unknown")}`,
|
|
46
|
+
);
|
|
47
|
+
} else if (context.type === "function") {
|
|
48
|
+
this.console.log(
|
|
49
|
+
` You're in a function directory: ${chalk.cyan(context.name || "unknown")}`,
|
|
50
|
+
);
|
|
51
|
+
} else if (context.parentPath) {
|
|
52
|
+
this.console.log(` You're in the project root`);
|
|
53
|
+
} else {
|
|
54
|
+
this.console.log(` ${process.cwd()}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.console.log("");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Show commands relevant to current context
|
|
62
|
+
*/
|
|
63
|
+
private showContextCommands(
|
|
64
|
+
context: ReturnType<typeof detectProjectContext>,
|
|
65
|
+
): void {
|
|
66
|
+
let commands: CommandSuggestion[] = [];
|
|
67
|
+
|
|
68
|
+
if (context.type === "component") {
|
|
69
|
+
commands = [
|
|
70
|
+
{ command: "validate", description: "Check component validity" },
|
|
71
|
+
{ command: "build", description: "Build for production" },
|
|
72
|
+
{ command: "dev", description: "Start development server" },
|
|
73
|
+
{ command: "deploy", description: "Deploy to cloud" },
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
this.console.log(chalk.blue("Common commands for this component:"));
|
|
77
|
+
} else if (context.type === "function") {
|
|
78
|
+
commands = [
|
|
79
|
+
{ command: "validate", description: "Check function validity" },
|
|
80
|
+
{ command: "build", description: "Build function" },
|
|
81
|
+
{ command: "test", description: "Run function tests" },
|
|
82
|
+
{ command: "deploy", description: "Deploy to cloud" },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
this.console.log(chalk.blue("Common commands for this function:"));
|
|
86
|
+
} else {
|
|
87
|
+
commands = [
|
|
88
|
+
{ command: "component create", description: "Create a new component" },
|
|
89
|
+
{ command: "function create", description: "Create a new function" },
|
|
90
|
+
{ command: "component list", description: "List all components" },
|
|
91
|
+
{ command: "function list", description: "List all functions" },
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
this.console.log(chalk.blue("Common commands:"));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Display commands
|
|
98
|
+
const maxCmdLength = Math.max(...commands.map((c) => c.command.length));
|
|
99
|
+
for (const { command, description } of commands) {
|
|
100
|
+
const paddedCmd = command.padEnd(maxCmdLength + 2);
|
|
101
|
+
this.console.log(` ${chalk.cyan(paddedCmd)} ${chalk.dim(description)}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this.console.log("");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Show recently used commands
|
|
109
|
+
*/
|
|
110
|
+
private showRecentCommands(): void {
|
|
111
|
+
// TODO: In a real implementation, we'd read from a history file
|
|
112
|
+
// For now, we'll show some common commands
|
|
113
|
+
const recentCommands = this.getMockRecentCommands();
|
|
114
|
+
|
|
115
|
+
if (recentCommands.length > 0) {
|
|
116
|
+
this.console.log(chalk.blue("Recent commands:"));
|
|
117
|
+
for (const { command, timeAgo } of recentCommands) {
|
|
118
|
+
this.console.log(
|
|
119
|
+
chalk.dim(` ollieshop ${command} ${chalk.dim(`(${timeAgo})`)}`),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
this.console.log("");
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Show helpful tip based on context
|
|
128
|
+
*/
|
|
129
|
+
private showTip(context: ReturnType<typeof detectProjectContext>): void {
|
|
130
|
+
let tip = "";
|
|
131
|
+
|
|
132
|
+
if (context.type === "component") {
|
|
133
|
+
tip = "Run 'ollieshop dev' to start developing this component";
|
|
134
|
+
} else if (context.type === "function") {
|
|
135
|
+
tip = "Run 'ollieshop test' to test this function locally";
|
|
136
|
+
} else if (context.parentPath) {
|
|
137
|
+
tip =
|
|
138
|
+
"Navigate to a component with 'cd components/<name>' or create one with 'ollieshop component create'";
|
|
139
|
+
} else {
|
|
140
|
+
tip = "Run 'ollieshop --help' to see all available commands";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.console.log(chalk.yellow(`💡 Tip: ${tip}`));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get mock recent commands for demo
|
|
148
|
+
* TODO: Implement actual command history
|
|
149
|
+
*/
|
|
150
|
+
private getMockRecentCommands(): Array<{ command: string; timeAgo: string }> {
|
|
151
|
+
const context = detectProjectContext();
|
|
152
|
+
|
|
153
|
+
if (context.type === "component") {
|
|
154
|
+
return [
|
|
155
|
+
{ command: "validate --fix", timeAgo: "2 min ago" },
|
|
156
|
+
{ command: "build --watch", timeAgo: "1 hour ago" },
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
if (context.type === "function") {
|
|
160
|
+
return [
|
|
161
|
+
{ command: "test", timeAgo: "5 min ago" },
|
|
162
|
+
{ command: "validate", timeAgo: "30 min ago" },
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Show command suggestions for the current context
|
|
172
|
+
*/
|
|
173
|
+
export function showCommandSuggestions(console: CliConsole): void {
|
|
174
|
+
const suggestions = new CommandSuggestions(console);
|
|
175
|
+
suggestions.show();
|
|
176
|
+
}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import ora, { type Ora } from "ora";
|
|
3
|
+
import type { OllieShopCLIError } from "./errors";
|
|
4
|
+
|
|
5
|
+
export interface ConsoleOptions {
|
|
6
|
+
quiet?: boolean;
|
|
7
|
+
verbose?: boolean;
|
|
8
|
+
noColor?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class Console {
|
|
12
|
+
private options: ConsoleOptions = {
|
|
13
|
+
quiet: false,
|
|
14
|
+
verbose: false,
|
|
15
|
+
noColor: false,
|
|
16
|
+
};
|
|
17
|
+
private prefix = chalk.blue.bold("[ollie]");
|
|
18
|
+
|
|
19
|
+
setOptions(options: ConsoleOptions = {}) {
|
|
20
|
+
this.options = { ...this.options, ...options };
|
|
21
|
+
|
|
22
|
+
if (options.noColor) {
|
|
23
|
+
chalk.level = 0;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
hint(message: string) {
|
|
28
|
+
const text = chalk.gray(message);
|
|
29
|
+
if (!this.options.quiet) {
|
|
30
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
status(message: string) {
|
|
37
|
+
global.console.log(`${this.prefix} ${message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
info(message: string) {
|
|
41
|
+
const text = `${chalk.blue("ℹ")} ${message}`;
|
|
42
|
+
|
|
43
|
+
if (!this.options.quiet) {
|
|
44
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return text;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
warn(message: string) {
|
|
51
|
+
const text = `${chalk.yellow("⚠")} ${chalk.yellow(message)}`;
|
|
52
|
+
if (!this.options.quiet) {
|
|
53
|
+
global.console.warn(`${this.prefix} ${text}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return text;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
success(message: string) {
|
|
60
|
+
const text = `${chalk.green("✓")} ${chalk.green(message)}`;
|
|
61
|
+
if (!this.options.quiet) {
|
|
62
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return text;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
error(message: string | OllieShopCLIError) {
|
|
69
|
+
if (typeof message === "string") {
|
|
70
|
+
const text = `${chalk.red("✗")} ${chalk.red.bold(message)}`;
|
|
71
|
+
global.console.error(`${this.prefix} ${text}`);
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Handle OllieShopCLIError with rich context
|
|
76
|
+
const text = `${chalk.red("✗")} ${chalk.red.bold(message.message)}`;
|
|
77
|
+
global.console.error(`${this.prefix} ${text}`);
|
|
78
|
+
|
|
79
|
+
if (message.context.command) {
|
|
80
|
+
global.console.error(
|
|
81
|
+
`${this.prefix} Command: ${chalk.cyan(message.context.command)}`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (message.context.file) {
|
|
86
|
+
global.console.error(
|
|
87
|
+
`${this.prefix} File: ${chalk.cyan(message.context.file)}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (message.recoveryActions.length > 0) {
|
|
92
|
+
global.console.error(`${this.prefix} ${chalk.yellow("💡 Suggestions")}:`);
|
|
93
|
+
for (const action of message.recoveryActions) {
|
|
94
|
+
global.console.error(`${this.prefix} • ${action.description}`);
|
|
95
|
+
if (action.command) {
|
|
96
|
+
global.console.error(
|
|
97
|
+
`${this.prefix} ${chalk.gray("$")} ${chalk.yellow(action.command)}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return text;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
prompt(message: string) {
|
|
107
|
+
const text = chalk.cyan(message);
|
|
108
|
+
if (!this.options.quiet) {
|
|
109
|
+
process.stdout.write(`${this.prefix} ${text}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return text;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
header(message: string) {
|
|
116
|
+
const text = chalk.bold.blue(message);
|
|
117
|
+
if (!this.options.quiet) {
|
|
118
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return text;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
processing(message: string) {
|
|
125
|
+
const text = chalk.blue(message);
|
|
126
|
+
if (!this.options.quiet) {
|
|
127
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return text;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
cancelled(message: string) {
|
|
134
|
+
const text = chalk.red(message);
|
|
135
|
+
if (!this.options.quiet) {
|
|
136
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return text;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
important(path: string) {
|
|
143
|
+
const text = chalk.bold(path);
|
|
144
|
+
if (!this.options.quiet) {
|
|
145
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return text;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
code(message: string) {
|
|
152
|
+
const text = chalk.cyan(message);
|
|
153
|
+
if (!this.options.quiet) {
|
|
154
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return text;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Additional methods needed by CLI commands
|
|
161
|
+
debug(message: string) {
|
|
162
|
+
if (this.options.quiet || !this.options.verbose) return;
|
|
163
|
+
|
|
164
|
+
const text = `${chalk.gray("🐛")} ${chalk.gray(message)}`;
|
|
165
|
+
global.console.log(`${this.prefix} ${text}`);
|
|
166
|
+
|
|
167
|
+
return text;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
log(message: string) {
|
|
171
|
+
if (!this.options.quiet) {
|
|
172
|
+
global.console.log(`${this.prefix} ${message}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return message;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
newLine() {
|
|
179
|
+
if (!this.options.quiet) {
|
|
180
|
+
global.console.log("");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
nextSteps(
|
|
185
|
+
title: string,
|
|
186
|
+
steps: Array<{ description: string; command?: string }>,
|
|
187
|
+
) {
|
|
188
|
+
if (this.options.quiet) return;
|
|
189
|
+
|
|
190
|
+
this.newLine();
|
|
191
|
+
this.info(title);
|
|
192
|
+
|
|
193
|
+
for (const step of steps) {
|
|
194
|
+
this.log(` • ${step.description}`);
|
|
195
|
+
if (step.command) {
|
|
196
|
+
this.log(` ${chalk.gray("$")} ${chalk.yellow(step.command)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this.newLine();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
suggestions(items: string[]) {
|
|
203
|
+
if (this.options.quiet) return;
|
|
204
|
+
|
|
205
|
+
this.newLine();
|
|
206
|
+
this.info("Suggestions:");
|
|
207
|
+
|
|
208
|
+
for (const item of items) {
|
|
209
|
+
this.log(` • ${item}`);
|
|
210
|
+
}
|
|
211
|
+
this.newLine();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
dim(message: string) {
|
|
215
|
+
const text = chalk.dim(message);
|
|
216
|
+
if (!this.options.quiet) {
|
|
217
|
+
global.console.log(text);
|
|
218
|
+
}
|
|
219
|
+
return text;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
bold(message: string) {
|
|
223
|
+
return chalk.bold(message);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
table(data: Array<Record<string, string | number | boolean>>) {
|
|
227
|
+
if (this.options.quiet || data.length === 0) return;
|
|
228
|
+
global.console.table(data);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
json(data: Record<string, unknown> | Array<unknown>) {
|
|
232
|
+
if (this.options.quiet) return;
|
|
233
|
+
global.console.log(JSON.stringify(data, null, 2));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async confirm(message: string): Promise<boolean> {
|
|
237
|
+
// For now, return false in non-interactive mode
|
|
238
|
+
// TODO: Implement interactive confirmation
|
|
239
|
+
this.warn(`${message} (y/N)`);
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
listResults<T>(items: T[], formatter: (item: T) => string) {
|
|
244
|
+
if (this.options.quiet) return;
|
|
245
|
+
|
|
246
|
+
if (items.length === 0) {
|
|
247
|
+
this.warn("No items found");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.info(`Found ${items.length} item(s):`);
|
|
252
|
+
this.newLine();
|
|
253
|
+
|
|
254
|
+
for (const item of items) {
|
|
255
|
+
this.log(formatter(item));
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Clean spinner implementation using ora with proper cleanup
|
|
260
|
+
spinner(options: { text: string } | string): Ora {
|
|
261
|
+
const text = typeof options === "string" ? options : options.text;
|
|
262
|
+
|
|
263
|
+
if (this.options.quiet) {
|
|
264
|
+
// Return a mock spinner for quiet mode
|
|
265
|
+
return {
|
|
266
|
+
start: () => this.spinner(options),
|
|
267
|
+
stop: () => this.spinner(options),
|
|
268
|
+
succeed: (msg?: string) => {
|
|
269
|
+
if (msg) this.success(msg);
|
|
270
|
+
return this.spinner(options);
|
|
271
|
+
},
|
|
272
|
+
fail: (msg?: string) => {
|
|
273
|
+
if (msg) this.error(msg);
|
|
274
|
+
return this.spinner(options);
|
|
275
|
+
},
|
|
276
|
+
} as Ora;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Create a real spinner using ora with prefix
|
|
280
|
+
return ora({
|
|
281
|
+
text,
|
|
282
|
+
color: "cyan",
|
|
283
|
+
prefixText: this.prefix,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Export Console type for backwards compatibility
|
|
289
|
+
export type CliConsole = Console;
|
|
290
|
+
|
|
291
|
+
export const console = new Console();
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
export interface ProjectContext {
|
|
5
|
+
type: "component" | "function" | "unknown";
|
|
6
|
+
path: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
parentPath?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detect if we're in a component or function directory
|
|
13
|
+
* by looking for characteristic files
|
|
14
|
+
*/
|
|
15
|
+
function checkForMetaType(
|
|
16
|
+
cwd: string,
|
|
17
|
+
hasComponentIndex: boolean,
|
|
18
|
+
hasFunctionIndex: boolean,
|
|
19
|
+
): ProjectContext | null {
|
|
20
|
+
try {
|
|
21
|
+
const metaContent = fs.readFileSync(path.join(cwd, "meta.json"), "utf-8");
|
|
22
|
+
const meta = JSON.parse(metaContent);
|
|
23
|
+
const name = meta.name || path.basename(cwd);
|
|
24
|
+
|
|
25
|
+
if (meta.type === "component" || hasComponentIndex) {
|
|
26
|
+
return { type: "component", path: cwd, name };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (meta.type === "function" || hasFunctionIndex) {
|
|
30
|
+
return { type: "function", path: cwd, name };
|
|
31
|
+
}
|
|
32
|
+
} catch (_error) {
|
|
33
|
+
// If meta.json is invalid, return null to fall back
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function checkByFilePresence(
|
|
40
|
+
cwd: string,
|
|
41
|
+
hasComponentIndex: boolean,
|
|
42
|
+
hasFunctionIndex: boolean,
|
|
43
|
+
): ProjectContext | null {
|
|
44
|
+
const name = path.basename(cwd);
|
|
45
|
+
|
|
46
|
+
if (hasComponentIndex) {
|
|
47
|
+
return { type: "component", path: cwd, name };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (hasFunctionIndex) {
|
|
51
|
+
return { type: "function", path: cwd, name };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function checkParentDirectory(cwd: string): ProjectContext | null {
|
|
58
|
+
const componentsDir = path.join(cwd, "components");
|
|
59
|
+
const functionsDir = path.join(cwd, "functions");
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
fs.existsSync(componentsDir) &&
|
|
63
|
+
fs.statSync(componentsDir).isDirectory()
|
|
64
|
+
) {
|
|
65
|
+
return { type: "unknown", path: cwd, parentPath: cwd };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (fs.existsSync(functionsDir) && fs.statSync(functionsDir).isDirectory()) {
|
|
69
|
+
return { type: "unknown", path: cwd, parentPath: cwd };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function checkInsideSubdirectory(cwd: string): ProjectContext | null {
|
|
76
|
+
const pathParts = cwd.split(path.sep);
|
|
77
|
+
const componentsIndex = pathParts.lastIndexOf("components");
|
|
78
|
+
const functionsIndex = pathParts.lastIndexOf("functions");
|
|
79
|
+
|
|
80
|
+
if (componentsIndex !== -1 && componentsIndex === pathParts.length - 2) {
|
|
81
|
+
return {
|
|
82
|
+
type: "component",
|
|
83
|
+
path: cwd,
|
|
84
|
+
name: pathParts[componentsIndex + 1],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (functionsIndex !== -1 && functionsIndex === pathParts.length - 2) {
|
|
89
|
+
return {
|
|
90
|
+
type: "function",
|
|
91
|
+
path: cwd,
|
|
92
|
+
name: pathParts[functionsIndex + 1],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function detectProjectContext(
|
|
100
|
+
cwd: string = process.cwd(),
|
|
101
|
+
): ProjectContext {
|
|
102
|
+
const hasPackageJson = fs.existsSync(path.join(cwd, "package.json"));
|
|
103
|
+
const hasMetaJson = fs.existsSync(path.join(cwd, "meta.json"));
|
|
104
|
+
const hasComponentIndex =
|
|
105
|
+
fs.existsSync(path.join(cwd, "index.tsx")) ||
|
|
106
|
+
fs.existsSync(path.join(cwd, "index.jsx"));
|
|
107
|
+
const hasFunctionIndex =
|
|
108
|
+
fs.existsSync(path.join(cwd, "index.ts")) ||
|
|
109
|
+
fs.existsSync(path.join(cwd, "index.js"));
|
|
110
|
+
|
|
111
|
+
// If we have the basic files, determine the type
|
|
112
|
+
if (hasPackageJson && hasMetaJson) {
|
|
113
|
+
const metaResult = checkForMetaType(
|
|
114
|
+
cwd,
|
|
115
|
+
hasComponentIndex,
|
|
116
|
+
hasFunctionIndex,
|
|
117
|
+
);
|
|
118
|
+
if (metaResult) return metaResult;
|
|
119
|
+
|
|
120
|
+
const fileResult = checkByFilePresence(
|
|
121
|
+
cwd,
|
|
122
|
+
hasComponentIndex,
|
|
123
|
+
hasFunctionIndex,
|
|
124
|
+
);
|
|
125
|
+
if (fileResult) return fileResult;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Check parent directory
|
|
129
|
+
const parentResult = checkParentDirectory(cwd);
|
|
130
|
+
if (parentResult) return parentResult;
|
|
131
|
+
|
|
132
|
+
// Check if inside subdirectory
|
|
133
|
+
const subdirResult = checkInsideSubdirectory(cwd);
|
|
134
|
+
if (subdirResult) return subdirResult;
|
|
135
|
+
|
|
136
|
+
return { type: "unknown", path: cwd };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get the appropriate path for a command based on context
|
|
141
|
+
*/
|
|
142
|
+
export function getContextualPath(
|
|
143
|
+
explicitPath: string | undefined,
|
|
144
|
+
expectedType: "component" | "function",
|
|
145
|
+
): string {
|
|
146
|
+
// If explicit path provided, use it
|
|
147
|
+
if (explicitPath) {
|
|
148
|
+
return explicitPath;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Detect current context
|
|
152
|
+
const context = detectProjectContext();
|
|
153
|
+
|
|
154
|
+
// If we're in the right type of directory, use current directory
|
|
155
|
+
if (context.type === expectedType) {
|
|
156
|
+
return context.path;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// If we're in a parent directory, provide helpful error
|
|
160
|
+
if (context.type === "unknown" && context.parentPath) {
|
|
161
|
+
const subdir = expectedType === "component" ? "components" : "functions";
|
|
162
|
+
throw new Error(
|
|
163
|
+
`No ${expectedType} found in current directory.\nTry navigating to a ${expectedType} directory:\n cd ${subdir}/<${expectedType}-name>\nOr specify the path:\n --path ./${subdir}/<${expectedType}-name>`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Wrong context type
|
|
168
|
+
if (context.type !== "unknown" && context.type !== expectedType) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Current directory appears to be a ${context.type}, not a ${expectedType}.\n` +
|
|
171
|
+
`Please navigate to a ${expectedType} directory or specify --path`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Default to current directory
|
|
176
|
+
return process.cwd();
|
|
177
|
+
}
|