@zapier/zapier-sdk-cli 0.34.11 → 0.35.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/CHANGELOG.md +21 -0
- package/README.md +33 -33
- package/dist/cli.cjs +632 -446
- package/dist/cli.mjs +632 -446
- package/dist/index.cjs +11 -7
- package/dist/index.mjs +11 -7
- package/dist/package.json +1 -1
- package/dist/src/plugins/init/index.js +1 -0
- package/dist/src/plugins/login/index.js +2 -3
- package/dist/src/plugins/logout/index.js +1 -0
- package/dist/src/utils/cli-generator.js +150 -255
- package/dist/src/utils/cli-renderer.d.ts +30 -0
- package/dist/src/utils/cli-renderer.js +226 -0
- package/dist/src/utils/errors.d.ts +17 -0
- package/dist/src/utils/errors.js +17 -0
- package/dist/src/utils/parameter-resolver.d.ts +15 -1
- package/dist/src/utils/parameter-resolver.js +89 -33
- package/dist/src/utils/string-utils.d.ts +1 -0
- package/dist/src/utils/string-utils.js +6 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FunctionRegistryEntry, SdkPage } from "@zapier/zapier-sdk";
|
|
2
|
+
export interface JsonErrorEntry {
|
|
3
|
+
code: string;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface JsonEnvelope {
|
|
7
|
+
data: unknown;
|
|
8
|
+
nextCursor?: string;
|
|
9
|
+
errors: JsonErrorEntry[];
|
|
10
|
+
}
|
|
11
|
+
type PagedSource = AsyncIterable<SdkPage> | PromiseLike<SdkPage>;
|
|
12
|
+
export interface CliRenderer {
|
|
13
|
+
renderPaginatedList(source: PagedSource, functionInfo: FunctionRegistryEntry): Promise<void>;
|
|
14
|
+
renderCollectedList(items: unknown[], options: {
|
|
15
|
+
maxItems?: number;
|
|
16
|
+
userSpecifiedMaxItems?: boolean;
|
|
17
|
+
functionInfo?: FunctionRegistryEntry;
|
|
18
|
+
}): void;
|
|
19
|
+
renderItem(value: unknown, options?: {
|
|
20
|
+
outputFile?: unknown;
|
|
21
|
+
commandName?: string;
|
|
22
|
+
functionInfo?: FunctionRegistryEntry;
|
|
23
|
+
}): void;
|
|
24
|
+
renderResponse(response: Response): Promise<void>;
|
|
25
|
+
renderError(error: unknown): never;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildJsonErrors(error: unknown): JsonErrorEntry[];
|
|
28
|
+
export declare function createJsonRenderer(): CliRenderer;
|
|
29
|
+
export declare function createInteractiveRenderer(): CliRenderer;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { ZapierCliMissingParametersError, ZapierCliExitError } from "./errors";
|
|
2
|
+
import { formatErrorMessage, ZapierError } from "@zapier/zapier-sdk";
|
|
3
|
+
import { formatItemsFromSchema, formatJsonOutput } from "./schema-formatter";
|
|
4
|
+
import { toKebabCase } from "./string-utils";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import inquirer from "inquirer";
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Shared error formatting
|
|
9
|
+
// ============================================================================
|
|
10
|
+
function formatMissingParamsError(error) {
|
|
11
|
+
return [
|
|
12
|
+
"Missing required parameters:",
|
|
13
|
+
...error.params.map(({ name, isPositional }) => isPositional
|
|
14
|
+
? ` • <${toKebabCase(name)}>`
|
|
15
|
+
: ` • --${toKebabCase(name)}`),
|
|
16
|
+
].join("\n");
|
|
17
|
+
}
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Shared error serialization (used by JSON renderer)
|
|
20
|
+
// ============================================================================
|
|
21
|
+
export function buildJsonErrors(error) {
|
|
22
|
+
if (error instanceof ZapierCliMissingParametersError) {
|
|
23
|
+
return error.params.map(({ name, isPositional }) => ({
|
|
24
|
+
code: error.code,
|
|
25
|
+
message: isPositional
|
|
26
|
+
? `<${toKebabCase(name)}> is required in non-interactive mode`
|
|
27
|
+
: `--${toKebabCase(name)} is required in non-interactive mode`,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
const code = error instanceof ZapierError ? error.code : "UNKNOWN_ERROR";
|
|
31
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
32
|
+
return [{ code, message }];
|
|
33
|
+
}
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// HTTP response unwrapping
|
|
36
|
+
// ============================================================================
|
|
37
|
+
async function unwrapHttpResponse(response) {
|
|
38
|
+
const text = await response.text().catch(() => "[unable to read body]");
|
|
39
|
+
try {
|
|
40
|
+
return {
|
|
41
|
+
statusCode: response.status,
|
|
42
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
43
|
+
body: JSON.parse(text),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new Error(`Failed to parse response as JSON (status: ${response.status}). Body: ${text.slice(0, 500)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// ============================================================================
|
|
51
|
+
// JSON renderer
|
|
52
|
+
// ============================================================================
|
|
53
|
+
function outputJson(envelope) {
|
|
54
|
+
console.log(JSON.stringify(envelope, null, 2));
|
|
55
|
+
}
|
|
56
|
+
export function createJsonRenderer() {
|
|
57
|
+
return {
|
|
58
|
+
async renderPaginatedList(source, _functionInfo) {
|
|
59
|
+
let page;
|
|
60
|
+
if (Symbol.asyncIterator in Object(source)) {
|
|
61
|
+
const iter = source[Symbol.asyncIterator]();
|
|
62
|
+
const result = await iter.next();
|
|
63
|
+
page = result.done ? undefined : result.value;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
page = await source;
|
|
67
|
+
}
|
|
68
|
+
const data = Array.isArray(page?.data) ? page.data : [];
|
|
69
|
+
outputJson({
|
|
70
|
+
data,
|
|
71
|
+
...(page?.nextCursor && { nextCursor: page.nextCursor }),
|
|
72
|
+
errors: [],
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
renderCollectedList(items, _options) {
|
|
76
|
+
outputJson({ data: items, errors: [] });
|
|
77
|
+
},
|
|
78
|
+
renderItem(value, options) {
|
|
79
|
+
outputJson({
|
|
80
|
+
data: options?.outputFile
|
|
81
|
+
? { outputFile: options.outputFile }
|
|
82
|
+
: (value ?? null),
|
|
83
|
+
errors: [],
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
async renderResponse(response) {
|
|
87
|
+
// Leave envelope as is for backwards compatibility
|
|
88
|
+
// until we fully deprecate fetch from CLI
|
|
89
|
+
console.log(JSON.stringify(await unwrapHttpResponse(response), null, 2));
|
|
90
|
+
},
|
|
91
|
+
renderError(error) {
|
|
92
|
+
outputJson({ data: null, errors: buildJsonErrors(error) });
|
|
93
|
+
throw new ZapierCliExitError(error instanceof Error ? error.message : String(error), 1);
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// ============================================================================
|
|
98
|
+
// Interactive renderer
|
|
99
|
+
// ============================================================================
|
|
100
|
+
function getItemName(functionInfo) {
|
|
101
|
+
if (functionInfo?.itemType)
|
|
102
|
+
return `${functionInfo.itemType} items`;
|
|
103
|
+
return "items";
|
|
104
|
+
}
|
|
105
|
+
function getListTitle(functionInfo) {
|
|
106
|
+
if (functionInfo.itemType)
|
|
107
|
+
return `Available ${functionInfo.itemType} items`;
|
|
108
|
+
return "items";
|
|
109
|
+
}
|
|
110
|
+
function renderItemsForDisplay(items, functionInfo, startingNumber = 0) {
|
|
111
|
+
if (functionInfo?.inputSchema) {
|
|
112
|
+
formatItemsFromSchema(functionInfo, items, startingNumber);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
items.forEach((item, index) => {
|
|
116
|
+
const obj = item;
|
|
117
|
+
const name = obj?.name || obj?.key || obj?.id || "Item";
|
|
118
|
+
console.log(`${chalk.gray(`${startingNumber + index + 1}.`)} ${chalk.cyan(String(name))}`);
|
|
119
|
+
if (obj?.description)
|
|
120
|
+
console.log(` ${chalk.dim(String(obj.description))}`);
|
|
121
|
+
console.log();
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function createInteractiveRenderer() {
|
|
126
|
+
return {
|
|
127
|
+
async renderPaginatedList(source, functionInfo) {
|
|
128
|
+
const itemName = getItemName(functionInfo);
|
|
129
|
+
if (!(Symbol.asyncIterator in Object(source))) {
|
|
130
|
+
const items = source?.data;
|
|
131
|
+
if (!Array.isArray(items) || items.length === 0) {
|
|
132
|
+
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
renderItemsForDisplay(items, functionInfo, 0);
|
|
136
|
+
console.log(chalk.green(`\n✅ Showing ${items.length} ${itemName}`));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
let totalShown = 0;
|
|
140
|
+
let pageCount = 0;
|
|
141
|
+
console.log(chalk.blue(`📋 ${getListTitle(functionInfo)}\n`));
|
|
142
|
+
for await (const page of source) {
|
|
143
|
+
const items = page.data ?? [];
|
|
144
|
+
pageCount++;
|
|
145
|
+
if (items.length === 0 && pageCount === 1) {
|
|
146
|
+
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (items.length === 0)
|
|
150
|
+
break;
|
|
151
|
+
if (pageCount > 1) {
|
|
152
|
+
console.clear();
|
|
153
|
+
console.log(chalk.blue(`📋 ${getListTitle(functionInfo)}\n`));
|
|
154
|
+
}
|
|
155
|
+
renderItemsForDisplay(items, functionInfo, totalShown);
|
|
156
|
+
totalShown += items.length;
|
|
157
|
+
console.log(chalk.green(`\n✅ Showing ${totalShown} ${itemName} (page ${pageCount})`));
|
|
158
|
+
if (page.nextCursor) {
|
|
159
|
+
const { continueReading } = await inquirer.prompt([
|
|
160
|
+
{
|
|
161
|
+
type: "confirm",
|
|
162
|
+
name: "continueReading",
|
|
163
|
+
message: "Load next page?",
|
|
164
|
+
default: true,
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
if (!continueReading)
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
console.log(chalk.gray(`\n📄 Finished browsing ${itemName}`));
|
|
175
|
+
},
|
|
176
|
+
renderCollectedList(items, { maxItems, userSpecifiedMaxItems, functionInfo } = {}) {
|
|
177
|
+
if (!Array.isArray(items)) {
|
|
178
|
+
formatJsonOutput(items);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const itemName = getItemName(functionInfo);
|
|
182
|
+
if (items.length === 0) {
|
|
183
|
+
console.log(chalk.yellow(`No ${itemName} found.`));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
console.log(chalk.green(`\n✅ Found ${items.length} ${itemName}:\n`));
|
|
187
|
+
renderItemsForDisplay(items, functionInfo);
|
|
188
|
+
if (userSpecifiedMaxItems && maxItems) {
|
|
189
|
+
console.log(chalk.gray(`\n📄 Showing up to ${maxItems} ${itemName} (--max-items ${maxItems})`));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
console.log(chalk.gray(`\n📄 All available ${itemName} shown`));
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
renderItem(value, options) {
|
|
196
|
+
if (options?.outputFile) {
|
|
197
|
+
const label = options.commandName
|
|
198
|
+
? `✅ ${options.commandName} completed successfully!`
|
|
199
|
+
: "✅ Done!";
|
|
200
|
+
console.log(chalk.green(label));
|
|
201
|
+
console.log(chalk.gray(`Output written to: ${options.outputFile}`));
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
formatJsonOutput(value);
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
async renderResponse(response) {
|
|
208
|
+
formatJsonOutput(await unwrapHttpResponse(response));
|
|
209
|
+
},
|
|
210
|
+
renderError(error) {
|
|
211
|
+
if (error instanceof ZapierCliMissingParametersError) {
|
|
212
|
+
console.error(chalk.red("❌ " + formatMissingParamsError(error)));
|
|
213
|
+
console.error("\n" + chalk.dim("Use --help to see available options"));
|
|
214
|
+
throw new ZapierCliExitError(error.message, 1);
|
|
215
|
+
}
|
|
216
|
+
if (error instanceof ZapierError) {
|
|
217
|
+
const formattedMessage = formatErrorMessage(error);
|
|
218
|
+
console.error(chalk.red("❌ Error:"), formattedMessage);
|
|
219
|
+
throw new ZapierCliExitError(formattedMessage, 1);
|
|
220
|
+
}
|
|
221
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
222
|
+
console.error(chalk.red("❌ Error:"), msg);
|
|
223
|
+
throw new ZapierCliExitError(msg, 1);
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -14,3 +14,20 @@ export declare class ZapierCliExitError extends ZapierCliError {
|
|
|
14
14
|
readonly exitCode: number;
|
|
15
15
|
constructor(message: string, exitCode?: number);
|
|
16
16
|
}
|
|
17
|
+
export interface MissingParam {
|
|
18
|
+
name: string;
|
|
19
|
+
isPositional: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare class ZapierCliValidationError extends ZapierCliError {
|
|
22
|
+
readonly name = "ZapierCliValidationError";
|
|
23
|
+
readonly code = "ZAPIER_CLI_VALIDATION_ERROR";
|
|
24
|
+
readonly exitCode = 1;
|
|
25
|
+
constructor(message: string);
|
|
26
|
+
}
|
|
27
|
+
export declare class ZapierCliMissingParametersError extends ZapierCliError {
|
|
28
|
+
readonly name = "ZapierCliMissingParametersError";
|
|
29
|
+
readonly code = "ZAPIER_CLI_MISSING_PARAMETERS";
|
|
30
|
+
readonly exitCode = 1;
|
|
31
|
+
readonly params: MissingParam[];
|
|
32
|
+
constructor(params: MissingParam[]);
|
|
33
|
+
}
|
package/dist/src/utils/errors.js
CHANGED
|
@@ -17,3 +17,20 @@ export class ZapierCliExitError extends ZapierCliError {
|
|
|
17
17
|
this.exitCode = exitCode;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
|
+
export class ZapierCliValidationError extends ZapierCliError {
|
|
21
|
+
constructor(message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = "ZapierCliValidationError";
|
|
24
|
+
this.code = "ZAPIER_CLI_VALIDATION_ERROR";
|
|
25
|
+
this.exitCode = 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export class ZapierCliMissingParametersError extends ZapierCliError {
|
|
29
|
+
constructor(params) {
|
|
30
|
+
super(`Missing required parameters: ${params.map((p) => p.name).join(", ")}`);
|
|
31
|
+
this.name = "ZapierCliMissingParametersError";
|
|
32
|
+
this.code = "ZAPIER_CLI_MISSING_PARAMETERS";
|
|
33
|
+
this.exitCode = 1;
|
|
34
|
+
this.params = params;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type ZapierSdk } from "@zapier/zapier-sdk";
|
|
3
3
|
export declare class SchemaParameterResolver {
|
|
4
|
-
resolveParameters(schema: z.ZodSchema, providedParams: unknown, sdk: ZapierSdk, functionName?: string
|
|
4
|
+
resolveParameters(schema: z.ZodSchema, providedParams: unknown, sdk: ZapierSdk, functionName?: string, options?: {
|
|
5
|
+
interactiveMode?: boolean;
|
|
6
|
+
}): Promise<unknown>;
|
|
5
7
|
private extractParametersFromSchema;
|
|
6
8
|
private analyzeFieldSchema;
|
|
7
9
|
private createResolvableParameter;
|
|
10
|
+
/**
|
|
11
|
+
* Calls `tryResolveWithoutPrompt` on a dynamic resolver.
|
|
12
|
+
* Returns the resolution result object, or null if unresolvable / throws.
|
|
13
|
+
* Note: { resolvedValue: null } is a valid result (e.g. connectionId when app has no auth);
|
|
14
|
+
* only a null return from the resolver itself means "could not auto-resolve".
|
|
15
|
+
*/
|
|
16
|
+
private tryAutoResolve;
|
|
17
|
+
/**
|
|
18
|
+
* Non-interactive resolution: auto-resolves what it can via tryAutoResolve,
|
|
19
|
+
* throws ZapierCliMissingParametersError for anything that requires user input.
|
|
20
|
+
*/
|
|
21
|
+
private resolveRequiredParamsNonInteractive;
|
|
8
22
|
private resolveParameter;
|
|
9
23
|
private resolveFieldsRecursively;
|
|
10
24
|
/**
|
|
@@ -2,7 +2,18 @@ import inquirer from "inquirer";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { runWithTelemetryContext, } from "@zapier/zapier-sdk";
|
|
5
|
-
import {
|
|
5
|
+
import { isPositional } from "@zapier/zapier-sdk";
|
|
6
|
+
import { ZapierCliUserCancellationError, ZapierCliMissingParametersError, ZapierCliValidationError, } from "./errors";
|
|
7
|
+
// Zod validation errors are structured objects; flatten them into a
|
|
8
|
+
// readable "field: reason" string for CLI error messages.
|
|
9
|
+
function formatZodError(error) {
|
|
10
|
+
return error.issues
|
|
11
|
+
.map((issue) => {
|
|
12
|
+
const field = issue.path.length > 0 ? issue.path.join(".") : "input";
|
|
13
|
+
return `${field}: ${issue.message}`;
|
|
14
|
+
})
|
|
15
|
+
.join(", ");
|
|
16
|
+
}
|
|
6
17
|
// ============================================================================
|
|
7
18
|
// Local Resolution Helper Functions
|
|
8
19
|
// ============================================================================
|
|
@@ -50,8 +61,9 @@ function getLocalResolutionOrderForParams(paramNames, resolvers) {
|
|
|
50
61
|
// Schema Parameter Resolver
|
|
51
62
|
// ============================================================================
|
|
52
63
|
export class SchemaParameterResolver {
|
|
53
|
-
async resolveParameters(schema, providedParams, sdk, functionName) {
|
|
64
|
+
async resolveParameters(schema, providedParams, sdk, functionName, options) {
|
|
54
65
|
return runWithTelemetryContext(async () => {
|
|
66
|
+
const interactiveMode = options?.interactiveMode ?? true;
|
|
55
67
|
// 1. Try to parse with current parameters
|
|
56
68
|
const parseResult = schema.safeParse(providedParams);
|
|
57
69
|
// Get all schema parameters to check which ones have resolvers
|
|
@@ -70,9 +82,10 @@ export class SchemaParameterResolver {
|
|
|
70
82
|
// Schema-required parameters are always functionally required
|
|
71
83
|
if (param.isRequired)
|
|
72
84
|
return true;
|
|
73
|
-
//
|
|
85
|
+
// Keep inputs prompting in interactive mode, but do not force it in
|
|
86
|
+
// non-interactive mode (--json), where optional inputs should remain optional.
|
|
74
87
|
if (param.name === "inputs") {
|
|
75
|
-
return
|
|
88
|
+
return interactiveMode;
|
|
76
89
|
}
|
|
77
90
|
return false;
|
|
78
91
|
});
|
|
@@ -97,7 +110,7 @@ export class SchemaParameterResolver {
|
|
|
97
110
|
if (functionallyRequired.length === 0 && alwaysPrompt.length === 0) {
|
|
98
111
|
// No functionally required parameters missing, but check if we can parse
|
|
99
112
|
if (!parseResult.success) {
|
|
100
|
-
throw parseResult.error;
|
|
113
|
+
throw new ZapierCliValidationError(formatZodError(parseResult.error));
|
|
101
114
|
}
|
|
102
115
|
return parseResult.data;
|
|
103
116
|
}
|
|
@@ -131,19 +144,24 @@ export class SchemaParameterResolver {
|
|
|
131
144
|
return param;
|
|
132
145
|
})
|
|
133
146
|
.filter((param) => param !== undefined);
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
if (!interactiveMode) {
|
|
148
|
+
await this.resolveRequiredParamsNonInteractive(orderedRequiredParams, context, resolvedParams, functionName);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
for (const param of orderedRequiredParams) {
|
|
152
|
+
try {
|
|
153
|
+
const value = await this.resolveParameter(param, context, functionName);
|
|
154
|
+
this.setNestedValue(resolvedParams, param.path, value);
|
|
155
|
+
// Update context with newly resolved value
|
|
156
|
+
context.resolvedParams = resolvedParams;
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (this.isUserCancellation(error)) {
|
|
160
|
+
console.log(chalk.yellow("\n\nOperation cancelled by user"));
|
|
161
|
+
throw new ZapierCliUserCancellationError();
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
145
164
|
}
|
|
146
|
-
throw error;
|
|
147
165
|
}
|
|
148
166
|
}
|
|
149
167
|
// Remove resolved dependencies from other categories to avoid double-prompting
|
|
@@ -151,8 +169,10 @@ export class SchemaParameterResolver {
|
|
|
151
169
|
alwaysPrompt.splice(0, alwaysPrompt.length, ...alwaysPrompt.filter((p) => !resolvedParamNames.has(p.name)));
|
|
152
170
|
trulyOptional.splice(0, trulyOptional.length, ...trulyOptional.filter((p) => !resolvedParamNames.has(p.name)));
|
|
153
171
|
}
|
|
154
|
-
// 3. Resolve parameters that should always be prompted for (but can be skipped)
|
|
155
|
-
if
|
|
172
|
+
// 3. Resolve parameters that should always be prompted for (but can be skipped).
|
|
173
|
+
// Skipped entirely in non-interactive mode - if connectionId was needed, it should
|
|
174
|
+
// have been passed explicitly via --connection-id.
|
|
175
|
+
if (interactiveMode && alwaysPrompt.length > 0) {
|
|
156
176
|
const alwaysPromptNames = alwaysPrompt.map((p) => p.name);
|
|
157
177
|
const alwaysPromptResolutionOrder = getLocalResolutionOrderForParams(alwaysPromptNames, localResolvers);
|
|
158
178
|
const orderedAlwaysPromptParams = alwaysPromptResolutionOrder
|
|
@@ -174,8 +194,8 @@ export class SchemaParameterResolver {
|
|
|
174
194
|
}
|
|
175
195
|
}
|
|
176
196
|
}
|
|
177
|
-
// 4. Ask user if they want to resolve truly optional parameters
|
|
178
|
-
if (trulyOptional.length > 0) {
|
|
197
|
+
// 4. Ask user if they want to resolve truly optional parameters (skipped in non-interactive mode)
|
|
198
|
+
if (interactiveMode && trulyOptional.length > 0) {
|
|
179
199
|
const optionalNames = trulyOptional.map((p) => p.name).join(", ");
|
|
180
200
|
const shouldResolveOptional = await inquirer.prompt([
|
|
181
201
|
{
|
|
@@ -212,8 +232,7 @@ export class SchemaParameterResolver {
|
|
|
212
232
|
// 5. Validate final parameters
|
|
213
233
|
const finalResult = schema.safeParse(resolvedParams);
|
|
214
234
|
if (!finalResult.success) {
|
|
215
|
-
|
|
216
|
-
throw finalResult.error;
|
|
235
|
+
throw new ZapierCliValidationError(`Parameter validation failed: ${formatZodError(finalResult.error)}`);
|
|
217
236
|
}
|
|
218
237
|
return finalResult.data;
|
|
219
238
|
});
|
|
@@ -258,6 +277,50 @@ export class SchemaParameterResolver {
|
|
|
258
277
|
isRequired,
|
|
259
278
|
};
|
|
260
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* Calls `tryResolveWithoutPrompt` on a dynamic resolver.
|
|
282
|
+
* Returns the resolution result object, or null if unresolvable / throws.
|
|
283
|
+
* Note: { resolvedValue: null } is a valid result (e.g. connectionId when app has no auth);
|
|
284
|
+
* only a null return from the resolver itself means "could not auto-resolve".
|
|
285
|
+
*/
|
|
286
|
+
async tryAutoResolve(dynamicResolver, context) {
|
|
287
|
+
if (!dynamicResolver.tryResolveWithoutPrompt)
|
|
288
|
+
return null;
|
|
289
|
+
try {
|
|
290
|
+
return await dynamicResolver.tryResolveWithoutPrompt(context.sdk, context.resolvedParams);
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
console.warn(`Auto-resolver threw unexpectedly; treating as unresolved. Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Non-interactive resolution: auto-resolves what it can via tryAutoResolve,
|
|
299
|
+
* throws ZapierCliMissingParametersError for anything that requires user input.
|
|
300
|
+
*/
|
|
301
|
+
async resolveRequiredParamsNonInteractive(params, context, resolvedParams, functionName) {
|
|
302
|
+
const missingParams = [];
|
|
303
|
+
for (const param of params) {
|
|
304
|
+
const resolver = this.getResolver(param.name, context.sdk, functionName);
|
|
305
|
+
const autoResolution = resolver?.type === "dynamic"
|
|
306
|
+
? await this.tryAutoResolve(resolver, context)
|
|
307
|
+
: null;
|
|
308
|
+
if (autoResolution != null) {
|
|
309
|
+
this.setNestedValue(resolvedParams, param.path, autoResolution.resolvedValue);
|
|
310
|
+
context.resolvedParams = resolvedParams;
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
missingParams.push({
|
|
314
|
+
name: param.name,
|
|
315
|
+
// Required params render as positional CLI args (<name>); so do explicitly positional optional params.
|
|
316
|
+
isPositional: param.isRequired || isPositional(param.schema),
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (missingParams.length > 0) {
|
|
321
|
+
throw new ZapierCliMissingParametersError(missingParams);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
261
324
|
async resolveParameter(param, context, functionName) {
|
|
262
325
|
const resolver = this.getResolver(param.name, context.sdk, functionName);
|
|
263
326
|
if (!resolver) {
|
|
@@ -279,16 +342,9 @@ export class SchemaParameterResolver {
|
|
|
279
342
|
}
|
|
280
343
|
else if (resolver.type === "dynamic") {
|
|
281
344
|
const dynamicResolver = resolver;
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (preResolvedValue != null) {
|
|
286
|
-
return preResolvedValue.resolvedValue;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
catch {
|
|
290
|
-
// Fall through to fetch/prompt if pre-resolution fails
|
|
291
|
-
}
|
|
345
|
+
const autoResolution = await this.tryAutoResolve(dynamicResolver, context);
|
|
346
|
+
if (autoResolution != null) {
|
|
347
|
+
return autoResolution.resolvedValue;
|
|
292
348
|
}
|
|
293
349
|
// Only show "Fetching..." for required parameters that typically have many options
|
|
294
350
|
if (param.isRequired && param.name !== "connectionId") {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function toKebabCase(str: string): string;
|