Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/cli/script/generate-sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,11 +633,14 @@ for (const { path, command } of allCommands) {
return `${camelCase(name)}: ${flag?.tsType ?? "string"}`;
});
const streamingConstraint = streamingFlagTypes.join("; ");
const streamingRest = hasVariadicPositional
? ", ...positional: string[]"
: "";

typeDecl = [
`${indent} /** ${brief} */`,
`${indent} ${methodName}: {`,
`${indent} (params: ${params?.name ?? "Record<string, never>"} & { ${streamingConstraint} }): AsyncIterable<unknown>;`,
`${indent} (params: ${params?.name ?? "Record<string, never>"} & { ${streamingConstraint} }${streamingRest}): AsyncIterable<unknown>;`,
`${indent} ${sig}: Promise<${returnType}>;`,
`${indent} };`,
].join("\n");
Expand Down
12 changes: 9 additions & 3 deletions packages/cli/src/lib/sdk-invoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@ export type FlagDef = {
default?: unknown;
optional?: boolean;
parse?: (value: string) => unknown;
variadic?: boolean;
/** `true` or a separator string — Stricli collects either form into an array. */
variadic?: boolean | string;
};

/** Resolved command: handler function + flag definitions for default application. */
Expand Down Expand Up @@ -161,6 +162,9 @@ function resolveFlagDefault(def: FlagDef): unknown {
* (same as Stricli's `parseInput` path in `parseInputsForFlag`).
* - For `kind: "boolean"` / `kind: "enum"` flags with a `default`, uses the raw value.
* - Skips flags already set (non-`undefined`) by the caller.
* - Wraps a scalar caller value for a variadic flag in an array. Stricli always
* hands variadic flags to the handler as arrays, so an untyped caller passing
* `{ field: "title" }` would otherwise crash on array methods (CLI-28C).
*
* @param flags - The flags object from the SDK caller (may have `undefined` values).
* @param flagDefs - The command's Stricli flag definitions.
Expand All @@ -174,9 +178,11 @@ export function applyFlagDefaults(
// Copy caller-provided flags, skipping undefined values so they
// don't shadow the defaults we're about to apply.
for (const [key, value] of Object.entries(flags)) {
if (value !== undefined) {
result[key] = value;
if (value === undefined) {
continue;
}
result[key] =
flagDefs[key]?.variadic && !Array.isArray(value) ? [value] : value;
}
// Apply defaults for any flag not already set by the caller
for (const [name, def] of Object.entries(flagDefs)) {
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/test/lib/sdk-flag-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,15 @@ describe("generated SDK flag types", () => {
expect(login).toContain("scope?: Array<string>;");
expect(login).toContain("token?: string;");
});

test("streaming overloads accept the target positional", () => {
// Streaming calls still take their target as trailing positionals, e.g.
// `sdk.log.list({ follow: "5" }, "org/project")` must type as an AsyncIterable.
expect(declarations).toContain(
"(params: LogListParams & { follow: string }, ...positional: string[]): AsyncIterable<unknown>;",
);
expect(declarations).toContain(
"(params: DashboardViewParams & { refresh: string }, ...positional: string[]): AsyncIterable<unknown>;",
);
});
});
34 changes: 34 additions & 0 deletions packages/cli/test/lib/sdk-invoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,40 @@ describe("applyFlagDefaults: boolean flags", () => {
});
});

// ---------------------------------------------------------------------------
// applyFlagDefaults — variadic flags
// ---------------------------------------------------------------------------

describe("applyFlagDefaults: variadic flags", () => {
const flagDefs: Record<string, FlagDef> = {
field: { kind: "parsed", parse: String, variadic: true, optional: true },
environment: {
kind: "parsed",
parse: String,
variadic: ",",
optional: true,
},
dataset: { kind: "parsed", parse: String, optional: true },
};

test("wraps a scalar caller value in an array", () => {
// Stricli always hands variadic flags to the handler as arrays (CLI-28C).
const result = applyFlagDefaults(
{ field: "title", environment: "prod", dataset: "spans" },
flagDefs,
);
expect(result.field).toEqual(["title"]);
expect(result.environment).toEqual(["prod"]);
expect(result.dataset).toBe("spans");
});

test("keeps caller arrays unchanged", () => {
const fields = ["title", "count()"];
const result = applyFlagDefaults({ field: fields }, flagDefs);
expect(result.field).toBe(fields);
});
});

// ---------------------------------------------------------------------------
// applyFlagDefaults — enum flags
// ---------------------------------------------------------------------------
Expand Down
Loading