comq 0.14.0 → 0.15.1

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.
@@ -25,9 +25,15 @@ class Connection {
25
25
  /** @type {Promex} */
26
26
  #recovery = new Promex()
27
27
 
28
+ /** @type {Promise<void> | null} */
29
+ #opening = null
30
+
28
31
  /** @type {boolean} */
29
32
  #running = false
30
33
 
34
+ /** @type {NodeJS.Timeout | null} */
35
+ #heartbeatTimer = null
36
+
31
37
  #diagnostics = emitter.create()
32
38
 
33
39
  /**
@@ -35,10 +41,17 @@ class Connection {
35
41
  */
36
42
  constructor (url) {
37
43
  this.#url = url
44
+
45
+ // EventEmitter throws on 'error' with no listeners
46
+ this.#diagnostics.on('error', noop)
38
47
  }
39
48
 
40
49
  async open () {
41
- await retry(this.#open)
50
+ if (this.#opening !== null) return this.#opening
51
+
52
+ this.#opening = retry(this.#open).finally(() => { this.#opening = null })
53
+
54
+ await this.#opening
42
55
 
43
56
  this.#running = true
44
57
  }
@@ -71,8 +84,11 @@ class Connection {
71
84
  }
72
85
 
73
86
  #open = async (retry) => {
87
+ /** @type {comq.amqp.Connection} */
88
+ let connection
89
+
74
90
  try {
75
- this.#connection = await amqp.connect(this.#url)
91
+ connection = await amqp.connect(this.#url)
76
92
  } catch (exception) {
77
93
  if (this.#transient(exception)) return retry
78
94
  else throw exception
@@ -80,40 +96,104 @@ class Connection {
80
96
 
81
97
  // This prevents the process from crashing; 'close' will be emitted next.
82
98
  // https://amqp-node.github.io/amqplib/channel_api.html#model_events
83
- this.#connection.on('error', noop)
99
+ connection.on('error', noop)
84
100
 
85
- this.#connection.on('close', this.#close)
101
+ connection.on('close', (error) => this.#close(connection, error))
102
+ this.#connection = connection
103
+ this.#armWatchdog(connection)
86
104
  this.#diagnostics.emit('open')
87
105
 
88
- for (const channel of this.#channels) await channel.recover(this.#connection)
106
+ try {
107
+ for (const channel of this.#channels) await channel.recover(connection)
108
+ } catch (exception) {
109
+ this.#disarmWatchdog()
110
+ this.#diagnostics.emit('error', exception)
111
+ connection.removeAllListeners()
112
+
113
+ await connection.close().catch(noop)
114
+
115
+ if (this.#connection === connection) this.#connection = undefined
116
+
117
+ return retry
118
+ }
89
119
 
90
120
  this.#recovery.resolve()
91
121
  this.#recovery = new Promex()
92
122
  }
93
123
 
94
124
  /**
95
- * @param {Error} error
125
+ * @param {comq.amqp.Connection} connection
126
+ * @param {Error} [error]
96
127
  */
97
- #close = async (error) => {
128
+ #close = (connection, error) => {
129
+ this.#disarmWatchdog()
130
+
131
+ if (this.#connection !== connection) return
132
+
98
133
  this.#diagnostics.emit('close', error)
99
- this.#connection.removeAllListeners()
134
+ connection.removeAllListeners()
100
135
  this.#connection = undefined
101
136
 
102
- if (error !== undefined) await this.open()
137
+ if (error !== undefined) {
138
+ this.open().catch((exception) => this.#diagnostics.emit('error', exception))
139
+ }
103
140
  }
104
141
 
105
142
  #recover () {
106
143
  return this.#recovery
107
144
  }
108
145
 
146
+ /**
147
+ * @param {comq.amqp.Connection} connection
148
+ * @param {number} [timeoutMs]
149
+ */
150
+ #armWatchdog (connection, timeoutMs = WATCHDOG_MS) {
151
+ const socket = connection.connection?.stream
152
+
153
+ if (socket === undefined) return
154
+
155
+ const reset = () => {
156
+ clearTimeout(this.#heartbeatTimer)
157
+ this.#heartbeatTimer = setTimeout(() => socket.destroy(), timeoutMs)
158
+ this.#heartbeatTimer.unref()
159
+ }
160
+
161
+ reset()
162
+ socket.on('data', reset)
163
+ }
164
+
165
+ #disarmWatchdog () {
166
+ clearTimeout(this.#heartbeatTimer)
167
+ this.#heartbeatTimer = null
168
+ }
169
+
109
170
  #transient (exception) {
110
- const abruptly = exception.message === 'Socket closed abruptly during opening handshake'
111
- const tls = exception.message === 'Client network socket disconnected before secure TLS connection was established'
171
+ if (this.#running) return true
172
+ if (TRANSIENT_CODES.has(exception.code)) return true
173
+ if (TRANSIENT_MESSAGES.has(exception.message)) return true
112
174
 
113
- return this.#running || abruptly || tls
175
+ return false
114
176
  }
115
177
  }
116
178
 
179
+ /** @type {number} */
180
+ const WATCHDOG_MS = 60_000
181
+
182
+ const TRANSIENT_CODES = new Set([
183
+ 'ECONNREFUSED',
184
+ 'EAI_AGAIN',
185
+ 'ENOTFOUND',
186
+ 'ETIMEDOUT',
187
+ 'ECONNRESET',
188
+ 'EHOSTUNREACH',
189
+ 'ENETUNREACH'
190
+ ])
191
+
192
+ const TRANSIENT_MESSAGES = new Set([
193
+ 'Socket closed abruptly during opening handshake',
194
+ 'Client network socket disconnected before secure TLS connection was established'
195
+ ])
196
+
117
197
  function noop () {}
118
198
 
119
199
  exports.Connection = Connection
package/source/events.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  /** @type {comq.diagnostics.Event[]} */
4
- exports.connection = ['open', 'close']
4
+ exports.connection = ['open', 'close', 'error']
5
5
 
6
6
  /** @type {comq.diagnostics.Event[]} */
7
7
  exports.channel = ['flow', 'drain', 'recover', 'discard', 'pause', 'resume']
package/source/io.js CHANGED
@@ -3,7 +3,7 @@
3
3
  const stream = require('node:stream')
4
4
  const { EventEmitter } = require('node:events')
5
5
  const { randomBytes } = require('node:crypto')
6
- const { setTimeout } = require('node:timers/promises')
6
+ const { setTimeout: delay } = require('node:timers/promises')
7
7
  const { Promex } = require('promex')
8
8
  const { memo, failsafe, lazy, track } = require('./attributes')
9
9
 
@@ -75,24 +75,45 @@ class IO {
75
75
  /**
76
76
  * @param {string} queue
77
77
  * @param {any | Readable} payload
78
- * @param {comq.Encoding} [encoding]
78
+ * @param {comq.Encoding | number} [encoding]
79
+ * @param {number} [timeout]
79
80
  * @returns {Promise<any | Readable>}
80
81
  */
81
- async (queue, payload, encoding) => {
82
+ async (queue, payload, encoding, timeout) => {
83
+ if (typeof encoding === 'number') {
84
+ timeout = encoding
85
+ encoding = undefined
86
+ }
87
+
82
88
  if (payload instanceof stream.Readable) {
83
89
  return pipeline(
84
90
  payload,
85
- (payload) => this.request(queue, payload, encoding),
91
+ (payload) => this.request(queue, payload, encoding, timeout),
86
92
  this.#requests
87
93
  )
88
94
  }
89
95
 
90
- const request = this.#createRequest(queue, payload, encoding)
96
+ const request = this.#createRequest(queue, payload, /** @type {comq.Encoding} */ encoding)
91
97
  const reply = this.#createReply(request)
92
98
 
93
- await this.#requests.send(queue, request.buffer, request.properties)
99
+ const done = this.#requests.send(queue, request.buffer, request.properties)
100
+ .then(() => reply)
101
+
102
+ if (typeof timeout !== 'number') return done
94
103
 
95
- return reply
104
+ try {
105
+ return await Promise.race([
106
+ done,
107
+ delay(timeout).then(() => Promise.reject(timeoutError(timeout)))
108
+ ])
109
+ } catch (exception) {
110
+ if (exception.code === 'ETIMEDOUT') {
111
+ request.emitter.removeAllListeners(request.properties.correlationId)
112
+ reply.reject(exception)
113
+ }
114
+
115
+ throw exception
116
+ }
96
117
  }))
97
118
 
98
119
  consume = lazy(this, this.#createEventChannel,
@@ -396,7 +417,7 @@ class IO {
396
417
  Even if these messages are lost, the reply stream will be closed anyway,
397
418
  either due to missing heartbeat or the deletion of the stream queue.
398
419
  */
399
- await setTimeout(50)
420
+ await delay(50)
400
421
  }
401
422
 
402
423
  /**
@@ -456,6 +477,19 @@ const DEFAULT = 'application/json'
456
477
 
457
478
  const RETRANSMISSION = /** @type {Error} */ Symbol('retransmission')
458
479
 
480
+ /**
481
+ * @param {number} timeout
482
+ * @returns {Error}
483
+ */
484
+ function timeoutError (timeout) {
485
+ const error = /** @type {Error & { code: string }} */ new Error(
486
+ `Request timed out after ${timeout}ms`)
487
+
488
+ error.code = 'ETIMEDOUT'
489
+
490
+ return error
491
+ }
492
+
459
493
  function noop () {}
460
494
 
461
495
  exports.IO = IO
@@ -0,0 +1,45 @@
1
+ 'use strict'
2
+
3
+ const { EventEmitter } = require('node:events')
4
+ const { once } = require('node:events')
5
+ const { ReplyStream } = require('../source/.io/ReplyStream')
6
+
7
+ /** @type {jest.Mock} */
8
+ let reply
9
+
10
+ /** @type {ReplyStream} */
11
+ let stream
12
+
13
+ beforeEach(() => {
14
+ global.COMQ_TESTING_MAX_BUFFER_SIZE = 3
15
+ global.COMQ_TESTING_IDLE_INTERVAL = 60_000
16
+
17
+ reply = jest.fn().mockResolvedValue(true)
18
+
19
+ const request = {
20
+ emitter: new EventEmitter(),
21
+ properties: { correlationId: 'test-correlation' }
22
+ }
23
+
24
+ stream = new ReplyStream(request, reply)
25
+ })
26
+
27
+ afterEach(() => {
28
+ delete global.COMQ_TESTING_MAX_BUFFER_SIZE
29
+ delete global.COMQ_TESTING_IDLE_INTERVAL
30
+
31
+ stream.destroy()
32
+ })
33
+
34
+ it('should destroy without calling reply when buffer overflows before control.ok', async () => {
35
+ const closed = once(stream, 'close')
36
+
37
+ for (let index = 1; index <= 5; index++) {
38
+ stream.arrange(index, { headers: { index } })
39
+ }
40
+
41
+ await closed
42
+
43
+ expect(stream.destroyed).toBe(true)
44
+ expect(reply).not.toHaveBeenCalled()
45
+ })
@@ -21,6 +21,12 @@ class Connection extends EventEmitter {
21
21
  constructor () {
22
22
  super()
23
23
 
24
+ const stream = new EventEmitter()
25
+
26
+ stream.destroy = jest.fn()
27
+
28
+ this.connection = { stream }
29
+
24
30
  // noinspection JSValidateTypes
25
31
  this.removeAllListeners = jest.spyOn(this, 'removeAllListeners')
26
32
 
@@ -552,6 +552,29 @@ describe('recovery', () => {
552
552
  await expect(channel.consume(queue, consumer)).rejects.toStrictEqual(exception)
553
553
  })
554
554
 
555
+ it('should not deadlock when recall hits a transient error during recover', async () => {
556
+ await channel.consume(queue, consumer)
557
+
558
+ const replacement = await amqplib.connect()
559
+ const method = `create${topology.confirms ? 'Confirm' : ''}Channel`
560
+
561
+ replacement[method].mockImplementationOnce(async () => {
562
+ const transient = await amqplib.connect()
563
+ const bad = await transient[method]()
564
+
565
+ bad.consume.mockImplementation(async () => { throw new Error('Channel closed') })
566
+
567
+ return bad
568
+ })
569
+
570
+ const deadlock = timeout(200).then(() => {
571
+ throw new Error('recover hung (deadlock)')
572
+ })
573
+
574
+ await expect(Promise.race([channel.recover(replacement), deadlock]))
575
+ .rejects.toThrow('Channel closed')
576
+ })
577
+
555
578
  describe.each(/** @type {[string, boolean][]} */ [
556
579
  ['', false],
557
580
  [' not', true]
@@ -40,7 +40,15 @@ describe('initial connection', () => {
40
40
  })
41
41
 
42
42
  it.each(/** @type {[string, Partial<Error>][]} */[
43
- ['Socket closed', { message: 'Socket closed abruptly during opening handshake' }]
43
+ ['Socket closed', { message: 'Socket closed abruptly during opening handshake' }],
44
+ ['TLS disconnect', { message: 'Client network socket disconnected before secure TLS connection was established' }],
45
+ ['ECONNREFUSED', { code: 'ECONNREFUSED' }],
46
+ ['EAI_AGAIN', { code: 'EAI_AGAIN' }],
47
+ ['ENOTFOUND', { code: 'ENOTFOUND' }],
48
+ ['ETIMEDOUT', { code: 'ETIMEDOUT' }],
49
+ ['ECONNRESET', { code: 'ECONNRESET' }],
50
+ ['EHOSTUNREACH', { code: 'EHOSTUNREACH' }],
51
+ ['ENETUNREACH', { code: 'ENETUNREACH' }]
44
52
  ])('should reconnect on %s',
45
53
  async (_, error) => {
46
54
  amqplib.connect.mockImplementationOnce(async () => { throw error })
@@ -50,10 +58,9 @@ describe('initial connection', () => {
50
58
  expect(amqplib.connect).toHaveBeenCalledTimes(2)
51
59
  })
52
60
 
53
- it.each(/** @type {[string, Partial<Error>][]} */[
54
- ['ECONNREFUSED', { code: 'ECONNREFUSED' }],
55
- ['any exception', new Error(generate())]
56
- ])('should throw if error is %s', async (_, exception) => {
61
+ it('should throw if error is permanent', async () => {
62
+ const exception = new Error(generate())
63
+
57
64
  amqplib.connect.mockImplementationOnce(async () => { throw exception })
58
65
 
59
66
  await expect(connection.open()).rejects.toStrictEqual(exception)
@@ -109,6 +116,68 @@ describe('reconnection', () => {
109
116
 
110
117
  expect(channel.recover).toHaveBeenCalledWith(replacement)
111
118
  })
119
+
120
+ it('should retry when channel recover fails', async () => {
121
+ const channel = await connection.createChannel('request')
122
+
123
+ channel.recover
124
+ .mockRejectedValueOnce(new Error('Channel closed'))
125
+ .mockResolvedValue(undefined)
126
+
127
+ conn.emit('close', new Error())
128
+
129
+ const start = Date.now()
130
+
131
+ while (channel.recover.mock.calls.length < 2 && Date.now() - start < 10000) {
132
+ await timeout(50)
133
+ }
134
+
135
+ expect(channel.recover).toHaveBeenCalledTimes(2)
136
+ expect(amqplib.connect.mock.calls.length).toBeGreaterThanOrEqual(3)
137
+ }, 15000)
138
+
139
+ it('should emit error when reconnect open fails', async () => {
140
+ const errors = []
141
+ const unhandled = jest.fn()
142
+
143
+ connection.diagnose('error', (exception) => errors.push(exception))
144
+ process.on('unhandledRejection', unhandled)
145
+
146
+ const boom = new Error('reconnect failed')
147
+
148
+ connection.open = jest.fn(async () => { throw boom })
149
+
150
+ conn.emit('close', new Error('broker down'))
151
+
152
+ await timeout(10)
153
+
154
+ process.off('unhandledRejection', unhandled)
155
+
156
+ expect(errors).toContain(boom)
157
+ expect(unhandled).not.toHaveBeenCalled()
158
+ })
159
+
160
+ it('should ignore close from a stale connection', async () => {
161
+ const stale = conn
162
+ const closeHandler = stale.on.mock.calls.find(([event]) => event === 'close')[1]
163
+
164
+ stale.emit('close', new Error('gone'))
165
+
166
+ await timeout(50)
167
+
168
+ const live = await amqplib.connect.mock.results[1].value
169
+ const connects = amqplib.connect.mock.calls.length
170
+
171
+ expect(live).not.toBe(stale)
172
+
173
+ live.removeAllListeners.mockClear()
174
+ closeHandler(new Error('late'))
175
+
176
+ await timeout(10)
177
+
178
+ expect(amqplib.connect).toHaveBeenCalledTimes(connects)
179
+ expect(live.removeAllListeners).not.toHaveBeenCalled()
180
+ })
112
181
  })
113
182
 
114
183
  describe('create channel', () => {
@@ -203,6 +272,44 @@ describe('create channel', () => {
203
272
  })
204
273
  })
205
274
 
275
+ describe('watchdog', () => {
276
+ afterEach(() => {
277
+ jest.useRealTimers()
278
+ })
279
+
280
+ it('should destroy silent connection when watchdog expires', async () => {
281
+ jest.useFakeTimers()
282
+
283
+ await connection.open()
284
+
285
+ const conn = await amqplib.connect.mock.results[0].value
286
+
287
+ await jest.advanceTimersByTimeAsync(60_000)
288
+
289
+ expect(conn.connection.stream.destroy).toHaveBeenCalled()
290
+ })
291
+
292
+ it('should reset watchdog on socket data', async () => {
293
+ jest.useFakeTimers()
294
+
295
+ await connection.open()
296
+
297
+ const conn = await amqplib.connect.mock.results[0].value
298
+
299
+ await jest.advanceTimersByTimeAsync(50_000)
300
+
301
+ conn.connection.stream.emit('data', Buffer.alloc(0))
302
+
303
+ await jest.advanceTimersByTimeAsync(50_000)
304
+
305
+ expect(conn.connection.stream.destroy).not.toHaveBeenCalled()
306
+
307
+ await jest.advanceTimersByTimeAsync(10_000)
308
+
309
+ expect(conn.connection.stream.destroy).toHaveBeenCalled()
310
+ })
311
+ })
312
+
206
313
  describe('close', () => {
207
314
  it('should close connection', async () => {
208
315
  await connection.open()
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+
3
+ const { generate } = require('randomstring')
4
+
5
+ const mock = require('./connection.mock')
6
+ const { IO } = require('../source/io')
7
+
8
+ /** @type {comq.IO} */
9
+ let io
10
+
11
+ /** @type {jest.MockedObject<comq.Connection>} */
12
+ let connection
13
+
14
+ const queue = generate()
15
+ const payload = { [generate()]: generate() }
16
+
17
+ beforeEach(() => {
18
+ jest.clearAllMocks()
19
+
20
+ connection = mock.connection()
21
+ io = new IO(connection)
22
+ })
23
+
24
+ it('should reject when request times out', async () => {
25
+ await expect(io.request(queue, payload, 50)).rejects.toMatchObject({
26
+ message: expect.stringContaining('timed out'),
27
+ code: 'ETIMEDOUT'
28
+ })
29
+ })
30
+
31
+ it('should reject when request times out with encoding', async () => {
32
+ await expect(io.request(queue, payload, 'application/json', 50)).rejects.toMatchObject({
33
+ code: 'ETIMEDOUT'
34
+ })
35
+ })
36
+
37
+ it('should time out while send is still pending', async () => {
38
+ connection.createChannel.mockImplementation(async () => hangingChannel())
39
+
40
+ await expect(io.request(queue, payload, 50)).rejects.toMatchObject({
41
+ code: 'ETIMEDOUT'
42
+ })
43
+ })
44
+
45
+ /**
46
+ * @returns {jest.MockedObject<comq.Channel>}
47
+ */
48
+ function hangingChannel () {
49
+ return /** @type {jest.MockedObject<comq.Channel>} */ ({
50
+ sharded: false,
51
+ consume: jest.fn(async () => undefined),
52
+ send: jest.fn(() => new Promise(() => {})),
53
+ deliver: jest.fn(async () => undefined),
54
+ fire: jest.fn(async () => undefined),
55
+ subscribe: jest.fn(async () => undefined),
56
+ publish: jest.fn(async () => undefined),
57
+ diagnose: jest.fn(),
58
+ seal: jest.fn(async () => undefined),
59
+ recover: jest.fn(async () => undefined)
60
+ })
61
+ }
@@ -1,6 +1,6 @@
1
1
  declare namespace comq.diagnostics {
2
2
 
3
- type Event = 'open' | 'close' | 'flow' | 'drain' | 'remove' | 'recover' | 'discard' |
3
+ type Event = 'open' | 'close' | 'error' | 'flow' | 'drain' | 'remove' | 'recover' | 'discard' |
4
4
  'pause' | 'resume' | 'return'
5
5
 
6
6
  interface Diagnosable {
package/types/io.d.ts CHANGED
@@ -27,9 +27,11 @@ declare namespace comq {
27
27
  interface IO extends _diagnostics.Diagnosable {
28
28
  reply(queue: string, produce: Producer): Promise<void>
29
29
 
30
- request<Reply = any, Request = any>(queue: string, payload: Request, encoding?: _encoding.Encoding): Promise<Reply> | Promise<Readable>
30
+ request<Reply = any, Request = any>(queue: string, payload: Request, encoding?: _encoding.Encoding, timeout?: number): Promise<Reply> | Promise<Readable>
31
31
 
32
- request(queue: string, stream: Readable, encoding?: _encoding.Encoding): Promise<Readable>
32
+ request<Reply = any, Request = any>(queue: string, payload: Request, timeout: number): Promise<Reply> | Promise<Readable>
33
+
34
+ request(queue: string, stream: Readable, encoding?: _encoding.Encoding, timeout?: number): Promise<Readable>
33
35
 
34
36
  consume<T = any>(exchange: string, group: string, consumer: Consumer<T>): Promise<void>
35
37
 
@@ -61,6 +63,8 @@ declare namespace comq {
61
63
 
62
64
  diagnose(event: 'close', listener: (index?: number) => void): void
63
65
 
66
+ diagnose(event: 'error', listener: (error: Error, index?: number) => void): void
67
+
64
68
  diagnose(event: 'flow', listener: (channel: _topology.type, index?: number) => void): void
65
69
 
66
70
  diagnose(event: 'drain', listener: (channel: _topology.type, index?: number) => void): void
@@ -1,78 +0,0 @@
1
- # For most projects, this workflow file will not need changing; you simply need
2
- # to commit it to your repository.
3
- #
4
- # You may wish to alter this file to override the set of languages analyzed,
5
- # or to provide custom queries or build logic.
6
- #
7
- # ******** NOTE ********
8
- # We have attempted to detect the languages in your repository. Please check
9
- # the `language` matrix defined below to confirm you have the correct set of
10
- # supported CodeQL languages.
11
- #
12
- name: "CodeQL"
13
-
14
- on:
15
- push:
16
- branches: [ "main" ]
17
- paths: [ "source/**" ]
18
- pull_request:
19
- # The branches below must be a subset of the branches above
20
- branches: [ "main" ]
21
- paths: [ "source/**" ]
22
- schedule:
23
- - cron: '15 8 * * 3'
24
-
25
- jobs:
26
- analyze:
27
- name: Analyze
28
- runs-on: ubuntu-latest
29
- permissions:
30
- actions: read
31
- contents: read
32
- security-events: write
33
-
34
- strategy:
35
- fail-fast: false
36
- matrix:
37
- language: [ 'javascript' ]
38
- # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
39
- # Use only 'java' to analyze code written in Java, Kotlin or both
40
- # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both
41
- # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
42
-
43
- steps:
44
- - name: Checkout repository
45
- uses: actions/checkout@v6
46
-
47
- # Initializes the CodeQL tools for scanning.
48
- - name: Initialize CodeQL
49
- uses: github/codeql-action/init@v4
50
- with:
51
- languages: ${{ matrix.language }}
52
- # If you wish to specify custom queries, you can do so here or in a config file.
53
- # By default, queries listed here will override any specified in a config file.
54
- # Prefix the list here with "+" to use these queries and those in the config file.
55
-
56
- # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
57
- # queries: security-extended,security-and-quality
58
-
59
-
60
- # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
61
- # If this step fails, then you should remove it and run the build manually (see below)
62
- - name: Autobuild
63
- uses: github/codeql-action/autobuild@v4
64
-
65
- # ℹ️ Command-line programs to run using the OS shell.
66
- # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
67
-
68
- # If the Autobuild fails above, remove it and uncomment the following three lines.
69
- # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
70
-
71
- # - run: |
72
- # echo "Run, Build Application using script"
73
- # ./location_of_script_within_repo/buildscript.sh
74
-
75
- - name: Perform CodeQL Analysis
76
- uses: github/codeql-action/analyze@v4
77
- with:
78
- category: "/language:${{matrix.language}}"