@ollie-shop/cli 0.3.3 → 1.0.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 +6 -9
- package/CHANGELOG.md +27 -0
- package/dist/index.js +1003 -40565
- package/package.json +15 -37
- package/src/README.md +126 -0
- package/src/cli.tsx +45 -0
- package/src/commands/help.tsx +79 -0
- package/src/commands/login.tsx +92 -0
- package/src/commands/start.tsx +411 -0
- package/src/index.tsx +8 -0
- package/src/utils/auth.ts +218 -21
- package/src/utils/bundle.ts +177 -0
- package/src/utils/config.ts +123 -0
- package/src/utils/esbuild.ts +533 -0
- package/tsconfig.json +10 -15
- package/tsup.config.ts +8 -10
- package/CLAUDE_CLI.md +0 -265
- package/README.md +0 -711
- package/__tests__/mocks/console.ts +0 -22
- package/__tests__/mocks/core.ts +0 -137
- package/__tests__/mocks/index.ts +0 -4
- package/__tests__/mocks/inquirer.ts +0 -16
- package/__tests__/mocks/progress.ts +0 -19
- package/dist/index.d.ts +0 -1
- package/src/__tests__/helpers/cli-test-helper.ts +0 -281
- package/src/__tests__/mocks/index.ts +0 -142
- package/src/actions/component.actions.ts +0 -278
- package/src/actions/function.actions.ts +0 -220
- package/src/actions/project.actions.ts +0 -131
- package/src/actions/version.actions.ts +0 -233
- package/src/commands/__tests__/component-validation.test.ts +0 -250
- package/src/commands/__tests__/component.test.ts +0 -318
- package/src/commands/__tests__/function-validation.test.ts +0 -220
- package/src/commands/__tests__/function.test.ts +0 -286
- package/src/commands/__tests__/store-version-validation.test.ts +0 -414
- package/src/commands/__tests__/store-version.test.ts +0 -402
- package/src/commands/component.ts +0 -178
- package/src/commands/docs.ts +0 -24
- package/src/commands/function.ts +0 -201
- package/src/commands/help.ts +0 -18
- package/src/commands/index.ts +0 -27
- package/src/commands/login.ts +0 -267
- package/src/commands/project.ts +0 -107
- package/src/commands/store-version.ts +0 -242
- package/src/commands/version.ts +0 -51
- package/src/commands/whoami.ts +0 -46
- package/src/index.ts +0 -116
- package/src/prompts/component.prompts.ts +0 -94
- package/src/prompts/function.prompts.ts +0 -168
- package/src/schemas/command.schema.ts +0 -644
- package/src/types/index.ts +0 -183
- package/src/utils/__tests__/command-parser.test.ts +0 -159
- package/src/utils/__tests__/command-suggestions.test.ts +0 -185
- package/src/utils/__tests__/console.test.ts +0 -192
- package/src/utils/__tests__/context-detector.test.ts +0 -258
- package/src/utils/__tests__/enhanced-error-handler.test.ts +0 -137
- package/src/utils/__tests__/error-handler.test.ts +0 -107
- package/src/utils/__tests__/rich-progress.test.ts +0 -181
- package/src/utils/__tests__/validation-error-formatter.test.ts +0 -175
- package/src/utils/__tests__/validation-helpers.test.ts +0 -125
- package/src/utils/cli-progress-reporter.ts +0 -84
- package/src/utils/command-builder.ts +0 -390
- package/src/utils/command-helpers.ts +0 -83
- package/src/utils/command-parser.ts +0 -245
- package/src/utils/command-suggestions.ts +0 -176
- package/src/utils/console.ts +0 -320
- package/src/utils/constants.ts +0 -39
- package/src/utils/context-detector.ts +0 -177
- package/src/utils/deploy-helpers.ts +0 -357
- package/src/utils/enhanced-error-handler.ts +0 -264
- package/src/utils/error-handler.ts +0 -60
- package/src/utils/errors.ts +0 -256
- package/src/utils/interactive-builder.ts +0 -325
- package/src/utils/rich-progress.ts +0 -331
- package/src/utils/store.ts +0 -23
- package/src/utils/validation-error-formatter.ts +0 -337
- package/src/utils/validation-helpers.ts +0 -325
- package/vitest.config.ts +0 -35
- package/vitest.setup.ts +0 -29
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import cliProgress from "cli-progress";
|
|
3
|
-
import type { ProgressParams, ProgressPayload, Spinner } from "../types";
|
|
4
|
-
import type { CliConsole } from "./console";
|
|
5
|
-
|
|
6
|
-
// Enhanced build progress type for CLI display
|
|
7
|
-
interface CLIBuildProgress {
|
|
8
|
-
phase: string;
|
|
9
|
-
message: string;
|
|
10
|
-
progress: number;
|
|
11
|
-
metadata?: {
|
|
12
|
-
bundleSize?: number;
|
|
13
|
-
gzippedSize?: number;
|
|
14
|
-
dependencies?: number;
|
|
15
|
-
removedExports?: number;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface ProgressStep {
|
|
20
|
-
name: string;
|
|
21
|
-
emoji: string;
|
|
22
|
-
weight: number;
|
|
23
|
-
status?: "pending" | "active" | "completed" | "failed";
|
|
24
|
-
message?: string;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export class RichProgressReporter {
|
|
28
|
-
private multibar: cliProgress.MultiBar;
|
|
29
|
-
private bars: Map<string, cliProgress.SingleBar> = new Map();
|
|
30
|
-
private steps: ProgressStep[] = [
|
|
31
|
-
{ name: "Validating", emoji: "🔍", weight: 10 },
|
|
32
|
-
{ name: "Building", emoji: "🔨", weight: 40 },
|
|
33
|
-
{ name: "Optimizing", emoji: "⚡", weight: 20 },
|
|
34
|
-
{ name: "Deploying", emoji: "🚀", weight: 20 },
|
|
35
|
-
{ name: "Verifying", emoji: "✅", weight: 10 },
|
|
36
|
-
];
|
|
37
|
-
private startTime: number = Date.now();
|
|
38
|
-
private stats: {
|
|
39
|
-
bundleSize?: number;
|
|
40
|
-
gzippedSize?: number;
|
|
41
|
-
dependencies?: number;
|
|
42
|
-
removedExports?: number;
|
|
43
|
-
} = {};
|
|
44
|
-
private cliConsole: CliConsole;
|
|
45
|
-
|
|
46
|
-
constructor(cliConsole: CliConsole) {
|
|
47
|
-
this.cliConsole = cliConsole;
|
|
48
|
-
// Create a formatter function that matches cli-progress expectations
|
|
49
|
-
const formatter = (
|
|
50
|
-
options: Record<string, unknown>,
|
|
51
|
-
params: Record<string, unknown>,
|
|
52
|
-
payload: Record<string, unknown>,
|
|
53
|
-
): string => {
|
|
54
|
-
// Safe type conversion with validation
|
|
55
|
-
const formatOptions = {
|
|
56
|
-
barCompleteString:
|
|
57
|
-
typeof options.barCompleteString === "string"
|
|
58
|
-
? options.barCompleteString
|
|
59
|
-
: undefined,
|
|
60
|
-
barIncompleteString:
|
|
61
|
-
typeof options.barIncompleteString === "string"
|
|
62
|
-
? options.barIncompleteString
|
|
63
|
-
: undefined,
|
|
64
|
-
barsize:
|
|
65
|
-
typeof options.barsize === "number" ? options.barsize : undefined,
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// Convert params to expected format
|
|
69
|
-
const progressParams: ProgressParams = {
|
|
70
|
-
progress: typeof params.progress === "number" ? params.progress : 0,
|
|
71
|
-
value: typeof params.value === "number" ? params.value : 0,
|
|
72
|
-
total: typeof params.total === "number" ? params.total : 0,
|
|
73
|
-
eta: typeof params.eta === "number" ? params.eta : 0,
|
|
74
|
-
startTime:
|
|
75
|
-
typeof params.startTime === "number" ? params.startTime : Date.now(),
|
|
76
|
-
stopTime: typeof params.stopTime === "number" ? params.stopTime : null,
|
|
77
|
-
duration: typeof params.duration === "number" ? params.duration : 0,
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// Convert payload to expected format
|
|
81
|
-
const progressPayload: ProgressPayload = {
|
|
82
|
-
...payload,
|
|
83
|
-
emoji: typeof payload.emoji === "string" ? payload.emoji : undefined,
|
|
84
|
-
step: typeof payload.step === "string" ? payload.step : undefined,
|
|
85
|
-
message:
|
|
86
|
-
typeof payload.message === "string" ? payload.message : undefined,
|
|
87
|
-
startTime:
|
|
88
|
-
typeof payload.startTime === "number" ? payload.startTime : undefined,
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
return this.formatBar(formatOptions, progressParams, progressPayload);
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Create a type-safe wrapper for the cli-progress formatter
|
|
95
|
-
// This ensures our formatter function matches the expected signature
|
|
96
|
-
const formatFunction: cliProgress.GenericFormatter = (
|
|
97
|
-
options: Record<string, unknown>,
|
|
98
|
-
params: Record<string, unknown>,
|
|
99
|
-
payload: Record<string, unknown>,
|
|
100
|
-
) => formatter(options, params, payload);
|
|
101
|
-
|
|
102
|
-
this.multibar = new cliProgress.MultiBar(
|
|
103
|
-
{
|
|
104
|
-
format: formatFunction,
|
|
105
|
-
barCompleteChar: "\u2588",
|
|
106
|
-
barIncompleteChar: "\u2591",
|
|
107
|
-
hideCursor: true,
|
|
108
|
-
clearOnComplete: false,
|
|
109
|
-
forceRedraw: true,
|
|
110
|
-
},
|
|
111
|
-
cliProgress.Presets.shades_classic,
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Custom format for progress bars
|
|
117
|
-
*/
|
|
118
|
-
private formatBar(
|
|
119
|
-
options: {
|
|
120
|
-
barCompleteString?: string;
|
|
121
|
-
barIncompleteString?: string;
|
|
122
|
-
barsize?: number;
|
|
123
|
-
[key: string]: unknown;
|
|
124
|
-
},
|
|
125
|
-
params: ProgressParams,
|
|
126
|
-
payload: ProgressPayload,
|
|
127
|
-
): string {
|
|
128
|
-
const barCompleteString = options.barCompleteString || "\u2588";
|
|
129
|
-
const barIncompleteString = options.barIncompleteString || "\u2591";
|
|
130
|
-
const barsize = options.barsize || 40;
|
|
131
|
-
|
|
132
|
-
const bar = barCompleteString.substring(
|
|
133
|
-
0,
|
|
134
|
-
Math.round(params.progress * barsize),
|
|
135
|
-
);
|
|
136
|
-
const empty = barIncompleteString.substring(0, barsize - bar.length);
|
|
137
|
-
|
|
138
|
-
const percent = Math.round(params.progress * 100);
|
|
139
|
-
const emoji = payload.emoji || "📦";
|
|
140
|
-
const step = payload.step || "Processing";
|
|
141
|
-
const message = payload.message || "";
|
|
142
|
-
|
|
143
|
-
let line = `${emoji} ${step.padEnd(12)} ${bar}${empty} ${percent.toString().padStart(3)}%`;
|
|
144
|
-
|
|
145
|
-
if (percent === 100 && payload.startTime) {
|
|
146
|
-
const duration = ((Date.now() - payload.startTime) / 1000).toFixed(1);
|
|
147
|
-
line += chalk.green(` ✓ ${duration}s`);
|
|
148
|
-
} else if (message) {
|
|
149
|
-
line += chalk.dim(` | ${message}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return line;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Start progress tracking
|
|
157
|
-
*/
|
|
158
|
-
start(): void {
|
|
159
|
-
this.cliConsole.info(chalk.blue.bold("\n🚀 Starting deployment...\n"));
|
|
160
|
-
|
|
161
|
-
// Create bars for each step
|
|
162
|
-
for (const step of this.steps) {
|
|
163
|
-
const bar = this.multibar.create(100, 0, {
|
|
164
|
-
emoji: step.emoji,
|
|
165
|
-
step: step.name,
|
|
166
|
-
startTime: Date.now(),
|
|
167
|
-
});
|
|
168
|
-
this.bars.set(step.name, bar);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private readonly stepMap: Record<string, string> = {
|
|
173
|
-
validate: "Validating",
|
|
174
|
-
build: "Building",
|
|
175
|
-
optimize: "Optimizing",
|
|
176
|
-
upload: "Deploying",
|
|
177
|
-
verify: "Verifying",
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Update progress based on CLIBuildProgress
|
|
182
|
-
*/
|
|
183
|
-
updateProgress(progress: CLIBuildProgress): void {
|
|
184
|
-
const { phase, message, progress: percent } = progress;
|
|
185
|
-
const stepName = this.stepMap[phase] || "Processing";
|
|
186
|
-
|
|
187
|
-
this.updateBar(stepName, percent, message);
|
|
188
|
-
this.updatePreviousSteps(stepName);
|
|
189
|
-
this.updateStats(progress.metadata);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
private updateBar(stepName: string, percent: number, message?: string): void {
|
|
193
|
-
const bar = this.bars.get(stepName);
|
|
194
|
-
if (bar) {
|
|
195
|
-
bar.update(percent * 100, { message });
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
private updatePreviousSteps(currentStepName: string): void {
|
|
200
|
-
let foundCurrent = false;
|
|
201
|
-
|
|
202
|
-
for (const step of this.steps) {
|
|
203
|
-
if (step.name === currentStepName) {
|
|
204
|
-
foundCurrent = true;
|
|
205
|
-
step.status = "active";
|
|
206
|
-
} else if (!foundCurrent) {
|
|
207
|
-
this.markStepComplete(step);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
private markStepComplete(step: ProgressStep): void {
|
|
213
|
-
step.status = "completed";
|
|
214
|
-
const bar = this.bars.get(step.name);
|
|
215
|
-
if (bar) {
|
|
216
|
-
bar.update(100);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
private updateStats(metadata?: CLIBuildProgress["metadata"]): void {
|
|
221
|
-
if (!metadata) return;
|
|
222
|
-
|
|
223
|
-
if (metadata.bundleSize) {
|
|
224
|
-
this.stats.bundleSize = metadata.bundleSize;
|
|
225
|
-
}
|
|
226
|
-
if (metadata.gzippedSize) {
|
|
227
|
-
this.stats.gzippedSize = metadata.gzippedSize;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Stop progress tracking
|
|
233
|
-
*/
|
|
234
|
-
stop(success = true): void {
|
|
235
|
-
// Complete all bars if they exist
|
|
236
|
-
if (this.bars.size > 0) {
|
|
237
|
-
for (const bar of this.bars.values()) {
|
|
238
|
-
bar.update(100);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
this.multibar.stop();
|
|
243
|
-
|
|
244
|
-
// Show summary
|
|
245
|
-
if (success) {
|
|
246
|
-
this.showSuccessSummary();
|
|
247
|
-
} else {
|
|
248
|
-
this.cliConsole.error(chalk.red("\n❌ Deployment failed\n"));
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Show success summary with stats
|
|
254
|
-
*/
|
|
255
|
-
private showSuccessSummary(): void {
|
|
256
|
-
const duration = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
257
|
-
|
|
258
|
-
this.cliConsole.success(
|
|
259
|
-
chalk.green.bold("\n✅ Deployment completed successfully!\n"),
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
if (Object.keys(this.stats).length > 0) {
|
|
263
|
-
this.cliConsole.info(chalk.blue("📊 Build Stats:"));
|
|
264
|
-
|
|
265
|
-
if (this.stats.bundleSize) {
|
|
266
|
-
const sizeKB = (this.stats.bundleSize / 1024).toFixed(1);
|
|
267
|
-
const gzipKB = this.stats.gzippedSize
|
|
268
|
-
? (this.stats.gzippedSize / 1024).toFixed(1)
|
|
269
|
-
: "?";
|
|
270
|
-
this.cliConsole.info(
|
|
271
|
-
` • Bundle size: ${sizeKB}KB (gzipped: ${gzipKB}KB)`,
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (this.stats.removedExports) {
|
|
276
|
-
this.cliConsole.info(
|
|
277
|
-
` • Tree shaking: Removed ${this.stats.removedExports} unused exports`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (this.stats.dependencies) {
|
|
282
|
-
this.cliConsole.info(` • Dependencies: ${this.stats.dependencies}`);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
this.cliConsole.info("");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
this.cliConsole.info(chalk.dim(`Total time: ${duration}s`));
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Simple progress reporter for single operations
|
|
294
|
-
*/
|
|
295
|
-
export class SimpleProgressReporter {
|
|
296
|
-
private spinner: Spinner | null = null;
|
|
297
|
-
private startTime: number;
|
|
298
|
-
|
|
299
|
-
constructor(private message: string) {
|
|
300
|
-
this.startTime = Date.now();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
start(): void {
|
|
304
|
-
const ora = require("ora");
|
|
305
|
-
this.spinner = ora({
|
|
306
|
-
text: this.message,
|
|
307
|
-
spinner: "dots",
|
|
308
|
-
}).start();
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
update(message: string): void {
|
|
312
|
-
if (this.spinner) {
|
|
313
|
-
this.spinner.text = message;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
succeed(message?: string): void {
|
|
318
|
-
if (this.spinner) {
|
|
319
|
-
const duration = ((Date.now() - this.startTime) / 1000).toFixed(1);
|
|
320
|
-
this.spinner.succeed(
|
|
321
|
-
message || `${this.message} ${chalk.dim(`(${duration}s)`)}`,
|
|
322
|
-
);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
fail(message?: string): void {
|
|
327
|
-
if (this.spinner) {
|
|
328
|
-
this.spinner.fail(message || this.message);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
package/src/utils/store.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
export type OllieConfig = {
|
|
5
|
-
storeId: string;
|
|
6
|
-
versionId: string;
|
|
7
|
-
platform: string;
|
|
8
|
-
platformStoreId: string;
|
|
9
|
-
sessionId: string;
|
|
10
|
-
props: unknown;
|
|
11
|
-
theme: Record<string, unknown>;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export async function getOllieConfig(): Promise<OllieConfig | null> {
|
|
15
|
-
try {
|
|
16
|
-
const configPath = path.join(process.cwd(), "ollie.json");
|
|
17
|
-
const raw = await fs.readFile(configPath, "utf-8");
|
|
18
|
-
const data: OllieConfig = JSON.parse(raw);
|
|
19
|
-
return data;
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import type { ZodError, ZodIssue } from "zod";
|
|
2
|
-
import type { CliConsole } from "./console";
|
|
3
|
-
|
|
4
|
-
export interface ValidationError {
|
|
5
|
-
field: string;
|
|
6
|
-
message: string;
|
|
7
|
-
suggestedValue?: string;
|
|
8
|
-
examples?: ValidationExample[];
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface ValidationExample {
|
|
12
|
-
description: string;
|
|
13
|
-
command: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const COMMON_EXAMPLES: Record<string, ValidationExample[]> = {
|
|
17
|
-
component: [
|
|
18
|
-
{
|
|
19
|
-
description: "Create a header component",
|
|
20
|
-
command: "ollieshop component create --name header-nav --slot header",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
description: "Create a product listing component",
|
|
24
|
-
command: "ollieshop component create --name product-list --slot main",
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
function: [
|
|
28
|
-
{
|
|
29
|
-
description: "Create a cart validation function",
|
|
30
|
-
command: "ollieshop function create --name validate-cart --event cart",
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
description: "Create an order processing function",
|
|
34
|
-
command:
|
|
35
|
-
"ollieshop function create --name process-order --event order --timing after",
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
"store-version": [
|
|
39
|
-
{
|
|
40
|
-
description: "Create a new version",
|
|
41
|
-
command: "ollieshop store-version create --name summer-2024",
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
description: "Clone from existing version",
|
|
45
|
-
command: "ollieshop store-version clone --source v1 --name v2",
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
function formatIssue(
|
|
51
|
-
issue: ZodIssue,
|
|
52
|
-
field: string,
|
|
53
|
-
): { message: string; suggestedValue?: string } {
|
|
54
|
-
switch (issue.code) {
|
|
55
|
-
case "invalid_type": {
|
|
56
|
-
const invalidTypeIssue = issue as ZodIssue & { received?: string };
|
|
57
|
-
if (invalidTypeIssue.received === "undefined") {
|
|
58
|
-
return {
|
|
59
|
-
message: `${humanizeFieldName(field)} is required`,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
return {
|
|
63
|
-
message: `${humanizeFieldName(field)} must be ${issue.expected}`,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
case "invalid_enum_value": {
|
|
68
|
-
const options =
|
|
69
|
-
(issue as ZodIssue & { options?: string[] }).options || [];
|
|
70
|
-
return {
|
|
71
|
-
message: `Invalid ${humanizeFieldName(field)}`,
|
|
72
|
-
suggestedValue: `Valid options: ${options.join(", ")}`,
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
case "too_small": {
|
|
77
|
-
const minimum = (issue as ZodIssue & { minimum?: number }).minimum || 0;
|
|
78
|
-
return {
|
|
79
|
-
message: `${humanizeFieldName(field)} must be at least ${minimum} characters`,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
case "invalid_string": {
|
|
84
|
-
const validation = (issue as ZodIssue & { validation?: string })
|
|
85
|
-
.validation;
|
|
86
|
-
if (!validation) {
|
|
87
|
-
return { message: humanizeMessage(issue.message, field) };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return formatStringValidation(validation, field);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
default:
|
|
94
|
-
return { message: humanizeMessage(issue.message, field) };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function formatStringValidation(
|
|
99
|
-
validation: string,
|
|
100
|
-
field: string,
|
|
101
|
-
): { message: string } {
|
|
102
|
-
const validationMessages: Record<string, string> = {
|
|
103
|
-
regex: getRegexMessage(field),
|
|
104
|
-
email: `${humanizeFieldName(field)} must be a valid email address`,
|
|
105
|
-
url: `${humanizeFieldName(field)} must be a valid URL`,
|
|
106
|
-
uuid: `${humanizeFieldName(field)} must be a valid UUID`,
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
message:
|
|
111
|
-
validationMessages[validation] ||
|
|
112
|
-
`Invalid ${humanizeFieldName(field)} format`,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Format Zod errors into user-friendly validation errors
|
|
118
|
-
*/
|
|
119
|
-
export function formatZodError(
|
|
120
|
-
error: ZodError,
|
|
121
|
-
context?: string,
|
|
122
|
-
): ValidationError[] {
|
|
123
|
-
const errors: ValidationError[] = [];
|
|
124
|
-
|
|
125
|
-
for (const issue of error.issues) {
|
|
126
|
-
const field = issue.path.join(".");
|
|
127
|
-
const { message, suggestedValue } = formatIssue(issue, field);
|
|
128
|
-
|
|
129
|
-
const error: ValidationError = {
|
|
130
|
-
field,
|
|
131
|
-
message,
|
|
132
|
-
suggestedValue,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
// Add examples based on context
|
|
136
|
-
if (context && COMMON_EXAMPLES[context]) {
|
|
137
|
-
error.examples = COMMON_EXAMPLES[context];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
errors.push(error);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return errors;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Display validation errors in a formatted way
|
|
148
|
-
*/
|
|
149
|
-
export function displayErrors(
|
|
150
|
-
console: CliConsole,
|
|
151
|
-
errors: ValidationError[],
|
|
152
|
-
command?: string,
|
|
153
|
-
): void {
|
|
154
|
-
console.error("❌ Validation failed:");
|
|
155
|
-
console.log("");
|
|
156
|
-
|
|
157
|
-
for (const error of errors) {
|
|
158
|
-
console.error(` • ${error.field}: ${error.message}`);
|
|
159
|
-
|
|
160
|
-
if (error.suggestedValue) {
|
|
161
|
-
console.log(` 💡 ${error.suggestedValue}`);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
if (error.examples && error.examples.length > 0) {
|
|
165
|
-
console.log("");
|
|
166
|
-
console.log(" 📝 Examples:");
|
|
167
|
-
for (const example of error.examples) {
|
|
168
|
-
console.log(` ${example.description}:`);
|
|
169
|
-
console.log(` $ ${example.command}`);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (command) {
|
|
175
|
-
console.log("");
|
|
176
|
-
console.info(`💡 For more help, run: ollieshop ${command} --help`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Suggest a command from available commands
|
|
182
|
-
*/
|
|
183
|
-
export function suggestCommand(
|
|
184
|
-
input: string,
|
|
185
|
-
availableCommands: string[],
|
|
186
|
-
): string | undefined {
|
|
187
|
-
const normalizedInput = input.toLowerCase();
|
|
188
|
-
|
|
189
|
-
// Exact match
|
|
190
|
-
if (availableCommands.includes(normalizedInput)) {
|
|
191
|
-
return normalizedInput;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Find commands that start with the input
|
|
195
|
-
const startsWithMatches = availableCommands.filter((cmd) =>
|
|
196
|
-
cmd.toLowerCase().startsWith(normalizedInput),
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
if (startsWithMatches.length === 1) {
|
|
200
|
-
return startsWithMatches[0];
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Find commands that contain the input
|
|
204
|
-
const containsMatches = availableCommands.filter((cmd) =>
|
|
205
|
-
cmd.toLowerCase().includes(normalizedInput),
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
if (containsMatches.length === 1) {
|
|
209
|
-
return containsMatches[0];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Simple Levenshtein distance for close matches
|
|
213
|
-
const closeMatches = availableCommands
|
|
214
|
-
.map((cmd) => ({
|
|
215
|
-
command: cmd,
|
|
216
|
-
distance: levenshteinDistance(normalizedInput, cmd.toLowerCase()),
|
|
217
|
-
}))
|
|
218
|
-
.filter((match) => match.distance <= 2)
|
|
219
|
-
.sort((a, b) => a.distance - b.distance);
|
|
220
|
-
|
|
221
|
-
if (closeMatches.length > 0) {
|
|
222
|
-
const firstMatch = closeMatches[0];
|
|
223
|
-
return firstMatch ? firstMatch.command : undefined;
|
|
224
|
-
}
|
|
225
|
-
return undefined;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* Transform field names to human-readable format
|
|
230
|
-
*/
|
|
231
|
-
function humanizeFieldName(field: string): string {
|
|
232
|
-
if (!field) return "Value";
|
|
233
|
-
|
|
234
|
-
return field
|
|
235
|
-
.split(/[._-]/)
|
|
236
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
237
|
-
.join(" ");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Transform error messages to be more user-friendly
|
|
242
|
-
*/
|
|
243
|
-
function humanizeMessage(message: string, field: string): string {
|
|
244
|
-
// Handle "Required" messages
|
|
245
|
-
if (message === "Required" || message.toLowerCase() === "required") {
|
|
246
|
-
return `${humanizeFieldName(field)} is required`;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Handle regex validation messages
|
|
250
|
-
if (message.includes("Invalid input")) {
|
|
251
|
-
return getRegexMessage(field);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return message;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get user-friendly message for regex validation
|
|
259
|
-
*/
|
|
260
|
-
function getRegexMessage(field: string): string {
|
|
261
|
-
const regexMessages: Record<string, string> = {
|
|
262
|
-
name: "Must be lowercase with hyphens only (e.g., 'my-component', 'header-nav')",
|
|
263
|
-
version: "Must follow semantic versioning (e.g., '1.0.0', '2.1.3')",
|
|
264
|
-
slot: "Must be a valid slot name",
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
return regexMessages[field] || "Invalid format";
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function initializeMatrix(a: string, b: string): number[][] {
|
|
271
|
-
const matrix: number[][] = [];
|
|
272
|
-
|
|
273
|
-
// Initialize matrix with proper dimensions
|
|
274
|
-
for (let i = 0; i <= b.length; i++) {
|
|
275
|
-
const row: number[] = [];
|
|
276
|
-
matrix[i] = row;
|
|
277
|
-
for (let j = 0; j <= a.length; j++) {
|
|
278
|
-
if (i === 0) {
|
|
279
|
-
row[j] = j;
|
|
280
|
-
} else if (j === 0) {
|
|
281
|
-
row[j] = i;
|
|
282
|
-
} else {
|
|
283
|
-
row[j] = 0;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return matrix;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function computeDistance(
|
|
292
|
-
a: string,
|
|
293
|
-
b: string,
|
|
294
|
-
i: number,
|
|
295
|
-
j: number,
|
|
296
|
-
matrix: number[][],
|
|
297
|
-
): number {
|
|
298
|
-
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
299
|
-
const prevRow = matrix[i - 1];
|
|
300
|
-
return prevRow ? (prevRow[j - 1] ?? 0) : 0;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
const currentRow = matrix[i];
|
|
304
|
-
const prevRow = matrix[i - 1];
|
|
305
|
-
|
|
306
|
-
const substitution = prevRow
|
|
307
|
-
? (prevRow[j - 1] ?? 0) + 1
|
|
308
|
-
: Number.MAX_SAFE_INTEGER;
|
|
309
|
-
const insertion = currentRow
|
|
310
|
-
? (currentRow[j - 1] ?? 0) + 1
|
|
311
|
-
: Number.MAX_SAFE_INTEGER;
|
|
312
|
-
const deletion = prevRow ? (prevRow[j] ?? 0) + 1 : Number.MAX_SAFE_INTEGER;
|
|
313
|
-
|
|
314
|
-
return Math.min(substitution, insertion, deletion);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Calculate Levenshtein distance between two strings
|
|
319
|
-
*/
|
|
320
|
-
function levenshteinDistance(a: string, b: string): number {
|
|
321
|
-
if (a.length === 0) return b.length;
|
|
322
|
-
if (b.length === 0) return a.length;
|
|
323
|
-
|
|
324
|
-
const matrix = initializeMatrix(a, b);
|
|
325
|
-
|
|
326
|
-
for (let i = 1; i <= b.length; i++) {
|
|
327
|
-
const row = matrix[i];
|
|
328
|
-
if (!row) continue;
|
|
329
|
-
|
|
330
|
-
for (let j = 1; j <= a.length; j++) {
|
|
331
|
-
row[j] = computeDistance(a, b, i, j, matrix);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const lastRow = matrix[b.length];
|
|
336
|
-
return lastRow ? (lastRow[a.length] ?? 0) : 0;
|
|
337
|
-
}
|