Skip to content

Commit eb45ce3

Browse files
committed
Merge branch 'refs/heads/main' into 7447_enforce-scoped-pats
# Conflicts: # forge/routes/auth/index.js # test/unit/forge/routes/api/user_spec.js
2 parents ddafda4 + 80066ac commit eb45ce3

8 files changed

Lines changed: 383 additions & 260 deletions

File tree

.github/workflows/release-publish.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,23 @@ jobs:
2121
- uses: JS-DevTools/npm-publish@0fd2f4369c5d6bcfcde6091a7c527d810b9b5c3f # v4.1.5
2222
with:
2323
token: ${{ secrets.NPM_PUBLISH_TOKEN }}
24+
25+
notify-slack:
26+
name: Notify about release failure
27+
needs: [publish]
28+
if: failure()
29+
runs-on: ubuntu-latest
30+
31+
steps:
32+
- name: Notify Slack
33+
uses: FlowFuse/github-actions-workflows/actions/slack_notification@slack_notification/v1
34+
with:
35+
bot-token: ${{ secrets.SLACK_GHBOT_TOKEN }}
36+
mode: channel
37+
blocks: |
38+
[
39+
{ "type": "header", "text": { "type": "plain_text", "text": ":x: ${{ github.workflow }} workflow failed", "emoji": true } },
40+
{ "type": "divider" },
41+
{ "type": "section", "text": { "type": "mrkdwn", "text": "<${{ github.server_url }}/${{ github.repository }}/actions/workflows/release-publish.yml|${{ github.workflow }}> workflow failed to complete successfully." } },
42+
{ "type": "section", "text": { "type": "mrkdwn", "text": "*Workflow run:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View>" } }
43+
]

forge/containers/stub/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
* @memberof forge.containers.drivers
1010
*
1111
*/
12-
const { normalize } = require('path')
12+
const { posix: { normalize } } = require('path')
1313

14-
const nrUtil = require('@node-red/util') // eslint-disable-line
14+
const nrUtil = {
15+
util: require('./red_util')
16+
}
1517

1618
const forgeUtils = require('../../db/utils')
1719

