Skip to content

Commit 7bbfe7b

Browse files
committed
fix: apply runtime input defaults in entrypoint
Signed-off-by: zebbern <185730623+zebbern@users.noreply.github.com>
1 parent 4e08e45 commit 7bbfe7b

2 files changed

Lines changed: 86 additions & 14 deletions

File tree

worker/src/components/core/__tests__/entry-point.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,68 @@ describe('entry-point component', () => {
136136
});
137137
});
138138

139+
it('should preserve and apply runtime input default values after parameter parsing', async () => {
140+
const component = componentRegistry.get<EntryPointInput, EntryPointOutput>(
141+
'core.workflow.entrypoint',
142+
);
143+
if (!component) throw new Error('Component not registered');
144+
145+
const context = createExecutionContext({
146+
runId: 'test-run',
147+
componentRef: 'trigger-test',
148+
});
149+
150+
const parsedParams = component.parameters!.parse({
151+
runtimeInputs: [
152+
{
153+
id: 'outOfScopePaths',
154+
label: 'Out-of-scope paths',
155+
type: 'array',
156+
required: false,
157+
defaultValue: [],
158+
},
159+
{
160+
id: 'previousSnapshot',
161+
label: 'Previous snapshot',
162+
type: 'json',
163+
required: false,
164+
defaultValue: { hosts: [], endpoints: [], findings: [] },
165+
},
166+
{
167+
id: 'includeLowSeverity',
168+
label: 'Include low severity',
169+
type: 'boolean',
170+
required: false,
171+
defaultValue: false,
172+
},
173+
{
174+
id: 'targetLimit',
175+
label: 'Target limit',
176+
type: 'number',
177+
required: false,
178+
defaultValue: 10,
179+
},
180+
],
181+
});
182+
183+
const result = (await component.execute(
184+
{
185+
inputs: {
186+
__runtimeData: {},
187+
},
188+
params: parsedParams,
189+
},
190+
context,
191+
)) as Record<string, unknown>;
192+
193+
expect(result).toEqual({
194+
outOfScopePaths: [],
195+
previousSnapshot: { hosts: [], endpoints: [], findings: [] },
196+
includeLowSeverity: false,
197+
targetLimit: 10,
198+
});
199+
});
200+
139201
it('should throw when required runtime input is missing', async () => {
140202
const component = componentRegistry.get<EntryPointInput, EntryPointOutput>(
141203
'core.workflow.entrypoint',

worker/src/components/core/entry-point.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const runtimeInputDefinitionSchema = z.preprocess(
3434
.describe('Type of input data'),
3535
required: z.boolean().default(true).describe('Whether this input is required'),
3636
description: z.string().optional().describe('Help text for the input'),
37+
defaultValue: z.unknown().optional().describe('Default value to use when input is omitted'),
3738
}),
3839
);
3940

@@ -143,33 +144,42 @@ const definition = defineComponent({
143144

144145
for (const inputDef of runtimeInputs) {
145146
const value = __runtimeData?.[inputDef.id];
147+
const hasValue = value !== undefined && value !== null;
148+
const hasDefaultValue =
149+
Object.prototype.hasOwnProperty.call(inputDef, 'defaultValue') &&
150+
inputDef.defaultValue !== undefined &&
151+
inputDef.defaultValue !== null;
152+
let outputValue = value;
153+
154+
if (!hasValue && hasDefaultValue) {
155+
outputValue = inputDef.defaultValue;
156+
}
146157

147-
if (inputDef.required && (value === undefined || value === null)) {
158+
if (inputDef.required && outputValue === undefined) {
148159
throw new ValidationError(
149160
`Required runtime input '${inputDef.label}' (${inputDef.id}) was not provided`,
150161
{
151162
fieldErrors: { [inputDef.id]: ['This field is required'] },
152163
},
153164
);
154165
}
155-
// Optional text inputs default to empty string so downstream script ports receive
156-
// a defined value instead of failing input validation / retrying indefinitely.
157-
if (
158-
(value === undefined || value === null) &&
159-
!inputDef.required &&
160-
inputDef.type === 'text'
161-
) {
162-
outputs[inputDef.id] = '';
163-
} else {
164-
outputs[inputDef.id] = value;
166+
167+
if (outputValue === undefined && !inputDef.required) {
168+
if (inputDef.type === 'text') {
169+
// Optional text inputs default to empty string so downstream script ports receive
170+
// a defined value instead of failing input validation / retrying indefinitely.
171+
outputValue = '';
172+
}
165173
}
174+
175+
outputs[inputDef.id] = outputValue;
166176
// Mask secret values in logs
167177
const logValue =
168178
inputDef.type === 'secret'
169179
? '***'
170-
: typeof value === 'object'
171-
? JSON.stringify(value)
172-
: value;
180+
: typeof outputValue === 'object'
181+
? JSON.stringify(outputValue)
182+
: outputValue;
173183
context.logger.info(`[EntryPoint] Output '${inputDef.id}' = ${logValue}`);
174184
}
175185

0 commit comments

Comments
 (0)