import fs from 'node:fs'; import path from 'node:path'; import crypto from 'node:crypto'; import {pathToFileURL} from 'node:url'; import {createFmodeClient} from './fmode-client.mjs'; export async function previewReport({reportDir,seriesKey,version,reportType,dataMode,artifactSubtype,stageCode},client=createFmodeClient()){ if(!stageCode||!seriesKey||!Number.isInteger(Number(version))||Number(version)<1)throw Error('必须明确提供 stageCode、seriesKey 和正整数 version'); const html=fs.readFileSync(path.join(reportDir,'report.html'),'utf8'),hash=crypto.createHash('sha256').update(Buffer.from(html)).digest('hex'); const reportIdentity={reportSeriesKey:seriesKey,version:Number(version),reportContentHash:hash}; const preview=await client.invoke('ksfCreateReportPreview',{requestId:'preview:'+seriesKey+'@v'+version+':'+hash,reportIdentity,html,reportType,dataMode,...(artifactSubtype?{artifactSubtype}:{})}); return{...preview,stageCode,reportType,dataMode,...(artifactSubtype?{artifactSubtype}:{}),employeeActionRequired:true}; } export async function submitConfirmedReport(opts){ if(!opts.stageCode||!opts.previewId||!opts.skillDir)throw Error('必须提供 stageCode、previewId 和冻结 r7 的 skillDir'); const client=createFmodeClient({reportMetadata:{stageCode:opts.stageCode}}); const html=fs.readFileSync(path.join(opts.reportDir,'report.html')); const identity={reportSeriesKey:opts.seriesKey,version:Number(opts.version),reportContentHash:crypto.createHash('sha256').update(html).digest('hex')}; const confirmation=await client.confirmation(opts.previewId,identity); const module=await import(pathToFileURL(path.resolve(opts.skillDir,'scripts/report-client.mjs')).href); return module.runReportPipeline({...opts,confirmation,callFn:client.callFn,transfer:client.transfer}); } if(process.argv[1]&&import.meta.url===pathToFileURL(path.resolve(process.argv[1])).href){ try{ const argv=process.argv.slice(2),arg=n=>{const i=argv.indexOf('--'+n);return i<0?null:argv[i+1];}; const opts={reportDir:arg('report-dir'),seriesKey:arg('series-key'),version:Number(arg('version')),reportType:arg('report-type'),dataMode:arg('data-mode'),artifactSubtype:arg('artifact-subtype'),stageCode:arg('stage-code'),previewId:arg('preview-id'),skillDir:arg('skill-dir')}; const receiptFile=arg('receipt');if(receiptFile){if(fs.existsSync(receiptFile))opts.priorReceipt=JSON.parse(fs.readFileSync(receiptFile,'utf8'));} const mode=arg('mode');if(!['preview','submit'].includes(mode))throw Error('--mode 必须为 preview 或 submit'); const result=mode==='preview'?await previewReport(opts):await submitConfirmedReport(opts); if(receiptFile&&result.artifacts?.receipt)fs.writeFileSync(receiptFile,JSON.stringify(result.artifacts.receipt,null,2)); // 不打印签名确认凭据或任何环境变量。 console.log(JSON.stringify(mode==='preview'?result:{steps:result.steps,artifactId:result.artifacts?.artifactId,cardId:result.artifacts?.cardId,fatal:result.fatal},null,2)); if(result.fatal)process.exitCode=result.fatal.code||1; }catch(e){console.error(e.message);process.exitCode=1;} }