import { isSNScope } from '@servicenow/sdk-build-core' import { CallExpressionShape, Plugin, type Shape } from '@servicenow/sdk-build-core' import { NowIdShape } from './now-id-plugin' const getRolesArray = (roles: string): Array => { return roles .split(',') .map((role) => role.trim()) .filter((role) => role) } export const PropertyPlugin = Plugin.create({ name: 'PropertyPlugin', records: { sys_properties: { toShape(record, { config }) { return { success: true, value: new CallExpressionShape({ source: record, callee: 'Property', args: [ record.transform(({ $ }) => ({ $id: $.val(NowIdShape.from(record)), name: $.from('name', 'suffix').map( (name, suffix) => name.ifString() ?? (config.scope === 'global' ? suffix : `${config.scope}.${suffix.asString().getValue()}`) ), value: $, description: $.def(''), type: $.def('string'), choices: $.map((c) => { const value = c.toString().getValue() return value .split(/[,\n]/) .map((v) => v.trim()) .filter((v) => v) }).def([]), isPrivate: $.from('is_private').toBoolean().def(false), ignoreCache: $.from('ignore_cache').toBoolean().def(false), roles: $.from('read_roles', 'write_roles') .def({ read: [], write: [] }) .map((r, w) => ({ read: r.ifString()?.pipe((v) => getRolesArray(v.getValue())), write: w.ifString()?.pipe((v) => getRolesArray(v.getValue())), })), })), ], }), } }, }, }, shapes: [ { shape: CallExpressionShape, fileTypes: ['fluent'], async toRecord(callExpression, { config, diagnostics, factory }) { if (callExpression.getCallee() !== 'Property') { return { success: false } } const prop = callExpression.getArgument(0).asObject() const scope = config.scope const name = prop.get('name').asString() if (!isSNScope(scope) && config.scope !== 'global' && !name.asString().startsWith(`${scope}.`)) { diagnostics.warn( name.getOriginalNode(), `Property names should begin with '${scope}.' to match the app scope and avoid collisions with properties from other scopes` ) } return { success: true, value: await factory.createRecord({ source: callExpression, table: 'sys_properties', explicitId: prop.get('$id'), properties: prop.transform(({ $, merge }) => ({ name: $, suffix: $.from('name').map((v) => v.asString().replace(new RegExp(`^${config.scope}\\.`), '') ), description: $, type: $.def('string'), value: $.toString(), // TODO: Should be allowed to assign non-strings to string properties is_private: $.from('isPrivate').def(false), ignore_cache: $.from('ignoreCache').def(false), choices: $.map((v) => v.ifArray()?.getValue().join(',')), [merge]: $.from('roles') .def({ read_roles: '', write_roles: '' }) .map((v) => v .ifObject() ?.transform(({ $ }) => ({ read_roles: $.from('read').map((r) => getRolesString(r)), write_roles: $.from('write').map((r) => getRolesString(r)), })) .properties() ), })), }), } }, }, ], }) function getRolesString(roles: Shape): string | undefined { return roles .ifArray() ?.pipe((r) => Array.from(new Set(r.getElements().map((r) => r.ifRecord()?.get('name').getValue() ?? r.getValue()))).join( ',' ) ) }