Improve type-check for scaffolder output parameters

If an action uses zod to define the actions output schema, it is now ensured that `ctx.output` is using only the defined properties

Signed-off-by: Andreas Berger <andreas@berger-ecommerce.com>
This commit is contained in:
Andreas Berger
2023-03-16 18:25:52 +01:00
committed by blam
parent 12f1a5feac
commit a7eb36c6e3
11 changed files with 567 additions and 438 deletions
+25 -6
View File
@@ -16,13 +16,19 @@ import { Writable } from 'stream';
import { z } from 'zod';
// @public
export type ActionContext<TActionInput extends JsonObject> = {
export type ActionContext<
TActionInput extends JsonObject,
TActionOutput extends JsonObject | undefined = undefined,
> = {
logger: Logger;
logStream: Writable;
secrets?: TaskSecrets;
workspacePath: string;
input: TActionInput;
output(name: string, value: JsonValue): void;
output<KEY extends keyof TActionOutput>(
name: TActionOutput extends undefined ? string : KEY,
value: TActionOutput extends undefined ? JsonValue : TActionOutput[KEY],
): void;
createTemporaryDirectory(): Promise<string>;
templateInfo?: TemplateInfo;
isDryRun?: boolean;
@@ -41,9 +47,12 @@ export const createTemplateAction: <
TActionInput = TInputSchema extends z.ZodType<any, any, infer IReturn>
? IReturn
: TParams,
TActionOutput = TOutputSchema extends z.ZodType<any, any, infer IReturn_1>
? IReturn_1
: undefined,
>(
action: TemplateActionOptions<TActionInput, TInputSchema, TOutputSchema>,
) => TemplateAction<TActionInput>;
) => TemplateAction<TActionInput, TActionOutput>;
// @alpha
export interface ScaffolderActionsExtensionPoint {
@@ -60,7 +69,10 @@ export type TaskSecrets = Record<string, string> & {
};
// @public (undocumented)
export type TemplateAction<TActionInput = unknown> = {
export type TemplateAction<
TActionInput = unknown,
TActionOutput = undefined,
> = {
id: string;
description?: string;
examples?: {
@@ -72,7 +84,7 @@ export type TemplateAction<TActionInput = unknown> = {
input?: Schema;
output?: Schema;
};
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
handler: (ctx: ActionContext<TActionInput, TActionOutput>) => Promise<void>;
};
// @public (undocumented)
@@ -92,6 +104,13 @@ export type TemplateActionOptions<
input?: TInputSchema;
output?: TOutputSchema;
};
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
handler: (
ctx: ActionContext<
TActionInput,
TOutputSchema extends z.ZodType<any, any, infer IReturn>
? IReturn
: undefined
>,
) => Promise<void>;
};
```
@@ -33,7 +33,14 @@ export type TemplateActionOptions<
input?: TInputSchema;
output?: TOutputSchema;
};
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
handler: (
ctx: ActionContext<
TActionInput,
TOutputSchema extends z.ZodType<any, any, infer IReturn>
? IReturn
: undefined
>,
) => Promise<void>;
};
/**
@@ -48,9 +55,12 @@ export const createTemplateAction = <
TActionInput = TInputSchema extends z.ZodType<any, any, infer IReturn>
? IReturn
: TParams,
TActionOutput = TOutputSchema extends z.ZodType<any, any, infer IReturn>
? IReturn
: undefined,
>(
action: TemplateActionOptions<TActionInput, TInputSchema, TOutputSchema>,
): TemplateAction<TActionInput> => {
): TemplateAction<TActionInput, TActionOutput> => {
const inputSchema =
action.schema?.input && 'safeParseAsync' in action.schema.input
? zodToJsonSchema(action.schema.input)
@@ -61,7 +71,7 @@ export const createTemplateAction = <
? zodToJsonSchema(action.schema.output)
: action.schema?.output;
const templateAction = {
return {
...action,
schema: {
...action.schema,
@@ -69,6 +79,4 @@ export const createTemplateAction = <
output: outputSchema,
},
};
return templateAction;
};
+14 -5
View File
@@ -17,7 +17,7 @@
import { Logger } from 'winston';
import { Writable } from 'stream';
import { JsonObject, JsonValue } from '@backstage/types';
import { TaskSecrets } from '../tasks/types';
import { TaskSecrets } from '../tasks';
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
import { UserEntity } from '@backstage/catalog-model';
import { Schema } from 'jsonschema';
@@ -26,13 +26,19 @@ import { Schema } from 'jsonschema';
* ActionContext is passed into scaffolder actions.
* @public
*/
export type ActionContext<TActionInput extends JsonObject> = {
export type ActionContext<
TActionInput extends JsonObject,
TActionOutput extends JsonObject | undefined = undefined,
> = {
logger: Logger;
logStream: Writable;
secrets?: TaskSecrets;
workspacePath: string;
input: TActionInput;
output(name: string, value: JsonValue): void;
output<KEY extends keyof TActionOutput>(
name: TActionOutput extends undefined ? string : KEY,
value: TActionOutput extends undefined ? JsonValue : TActionOutput[KEY],
): void;
/**
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
@@ -68,7 +74,10 @@ export type ActionContext<TActionInput extends JsonObject> = {
};
/** @public */
export type TemplateAction<TActionInput = unknown> = {
export type TemplateAction<
TActionInput = unknown,
TActionOutput = undefined,
> = {
id: string;
description?: string;
examples?: { description: string; example: string }[];
@@ -77,5 +86,5 @@ export type TemplateAction<TActionInput = unknown> = {
input?: Schema;
output?: Schema;
};
handler: (ctx: ActionContext<TActionInput>) => Promise<void>;
handler: (ctx: ActionContext<TActionInput, TActionOutput>) => Promise<void>;
};