From 4153d6b7e969904ba7ef8160cd8b8c428aaffbaa Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sun, 11 Oct 2026 10:13:40 +1100 Subject: [PATCH 1/4] fix: validate nested description shapes without rejecting scalar defaults --- src/Action.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Action.ts b/src/Action.ts index d55dad20..4e52afe4 100644 --- a/src/Action.ts +++ b/src/Action.ts @@ -128,6 +128,19 @@ export default class Action implements ActionYaml { return false; } const record = obj as Record; + const isRecord = (value: unknown): value is Record => + typeof value === 'object' && value !== null && !Array.isArray(value); + const validDescriptions = (value: unknown): boolean => + value === undefined || + (isRecord(value) && + Object.values(value).every( + (entry) => + isRecord(entry) && + (entry.description === undefined || typeof entry.description === 'string'), + )); + if (!validDescriptions(record.inputs) || !validDescriptions(record.outputs)) { + return false; + } if ('name' in record && 'description' in record && 'runs' in record) { const runs = record.runs; if (typeof runs === 'object' && runs !== null && !Array.isArray(runs) && 'using' in runs) { From b54da897e5e233afc25f2a7ae93942ad5fd95be2 Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sun, 11 Oct 2026 10:19:13 +1100 Subject: [PATCH 2/4] test: cover nested YAML validation and supported scalar defaults --- __tests__/action.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/__tests__/action.test.ts b/__tests__/action.test.ts index 32a93306..84eb70e3 100644 --- a/__tests__/action.test.ts +++ b/__tests__/action.test.ts @@ -137,6 +137,44 @@ describe('Action', () => { }); }); + describe('nested metadata validation', () => { + const metadata = { + name: 'Fixture action', + description: 'Fixture description', + runs: { using: 'node24', main: 'index.js' }, + }; + + it.each([ + { inputs: { option: null } }, + { inputs: { option: 42 } }, + { inputs: { option: { description: 42 } } }, + { outputs: { result: null } }, + { outputs: { result: { description: false } } }, + { inputs: 'not a mapping' }, + ])('rejects malformed nested metadata through YAML loading: %j', async (nested) => { + vi.mocked(fs.readFileSync).mockReturnValueOnce(YAML.stringify({ ...metadata, ...nested })); + const { default: Action } = await import('../src/Action.js'); + expect(() => new Action(actTestYmlPath, mockLogTask)).toThrow('Invalid action metadata syntax'); + }); + + it('accepts scalar defaults and optional metadata through YAML loading', async () => { + vi.mocked(fs.readFileSync).mockReturnValueOnce(YAML.stringify({ + ...metadata, + inputs: { + enabled: { description: 'Enabled', default: false, required: false }, + retries: { description: 'Retries', default: 3 }, + optional: { default: null }, + }, + outputs: { result: { description: 'Result' } }, + })); + const { default: Action } = await import('../src/Action.js'); + const action = new Action(actTestYmlPath, mockLogTask); + expect(action.inputs?.enabled?.default).toBe(false); + expect(action.inputs?.retries?.default).toBe(3); + expect(action.outputs?.result?.description).toBe('Result'); + }); + }); + describe('inputDefault', () => { it('should return the default value for an input', async () => { const { default: Action } = await import('../src/Action.js'); From 6c9f771a92b8ee6cee1c4563ce4ef475bc7ae7f8 Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sun, 11 Oct 2026 10:53:51 +1100 Subject: [PATCH 3/4] fix: preserve null descriptions accepted by renderer --- src/Action.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Action.ts b/src/Action.ts index 4e52afe4..c54d3647 100644 --- a/src/Action.ts +++ b/src/Action.ts @@ -136,7 +136,7 @@ export default class Action implements ActionYaml { Object.values(value).every( (entry) => isRecord(entry) && - (entry.description === undefined || typeof entry.description === 'string'), + (entry.description == null || typeof entry.description === 'string'), )); if (!validDescriptions(record.inputs) || !validDescriptions(record.outputs)) { return false; From 7a6637470495fc42dc1f011899502e882d3bd59c Mon Sep 17 00:00:00 2001 From: Jamie Nelson Date: Sun, 11 Oct 2026 10:53:53 +1100 Subject: [PATCH 4/4] test: preserve YAML null description compatibility --- __tests__/action.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/__tests__/action.test.ts b/__tests__/action.test.ts index 84eb70e3..f81848ca 100644 --- a/__tests__/action.test.ts +++ b/__tests__/action.test.ts @@ -157,21 +157,21 @@ describe('Action', () => { expect(() => new Action(actTestYmlPath, mockLogTask)).toThrow('Invalid action metadata syntax'); }); - it('accepts scalar defaults and optional metadata through YAML loading', async () => { + it('accepts scalar defaults and nullish descriptions through YAML loading', async () => { vi.mocked(fs.readFileSync).mockReturnValueOnce(YAML.stringify({ ...metadata, inputs: { enabled: { description: 'Enabled', default: false, required: false }, retries: { description: 'Retries', default: 3 }, - optional: { default: null }, + optional: { default: null, description: null }, }, - outputs: { result: { description: 'Result' } }, + outputs: { result: { description: null } }, })); const { default: Action } = await import('../src/Action.js'); const action = new Action(actTestYmlPath, mockLogTask); expect(action.inputs?.enabled?.default).toBe(false); expect(action.inputs?.retries?.default).toBe(3); - expect(action.outputs?.result?.description).toBe('Result'); + expect(action.outputs?.result?.description).toBeNull(); }); });