@ollie-shop/cli 1.0.1 → 1.1.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.
@@ -0,0 +1,136 @@
1
+ import { z } from "zod";
2
+ import { zodToJsonSchema } from "zod-to-json-schema";
3
+
4
+ const PLATFORM_VENDORS = ["vtex", "shopify", "vnda", "custom"] as const;
5
+
6
+ export const storeCreateSchema = z.object({
7
+ name: z.string().min(1).describe("Store display name"),
8
+ platform: z.enum(PLATFORM_VENDORS).describe("E-commerce platform vendor"),
9
+ platformStoreId: z
10
+ .string()
11
+ .min(1)
12
+ .describe("Platform-specific store identifier (e.g. account name)"),
13
+ logo: z.string().url().optional().describe("Store logo URL"),
14
+ settings: z.string().optional().describe("JSON settings string"),
15
+ });
16
+
17
+ export const storeListSchema = z.object({}).describe("No parameters required");
18
+
19
+ export const versionCreateSchema = z.object({
20
+ storeId: z.string().uuid().describe("Parent store UUID"),
21
+ name: z.string().min(1).describe("Version name (e.g. v1, main)"),
22
+ active: z.boolean().default(false).describe("Whether version is active"),
23
+ default: z
24
+ .boolean()
25
+ .default(false)
26
+ .describe("Whether this is the default version"),
27
+ template: z
28
+ .string()
29
+ .nullable()
30
+ .default(null)
31
+ .describe("Template name or null"),
32
+ });
33
+
34
+ export const versionListSchema = z.object({
35
+ storeId: z.string().uuid().describe("Store UUID to list versions for"),
36
+ });
37
+
38
+ export const componentCreateSchema = z.object({
39
+ versionId: z.string().uuid().describe("Parent version UUID"),
40
+ name: z.string().min(1).describe("Component name (e.g. FreeShippingBar)"),
41
+ slot: z
42
+ .string()
43
+ .min(1)
44
+ .describe(
45
+ "Target slot (e.g. cart_header_full_page, shipping_address_details_form)",
46
+ ),
47
+ active: z.boolean().default(true).describe("Whether component is active"),
48
+ props: z
49
+ .record(z.unknown())
50
+ .nullable()
51
+ .default(null)
52
+ .describe("Default component props as JSON object"),
53
+ });
54
+
55
+ export const componentListSchema = z.object({
56
+ storeId: z.string().uuid().describe("Store UUID"),
57
+ versionId: z
58
+ .string()
59
+ .uuid()
60
+ .optional()
61
+ .describe("Optional version UUID to filter by"),
62
+ });
63
+
64
+ export const functionCreateSchema = z.object({
65
+ versionId: z.string().uuid().describe("Parent version UUID"),
66
+ name: z.string().min(1).describe("Function name"),
67
+ trigger: z
68
+ .string()
69
+ .min(1)
70
+ .describe("Function trigger (e.g. beforePayment, afterShipping)"),
71
+ active: z.boolean().default(true).describe("Whether function is active"),
72
+ });
73
+
74
+ export const functionListSchema = z.object({
75
+ versionId: z.string().uuid().describe("Version UUID to list functions for"),
76
+ });
77
+
78
+ type SchemaMap = Record<string, z.ZodTypeAny>;
79
+
80
+ const schemas: Record<string, SchemaMap> = {
81
+ store: {
82
+ create: storeCreateSchema,
83
+ list: storeListSchema,
84
+ },
85
+ version: {
86
+ create: versionCreateSchema,
87
+ list: versionListSchema,
88
+ },
89
+ component: {
90
+ create: componentCreateSchema,
91
+ list: componentListSchema,
92
+ },
93
+ function: {
94
+ create: functionCreateSchema,
95
+ list: functionListSchema,
96
+ },
97
+ };
98
+
99
+ export function getSchemaNames(): string[] {
100
+ const names: string[] = [];
101
+ for (const [resource, actions] of Object.entries(schemas)) {
102
+ names.push(resource);
103
+ for (const action of Object.keys(actions)) {
104
+ names.push(`${resource}.${action}`);
105
+ }
106
+ }
107
+ return names;
108
+ }
109
+
110
+ export function getJsonSchema(name: string): Record<string, unknown> | null {
111
+ const parts = name.split(".");
112
+ const resource = parts[0];
113
+ const action = parts[1];
114
+
115
+ if (!resource || !schemas[resource]) return null;
116
+
117
+ if (action) {
118
+ const schema = schemas[resource][action];
119
+ if (!schema) return null;
120
+ return zodToJsonSchema(schema, { name: `${resource}.${action}` }) as Record<
121
+ string,
122
+ unknown
123
+ >;
124
+ }
125
+
126
+ // Return all actions for a resource
127
+ const result: Record<string, unknown> = {};
128
+ for (const [actionName, schema] of Object.entries(schemas[resource])) {
129
+ result[actionName] = zodToJsonSchema(schema, {
130
+ name: `${resource}.${actionName}`,
131
+ });
132
+ }
133
+ return result;
134
+ }
135
+
136
+ export { PLATFORM_VENDORS };
@@ -0,0 +1,76 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import { getOrganizationId } from "../utils/supabase.js";
3
+ import { storeCreateSchema } from "./schema.js";
4
+
5
+ export interface CreateStoreInput {
6
+ name: string;
7
+ platform: string;
8
+ platformStoreId: string;
9
+ logo?: string;
10
+ settings?: string;
11
+ }
12
+
13
+ export interface StoreRecord {
14
+ id: string;
15
+ name: string;
16
+ platform: string;
17
+ platform_store_id: string;
18
+ organization_id: string;
19
+ logo: string | null;
20
+ settings: string | null;
21
+ created_at: string;
22
+ }
23
+
24
+ export async function createStore(
25
+ client: SupabaseClient,
26
+ input: CreateStoreInput,
27
+ ): Promise<{ data?: { id: string }; error?: { message: string } }> {
28
+ // Validate input against schema
29
+ const parsed = storeCreateSchema.safeParse(input);
30
+ if (!parsed.success) {
31
+ return {
32
+ error: { message: parsed.error.issues.map((i) => i.message).join("; ") },
33
+ };
34
+ }
35
+
36
+ const organizationId = await getOrganizationId(client);
37
+
38
+ const { data, error } = await client
39
+ .from("stores")
40
+ .insert({
41
+ name: parsed.data.name,
42
+ platform: parsed.data.platform,
43
+ platform_store_id: parsed.data.platformStoreId,
44
+ organization_id: organizationId,
45
+ logo: parsed.data.logo ?? null,
46
+ settings: parsed.data.settings ?? null,
47
+ })
48
+ .select("id")
49
+ .single();
50
+
51
+ if (error) {
52
+ return { error: { message: error.message } };
53
+ }
54
+
55
+ return { data: { id: data.id } };
56
+ }
57
+
58
+ export async function listStores(
59
+ client: SupabaseClient,
60
+ ): Promise<{ data?: StoreRecord[]; error?: { message: string } }> {
61
+ const organizationId = await getOrganizationId(client);
62
+
63
+ const { data, error } = await client
64
+ .from("stores")
65
+ .select(
66
+ "id, name, platform, platform_store_id, organization_id, logo, settings, created_at",
67
+ )
68
+ .eq("organization_id", organizationId)
69
+ .order("created_at", { ascending: false });
70
+
71
+ if (error) {
72
+ return { error: { message: error.message } };
73
+ }
74
+
75
+ return { data: data as StoreRecord[] };
76
+ }
@@ -0,0 +1,67 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import { versionCreateSchema } from "./schema.js";
3
+
4
+ export interface CreateVersionInput {
5
+ storeId: string;
6
+ name: string;
7
+ active?: boolean;
8
+ default?: boolean;
9
+ template?: string | null;
10
+ }
11
+
12
+ export interface VersionRecord {
13
+ id: string;
14
+ name: string;
15
+ active: boolean;
16
+ default: boolean;
17
+ store_id: string;
18
+ template: string | null;
19
+ created_at: string;
20
+ }
21
+
22
+ export async function createVersion(
23
+ client: SupabaseClient,
24
+ input: CreateVersionInput,
25
+ ): Promise<{ data?: { id: string }; error?: { message: string } }> {
26
+ const parsed = versionCreateSchema.safeParse(input);
27
+ if (!parsed.success) {
28
+ return {
29
+ error: { message: parsed.error.issues.map((i) => i.message).join("; ") },
30
+ };
31
+ }
32
+
33
+ const { data, error } = await client
34
+ .from("versions")
35
+ .insert({
36
+ name: parsed.data.name,
37
+ active: parsed.data.active,
38
+ default: parsed.data.default,
39
+ store_id: parsed.data.storeId,
40
+ template: parsed.data.template,
41
+ })
42
+ .select("id")
43
+ .single();
44
+
45
+ if (error) {
46
+ return { error: { message: error.message } };
47
+ }
48
+
49
+ return { data: { id: data.id } };
50
+ }
51
+
52
+ export async function listVersions(
53
+ client: SupabaseClient,
54
+ storeId: string,
55
+ ): Promise<{ data?: VersionRecord[]; error?: { message: string } }> {
56
+ const { data, error } = await client
57
+ .from("versions")
58
+ .select("id, name, active, default, store_id, template, created_at")
59
+ .eq("store_id", storeId)
60
+ .order("created_at", { ascending: false });
61
+
62
+ if (error) {
63
+ return { error: { message: error.message } };
64
+ }
65
+
66
+ return { data: data as VersionRecord[] };
67
+ }
package/src/index.tsx CHANGED
@@ -1,8 +1,38 @@
1
1
  import { render } from "ink";
