1// Static analysis to infer ep-type from deploy script file contents 4import { execSync } from 'child_process' 5import ts from 'typescript' 12// Resolve script path to absolute (handles both absolute and relative paths) 13export const resolveScriptPath = (scriptPath: string) => 14 scriptPath.startsWith('/') ? scriptPath : pathDownJoin(ptDir, scriptPath) 16// Top-level package scopes (each is a monorepo package root). 17// Discovery is glob-based (mkEpGlob(scope)), so all package dirs are scanned for ep*.{mjs,mts}. 18const nonPackageDirs = ['node_modules', '.git', 'tmp', '.opencode', '.next', 'dist'] 19export const getRegistryScopes = () => 20 fs.readdirSync(ptDir, { withFileTypes: true }) 21 .filter(d => d.isDirectory() && !nonPackageDirs.includes(d.name)) 24export const readFileContent = (scriptPath: string) => { 25 const absPath = resolveScriptPath(scriptPath) 26 if (!fs.existsSync(absPath)) return null 27 return fs.readFileSync(absPath, 'utf8') 30export const inferEpTypeFromFile = (scriptPath: string) => { 31 const contents = readFileContent(scriptPath) 32 if (!contents) return null 34 let result: string | null = null 35 const visit = (node: ts.Node) => { 37 if (ts.isCallExpression(node)) { 38 const fnText = node.expression.getText(sourceFile) 39 const m = fnText.match(/^ept([A-Z][a-zA-Z0-9]*)$/) 40 if (m) result = 'ept' + m[1] 42 ts.forEachChild(node, visit) 48export const inferDbQsNameFromFile = (scriptPath: string): string | null => { 49 const contents = readFileContent(scriptPath) 50 if (!contents) return null 52 let cnpgResult: string | null = null 53 let vanillaResult: string | null = null 54 const visit = (node: ts.Node) => { 55 if (cnpgResult) return 56 if (ts.isCallExpression(node)) { 57 const fnText = node.expression.getText(sourceFile) 58 if (fnText === 'eptCnPgDb' && node.arguments[0] && ts.isObjectLiteralExpression(node.arguments[0])) { 59 for (const prop of node.arguments[0].properties) { 60 if (ts.isPropertyAssignment(prop) && prop.name.getText() === 'name' && ts.isStringLiteral(prop.initializer)) { 61 cnpgResult = prop.initializer.text + '_qs' 66 if (!vanillaResult && ts.isPropertyAssignment(node) && 67 node.name.getText() === 'dbQsName' && ts.isStringLiteral(node.initializer)) { 68 vanillaResult = node.initializer.text 70 if (!cnpgResult) ts.forEachChild(node, visit) 73 return cnpgResult || vanillaResult 76const epTypeDefCache = new Map() 77export const getEpTypeDefPath = (epType: string | null, searchRepoWide = false) => { 78 if (!epType) return null 79 const cacheKey = searchRepoWide ? `${epType}|repo` : epType 80 if (epTypeDefCache.has(cacheKey)) return epTypeDefCache.get(cacheKey) 82 const scopeGlob = searchRepoWide ? '' : ' pt0/' 83 const result = execSync(`rg -l "export const ${epType}" --glob "${extGlob(epFileExtA)}"${scopeGlob}`, {encoding: 'utf8', cwd: ptDir, maxBuffer: 1024 * 1024}) 84 const defPath = result.trim().split('\n')[0] || null 85 epTypeDefCache.set(cacheKey, defPath) 88 if (e.status === 1) { epTypeDefCache.set(cacheKey, null); return null } 93export const inferK8sResFromEpType = (epType: string | null): {kind: string, apiVersion: string} | null => { 94 if (!epType) return null 95 const defPath = getEpTypeDefPath(epType, true) 96 if (!defPath) return null 97 const contents = readFileContent(defPath) 98 if (!contents) return null 99 const targetName = `${epType}K8sRes` 101 let result: {kind: string, apiVersion: string} | null = null 102 const visit = (node: ts.Node) => { 104 if (ts.isVariableStatement(node) && node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) { 105 for (const decl of node.declarationList.declarations) { 107 const init = decl.initializer 108 if (ts.isIdentifier(decl.name) && decl.name.text === targetName && init && ts.isObjectLiteralExpression(init)) { 109 const props = new Map<string, string>() 110 for (const p of init.properties) { 111 const val = (p as ts.PropertyAssignment).initializer 112 if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && val && ts.isStringLiteral(val)) props.set(p.name.text, val.text) 114 const kind = props.get('kind'), apiVersion = props.get('apiVersion') 115 if (kind && apiVersion) result = {kind, apiVersion} 119 ts.forEachChild(node, visit)