@ollie-shop/cli 0.3.4 → 1.0.1

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 (79) hide show
  1. package/.turbo/turbo-build.log +6 -9
  2. package/CHANGELOG.md +27 -0
  3. package/dist/index.js +993 -3956
  4. package/package.json +15 -37
  5. package/src/README.md +126 -0
  6. package/src/cli.tsx +45 -0
  7. package/src/commands/help.tsx +79 -0
  8. package/src/commands/login.tsx +92 -0
  9. package/src/commands/start.tsx +411 -0
  10. package/src/index.tsx +8 -0
  11. package/src/utils/auth.ts +218 -21
  12. package/src/utils/bundle.ts +177 -0
  13. package/src/utils/config.ts +123 -0
  14. package/src/utils/esbuild.ts +541 -0
  15. package/tsconfig.json +10 -15
  16. package/tsup.config.ts +7 -7
  17. package/CLAUDE_CLI.md +0 -265
  18. package/README.md +0 -711
  19. package/__tests__/mocks/console.ts +0 -22
  20. package/__tests__/mocks/core.ts +0 -137
  21. package/__tests__/mocks/index.ts +0 -4
  22. package/__tests__/mocks/inquirer.ts +0 -16
  23. package/__tests__/mocks/progress.ts +0 -19
  24. package/dist/index.d.ts +0 -1
  25. package/src/__tests__/helpers/cli-test-helper.ts +0 -281
  26. package/src/__tests__/mocks/index.ts +0 -142
  27. package/src/actions/component.actions.ts +0 -278
  28. package/src/actions/function.actions.ts +0 -220
  29. package/src/actions/project.actions.ts +0 -131
  30. package/src/actions/version.actions.ts +0 -233
  31. package/src/commands/__tests__/component-validation.test.ts +0 -250
  32. package/src/commands/__tests__/component.test.ts +0 -318
  33. package/src/commands/__tests__/function-validation.test.ts +0 -220
  34. package/src/commands/__tests__/function.test.ts +0 -286
  35. package/src/commands/__tests__/store-version-validation.test.ts +0 -414
  36. package/src/commands/__tests__/store-version.test.ts +0 -402
  37. package/src/commands/component.ts +0 -178
  38. package/src/commands/docs.ts +0 -24
  39. package/src/commands/function.ts +0 -201
  40. package/src/commands/help.ts +0 -18
  41. package/src/commands/index.ts +0 -27
  42. package/src/commands/login.ts +0 -267
  43. package/src/commands/project.ts +0 -107
  44. package/src/commands/store-version.ts +0 -242
  45. package/src/commands/version.ts +0 -51
  46. package/src/commands/whoami.ts +0 -46
  47. package/src/index.ts +0 -116
  48. package/src/prompts/component.prompts.ts +0 -94
  49. package/src/prompts/function.prompts.ts +0 -168
  50. package/src/schemas/command.schema.ts +0 -644
  51. package/src/types/index.ts +0 -183
  52. package/src/utils/__tests__/command-parser.test.ts +0 -159
  53. package/src/utils/__tests__/command-suggestions.test.ts +0 -185
  54. package/src/utils/__tests__/console.test.ts +0 -192
  55. package/src/utils/__tests__/context-detector.test.ts +0 -258
  56. package/src/utils/__tests__/enhanced-error-handler.test.ts +0 -137
  57. package/src/utils/__tests__/error-handler.test.ts +0 -107
  58. package/src/utils/__tests__/rich-progress.test.ts +0 -181
  59. package/src/utils/__tests__/validation-error-formatter.test.ts +0 -175
  60. package/src/utils/__tests__/validation-helpers.test.ts +0 -125
  61. package/src/utils/cli-progress-reporter.ts +0 -84
  62. package/src/utils/command-builder.ts +0 -390
  63. package/src/utils/command-helpers.ts +0 -83
  64. package/src/utils/command-parser.ts +0 -245
  65. package/src/utils/command-suggestions.ts +0 -176
  66. package/src/utils/console.ts +0 -320
  67. package/src/utils/constants.ts +0 -39
  68. package/src/utils/context-detector.ts +0 -177
  69. package/src/utils/deploy-helpers.ts +0 -357
  70. package/src/utils/enhanced-error-handler.ts +0 -264
  71. package/src/utils/error-handler.ts +0 -60
  72. package/src/utils/errors.ts +0 -256
  73. package/src/utils/interactive-builder.ts +0 -325
  74. package/src/utils/rich-progress.ts +0 -331
  75. package/src/utils/store.ts +0 -23
  76. package/src/utils/validation-error-formatter.ts +0 -337
  77. package/src/utils/validation-helpers.ts +0 -325
  78. package/vitest.config.ts +0 -35
  79. package/vitest.setup.ts +0 -29
