diff --git a/.changeset/fair-bobcats-applaud.md b/.changeset/fair-bobcats-applaud.md new file mode 100644 index 000000000..bc5da4f74 --- /dev/null +++ b/.changeset/fair-bobcats-applaud.md @@ -0,0 +1,5 @@ +--- +'@openfn/engine-multi': patch +--- + +Record adaptor versions as an array diff --git a/.changeset/yellow-clouds-thank.md b/.changeset/yellow-clouds-thank.md new file mode 100644 index 000000000..fabbc3d3b --- /dev/null +++ b/.changeset/yellow-clouds-thank.md @@ -0,0 +1,5 @@ +--- +'@openfn/ws-worker': patch +--- + +Move version log to workflow start diff --git a/packages/engine-multi/src/api/autoinstall.ts b/packages/engine-multi/src/api/autoinstall.ts index 1eb87b961..1264d8870 100644 --- a/packages/engine-multi/src/api/autoinstall.ts +++ b/packages/engine-multi/src/api/autoinstall.ts @@ -135,11 +135,13 @@ const autoinstall = async (context: ExecutionContext): Promise => { const v = version || 'unknown'; - // Write the adaptor version to the context - // This is a reasonably accurate, but not totally bulletproof, report - // @ts-ignore - // TODO need to remove this soon as it's basically lying - context.versions[name] = v; + // Write the adaptor version to the context for reporting later + if (!context.versions[name]) { + context.versions[name] = []; + } + if (!context.versions[name].includes(v)) { + (context.versions[name] as string[]).push(v); + } paths[a] = { path: `${repoDir}/node_modules/${alias}`, diff --git a/packages/engine-multi/src/api/lifecycle.ts b/packages/engine-multi/src/api/lifecycle.ts index f7c71101b..7b66ce6ae 100644 --- a/packages/engine-multi/src/api/lifecycle.ts +++ b/packages/engine-multi/src/api/lifecycle.ts @@ -38,6 +38,7 @@ export const workflowStart = ( // forward the event on to any external listeners context.emit(externalEvents.WORKFLOW_START, { threadId, + versions: context.versions, }); }; @@ -81,7 +82,6 @@ export const jobStart = ( context.emit(externalEvents.JOB_START, { jobId, threadId, - versions: context.versions, }); }; diff --git a/packages/engine-multi/src/classes/ExecutionContext.ts b/packages/engine-multi/src/classes/ExecutionContext.ts index 0e7c70480..db6c90c49 100644 --- a/packages/engine-multi/src/classes/ExecutionContext.ts +++ b/packages/engine-multi/src/classes/ExecutionContext.ts @@ -12,7 +12,7 @@ import type { import type { ExternalEvents, EventMap } from '../events'; /** - * The ExeuctionContext class wraps an event emitter with some useful context + * The ExecutionContext class wraps an event emitter with some useful context * and automatically appends the workflow id to each emitted events * * Each running workflow has its own context object diff --git a/packages/engine-multi/src/events.ts b/packages/engine-multi/src/events.ts index 4dc1d63e6..f32c5eaca 100644 --- a/packages/engine-multi/src/events.ts +++ b/packages/engine-multi/src/events.ts @@ -49,7 +49,9 @@ interface ExternalEvent { workflowId: string; } -export interface WorkflowStartPayload extends ExternalEvent {} +export interface WorkflowStartPayload extends ExternalEvent { + versions: Versions; +} export interface WorkflowCompletePayload extends ExternalEvent { state: any; @@ -64,7 +66,6 @@ export interface WorkflowErrorPayload extends ExternalEvent { export interface JobStartPayload extends ExternalEvent { jobId: string; - versions: Versions; } export interface JobCompletePayload extends ExternalEvent { diff --git a/packages/engine-multi/src/types.ts b/packages/engine-multi/src/types.ts index bc69b7445..76bead978 100644 --- a/packages/engine-multi/src/types.ts +++ b/packages/engine-multi/src/types.ts @@ -83,5 +83,6 @@ export type Versions = { node: string; engine: string; compiler: string; - [adaptor: string]: string; + + [adaptor: string]: string | string[]; }; diff --git a/packages/engine-multi/test/api/autoinstall.test.ts b/packages/engine-multi/test/api/autoinstall.test.ts index d1ee4b235..e63c306d3 100644 --- a/packages/engine-multi/test/api/autoinstall.test.ts +++ b/packages/engine-multi/test/api/autoinstall.test.ts @@ -565,7 +565,7 @@ test('write versions to context', async (t) => { await autoinstall(context); // @ts-ignore - t.is(context.versions['@openfn/language-common'], '1.0.0'); + t.deepEqual(context.versions['@openfn/language-common'], ['1.0.0']); }); test("write versions to context even if we don't install", async (t) => { @@ -578,5 +578,5 @@ test("write versions to context even if we don't install", async (t) => { await autoinstall(context); // @ts-ignore - t.is(context.versions['@openfn/language-common'], '1.0.0'); + t.deepEqual(context.versions['@openfn/language-common'], ['1.0.0']); }); diff --git a/packages/engine-multi/test/api/execute.test.ts b/packages/engine-multi/test/api/execute.test.ts index 9b46e2a74..e20727c1e 100644 --- a/packages/engine-multi/test/api/execute.test.ts +++ b/packages/engine-multi/test/api/execute.test.ts @@ -86,6 +86,7 @@ test.serial('should emit a workflow-start event', async (t) => { id: 'x', plan, } as WorkflowState; + let workflowStart; const context = createContext({ state, options }); @@ -96,6 +97,9 @@ test.serial('should emit a workflow-start event', async (t) => { // No need to do a deep test of the event payload here t.is(workflowStart!.workflowId!, 'x'); + // Just a shallow test on the actual version object to verify that it's been attached + t.truthy(workflowStart!.versions); + t.regex(workflowStart!.versions.node, new RegExp(/(\d+).(\d+).\d+/)); }); test.serial('should emit a log event with the memory limit', async (t) => { @@ -156,9 +160,6 @@ test.serial('should emit a job-start event', async (t) => { await execute(context); t.is(event.jobId, 'j'); - t.truthy(event.versions); - // Just a shallow test on the actual version object to verify that it's been attached - t.regex(event.versions.node, new RegExp(/(\d+).(\d+).\d+/)); }); test.serial('should emit a job-complete event', async (t) => { diff --git a/packages/engine-multi/test/api/lifecycle.test.ts b/packages/engine-multi/test/api/lifecycle.test.ts index 329128e5d..05896c20a 100644 --- a/packages/engine-multi/test/api/lifecycle.test.ts +++ b/packages/engine-multi/test/api/lifecycle.test.ts @@ -36,10 +36,9 @@ test(`workflowStart: emits ${e.WORKFLOW_START}`, (t) => { }; context.on(e.WORKFLOW_START, (evt) => { - t.deepEqual(evt, { - workflowId, - threadId: '123', - }); + t.truthy(evt.versions); + t.is(evt.workflowId, workflowId); + t.is(evt.threadId, '123'); done(); }); diff --git a/packages/engine-multi/test/integration.test.ts b/packages/engine-multi/test/integration.test.ts index 21c34e2cd..93a489850 100644 --- a/packages/engine-multi/test/integration.test.ts +++ b/packages/engine-multi/test/integration.test.ts @@ -56,6 +56,7 @@ test.serial('trigger workflow-start', (t) => { api.execute(plan, emptyState).on('workflow-start', (evt) => { t.is(evt.workflowId, plan.id); t.truthy(evt.threadId); + t.truthy(evt.versions); t.pass('workflow started'); done(); }); @@ -77,7 +78,6 @@ test.serial('trigger job-start', (t) => { t.is(e.workflowId, '2'); t.is(e.jobId, 'j1'); t.truthy(e.threadId); - t.truthy(e.versions); t.pass('job started'); done(); }); diff --git a/packages/ws-worker/src/api/execute.ts b/packages/ws-worker/src/api/execute.ts index c35ea2ca9..6cac55c12 100644 --- a/packages/ws-worker/src/api/execute.ts +++ b/packages/ws-worker/src/api/execute.ts @@ -1,11 +1,7 @@ import type { ExecutionPlan, Lazy, State } from '@openfn/lexicon'; -import type { RunLogPayload, RunStartPayload } from '@openfn/lexicon/lightning'; +import type { RunLogPayload } from '@openfn/lexicon/lightning'; import type { Logger } from '@openfn/logger'; -import type { - RuntimeEngine, - Resolvers, - WorkflowStartPayload, -} from '@openfn/engine-multi'; +import type { RuntimeEngine, Resolvers } from '@openfn/engine-multi'; import { getWithReply, @@ -21,6 +17,7 @@ import { STEP_START, GET_CREDENTIAL, } from '../events'; +import handleRunStart from '../events/run-start'; import handleStepComplete from '../events/step-complete'; import handleStepStart from '../events/step-start'; import handleRunComplete from '../events/run-complete'; @@ -114,7 +111,7 @@ export function execute( // so that they send in order const listeners = Object.assign( {}, - addEvent('workflow-start', throttle(onWorkflowStart)), + addEvent('workflow-start', throttle(handleRunStart)), addEvent('job-start', throttle(handleStepStart)), addEvent('job-complete', throttle(handleStepComplete)), addEvent('job-error', throttle(onJobError)), @@ -213,13 +210,6 @@ export function onJobError(context: Context, event: any) { } } -export function onWorkflowStart( - { channel }: Context, - _event: WorkflowStartPayload -) { - return sendEvent(channel, RUN_START); -} - export function onJobLog({ channel, state }: Context, event: JSONLog) { const timeInMicroseconds = BigInt(event.time) / BigInt(1e3); diff --git a/packages/ws-worker/src/events/run-start.ts b/packages/ws-worker/src/events/run-start.ts new file mode 100644 index 000000000..8845f5446 --- /dev/null +++ b/packages/ws-worker/src/events/run-start.ts @@ -0,0 +1,47 @@ +import type { RunStartPayload } from '@openfn/lexicon/lightning'; +import { timestamp } from '@openfn/logger'; +import type { WorkflowStartPayload } from '@openfn/engine-multi'; + +import { RUN_START } from '../events'; +import { sendEvent, Context, onJobLog } from '../api/execute'; +import calculateVersionString from '../util/versions'; + +import pkg from '../../package.json' assert { type: 'json' }; + +export default async function onRunStart( + context: Context, + event: WorkflowStartPayload +) { + const { channel, state } = context; + // Cheat on the timestamp time to make sure this is the first thing in the log + const time = (timestamp() - BigInt(10e6)).toString(); + + // Send the log with its own little state object + // to preserve the run id + // Otherwise, by the time the log sends, + // the active step could have changed + // TODO if I fix ordering I think I can kill this + const versionLogContext = { + ...context, + state: { + ...state, + activeStep: state.activeStep, + }, + }; + + const versions = { + worker: pkg.version, + ...event.versions, + }; + + await sendEvent(channel, RUN_START, { versions }); + + const versionMessage = calculateVersionString(versions); + + await onJobLog(versionLogContext, { + time, + message: [versionMessage], + level: 'info', + name: 'VER', + }); +} diff --git a/packages/ws-worker/src/events/step-start.ts b/packages/ws-worker/src/events/step-start.ts index 561652431..3eea012e1 100644 --- a/packages/ws-worker/src/events/step-start.ts +++ b/packages/ws-worker/src/events/step-start.ts @@ -1,70 +1,25 @@ import crypto from 'node:crypto'; -import { timestamp } from '@openfn/logger'; import { JobStartPayload } from '@openfn/engine-multi'; -import type { Job } from '@openfn/lexicon'; import type { StepStartPayload } from '@openfn/lexicon/lightning'; -import pkg from '../../package.json' assert { type: 'json' }; import { STEP_START } from '../events'; -import { sendEvent, Context, onJobLog } from '../api/execute'; -import calculateVersionString from '../util/versions'; +import { sendEvent, Context } from '../api/execute'; export default async function onStepStart( context: Context, event: JobStartPayload ) { - // Cheat on the timestamp time to make sure this is the first thing in the log - const time = (timestamp() - BigInt(10e6)).toString(); - const { channel, state } = context; // generate a run id and write it to state state.activeStep = crypto.randomUUID(); state.activeJob = event.jobId; - const job = state.plan.workflow.steps.find( - ({ id }) => id === event.jobId - ) as Job; - const input_dataclip_id = state.inputDataclips[event.jobId]; - const versions = { - worker: pkg.version, - ...event.versions, - }; - - // Send the log with its own little state object - // to preserve the run id - // Otherwise, by the time the log sends, - // the active step could have changed - // TODO if I fix ordering I think I can kill this - const versionLogContext = { - ...context, - state: { - ...state, - activeStep: state.activeStep, - }, - }; - await sendEvent(channel, STEP_START, { step_id: state.activeStep!, job_id: state.activeJob!, input_dataclip_id, - - versions, - }); - - const versionMessage = calculateVersionString( - versionLogContext.state.activeStep, - versions, - job?.adaptor - ); - - await onJobLog(versionLogContext, { - time, - message: [versionMessage], - level: 'info', - name: 'VER', }); - return; } diff --git a/packages/ws-worker/src/mock/runtime-engine.ts b/packages/ws-worker/src/mock/runtime-engine.ts index f96541056..6e0c9ef46 100644 --- a/packages/ws-worker/src/mock/runtime-engine.ts +++ b/packages/ws-worker/src/mock/runtime-engine.ts @@ -98,6 +98,7 @@ async function createMock() { level: 'info', json: true, message: JSON.stringify(args), + name: 'JOB', time: Date.now(), }); }, diff --git a/packages/ws-worker/src/util/versions.ts b/packages/ws-worker/src/util/versions.ts index 8c3ba4383..4c955b72e 100644 --- a/packages/ws-worker/src/util/versions.ts +++ b/packages/ws-worker/src/util/versions.ts @@ -6,33 +6,35 @@ export type Versions = { node: string; worker: string; - [adaptor: string]: string; + [adaptor: string]: string | string[]; }; -export default (stepId: string, versions: Versions, adaptor?: string) => { +export default (versions: Versions) => { let longest = 'worker'.length; // Bit wierdly defensive but ensure padding is reasonable even if version has no props for (const v in versions) { longest = Math.max(v.length, longest); } - const { node, worker, ...adaptors } = versions; + const { node, worker, compiler, runtime, engine, ...adaptors } = versions; // Prefix and pad version numbers const prefix = (str: string) => ` ${t} ${str.padEnd(longest + 4, ' ')}`; - let str = `Versions for step ${stepId}: + let str = `Versions: ${prefix('node.js')}${versions.node || 'unknown'} ${prefix('worker')}${versions.worker || 'unknown'}`; if (Object.keys(adaptors).length) { let allAdaptors = Object.keys(adaptors); - if (adaptor) { - allAdaptors = allAdaptors.filter((name) => adaptor.startsWith(name)); - } str += '\n' + allAdaptors .sort() - .map((adaptorName) => `${prefix(adaptorName)}${adaptors[adaptorName]}`) + .map( + (adaptorName) => + `${prefix(adaptorName)}${(adaptors[adaptorName] as string[]) + .sort() + .join(', ')}` + ) .join('\n'); } diff --git a/packages/ws-worker/test/api/execute.test.ts b/packages/ws-worker/test/api/execute.test.ts index 4c74bfb65..818b2c08f 100644 --- a/packages/ws-worker/test/api/execute.test.ts +++ b/packages/ws-worker/test/api/execute.test.ts @@ -14,7 +14,6 @@ import { import { onJobLog, execute, - onWorkflowStart, loadDataclip, loadCredential, sendEvent, @@ -181,17 +180,6 @@ test('jobError should trigger step:complete with a reason and default state', as t.deepEqual(stepCompleteEvent.output_dataclip, '{}'); }); -test('workflowStart should send an empty run:start event', async (t) => { - const channel = mockChannel({ - [RUN_START]: () => { - t.pass(); - }, - }); - - // @ts-ignore - await onWorkflowStart({ channel }); -}); - // test('workflowComplete should send an run:complete event', async (t) => { // const result = { answer: 42 }; diff --git a/packages/ws-worker/test/events/run-start.test.ts b/packages/ws-worker/test/events/run-start.test.ts new file mode 100644 index 000000000..4eab3a903 --- /dev/null +++ b/packages/ws-worker/test/events/run-start.test.ts @@ -0,0 +1,101 @@ +import test from 'ava'; +import type { WorkflowStartPayload } from '@openfn/engine-multi'; + +import handleRunStart from '../../src/events/run-start'; +import { mockChannel } from '../../src/mock/sockets'; +import { createRunState } from '../../src/util'; +import { RUN_LOG, RUN_START } from '../../src/events'; + +import pkg from '../../package.json' assert { type: 'json' }; + +test('run:start event should include versions', async (t) => { + const plan = { + id: 'run-1', + workflow: { + steps: [{ id: 'job-1', expression: '.' }], + }, + options: {}, + }; + const input = 'abc'; + const jobId = 'job-1'; + + const versions = { + node: process.version.substring(1), + '@openfn/language-common': ['1.0.0'], + }; + + // Simulate an event that would be generated by the worker + const event: WorkflowStartPayload = { + workflowId: plan.id, + // @ts-ignore + versions, + }; + + const state = createRunState(plan, input); + state.activeJob = jobId; + state.activeStep = 'b'; + + const channel = mockChannel({ + [RUN_START]: (evt) => { + t.deepEqual(evt.versions, { + ...versions, + worker: pkg.version, + }); + return true; + }, + [RUN_LOG]: () => true, + }); + + await handleRunStart({ channel, state } as any, event); +}); + +test('run:start should log the version number', async (t) => { + let logEvent: any; + const plan = { + id: 'run-1', + workflow: { + steps: [{ id: 'job-1', expression: '.' }], + }, + options: {}, + }; + const input = 'abc'; + const jobId = 'job-1'; + + const versions = { + node: process.version.substring(1), + engine: '1.0.0', + compiler: '1.0.0', + worker: pkg.version, + '@openfn/language-common': ['1.0.0'], + }; + + // Simulate an event that would be generated by the worker + const event: WorkflowStartPayload = { + workflowId: plan.id, + versions, + }; + + const state = createRunState(plan, input); + state.activeJob = jobId; + state.activeStep = 'b'; + + const channel = mockChannel({ + [RUN_START]: () => true, + [RUN_LOG]: (evt) => { + if (evt.source === 'VER') { + logEvent = evt; + } + return true; + }, + }); + + await handleRunStart({ channel, state } as any, event); + + t.truthy(logEvent); + t.is(logEvent.level, 'info'); + const [message] = logEvent.message; + t.log(message); + // This just a light test of the string to make sure it's here + // It uses src/util/versions, which is tested elsewhere + t.regex(message, /(node\.js).+(worker).+(@openfn\/language-common)/is); +}); diff --git a/packages/ws-worker/test/events/step-start.test.ts b/packages/ws-worker/test/events/step-start.test.ts index e97b69a61..343ef5bd0 100644 --- a/packages/ws-worker/test/events/step-start.test.ts +++ b/packages/ws-worker/test/events/step-start.test.ts @@ -1,14 +1,10 @@ import test from 'ava'; import handleStepStart from '../../src/events/step-start'; -import { JobStartPayload } from '@openfn/engine-multi'; - import { mockChannel } from '../../src/mock/sockets'; import { createRunState } from '../../src/util'; import { RUN_LOG, STEP_START } from '../../src/events'; -import pkg from '../../package.json' assert { type: 'json' }; - test('set a step id and active job on state', async (t) => { const plan = { id: 'run-1', @@ -60,97 +56,3 @@ test('send a step:start event', async (t) => { await handleStepStart({ channel, state } as any, { jobId } as any); }); - -test('step:start event should include versions', async (t) => { - const plan = { - id: 'run-1', - workflow: { - steps: [{ id: 'job-1', expression: '.' }], - }, - options: {}, - }; - const input = 'abc'; - const jobId = 'job-1'; - - const versions = { - node: process.version.substring(1), - engine: '1.0.0', - compiler: '1.0.0', - worker: pkg.version, - }; - - // Simulate an event that would be generated by the worker - const event: JobStartPayload = { - jobId, - workflowId: plan.id, - versions, - }; - - const state = createRunState(plan, input); - state.activeJob = jobId; - state.activeStep = 'b'; - - const channel = mockChannel({ - [STEP_START]: (evt) => { - t.deepEqual(evt.versions, { - ...versions, - worker: pkg.version, - }); - return true; - }, - [RUN_LOG]: () => true, - }); - - await handleStepStart({ channel, state } as any, event); -}); - -test('also logs the version number', async (t) => { - let logEvent: any; - const plan = { - id: 'run-1', - workflow: { - steps: [{ id: 'job-1', expression: '.' }], - }, - options: {}, - }; - const input = 'abc'; - const jobId = 'job-1'; - - const versions = { - node: process.version.substring(1), - engine: '1.0.0', - compiler: '1.0.0', - worker: pkg.version, - }; - - // Simulate an event that would be generated by the worker - const event: JobStartPayload = { - jobId, - workflowId: plan.id, - versions, - }; - - const state = createRunState(plan, input); - state.activeJob = jobId; - state.activeStep = 'b'; - - const channel = mockChannel({ - [STEP_START]: () => true, - [RUN_LOG]: (evt) => { - if (evt.source === 'VER') { - logEvent = evt; - } - return true; - }, - }); - - await handleStepStart({ channel, state } as any, event); - - t.truthy(logEvent); - t.is(logEvent.level, 'info'); - const [message] = logEvent.message; - t.log(message); - // This just a light test of the string to make sure it's here - // It uses src/util/versions, which is tested elsewhere - t.regex(message, /(node\.js).+(worker).+(engine)/is); -}); diff --git a/packages/ws-worker/test/lightning.test.ts b/packages/ws-worker/test/lightning.test.ts index 1fe55c90a..bc40d2133 100644 --- a/packages/ws-worker/test/lightning.test.ts +++ b/packages/ws-worker/test/lightning.test.ts @@ -402,15 +402,22 @@ test.serial(`events: lightning should receive a ${e.RUN_LOG} event`, (t) => { ], }; - lng.onSocketEvent(e.RUN_LOG, run.id, ({ payload }: any) => { - const log = payload; - - t.is(log.level, 'info'); - t.truthy(log.run_id); - t.truthy(log.step_id); - t.truthy(log.message); - t.deepEqual(log.message, ['x']); - }); + lng.onSocketEvent( + e.RUN_LOG, + run.id, + ({ payload }: any) => { + if (payload.source === 'JOB') { + const log = payload; + + t.is(log.level, 'info'); + t.truthy(log.run_id); + t.truthy(log.step_id); + t.truthy(log.message); + t.deepEqual(log.message, ['x']); + } + }, + false + ); lng.onSocketEvent(e.RUN_COMPLETE, run.id, () => { done(); @@ -712,11 +719,16 @@ test.serial(`worker should send a success reason in the logs`, (t) => { ], }; - lng.onSocketEvent(e.RUN_LOG, run.id, ({ payload }: any) => { - if (payload.message[0].match(/Run complete with status: success/)) { - log = payload.message[0]; - } - }); + lng.onSocketEvent( + e.RUN_LOG, + run.id, + ({ payload }: any) => { + if (payload.message[0].match(/Run complete with status: success/)) { + log = payload.message[0]; + } + }, + false + ); lng.onSocketEvent(e.RUN_COMPLETE, run.id, () => { t.truthy(log); @@ -740,11 +752,16 @@ test.serial(`worker should send a fail reason in the logs`, (t) => { ], }; - lng.onSocketEvent(e.RUN_LOG, run.id, ({ payload }: any) => { - if (payload.message[0].match(/Run complete with status: fail/)) { - log = payload.message[0]; - } - }); + lng.onSocketEvent( + e.RUN_LOG, + run.id, + ({ payload }: any) => { + if (payload.message[0].match(/Run complete with status: fail/)) { + log = payload.message[0]; + } + }, + false + ); lng.onSocketEvent(e.RUN_COMPLETE, run.id, () => { t.truthy(log); diff --git a/packages/ws-worker/test/util/versions.test.ts b/packages/ws-worker/test/util/versions.test.ts index 6aadf00a3..1a0911bdf 100644 --- a/packages/ws-worker/test/util/versions.test.ts +++ b/packages/ws-worker/test/util/versions.test.ts @@ -25,18 +25,18 @@ const parse = (str: string) => { }; test('calculate version string', (t) => { - const str = calculateVersionString('step-1', versions); + const str = calculateVersionString(versions); // Formatting is super fussy in this test but it's sort of OK t.is( str, - `Versions for step step-1: + `Versions: ▸ node.js 1 ▸ worker 2` ); }); test('helper should parse a version string and return the correct order', (t) => { - const str = calculateVersionString('step-1', versions); + const str = calculateVersionString(versions); const parsed = parse(str); t.deepEqual(parsed, [ @@ -47,7 +47,7 @@ test('helper should parse a version string and return the correct order', (t) => test("show unknown if a version isn't passed", (t) => { // @ts-ignore - const str = calculateVersionString('step-1', {}); + const str = calculateVersionString({}); const parsed = parse(str); t.deepEqual(parsed, [ @@ -57,8 +57,8 @@ test("show unknown if a version isn't passed", (t) => { }); test('show adaptors last', (t) => { - const str = calculateVersionString('step-1', { - '@openfn/language-common': '1.0.0', + const str = calculateVersionString({ + '@openfn/language-common': ['1.0.0'], ...versions, }); const parsed = parse(str); @@ -67,10 +67,10 @@ test('show adaptors last', (t) => { }); test('sort and list multiple adaptors', (t) => { - const str = calculateVersionString('step-1', { - j: '2', - z: '3', - a: '1', + const str = calculateVersionString({ + j: ['2'], + z: ['3'], + a: ['1'], ...versions, }); @@ -84,3 +84,13 @@ test('sort and list multiple adaptors', (t) => { t.deepEqual(j, ['j', '2']); t.deepEqual(z, ['z', '3']); }); + +test('show multiple adaptor versions', (t) => { + const str = calculateVersionString({ + '@openfn/language-common': ['1.0.0', '2.0.0'], + ...versions, + }); + const parsed = parse(str); + const common = parsed[2]; + t.deepEqual(common, ['@openfn/language-common', '1.0.0,', '2.0.0']); +});