forge/containers/stub/red_util.js

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
/* eslint-disable no-useless-escape */
2+
3+
// Extracted from @node-red/util/util.js to avoid dependency on @node-red/util package
4+
5+
function createError (code, message) {
6+
const e = new Error(message)
7+
e.code = code
8+
return e
9+
}
10+
11+
/**
12+
* Parses a property expression, such as `msg.foo.bar[3]` to validate it
13+
* and convert it to a canonical version expressed as an Array of property
14+
* names.
15+
*
16+
* For example, `a["b"].c` returns `['a','b','c']`
17+
*
18+
* If `msg` is provided, any internal cross-references will be evaluated against that
19+
* object. Otherwise, it will return a nested set of properties
20+
*
21+
* For example, without msg set, 'a[msg.foo]' returns `['a', [ 'msg', 'foo'] ]`
22+
* But if msg is set to '{"foo": "bar"}', 'a[msg.foo]' returns `['a', 'bar' ]`
23+
*
24+
* @param {String} str - the property expression
25+
* @return {Array} the normalised expression
26+
* @memberof @node-red/util_util
27+
*/
28+
function normalisePropertyExpression (str, msg, toString) {
29+
// This must be kept in sync with validatePropertyExpression
30+
// in editor/js/ui/utils.js
31+
32+
const length = str.length
33+
if (length === 0) {
34+
throw createError('INVALID_EXPR', 'Invalid property expression: zero-length')
35+
}
36+
const parts = []
37+
let start = 0
38+
let inString = false
39+
let inBox = false
40+
let quoteChar
41+
let v
42+
for (let i = 0; i < length; i++) {
43+
const c = str[i]
44+
if (!inString) {
45+
if (c === "'" || c === '"') {
46+
if (i !== start) {
47+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected ' + c + ' at position ' + i)
48+
}
49+
inString = true
50+
quoteChar = c
51+
start = i + 1
52+
} else if (c === '.') {
53+
if (i === 0) {
54+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected . at position 0')
55+
}
56+
if (start !== i) {
57+
v = str.substring(start, i)
58+
if (/^\d+$/.test(v)) {
59+
parts.push(parseInt(v))
60+
} else {
61+
parts.push(v)
62+
}
63+
}
64+
if (i === length - 1) {
65+
throw createError('INVALID_EXPR', 'Invalid property expression: unterminated expression')
66+
}
67+
// Next char is first char of an identifier: a-z 0-9 $ _
68+
if (!/[a-z0-9\$\_]/i.test(str[i + 1])) {
69+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected ' + str[i + 1] + ' at position ' + (i + 1))
70+
}
71+
start = i + 1
72+
} else if (c === '[') {
73+
if (i === 0) {
74+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected ' + c + ' at position ' + i)
75+
}
76+
if (start !== i) {
77+
parts.push(str.substring(start, i))
78+
}
79+
if (i === length - 1) {
80+
throw createError('INVALID_EXPR', 'Invalid property expression: unterminated expression')
81+
}
82+
// Start of a new expression. If it starts with msg it is a nested expression
83+
// Need to scan ahead to find the closing bracket
84+
if (/^msg[.\[]/.test(str.substring(i + 1))) {
85+
let depth = 1
86+
let inLocalString = false
87+
let localStringQuote
88+
for (let j = i + 1; j < length; j++) {
89+
if (/["']/.test(str[j])) {
90+
if (inLocalString) {
91+
if (str[j] === localStringQuote) {
92+
inLocalString = false
93+
}
94+
} else {
95+
inLocalString = true
96+
localStringQuote = str[j]
97+
}
98+
}
99+
if (str[j] === '[') {
100+
depth++
101+
} else if (str[j] === ']') {
102+
depth--
103+
}
104+
if (depth === 0) {
105+
try {
106+
if (msg) {
107+
const crossRefProp = getMessageProperty(msg, str.substring(i + 1, j))
108+
if (crossRefProp === undefined) {
109+
throw createError('INVALID_EXPR', 'Invalid expression: undefined reference at position ' + (i + 1) + ' : ' + str.substring(i + 1, j))
110+
}
111+
parts.push(crossRefProp)
112+
} else {
113+
parts.push(normalisePropertyExpression(str.substring(i + 1, j), msg))
114+
}
115+
inBox = false
116+
i = j
117+
start = j + 1
118+
break
119+
} catch (err) {
120+
throw createError('INVALID_EXPR', 'Invalid expression started at position ' + (i + 1))
121+
}
122+
}
123+
}
124+
if (depth > 0) {
125+
throw createError('INVALID_EXPR', "Invalid property expression: unmatched '[' at position " + i)
126+
}
127+
continue
128+
} else if (!/["'\d]/.test(str[i + 1])) {
129+
// Next char is either a quote or a number
130+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected ' + str[i + 1] + ' at position ' + (i + 1))
131+
}
132+
start = i + 1
133+
inBox = true
134+
} else if (c === ']') {
135+
if (!inBox) {
136+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected ' + c + ' at position ' + i)
137+
}
138+
if (start !== i) {
139+
v = str.substring(start, i)
140+
if (/^\d+$/.test(v)) {
141+
parts.push(parseInt(v))
142+
} else {
143+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected array expression at position ' + start)
144+
}
145+
}
146+
start = i + 1
147+
inBox = false
148+
} else if (c === ' ') {
149+
throw createError('INVALID_EXPR', "Invalid property expression: unexpected ' ' at position " + i)
150+
}
151+
} else {
152+
if (c === quoteChar) {
153+
if (i - start === 0) {
154+
throw createError('INVALID_EXPR', 'Invalid property expression: zero-length string at position ' + start)
155+
}
156+
parts.push(str.substring(start, i))
157+
// If inBox, next char must be a ]. Otherwise it may be [ or .
158+
if (inBox && !/\]/.test(str[i + 1])) {
159+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected array expression at position ' + start)
160+
} else if (!inBox && i + 1 !== length && !/[\[\.]/.test(str[i + 1])) {
161+
throw createError('INVALID_EXPR', 'Invalid property expression: unexpected ' + str[i + 1] + ' expression at position ' + (i + 1))
162+
}
163+
start = i + 1
164+
inString = false
165+
}
166+
}
167+
}
168+
if (inBox || inString) {
169+
// eslint-disable-next-line new-cap
170+
throw new createError('INVALID_EXPR', 'Invalid property expression: unterminated expression')
171+
}
172+
if (start < length) {
173+
parts.push(str.substring(start))
174+
}
175+
176+
if (toString) {
177+
let result = parts.shift()
178+
while (parts.length > 0) {
179+
let p = parts.shift()
180+
if (typeof p === 'string') {
181+
if (/"/.test(p)) {
182+
p = "'" + p + "'"
183+
} else {
184+
p = '"' + p + '"'
185+
}
186+
}
187+
result = result + '[' + p + ']'
188+
}
189+
return result
190+
}
191+
192+
return parts
193+
}
194+
195+
/**
196+
* Gets a property of a message object.
197+
*
198+
* Unlike {@link @node-red/util-util.getObjectProperty}, this function will strip `msg.` from the
199+
* front of the property expression if present.
200+
*
201+
* @param {Object} msg - the message object
202+
* @param {String} expr - the property expression
203+
* @return {any} the message property, or undefined if it does not exist
204+
* @throws Will throw an error if the *parent* of the property does not exist
205+
* @memberof @node-red/util_util
206+
*/
207+
function getMessageProperty (msg, expr) {
208+
if (expr.indexOf('msg.') === 0) {
209+
expr = expr.substring(4)
210+
}
211+
return getObjectProperty(msg, expr)
212+
}
213+
214+
/**
215+
* Gets a property of an object.
216+
*
217+
* Given the object:
218+
*
219+
* {
220+
* "pet": {
221+
* "type": "cat"
222+
* }
223+
* }
224+
*
225+
* - `pet.type` will return `"cat"`.
226+
* - `pet.name` will return `undefined`
227+
* - `car` will return `undefined`
228+
* - `car.type` will throw an Error (as `car` does not exist)
229+
*
230+
* @param {Object} msg - the object
231+
* @param {String} expr - the property expression
232+
* @return {any} the object property, or undefined if it does not exist
233+
* @throws Will throw an error if the *parent* of the property does not exist
234+
* @memberof @node-red/util_util
235+
*/
236+
function getObjectProperty (msg, expr) {
237+
let result = null
238+
const msgPropParts = normalisePropertyExpression(expr, msg)
239+
msgPropParts.reduce(function (obj, key) {
240+
result = (typeof obj[key] !== 'undefined' ? obj[key] : undefined)
241+
return result
242+
}, msg)
243+
return result
244+
}
245+
246+
/**
247+
* Sets a property of an object.
248+
*
249+
* @param {Object} msg - the object
250+
* @param {String} prop - the property expression
251+
* @param {any} value - the value to set
252+
* @param {boolean} createMissing - whether to create missing parent properties
253+
* @memberof @node-red/util_util
254+
*/
255+
function setObjectProperty (msg, prop, value, createMissing) {
256+
if (typeof createMissing === 'undefined') {
257+
createMissing = (typeof value !== 'undefined')
258+
}
259+
const msgPropParts = normalisePropertyExpression(prop, msg)
260+
const length = msgPropParts.length
261+
let obj = msg
262+
let key
263+
for (let i = 0; i < length - 1; i++) {
264+
key = msgPropParts[i]
265+
if (typeof key === 'string' || (typeof key === 'number' && !Array.isArray(obj))) {
266+
if (hasOwnProperty.call(obj, key)) {
267+
if (length > 1 && ((typeof obj[key] !== 'object' && typeof obj[key] !== 'function') || obj[key] === null)) {
268+
// Break out early as we cannot create a property beneath
269+
// this type of value
270+
return false
271+
}
272+
obj = obj[key]
273+
} else if (createMissing) {
274+
if (typeof msgPropParts[i + 1] === 'string') {
275+
obj[key] = {}
276+
} else {
277+
obj[key] = []
278+
}
279+
obj = obj[key]
280+
} else {
281+
return false
282+
}
283+
} else if (typeof key === 'number') {
284+
// obj is an array
285+
if (obj[key] === undefined) {
286+
if (createMissing) {
287+
if (typeof msgPropParts[i + 1] === 'string') {
288+
obj[key] = {}
289+
} else {
290+
obj[key] = []
291+
}
292+
obj = obj[key]
293+
} else {
294+
return false
295+
}
296+
} else {
297+
obj = obj[key]
298+
}
299+
}
300+
}
301+
key = msgPropParts[length - 1]
302+
if (typeof value === 'undefined') {
303+
if (typeof key === 'number' && Array.isArray(obj)) {
304+
obj.splice(key, 1)
305+
} else {
306+
delete obj[key]
307+
}
308+
} else {
309+
if (typeof obj === 'object' && obj !== null) {
310+
obj[key] = value
311+
} else {
312+
// Cannot set a property of a non-object/array
313+
return false
314+
}
315+
}
316+
return true
317+
}
318+
319+
module.exports = {
320+
getObjectProperty,
321+
setObjectProperty
322+
}

forge/ee/lib/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ module.exports = fp(async function (app, opts) {
4949
// Set the assistant inline completions Feature Flag
5050
app.config.features.register('assistantInlineCompletions', isAssistantConfigured, true)
5151

52-
// Set the expert assistant Feature Flag
53-
app.config.features.register('expertAssistant', isAiEnabled && (app.config?.expert?.enabled ?? false), true)
54-
5552
// Set the expert platform automation Feature Flag (MCP platform tools server)
5653
app.config.features.register('expertPlatformAutomation', isAiEnabled && (app.config?.expert?.enabled ?? false), true)
5754

58-
// temporary until FF Expert Insights can be enabled on Self Hosted EE instance
55+
// Set the expert assistant Feature Flag
56+
app.config.features.register('expertAssistant', isAiEnabled && (app.config?.expert?.enabled ?? false), true)
57+
58+
// Set the Expert Insights flag
5959
const isInsightsEnabled = isAiEnabled && app.config?.expert?.enabled && app.config?.expert?.insights?.enabled
60-
app.config.features.register('expertInsights', isInsightsEnabled ?? false, false)
60+
app.config.features.register('expertInsights', isInsightsEnabled ?? false, true)
6161
}
6262

6363
// Set the Team Library Feature Flag

0 commit comments

Comments
 (0)