5import { basename } from 'path' 10import * as _ from 'lodash-es' 12export type NodePortAssign = { 19const isNodePortFile = (ptPath: string) => { 20 const parts = ptPath.split('/') 21 const dir = parts[parts.length - 2] 22 return (dir === 'nodeports' || dir === 'nodeportsF') && ptPath.endsWith('.mjs') && !ptPath.endsWith('index.mjs') 25const rangeConstNameA = ['openPortNoA', 'routerFwdPortNoA'] as const 26type RangeConstName = typeof rangeConstNameA[number] 27export type ClusterPortRanges = {cluster: string; ptPath: string} & Partial<Record<RangeConstName, OpenPortNoA>> 29const evalPortRangeArray = (node: AcornNode): OpenPortNoA => { 30 const out: OpenPortNoA = [] 31 for (const el of node.elements ?? []) { 32 if (el?.type === 'Literal' && typeof el.value === 'number') out.push(el.value) 33 else if (el?.type === 'ArrayExpression') { 34 const nums = (el.elements ?? []).filter((e: AcornNode) => e?.type === 'Literal' && typeof e.value === 'number').map((e: AcornNode) => e.value as number) 35 if (nums.length === 2) out.push([nums[0], nums[1]]) 41type ParsedNodePortFile = {assigns: NodePortAssign[]; ranges: ClusterPortRanges} 43const parseNodePortFile = async (ptPath: string): Promise<ParsedNodePortFile> => { 45 const cluster = basename(ptPath, '.mjs') 46 const assigns: NodePortAssign[] = [] 47 const ranges: ClusterPortRanges = {cluster, ptPath} 48 const ast = acornParse({contents: content, ptPath: ptPath as absFileDirPath}) as unknown as AcornNode 49 for (const stmt of ast.body ?? []) { 50 if (stmt.type !== 'ExportNamedDeclaration' || stmt.declaration?.type !== 'VariableDeclaration') continue 51 for (const decl of stmt.declaration.declarations ?? []) { 52 if (decl.id?.type !== 'Identifier' || !decl.init) continue 53 const name = decl.id.name as string 54 const init = decl.init 55 if (init.type === 'Literal' && typeof init.value === 'number') { 56 assigns.push({cluster, svcName: name, port: init.value, ptPath}) 57 } else if (init.type === 'ObjectExpression') { 58 const portProp = (init.properties ?? []).find((p: AcornNode) => p?.type === 'Property' && p.key?.type === 'Identifier' && p.key.name === 'ptNodePortNo') 59 if (portProp?.value?.type === 'Literal' && typeof portProp.value.value === 'number') { 60 assigns.push({cluster, svcName: name, port: portProp.value.value, ptPath}) 62 } else if (init.type === 'ArrayExpression' && (rangeConstNameA as readonly string[]).includes(name)) { 63 ranges[name as RangeConstName] = evalPortRangeArray(init) 67 return {assigns, ranges} 70const collectParsed = async (): Promise<ParsedNodePortFile[]> => { 72 const nodePortFiles = allFiles.map(absPathToPtPath).filter(isNodePortFile) 73 return Promise.all(nodePortFiles.map(parseNodePortFile)) 76export const collectNodePorts = async (): Promise<NodePortAssign[]> => (await collectParsed()).flatMap(p => p.assigns) 78export const collectPortRanges = async (): Promise<ClusterPortRanges[]> => (await collectParsed()).map(p => p.ranges) 80export type PortDupe = { 82 entries: { cluster: string; svcName: string; ptPath: string }[] 85export const findWithinClusterDupes = (assigns: NodePortAssign[]): PortDupe[] => { 86 const dupes: PortDupe[] = [] 87 for (const cluster of _.uniq(assigns.map(a => a.cluster))) { 88 const byPort = _.groupBy(assigns.filter(a => a.cluster === cluster), 'port') 89 for (const [port, group] of Object.entries(byPort)) { 90 const distinctSvcs = _.uniqBy(group, 'svcName') 91 if (distinctSvcs.length > 1) { 92 dupes.push({port: parseInt(port), entries: distinctSvcs.map(e => ({cluster: e.cluster, svcName: e.svcName, ptPath: e.ptPath}))}) 99export const findCrossClusterDupes = (assigns: NodePortAssign[]): PortDupe[] => { 100 const byPort = _.groupBy(assigns, 'port') 101 const dupes: PortDupe[] = [] 102 for (const [port, group] of Object.entries(byPort)) { 103 const distinctClusters = _.uniq(group.map(a => a.cluster)) 104 if (distinctClusters.length > 1) { 105 dupes.push({port: parseInt(port), entries: group.map(e => ({cluster: e.cluster, svcName: e.svcName, ptPath: e.ptPath}))}) 111export type OpenPortViol = { cluster: string; ptPath: string; detail: string } 113export const findOpenNotInFwd = (ranges: ClusterPortRanges[]): OpenPortViol[] => { 114 const viols: OpenPortViol[] = [] 115 for (const {cluster, ptPath, openPortNoA, routerFwdPortNoA} of ranges) { 116 if (!openPortNoA) continue 117 if (!routerFwdPortNoA) { viols.push({cluster, ptPath, detail: 'openPortNoA declared but routerFwdPortNoA missing'}); continue } 118 const uncovered: number[] = [] 119 for (const item of openPortNoA) { 120 const [lo, hi] = Array.isArray(item) ? item : [item, item] 121 for (let p = lo!; p <= hi!; p++) if (!portInOpenA(p, routerFwdPortNoA)) uncovered.push(p) 123 if (uncovered.length) viols.push({cluster, ptPath, detail: `${uncovered.length} port(s) in openPortNoA not forwarded by router: ${uncovered.slice(0, 10).join(',')}${uncovered.length > 10 ? '…' : ''}`})