@sanity/cli 3.88.0 → 3.88.1-typegen-experimental.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/_chunks-cjs/cli.js +58748 -56759
- package/lib/_chunks-cjs/cli.js.map +1 -1
- package/lib/_chunks-cjs/generateAction.js +113 -111
- package/lib/_chunks-cjs/generateAction.js.map +1 -1
- package/lib/_chunks-cjs/loadEnv.js +3 -3
- package/lib/_chunks-cjs/loadEnv.js.map +1 -1
- package/lib/_chunks-cjs/workerChannel.js +84 -0
- package/lib/_chunks-cjs/workerChannel.js.map +1 -0
- package/lib/workers/typegenGenerate.d.ts +144 -33
- package/lib/workers/typegenGenerate.js +83 -112
- package/lib/workers/typegenGenerate.js.map +1 -1
- package/package.json +19 -21
- package/src/actions/typegen/generate.telemetry.ts +9 -3
- package/src/actions/typegen/generateAction.ts +159 -152
- package/src/cli.ts +0 -0
- package/src/commands/blueprints/addBlueprintsCommand.ts +52 -56
- package/src/commands/blueprints/blueprintsGroup.ts +0 -1
- package/src/commands/blueprints/configBlueprintsCommand.ts +50 -74
- package/src/commands/blueprints/deployBlueprintsCommand.ts +41 -133
- package/src/commands/blueprints/destroyBlueprintsCommand.ts +76 -0
- package/src/commands/blueprints/infoBlueprintsCommand.ts +29 -51
- package/src/commands/blueprints/initBlueprintsCommand.ts +55 -73
- package/src/commands/blueprints/logsBlueprintsCommand.ts +43 -81
- package/src/commands/blueprints/planBlueprintsCommand.ts +26 -36
- package/src/commands/blueprints/stacksBlueprintsCommand.ts +43 -51
- package/src/commands/functions/devFunctionsCommand.ts +1 -2
- package/src/commands/functions/envFunctionsCommand.ts +55 -46
- package/src/commands/functions/functionsGroup.ts +1 -2
- package/src/commands/functions/logsFunctionsCommand.ts +101 -58
- package/src/commands/functions/testFunctionsCommand.ts +56 -36
- package/src/commands/index.ts +6 -4
- package/src/commands/projects/listProjectsCommand.ts +0 -0
- package/src/commands/projects/projectsGroup.ts +0 -0
- package/src/util/__tests__/workerChannel.test.ts +222 -0
- package/src/util/workerChannel.ts +312 -0
- package/src/workers/typegenGenerate.ts +181 -183
- package/templates/app-sanity-ui/src/ExampleComponent.tsx +1 -1
@@ -1,44 +1,49 @@
|
|
1
|
-
import
|
1
|
+
import chalk from 'chalk'
|
2
|
+
import inquirer from 'inquirer'
|
2
3
|
|
3
4
|
import {type CliCommandDefinition} from '../../types'
|
4
5
|
|
5
|
-
type StackFunctionResource = types.StackFunctionResource
|
6
|
-
|
7
6
|
const helpText = `
|
7
|
+
Arguments
|
8
|
+
<name> The name of the Function to retrieve logs for
|
9
|
+
|
8
10
|
Options
|
9
|
-
--name <name> The name of the function to retrieve logs for
|
10
11
|
--limit <limit> The number of log entries to retrieve [default 50]
|
11
12
|
--json If set return json
|
12
13
|
--utc Use UTC dates in logs
|
13
14
|
|
14
15
|
Examples
|
15
|
-
# Retrieve logs for Sanity Function
|
16
|
-
sanity functions logs
|
16
|
+
# Retrieve logs for Sanity Function
|
17
|
+
sanity functions logs echo
|
17
18
|
|
18
|
-
# Retrieve the last two log entries for Sanity Function
|
19
|
-
sanity functions logs
|
19
|
+
# Retrieve the last two log entries for Sanity Function
|
20
|
+
sanity functions logs echo --limit 2
|
20
21
|
|
21
|
-
# Retrieve logs for Sanity Function
|
22
|
+
# Retrieve logs for Sanity Function in json format
|
22
23
|
sanity functions logs --name echo --json
|
24
|
+
|
25
|
+
# Delete all logs for Sanity Function
|
26
|
+
sanity functions logs --name echo --delete
|
23
27
|
`
|
24
28
|
|
25
29
|
const defaultFlags = {
|
26
|
-
name: '',
|
27
30
|
limit: 50,
|
28
31
|
json: false,
|
29
32
|
utc: false,
|
33
|
+
delete: false,
|
34
|
+
force: false,
|
30
35
|
}
|
31
36
|
|
32
37
|
const logsFunctionsCommand: CliCommandDefinition = {
|
33
38
|
name: 'logs',
|
34
39
|
group: 'functions',
|
35
40
|
helpText,
|
36
|
-
signature: '',
|
37
|
-
description: 'Retrieve logs for a Sanity Function',
|
38
|
-
hideFromHelp: true,
|
41
|
+
signature: '<name> [--limit <number>] [--json] [--utc] [--delete [--force]]',
|
42
|
+
description: 'Retrieve or delete logs for a Sanity Function',
|
39
43
|
async action(args, context) {
|
40
44
|
const {apiClient, output} = context
|
41
|
-
const {print} = output
|
45
|
+
const {print, error: printError} = output
|
46
|
+
const [name] = args.argsWithoutOptions
|
42
47
|
const flags = {...defaultFlags, ...args.extOptions}
|
43
48
|
|
44
49
|
const client = apiClient({
|
@@ -46,69 +51,97 @@ const logsFunctionsCommand: CliCommandDefinition = {
|
|
46
51
|
requireProject: false,
|
47
52
|
})
|
48
53
|
|
49
|
-
if (
|
50
|
-
throw new Error('You must provide a function name
|
54
|
+
if (!name) {
|
55
|
+
throw new Error('You must provide a function name as the first argument')
|
51
56
|
}
|
52
57
|
|
53
58
|
const token = client.config().token
|
54
|
-
|
59
|
+
if (!token) throw new Error('No API token found. Please run `sanity login`.')
|
60
|
+
|
61
|
+
const {getBlueprintAndStack} = await import('@sanity/runtime-cli/actions/blueprints')
|
55
62
|
const {findFunction} = await import('@sanity/runtime-cli/utils')
|
56
63
|
|
57
|
-
const {deployedStack} = await
|
58
|
-
getStack: true,
|
59
|
-
token,
|
60
|
-
})
|
64
|
+
const {deployedStack} = await getBlueprintAndStack({token})
|
61
65
|
|
62
66
|
if (!deployedStack) {
|
63
67
|
throw new Error('Stack not found')
|
64
68
|
}
|
65
69
|
|
66
|
-
const
|
67
|
-
const projectId = blueprintConfig?.projectId
|
70
|
+
const {projectId} = deployedStack
|
68
71
|
|
69
|
-
const {externalId} = findFunction.findFunctionByName(
|
70
|
-
deployedStack,
|
71
|
-
flags.name,
|
72
|
-
) as StackFunctionResource
|
72
|
+
const {externalId} = findFunction.findFunctionByName(deployedStack, name)
|
73
73
|
|
74
74
|
if (token && projectId) {
|
75
75
|
const {logs: logsAction} = await import('@sanity/runtime-cli/actions/functions')
|
76
|
-
const {ok, error, logs, total} = await logsAction.logs(
|
77
|
-
externalId,
|
78
|
-
{limit: flags.limit},
|
79
|
-
{token, projectId},
|
80
|
-
)
|
81
|
-
|
82
|
-
if (!ok) {
|
83
|
-
print(`Error: ${error || 'Unknown error'}`)
|
84
|
-
return
|
85
|
-
}
|
86
76
|
|
87
|
-
|
88
|
-
|
89
|
-
)
|
77
|
+
if (!externalId) throw new Error('Unable to delete logs. Unable to determine function ID.')
|
78
|
+
|
79
|
+
if (flags.delete) {
|
80
|
+
if (!flags.force) {
|
81
|
+
const {certain} = await inquirer.prompt({
|
82
|
+
type: 'confirm',
|
83
|
+
name: 'certain',
|
84
|
+
message: `Are you sure you want to delete ${chalk.bold('all')} logs for function ${chalk.yellow(name)}?`,
|
85
|
+
default: false,
|
86
|
+
})
|
87
|
+
if (!certain) return
|
88
|
+
}
|
90
89
|
|
91
|
-
|
92
|
-
|
93
|
-
return
|
94
|
-
}
|
90
|
+
print(`Deleting logs for function ${chalk.yellow(name)}`)
|
91
|
+
const {ok, error} = await logsAction.deleteLogs(externalId, {token, projectId})
|
95
92
|
|
96
|
-
|
97
|
-
|
93
|
+
if (!ok) {
|
94
|
+
printError(`${chalk.red('Failed')} to retrieve logs`)
|
95
|
+
printError(`Error: ${error || 'Unknown error'}`)
|
96
|
+
return
|
97
|
+
}
|
98
|
+
|
99
|
+
print('Logs deleted')
|
98
100
|
} else {
|
99
|
-
print(`
|
100
|
-
|
101
|
-
|
101
|
+
print(`Finding logs for function "${name}"`)
|
102
|
+
|
103
|
+
const {ok, error, logs, total} = await logsAction.logs(
|
104
|
+
externalId,
|
105
|
+
{limit: flags.limit},
|
106
|
+
{token, projectId},
|
107
|
+
)
|
108
|
+
|
109
|
+
if (!ok) {
|
110
|
+
printError(`${chalk.red('Failed')} to retrieve logs`)
|
111
|
+
printError(`Error: ${error || 'Unknown error'}`)
|
112
|
+
return
|
113
|
+
}
|
114
|
+
|
115
|
+
const filteredLogs = logs.filter(
|
116
|
+
(entry: {level: string; message: string}) => entry.level && entry.message,
|
117
|
+
)
|
118
|
+
|
119
|
+
if (filteredLogs.length === 0) {
|
120
|
+
print(`No logs found for function ${name}`)
|
121
|
+
return
|
102
122
|
}
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
print(
|
123
|
+
|
124
|
+
if (flags.json) {
|
125
|
+
print(JSON.stringify(filteredLogs, null, 2))
|
126
|
+
} else {
|
127
|
+
print(`Found ${total} log entries for function ${name}`)
|
128
|
+
if (logs.length < total) {
|
129
|
+
print(`Here are the last ${filteredLogs.length} entries`)
|
130
|
+
}
|
131
|
+
print('\n')
|
132
|
+
|
133
|
+
for (const log of filteredLogs) {
|
134
|
+
const {time, level, message} = log
|
135
|
+
const date = new Date(time)
|
136
|
+
const [dateString, timeString] = flags.utc
|
137
|
+
? date.toISOString().slice(0, 19).split('T')
|
138
|
+
: [date.toLocaleDateString(), date.toLocaleTimeString()]
|
139
|
+
print(
|
140
|
+
[chalk.bold(dateString), chalk.bold.blue(timeString), logLevel(level), message].join(
|
141
|
+
' ',
|
142
|
+
),
|
143
|
+
)
|
144
|
+
}
|
112
145
|
}
|
113
146
|
}
|
114
147
|
} else {
|
@@ -117,4 +150,14 @@ const logsFunctionsCommand: CliCommandDefinition = {
|
|
117
150
|
},
|
118
151
|
}
|
119
152
|
|
153
|
+
function logLevel(level: string) {
|
154
|
+
if (level === 'ERROR') {
|
155
|
+
return chalk.red(level)
|
156
|
+
}
|
157
|
+
if (level === 'WARN') {
|
158
|
+
return chalk.yellow(level)
|
159
|
+
}
|
160
|
+
return chalk.green(level)
|
161
|
+
}
|
162
|
+
|
120
163
|
export default logsFunctionsCommand
|
@@ -1,76 +1,96 @@
|
|
1
1
|
import {type CliCommandDefinition} from '../../types'
|
2
2
|
|
3
3
|
const helpText = `
|
4
|
+
Arguments
|
5
|
+
<name> The name of the Sanity Function
|
6
|
+
|
4
7
|
Options
|
5
8
|
--data <data> Data to send to the function
|
6
9
|
--file <file> Read data from file and send to the function
|
7
|
-
--name <name> The name of your Sanity Function
|
8
10
|
--timeout <timeout> Execution timeout value in seconds
|
11
|
+
--api <version> Sanity API Version to use
|
12
|
+
--dataset <dataset> The Sanity dataset to use
|
13
|
+
--project-id <id> Sanity Project ID to use
|
14
|
+
|
9
15
|
|
10
16
|
Examples
|
11
17
|
# Test function passing event data on command line
|
12
|
-
sanity functions test
|
18
|
+
sanity functions test echo --data '{ "id": 1 }'
|
13
19
|
|
14
20
|
# Test function passing event data via a file
|
15
|
-
sanity functions test
|
21
|
+
sanity functions test echo --file 'payload.json'
|
16
22
|
|
17
23
|
# Test function passing event data on command line and cap execution time to 60 seconds
|
18
|
-
sanity functions test
|
24
|
+
sanity functions test echo --data '{ "id": 1 }' --timeout 60
|
19
25
|
`
|
20
26
|
|
21
27
|
const defaultFlags = {
|
22
|
-
data: undefined,
|
23
|
-
file: undefined,
|
24
|
-
|
25
|
-
|
28
|
+
'data': undefined,
|
29
|
+
'file': undefined,
|
30
|
+
'timeout': 10, // seconds
|
31
|
+
'api': undefined,
|
32
|
+
'dataset': undefined,
|
33
|
+
'project-id': undefined,
|
34
|
+
'project': undefined,
|
35
|
+
'projectId': undefined,
|
26
36
|
}
|
27
37
|
|
28
38
|
const testFunctionsCommand: CliCommandDefinition = {
|
29
39
|
name: 'test',
|
30
40
|
group: 'functions',
|
31
41
|
helpText,
|
32
|
-
signature:
|
42
|
+
signature:
|
43
|
+
'<name> [--data <json>] [--file <filename>] [--timeout <seconds>] [--api <version>] [--dataset <name>] [--project-id] <id>]',
|
33
44
|
description: 'Invoke a local Sanity Function',
|
34
|
-
hideFromHelp: true,
|
35
45
|
async action(args, context) {
|
36
46
|
const {output} = context
|
37
|
-
const {print} = output
|
47
|
+
const {print, error: printError} = output
|
48
|
+
const [name] = args.argsWithoutOptions
|
38
49
|
const flags = {...defaultFlags, ...args.extOptions}
|
39
50
|
|
40
|
-
if (
|
41
|
-
throw new Error('You must provide a function name
|
51
|
+
if (!name) {
|
52
|
+
throw new Error('You must provide a function name as the first argument')
|
42
53
|
}
|
43
54
|
|
44
55
|
const {test} = await import('@sanity/runtime-cli/actions/functions')
|
45
56
|
const {blueprint} = await import('@sanity/runtime-cli/actions/blueprints')
|
46
57
|
const {findFunction} = await import('@sanity/runtime-cli/utils')
|
47
58
|
|
48
|
-
const {parsedBlueprint} = await blueprint.
|
49
|
-
getStack: false,
|
50
|
-
})
|
59
|
+
const {parsedBlueprint} = await blueprint.readLocalBlueprint()
|
51
60
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
61
|
+
try {
|
62
|
+
const fn = findFunction.findFunctionByName(parsedBlueprint, name)
|
63
|
+
if (!fn) {
|
64
|
+
throw new Error(`Function ${name} has no source code`)
|
65
|
+
}
|
56
66
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
67
|
+
const projectId = flags['project-id'] ?? flags.projectId ?? flags.project
|
68
|
+
const {json, logs, error} = await test.testAction(
|
69
|
+
fn,
|
70
|
+
{
|
71
|
+
data: flags.data,
|
72
|
+
file: flags.file,
|
73
|
+
timeout: flags.timeout,
|
74
|
+
},
|
75
|
+
{
|
76
|
+
clientOptions: {
|
77
|
+
apiVersion: flags.api,
|
78
|
+
dataset: flags.dataset,
|
79
|
+
projectId: projectId,
|
80
|
+
},
|
81
|
+
},
|
82
|
+
)
|
66
83
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
84
|
+
if (error) {
|
85
|
+
print(error.toString())
|
86
|
+
} else {
|
87
|
+
print('Logs:')
|
88
|
+
print(logs)
|
89
|
+
print('Response:')
|
90
|
+
print(JSON.stringify(json, null, 2))
|
91
|
+
}
|
92
|
+
} catch (error) {
|
93
|
+
printError(`Error: ${error || 'Unknown error'}`)
|
74
94
|
}
|
75
95
|
},
|
76
96
|
}
|
package/src/commands/index.ts
CHANGED
@@ -3,6 +3,7 @@ import addBlueprintsCommand from './blueprints/addBlueprintsCommand'
|
|
3
3
|
import blueprintsGroup from './blueprints/blueprintsGroup'
|
4
4
|
import configBlueprintsCommand from './blueprints/configBlueprintsCommand'
|
5
5
|
import deployBlueprintsCommand from './blueprints/deployBlueprintsCommand'
|
6
|
+
import destroyBlueprintsCommand from './blueprints/destroyBlueprintsCommand'
|
6
7
|
import infoBlueprintsCommand from './blueprints/infoBlueprintsCommand'
|
7
8
|
import initBlueprintsCommand from './blueprints/initBlueprintsCommand'
|
8
9
|
import logsBlueprintsCommand from './blueprints/logsBlueprintsCommand'
|
@@ -59,12 +60,13 @@ export const baseCommands: (CliCommandDefinition | CliCommandGroupDefinition)[]
|
|
59
60
|
testfunctionsCommand,
|
60
61
|
envFunctionsCommand,
|
61
62
|
blueprintsGroup,
|
63
|
+
addBlueprintsCommand,
|
64
|
+
configBlueprintsCommand,
|
65
|
+
deployBlueprintsCommand,
|
66
|
+
destroyBlueprintsCommand,
|
62
67
|
infoBlueprintsCommand,
|
63
|
-
listBlueprintsCommand,
|
64
68
|
initBlueprintsCommand,
|
65
|
-
|
69
|
+
listBlueprintsCommand,
|
66
70
|
logsBlueprintsCommand,
|
67
|
-
addBlueprintsCommand,
|
68
|
-
configBlueprintsCommand,
|
69
71
|
planBlueprintsCommand,
|
70
72
|
]
|
File without changes
|
File without changes
|
@@ -0,0 +1,222 @@
|
|
1
|
+
import {EventEmitter} from 'node:events'
|
2
|
+
import {type MessagePort, type Worker} from 'node:worker_threads'
|
3
|
+
|
4
|
+
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
|
5
|
+
|
6
|
+
import {
|
7
|
+
createReceiver,
|
8
|
+
createReporter,
|
9
|
+
type WorkerChannel,
|
10
|
+
type WorkerChannelEvent,
|
11
|
+
type WorkerChannelStream,
|
12
|
+
} from '../workerChannel'
|
13
|
+
|
14
|
+
// Define a sample worker channel for testing
|
15
|
+
type TestWorkerChannel = WorkerChannel<{
|
16
|
+
simpleEvent: WorkerChannelEvent<string>
|
17
|
+
dataEvent: WorkerChannelEvent<{id: number; value: boolean}>
|
18
|
+
simpleStream: WorkerChannelStream<number>
|
19
|
+
endStream: WorkerChannelStream<void>
|
20
|
+
}>
|
21
|
+
|
22
|
+
// Mock Worker and MessagePort
|
23
|
+
class MockWorker extends EventEmitter {
|
24
|
+
terminated = false
|
25
|
+
terminate = vi.fn(async () => {
|
26
|
+
this.terminated = true
|
27
|
+
return 0
|
28
|
+
})
|
29
|
+
postMessage = vi.fn((message: unknown) => {
|
30
|
+
this.emit('message', message)
|
31
|
+
})
|
32
|
+
|
33
|
+
// Helper to simulate receiving a message from the parent (if needed)
|
34
|
+
receiveMessage(message: unknown) {
|
35
|
+
this.emit('message', message)
|
36
|
+
}
|
37
|
+
|
38
|
+
// Helper to simulate an error from the worker
|
39
|
+
emitError(error: unknown) {
|
40
|
+
this.emit('error', error)
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
class MockMessagePort extends EventEmitter {
|
45
|
+
postMessage = vi.fn((message: unknown) => {
|
46
|
+
// Simulate the message being sent back to the parent/receiver
|
47
|
+
// In a real scenario, this would go to the Worker's listener
|
48
|
+
mockWorkerInstance?.receiveMessage(message)
|
49
|
+
})
|
50
|
+
|
51
|
+
// Helper to simulate receiving a message (e.g., from the parent)
|
52
|
+
receiveMessage(message: unknown) {
|
53
|
+
this.emit('message', message)
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
let mockWorkerInstance: MockWorker
|
58
|
+
let mockParentPortInstance: MockMessagePort
|
59
|
+
let receiver: ReturnType<typeof createReceiver<TestWorkerChannel>>
|
60
|
+
let reporter: ReturnType<typeof createReporter<TestWorkerChannel>>
|
61
|
+
|
62
|
+
beforeEach(() => {
|
63
|
+
mockWorkerInstance = new MockWorker()
|
64
|
+
mockParentPortInstance = new MockMessagePort()
|
65
|
+
receiver = createReceiver<TestWorkerChannel>(mockWorkerInstance as unknown as Worker)
|
66
|
+
reporter = createReporter<TestWorkerChannel>(mockParentPortInstance as unknown as MessagePort)
|
67
|
+
})
|
68
|
+
|
69
|
+
afterEach(() => {
|
70
|
+
vi.clearAllMocks()
|
71
|
+
})
|
72
|
+
|
73
|
+
describe('workerChannel', () => {
|
74
|
+
it('should send and receive a simple event', async () => {
|
75
|
+
const receivedPromise = receiver.event.simpleEvent()
|
76
|
+
reporter.event.simpleEvent('hello')
|
77
|
+
|
78
|
+
await expect(receivedPromise).resolves.toBe('hello')
|
79
|
+
})
|
80
|
+
|
81
|
+
it('should send and receive an event with data object', async () => {
|
82
|
+
const payload = {id: 123, value: true}
|
83
|
+
const receivedPromise = receiver.event.dataEvent()
|
84
|
+
reporter.event.dataEvent(payload)
|
85
|
+
|
86
|
+
await expect(receivedPromise).resolves.toEqual(payload)
|
87
|
+
})
|
88
|
+
|
89
|
+
it('should send and receive a stream of data', async () => {
|
90
|
+
const receivedItems: number[] = []
|
91
|
+
const streamPromise = (async () => {
|
92
|
+
for await (const item of receiver.stream.simpleStream()) {
|
93
|
+
receivedItems.push(item)
|
94
|
+
}
|
95
|
+
})()
|
96
|
+
|
97
|
+
reporter.stream.simpleStream.emit(1)
|
98
|
+
reporter.stream.simpleStream.emit(2)
|
99
|
+
reporter.stream.simpleStream.emit(3)
|
100
|
+
reporter.stream.simpleStream.end()
|
101
|
+
|
102
|
+
await streamPromise // Wait for the stream processing to complete
|
103
|
+
|
104
|
+
expect(receivedItems).toEqual([1, 2, 3])
|
105
|
+
})
|
106
|
+
|
107
|
+
it('should handle an empty stream correctly', async () => {
|
108
|
+
let streamEntered = false
|
109
|
+
const streamPromise = (async () => {
|
110
|
+
for await (const _item of receiver.stream.endStream()) {
|
111
|
+
streamEntered = true // This should not happen
|
112
|
+
}
|
113
|
+
})()
|
114
|
+
|
115
|
+
reporter.stream.endStream.end() // End immediately
|
116
|
+
|
117
|
+
await streamPromise
|
118
|
+
|
119
|
+
expect(streamEntered).toBe(false)
|
120
|
+
})
|
121
|
+
|
122
|
+
it('should propagate errors from the worker via event receiver', async () => {
|
123
|
+
const error = new Error('Worker failed')
|
124
|
+
|
125
|
+
const receivedPromise = receiver.event.simpleEvent()
|
126
|
+
mockWorkerInstance?.emitError(error)
|
127
|
+
|
128
|
+
await expect(receivedPromise).rejects.toThrow(error)
|
129
|
+
})
|
130
|
+
|
131
|
+
it('should propagate errors from the worker via stream receiver', async () => {
|
132
|
+
const error = new Error('Worker failed during stream')
|
133
|
+
|
134
|
+
const streamPromise = (async () => {
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
136
|
+
for await (const _item of receiver.stream.simpleStream()) {
|
137
|
+
// no-op
|
138
|
+
}
|
139
|
+
})()
|
140
|
+
|
141
|
+
// Emit error before ending the stream
|
142
|
+
mockWorkerInstance?.emitError(error)
|
143
|
+
|
144
|
+
await expect(streamPromise).rejects.toThrow(error)
|
145
|
+
})
|
146
|
+
|
147
|
+
it('should handle messages arriving before receiver awaits', async () => {
|
148
|
+
// Reporter sends event *before* receiver awaits it
|
149
|
+
reporter.event.simpleEvent('early bird')
|
150
|
+
|
151
|
+
// Give a tick for the message to be processed internally by the mock
|
152
|
+
await new Promise((resolve) => setImmediate(resolve))
|
153
|
+
|
154
|
+
const receivedPromise = receiver.event.simpleEvent()
|
155
|
+
|
156
|
+
await expect(receivedPromise).resolves.toBe('early bird')
|
157
|
+
})
|
158
|
+
|
159
|
+
it('should handle stream emissions arriving before receiver iterates', async () => {
|
160
|
+
// Reporter sends stream data *before* receiver starts iterating
|
161
|
+
reporter.stream.simpleStream.emit(10)
|
162
|
+
reporter.stream.simpleStream.emit(20)
|
163
|
+
|
164
|
+
// Give a tick for messages to process
|
165
|
+
await new Promise((resolve) => setImmediate(resolve))
|
166
|
+
|
167
|
+
const receivedItems: number[] = []
|
168
|
+
const streamPromise = (async () => {
|
169
|
+
for await (const item of receiver.stream.simpleStream()) {
|
170
|
+
receivedItems.push(item)
|
171
|
+
}
|
172
|
+
})()
|
173
|
+
|
174
|
+
// Send remaining data and end
|
175
|
+
reporter.stream.simpleStream.emit(30)
|
176
|
+
reporter.stream.simpleStream.end()
|
177
|
+
|
178
|
+
await streamPromise
|
179
|
+
|
180
|
+
expect(receivedItems).toEqual([10, 20, 30])
|
181
|
+
})
|
182
|
+
|
183
|
+
it('dispose() should remove listeners and terminate worker', async () => {
|
184
|
+
expect(mockWorkerInstance?.listenerCount('message')).toBe(1)
|
185
|
+
expect(mockWorkerInstance?.listenerCount('error')).toBe(1)
|
186
|
+
|
187
|
+
const terminatePromise = receiver.dispose()
|
188
|
+
|
189
|
+
await expect(terminatePromise).resolves.toBe(0)
|
190
|
+
expect(mockWorkerInstance?.terminate).toHaveBeenCalledTimes(1)
|
191
|
+
expect(mockWorkerInstance?.listenerCount('message')).toBe(0)
|
192
|
+
expect(mockWorkerInstance?.listenerCount('error')).toBe(0)
|
193
|
+
expect(mockWorkerInstance?.terminated).toBe(true)
|
194
|
+
})
|
195
|
+
|
196
|
+
it('should throw error if parentPort is null for reporter', () => {
|
197
|
+
expect(() => createReporter<TestWorkerChannel>(null)).toThrow('parentPart was falsy')
|
198
|
+
})
|
199
|
+
|
200
|
+
it('should ignore non-worker channel messages', async () => {
|
201
|
+
const receivedPromise = receiver.event.simpleEvent()
|
202
|
+
|
203
|
+
// Send a valid message
|
204
|
+
reporter.event.simpleEvent('valid')
|
205
|
+
await expect(receivedPromise).resolves.toBe('valid')
|
206
|
+
|
207
|
+
const nextReceivedPromise = receiver.event.simpleEvent()
|
208
|
+
|
209
|
+
// Send an invalid message
|
210
|
+
mockWorkerInstance?.receiveMessage({foo: 'bar'}) // Not a valid WorkerChannelMessage
|
211
|
+
mockWorkerInstance?.receiveMessage('just a string')
|
212
|
+
mockWorkerInstance?.receiveMessage(null)
|
213
|
+
mockWorkerInstance?.receiveMessage(undefined)
|
214
|
+
mockWorkerInstance?.receiveMessage({type: 'unknown'})
|
215
|
+
|
216
|
+
// Send the actual message we are waiting for
|
217
|
+
reporter.event.simpleEvent('after invalid')
|
218
|
+
|
219
|
+
// It should eventually resolve with the correct message, ignoring the invalid ones
|
220
|
+
await expect(nextReceivedPromise).resolves.toBe('after invalid')
|
221
|
+
})
|
222
|
+
})
|