import { Plugin, type Project, type NowConfig, type Record } from '@servicenow/sdk-build-core' import { JsonFileShape } from './json-plugin' export const CLAIMS_FILE = '.claims.json' function getClaimsPath(project: Project, config: NowConfig): string { return project.resolvePath(config.generatedDir, CLAIMS_FILE) } export const ClaimsPlugin = Plugin.create({ name: 'ClaimsPlugin', noTelemetry: true, files: [ { entryPoint: true, matcher: (path, { project, config }) => path === getClaimsPath(project, config), }, ], records: { sys_claim: { coalesce: ['claim_owner_scope', 'previous_claim_scope', 'metadata_update_name'], toFile() { // Claims don't get their own file, they're appended to the corresponding record's payload return { success: true, value: [] } }, toShape(root, { descendants, database, project, config }) { descendants.insert(...database.query('sys_claim')) // Adding them as descendants will mark them as handled so they don't get reprocessed const unsorted: globalThis.Record = {} for (const claim of descendants) { const updateName = claim.get('metadata_update_name').asString().getValue().trim() const timestamp = claim.get('claim_timestamp').asString().getValue().trim() const ownerScope = claim.get('claim_owner_scope').asString().getValue().trim() const previousScope = claim.get('previous_claim_scope').asString().getValue().trim() const previousVersion = claim.get('previous_claim_app_version').ifString()?.getValue().trim() ?? '' const array = (unsorted[updateName] ??= []) array.push(`${timestamp};${ownerScope};${previousScope};${previousVersion}`) } const sorted = Object.fromEntries( Object.entries(unsorted).map(([updateName, claims]) => [updateName, claims.sort()]) ) return { success: true, value: new JsonFileShape({ source: root, path: getClaimsPath(project, config), content: JSON.stringify(sorted), json: sorted, }), } }, }, }, shapes: [ { shape: JsonFileShape, async toRecord(file, { project, config, factory }) { if (file.getPath() !== getClaimsPath(project, config)) { return { success: false } } const allClaims = file.getJson().asObject() const records: Record[] = [] for (const [updateName, claims] of allClaims.entries()) { for (const claim of claims .asArray( `Claims entry for ${updateName} is malformed. Expected array, but received: ${claims.getDescription()}` ) .getElements()) { const [timestamp, ownerScope, previousScope, previousVersion] = claim .asString( `Claim for ${updateName} is malformed. Expected string, but received: ${claim.getDescription()}` ) .split(';') .map((part) => part.trim()) if (!timestamp) { throw new Error( `Claim for ${updateName} is malformed. Expected timestamp at first position.` ) } if (!ownerScope) { throw new Error( `Claim for ${updateName} is malformed. Expected owner scope at second position.` ) } if (!previousScope) { throw new Error( `Claim for ${updateName} is malformed. Expected previous scope at third position.` ) } records.push( await factory.createRecord({ source: file, table: 'sys_claim', properties: { metadata_update_name: updateName, claim_timestamp: timestamp, claim_owner_scope: ownerScope, previous_claim_scope: previousScope, previous_claim_app_version: previousVersion || undefined, }, }) ) } } const [first, ...rest] = records return first ? { success: true, value: first.with(...rest) } : { success: false } }, }, ], })