2
2
  import { App } from "./cli.js";
3
+ import { componentCommand } from "./commands/component-cmd.js";
4
+ import { initCommand } from "./commands/init-cmd.js";
5
+ import { schemaCommand } from "./commands/schema-cmd.js";
6
+ import { storeCommand } from "./commands/store-cmd.js";
7
+ import { versionCommand } from "./commands/version-cmd.js";
8
+ import { whoamiCommand } from "./commands/whoami.js";
9
+ import { parseArgs } from "./utils/parse-args.js";
3
10
 
4
- const args = process.argv.slice(2);
5
- const command = args[0] || "help";
6
- const commandArgs = args.slice(1);
11
+ // Agent-first commands: plain async, no Ink, structured output
12
+ const AGENT_COMMANDS: Record<
13
+ string,
14
+ (parsed: ReturnType<typeof parseArgs>) => Promise<void>
15
+ > = {
16
+ whoami: whoamiCommand,
17
+ store: storeCommand,
18
+ version: versionCommand,
19
+ component: componentCommand,
20
+ schema: schemaCommand,
21
+ init: initCommand,
22
+ };
7
23
 
8
- render(<App command={command} args={commandArgs} />);
24
+ const parsed = parseArgs(process.argv);
25
+
26
+ if (parsed.command in AGENT_COMMANDS) {
27
+ AGENT_COMMANDS[parsed.command](parsed).catch((err: unknown) => {
28
+ const msg = err instanceof Error ? err.message : String(err);
29
+ process.stderr.write(`${JSON.stringify({ error: { message: msg } })}\n`);
30
+ process.exit(1);
31
+ });
32
+ } else {
33
+ // Interactive commands: Ink UI (login, start, help, version)
34
+ const args = process.argv.slice(2);
35
+ const command = args[0] || "help";
36
+ const commandArgs = args.slice(1);
37
+ render(<App command={command} args={commandArgs} />);
38
+ }
@@ -280,6 +280,7 @@ export async function startDevServer(
280
280
  // Set CORS headers
281
281
  res.setHeader("Access-Control-Allow-Origin", "*");
282
282
  res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
283
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
283
284
 
284
285
  try {
285
286
  const stream = await createComponentBundle({ cwd, componentName });
@@ -328,6 +329,7 @@ export async function startDevServer(
328
329
  res.setHeader("Access-Control-Allow-Origin", "*");
329
330
  res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
330
331
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
332
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
331
333
 
332
334
  // Read request body
333
335
  let body = "";
@@ -380,6 +382,8 @@ export async function startDevServer(
380
382
  if (req.method === "OPTIONS") {
381
383
  res.setHeader("Access-Control-Allow-Origin", "*");
382
384
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
385
+ // Allow Private Network Access (public origin → localhost)
386
+ res.setHeader("Access-Control-Allow-Private-Network", "true");
383
387
  // Reflect requested headers back (supports traceparent, tracestate, etc.)
384
388
  const requestedHeaders = req.headers["access-control-request-headers"];
385
389
  res.setHeader(
@@ -405,6 +409,7 @@ export async function startDevServer(
405
409
  const responseHeaders = {
406
410
  ...proxyRes.headers,
407
411
  "access-control-allow-origin": "*",
412
+ "access-control-allow-private-network": "true",
408
413
  };
409
414
 
410
415
  res.writeHead(proxyRes.statusCode || 200, responseHeaders);
@@ -0,0 +1,125 @@
1
+ export type OutputFormat = "json" | "pretty";
2
+
3
+ export function detectOutputFormat(cliOutput?: string): OutputFormat {
4
+ if (cliOutput === "json" || cliOutput === "j") return "json";
5
+ if (cliOutput === "pretty") return "pretty";
6
+ // Auto-detect: JSON when piped, pretty when TTY
7
+ return process.stdout.isTTY ? "pretty" : "json";
8
+ }
9
+
10
+ export function filterFields(
11
+ data: Record<string, unknown>,
12
+ fields?: string[],
13
+ ): Record<string, unknown> {
14
+ if (!fields || fields.length === 0) return data;
15
+ const result: Record<string, unknown> = {};
16
+ for (const field of fields) {
17
+ if (field in data) {
18
+ result[field] = data[field];
19
+ }
20
+ }
21
+ return result;
22
+ }
23
+
24
+ export function outputResult(
25
+ result: { data?: unknown; error?: unknown },
26
+ format: OutputFormat,
27
+ fields?: string[],
28
+ ): void {
29
+ if (format === "json") {
30
+ let output = result;
31
+ if (fields && result.data && typeof result.data === "object") {
32
+ if (Array.isArray(result.data)) {
33
+ output = {
34
+ ...result,
35
+ data: result.data.map((item) =>
36
+ filterFields(item as Record<string, unknown>, fields),
37
+ ),
38
+ };
39
+ } else {
40
+ output = {
41
+ ...result,
42
+ data: filterFields(result.data as Record<string, unknown>, fields),
43
+ };
44
+ }
45
+ }
46
+ process.stdout.write(`${JSON.stringify(output)}\n`);
47
+ return;
48
+ }
49
+
50
+ // Pretty output
51
+ if (result.error) {
52
+ const errMsg =
53
+ typeof result.error === "object" && result.error !== null
54
+ ? (result.error as { message?: string }).message ||
55
+ JSON.stringify(result.error)
56
+ : String(result.error);
57
+ console.error(`\x1b[31mError:\x1b[0m ${errMsg}`);
58
+ return;
59
+ }
60
+
61
+ if (Array.isArray(result.data)) {
62
+ printTable(result.data as Record<string, unknown>[], fields);
63
+ } else if (typeof result.data === "object" && result.data !== null) {
64
+ const filtered = fields
65
+ ? filterFields(result.data as Record<string, unknown>, fields)
66
+ : result.data;
67
+ for (const [key, value] of Object.entries(
68
+ filtered as Record<string, unknown>,
69
+ )) {
70
+ console.log(`\x1b[1m${key}:\x1b[0m ${value}`);
71
+ }
72
+ } else {
73
+ console.log(result.data);
74
+ }
75
+ }
76
+
77
+ function printTable(rows: Record<string, unknown>[], fields?: string[]): void {
78
+ if (rows.length === 0) {
79
+ console.log("(no results)");
80
+ return;
81
+ }
82
+
83
+ const keys = fields || Object.keys(rows[0]);
84
+ const widths: Record<string, number> = {};
85
+ for (const key of keys) {
86
+ widths[key] = key.length;
87
+ for (const row of rows) {
88
+ const val = String(row[key] ?? "");
89
+ widths[key] = Math.max(widths[key], val.length);
90
+ }
91
+ widths[key] = Math.min(widths[key], 40); // cap column width
92
+ }
93
+
94
+ // Header
95
+ const header = keys.map((k) => k.padEnd(widths[k])).join(" ");
96
+ console.log(`\x1b[1m${header}\x1b[0m`);
97
+ console.log(keys.map((k) => "─".repeat(widths[k])).join("──"));
98
+
99
+ // Rows
100
+ for (const row of rows) {
101
+ const line = keys
102
+ .map((k) =>
103
+ String(row[k] ?? "")
104
+ .padEnd(widths[k])
105
+ .slice(0, widths[k]),
106
+ )
107
+ .join(" ");
108
+ console.log(line);
109
+ }
110
+ }
111
+
112
+ export function outputDryRun(
113
+ action: string,
114
+ data: Record<string, unknown>,
115
+ format: OutputFormat,
116
+ ): void {
117
+ if (format === "json") {
118
+ process.stdout.write(`${JSON.stringify({ dryRun: true, action, data })}\n`);
119
+ } else {
120
+ console.log(`\x1b[33m[dry-run]\x1b[0m Would execute: ${action}`);
121
+ for (const [key, value] of Object.entries(data)) {
122
+ console.log(` ${key}: ${JSON.stringify(value)}`);
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,90 @@
1
+ export interface GlobalFlags {
2
+ output?: string;
3
+ dryRun: boolean;
4
+ fields?: string[];
5
+ data?: string;
6
+ }
7
+
8
+ export interface ParsedArgs {
9
+ command: string;
10
+ subcommand?: string;
11
+ flags: Record<string, string | boolean>;
12
+ global: GlobalFlags;
13
+ positional: string[];
14
+ }
15
+
16
+ export function parseArgs(argv: string[]): ParsedArgs {
17
+ const args = argv.slice(2);
18
+ const command = args[0] || "help";
19
+ const flags: Record<string, string | boolean> = {};
20
+ const positional: string[] = [];
21
+
22
+ let subcommand: string | undefined;
23
+
24
+ // Determine if second arg is a subcommand (not a flag)
25
+ if (args[1] && !args[1].startsWith("-")) {
26
+ subcommand = args[1];
27
+ }
28
+
29
+ const startIdx = subcommand ? 2 : 1;
30
+
31
+ for (let i = startIdx; i < args.length; i++) {
32
+ const arg = args[i];
33
+ if (arg.startsWith("--")) {
34
+ const key = arg.slice(2);
35
+ const next = args[i + 1];
36
+ if (next && !next.startsWith("-")) {
37
+ flags[key] = next;
38
+ i++;
39
+ } else {
40
+ flags[key] = true;
41
+ }
42
+ } else if (arg.startsWith("-") && arg.length === 2) {
43
+ const key = arg.slice(1);
44
+ const next = args[i + 1];
45
+ if (next && !next.startsWith("-")) {
46
+ flags[key] = next;
47
+ i++;
48
+ } else {
49
+ flags[key] = true;
50
+ }
51
+ } else {
52
+ positional.push(arg);
53
+ }
54
+ }
55
+
56
+ // Extract global flags
57
+ const global: GlobalFlags = {
58
+ output: (flags.output as string) || (flags.o as string) || undefined,
59
+ dryRun: flags["dry-run"] === true,
60
+ fields: flags.fields
61
+ ? String(flags.fields)
62
+ .split(",")
63
+ .map((f) => f.trim())
64
+ : undefined,
65
+ data: (flags.data as string) || (flags.d as string) || undefined,
66
+ };
67
+
68
+ return { command, subcommand, flags, global, positional };
69
+ }
70
+
71
+ export function getFlag(
72
+ flags: Record<string, string | boolean>,
73
+ ...names: string[]
74
+ ): string | undefined {
75
+ for (const name of names) {
76
+ const val = flags[name];
77
+ if (typeof val === "string") return val;
78
+ }
79
+ return undefined;
80
+ }
81
+
82
+ export function getBoolFlag(
83
+ flags: Record<string, string | boolean>,
84
+ ...names: string[]
85
+ ): boolean {
86
+ for (const name of names) {
87
+ if (flags[name] === true) return true;
88
+ }
89
+ return false;
90
+ }
@@ -0,0 +1,59 @@
1
+ import { type SupabaseClient, createClient } from "@supabase/supabase-js";
2
+ import { getCredentials } from "./auth.js";
3
+
4
+ function requireEnv(name: string): string {
5
+ const value = process.env[name];
6
+ if (!value) {
7
+ throw new Error(
8
+ `Missing required environment variable: ${name}. Set it in your .env file or shell.`,
9
+ );
10
+ }
11
+ return value;
12
+ }
13
+
14
+ export async function getAuthenticatedClient(): Promise<SupabaseClient> {
15
+ const credentials = await getCredentials();
16
+ if (!credentials) {
17
+ throw new Error("Not authenticated. Run `ollieshop login` first.");
18
+ }
19
+
20
+ const supabaseUrl = requireEnv("OLLIE_SUPABASE_URL");
21
+ const supabaseAnonKey = requireEnv("OLLIE_SUPABASE_ANON_KEY");
22
+
23
+ const client = createClient(supabaseUrl, supabaseAnonKey, {
24
+ auth: {
25
+ autoRefreshToken: false,
26
+ persistSession: false,
27
+ },
28
+ });
29
+
30
+ await client.auth.setSession({
31
+ access_token: credentials.accessToken,
32
+ refresh_token: credentials.refreshToken,
33
+ });
34
+
35
+ return client;
36
+ }
37
+
38
+ export async function getOrganizationId(
39
+ client: SupabaseClient,
40
+ ): Promise<string> {
41
+ const { data: org, error } = await client
42
+ .from("organizations")
43
+ .select("id")
44
+ .order("created_at", { ascending: true })
45
+ .limit(1)
46
+ .maybeSingle();
47
+
48
+ if (error) {
49
+ throw new Error(`Failed to get organization: ${error.message}`);
50
+ }
51
+
52
+ if (!org) {
53
+ throw new Error(
54
+ "No organization found for your account. Create one at https://admin.ollie.shop",
55
+ );
56
+ }
57
+
58
+ return org.id;
59
+ }
@@ -0,0 +1,58 @@
1
+ const UUID_REGEX =
2
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+
4
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: intentional — rejecting control chars in user input
5
+ const CONTROL_CHAR_REGEX = /[\x00-\x1f\x7f]/;
6
+
7
+ const RESOURCE_ID_INVALID_CHARS = /[?#%\/\\]/;
8
+
9
+ export function validateUuid(value: string, name: string): string {
10
+ if (!UUID_REGEX.test(value)) {
11
+ throw new Error(
12
+ `Invalid ${name}: "${value}". Must be a valid UUID (e.g. 7217542a-d7c6-40d3-a20e-db13b310a781).`,
13
+ );
14
+ }
15
+ return value;
16
+ }
17
+
18
+ export function rejectControlChars(value: string, name: string): string {
19
+ if (CONTROL_CHAR_REGEX.test(value)) {
20
+ throw new Error(
21
+ `Invalid ${name}: contains control characters. Input must be printable text.`,
22
+ );
23
+ }
24
+ return value;
25
+ }
26
+
27
+ export function validateResourceName(value: string, name: string): string {
28
+ rejectControlChars(value, name);
29
+ if (RESOURCE_ID_INVALID_CHARS.test(value)) {
30
+ throw new Error(
31
+ `Invalid ${name}: "${value}". Must not contain ?, #, %, /, or \\.`,
32
+ );
33
+ }
34
+ return value;
35
+ }
36
+
37
+ export function validateEnum<T extends string>(
38
+ value: string,
39
+ allowed: readonly T[],
40
+ name: string,
41
+ ): T {
42
+ if (!allowed.includes(value as T)) {
43
+ throw new Error(
44
+ `Invalid ${name}: "${value}". Must be one of: ${allowed.join(", ")}`,
45
+ );
46
+ }
47
+ return value as T;
48
+ }
49
+
50
+ export function validateRequired(
51
+ value: string | undefined | null,
52
+ name: string,
53
+ ): string {
54
+ if (!value || value.trim() === "") {
55
+ throw new Error(`Missing required parameter: --${name}`);
56
+ }
57
+ return rejectControlChars(value.trim(), name);
58
+ }