@@ -1,176 +0,0 @@
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
- }
@@ -1,320 +0,0 @@
1
- import chalk from "chalk";
2
- import inquirer from "inquirer";
3
- import ora, { type Ora } from "ora";
4
- import type { OllieShopCLIError } from "./errors";
5
-
6
- export interface ConsoleOptions {
7
- quiet?: boolean;
8
- verbose?: boolean;
9
- noColor?: boolean;
10
- }
11
-
12
- export class Console {
13
- private options: ConsoleOptions = {
14
- quiet: false,
15
- verbose: false,
16
- noColor: false,
17
- };
18
- private prefix = chalk.blue.bold("[ollie]");
19
-
20
- setOptions(options: ConsoleOptions = {}) {
21
- this.options = { ...this.options, ...options };
22
-
23
- if (options.noColor) {
24
- chalk.level = 0;
25
- }
26
- }
27
-
28
- hint(message: string) {
29
- const text = chalk.gray(message);
30
- if (!this.options.quiet) {
31
- global.console.log(`${this.prefix} ${text}`);
32
- }
33
-
34
- return text;
35
- }
36
-
37
- status(message: string) {
38
- global.console.log(`${this.prefix} ${message}`);
39
- }
40
-
41
- info(message: string) {
42
- const text = `${chalk.blue("ℹ")} ${message}`;
43
-
44
- if (!this.options.quiet) {
45
- global.console.log(`${this.prefix} ${text}`);
46
- }
47
-
48
- return text;
49
- }
50
-
51
- warn(message: string) {
52
- const text = `${chalk.yellow("⚠")} ${chalk.yellow(message)}`;
53
- if (!this.options.quiet) {
54
- global.console.warn(`${this.prefix} ${text}`);
55
- }
56
-
57
- return text;
58
- }
59
-
60
- success(message: string) {
61
- const text = `${chalk.green("✓")} ${chalk.green(message)}`;
62
- if (!this.options.quiet) {
63
- global.console.log(`${this.prefix} ${text}`);
64
- }
65
-
66
- return text;
67
- }
68
-
69
- error(message: string | OllieShopCLIError) {
70
- if (typeof message === "string") {
71
- const text = `${chalk.red("✗")} ${chalk.red.bold(message)}`;
72
- global.console.error(`${this.prefix} ${text}`);
73
- return text;
74
- }
75
-
76
- // Handle OllieShopCLIError with rich context
77
- const text = `${chalk.red("✗")} ${chalk.red.bold(message.message)}`;
78
- global.console.error(`${this.prefix} ${text}`);
79
-
80
- if (message.context.command) {
81
- global.console.error(
82
- `${this.prefix} Command: ${chalk.cyan(message.context.command)}`,
83
- );
84
- }
85
-
86
- if (message.context.file) {
87
- global.console.error(
88
- `${this.prefix} File: ${chalk.cyan(message.context.file)}`,
89
- );
90
- }
91
-
92
- if (message.recoveryActions.length > 0) {
93
- global.console.error(`${this.prefix} ${chalk.yellow("💡 Suggestions")}:`);
94
- for (const action of message.recoveryActions) {
95
- global.console.error(`${this.prefix} • ${action.description}`);
96
- if (action.command) {
97
- global.console.error(
98
- `${this.prefix} ${chalk.gray("$")} ${chalk.yellow(action.command)}`,
99
- );
100
- }
101
- }
102
- }
103
-
104
- return text;
105
- }
106
-
107
- prompt(message: string) {
108
- const text = chalk.cyan(message);
109
- if (!this.options.quiet) {
110
- process.stdout.write(`${this.prefix} ${text}`);
111
- }
112
-
113
- return text;
114
- }
115
-
116
- header(message: string) {
117
- const text = chalk.bold.blue(message);
118
- if (!this.options.quiet) {
119
- global.console.log(`${this.prefix} ${text}`);
120
- }
121
-
122
- return text;
123
- }
124
-
125
- processing(message: string) {
126
- const text = chalk.blue(message);
127
- if (!this.options.quiet) {
128
- global.console.log(`${this.prefix} ${text}`);
129
- }
130
-
131
- return text;
132
- }
133
-
134
- cancelled(message: string) {
135
- const text = chalk.red(message);
136
- if (!this.options.quiet) {
137
- global.console.log(`${this.prefix} ${text}`);
138
- }
139
-
140
- return text;
141
- }
142
-
143
- important(path: string) {
144
- const text = chalk.bold(path);
145
- if (!this.options.quiet) {
146
- global.console.log(`${this.prefix} ${text}`);
147
- }
148
-
149
- return text;
150
- }
151
-
152
- code(message: string) {
153
- const text = chalk.cyan(message);
154
- if (!this.options.quiet) {
155
- global.console.log(`${this.prefix} ${text}`);
156
- }
157
-
158
- return text;
159
- }
160
-
161
- // Additional methods needed by CLI commands
162
- debug(message: string) {
163
- if (this.options.quiet || !this.options.verbose) return;
164
-
165
- const text = `${chalk.gray("🐛")} ${chalk.gray(message)}`;
166
- global.console.log(`${this.prefix} ${text}`);
167
-
168
- return text;
169
- }
170
-
171
- log(message: string) {
172
- if (!this.options.quiet) {
173
- global.console.log(`${this.prefix} ${message}`);
174
- }
175
-
176
- return message;
177
- }
178
-
179
- newLine() {
180
- if (!this.options.quiet) {
181
- global.console.log("");
182
- }
183
- }
184
-
185
- nextSteps(
186
- title: string,
187
- steps: Array<{ description: string; command?: string }>,
188
- ) {
189
- if (this.options.quiet) return;
190
-
191
- this.newLine();
192
- this.info(title);
193
-
194
- for (const step of steps) {
195
- this.log(` • ${step.description}`);
196
- if (step.command) {
197
- this.log(` ${chalk.gray("$")} ${chalk.yellow(step.command)}`);
198
- }
199
- }
200
- this.newLine();
201
- }
202
-
203
- suggestions(items: string[]) {
204
- if (this.options.quiet) return;
205
-
206
- this.newLine();
207
- this.info("Suggestions:");
208
-
209
- for (const item of items) {
210
- this.log(` • ${item}`);
211
- }
212
- this.newLine();
213
- }
214
-
215
- dim(message: string) {
216
- const text = chalk.dim(message);
217
- if (!this.options.quiet) {
218
- global.console.log(text);
219
- }
220
- return text;
221
- }
222
-
223
- bold(message: string) {
224
- return chalk.bold(message);
225
- }
226
-
227
- table(data: Array<Record<string, string | number | boolean>>) {
228
- if (this.options.quiet || data.length === 0) return;
229
- global.console.table(data);
230
- }
231
-
232
- json(data: Record<string, unknown> | Array<unknown>) {
233
- if (this.options.quiet) return;
234
- global.console.log(JSON.stringify(data, null, 2));
235
- }
236
-
237
- async confirm(message: string): Promise<boolean> {
238
- // In quiet mode, default to false for non-interactive usage
239
- if (this.options.quiet) {
240
- return false;
241
- }
242
-
243
- // Check if we're in a TTY environment (interactive terminal)
244
- if (!process.stdin.isTTY) {
245
- this.warn(`${message} (y/N) - Non-interactive mode, defaulting to 'No'`);
246
- return false;
247
- }
248
-
249
- try {
250
- const answer = await inquirer.prompt([
251
- {
252
- type: "confirm",
253
- name: "confirmed",
254
- message,
255
- default: false,
256
- },
257
- ]);
258
-
259
- return answer.confirmed;
260
- } catch (error) {
261
- // Handle Ctrl+C or other interruptions gracefully
262
- if (error && typeof error === "object" && "isTtyError" in error) {
263
- this.warn("Interactive prompt not supported in this environment");
264
- return false;
265
- }
266
-
267
- // For other errors, re-throw
268
- throw error;
269
- }
270
- }
271
-
272
- listResults<T>(items: T[], formatter: (item: T) => string) {
273
- if (this.options.quiet) return;
274
-
275
- if (items.length === 0) {
276
- this.warn("No items found");
277
- return;
278
- }
279
-
280
- this.info(`Found ${items.length} item(s):`);
281
- this.newLine();
282
-
283
- for (const item of items) {
284
- this.log(formatter(item));
285
- }
286
- }
287
-
288
- // Clean spinner implementation using ora with proper cleanup
289
- spinner(options: { text: string } | string): Ora {
290
- const text = typeof options === "string" ? options : options.text;
291
-
292
- if (this.options.quiet) {
293
- // Return a mock spinner for quiet mode
294
- return {
295
- start: () => this.spinner(options),
296
- stop: () => this.spinner(options),
297
- succeed: (msg?: string) => {
298
- if (msg) this.success(msg);
299
- return this.spinner(options);
300
- },
301
- fail: (msg?: string) => {
302
- if (msg) this.error(msg);
303
- return this.spinner(options);
304
- },
305
- } as Ora;
306
- }
307
-
308
- // Create a real spinner using ora with prefix
309
- return ora({
310
- text,
311
- color: "cyan",
312
- prefixText: this.prefix,
313
- });
314
- }
315
- }
316
-
317
- // Export Console type for backwards compatibility
318
- export type CliConsole = Console;
319
-
320
- export const console = new Console();
@@ -1,39 +0,0 @@
1
- export const SRC_DIR = "src";
2
- export const COMPONENTS_DIR = "components";
3
- export const DIST_DIR = "dist";
4
- export const META_FILE = "meta.json";
5
- export const CONFIG_FILE = "ollie.json";
6
- export const OLLIE_SHOP_ASSETS_DIR = "node_modules/.ollie-shop";
7
- export const TEMPLATES_DIR = "templates";
8
-
9
- // Note: Deployment URLs are dynamically generated by the infrastructure
10
- // Components are deployed to CloudFront (router.url from SST)
11
- // Functions are deployed to Lambda (managed by AWS)
12
-
13
- // Error messages with actionable guidance
14
- export const ERROR_MESSAGES = {
15
- DEPLOYMENT_TIMEOUT:
16
- "⏱️ Deployment timed out after 5 minutes. This may indicate a network issue or resource constraints.",
17
- BUILD_FAILED:
18
- "❌ Build failed. Check the build logs above for specific error details.",
19
- BUILD_VALIDATION_FAILED:
20
- "❌ Build validation failed. Ensure your code meets the required standards.",
21
- UNKNOWN_ERROR:
22
- "❌ An unexpected error occurred. Please try again or contact support if the issue persists.",
23
- COMPONENT_ID_REQUIRED:
24
- "Component ID is required for deployment. Use: ollieshop component deploy --id <component-id>",
25
- FUNCTION_ID_REQUIRED:
26
- "Function ID is required for deployment. Use: ollieshop function deploy --id <function-id>",
27
- } as const;
28
-
29
- // Note: Build status messages are available from @ollie-shop/core
30
- // Use getBuildProgressMessage() from core instead of duplicating
31
-
32
- // Deployment settings
33
- export const DEPLOYMENT_SETTINGS = {
34
- DEFAULT_POLL_INTERVAL_MS: 2000,
35
- DEFAULT_MAX_WAIT_MS: 300000, // 5 minutes
36
- PROGRESS_UPDATE_INTERVAL_MS: 1000,
37
- ESTIMATED_BUILD_DURATION_MS: 30000, // 30 seconds
38
- MAX_PROGRESS_BEFORE_COMPLETION: 0.95,
39
- } as const;
@@ -1,177 +0,0 @@
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
- }