fix: harden release gate and page delivery
Memind CI / Test, build, and release guards (push) Failing after 12m3s
Memind CI / Test, build, and release guards (push) Failing after 12m3s
This commit is contained in:
@@ -75,6 +75,25 @@ function isWithinRoot(root, candidate) {
|
||||
return candidate === root || candidate.startsWith(`${root}${path.sep}`);
|
||||
}
|
||||
|
||||
function safeSourceDownloadUrl(value) {
|
||||
const raw = normalizeText(value);
|
||||
if (!raw) return null;
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(raw, 'http://local.invalid');
|
||||
} catch {
|
||||
throw Object.assign(new Error('Excel 原始文件下载地址无效'), { code: 'INVALID_SOURCE_DOWNLOAD_URL' });
|
||||
}
|
||||
if (parsed.origin !== 'http://local.invalid'
|
||||
|| !parsed.pathname.startsWith('/api/mindspace/v1/assets/')
|
||||
|| !parsed.pathname.endsWith('/download')) {
|
||||
throw Object.assign(new Error('Excel 原始文件下载地址必须是受保护的站内资产下载路径'), {
|
||||
code: 'INVALID_SOURCE_DOWNLOAD_URL',
|
||||
});
|
||||
}
|
||||
return `${parsed.pathname}${parsed.search}`;
|
||||
}
|
||||
|
||||
function validateZipBudget(buffer, {
|
||||
maxUncompressedBytes = DEFAULT_MAX_UNCOMPRESSED_BYTES,
|
||||
maxEntries = DEFAULT_MAX_ZIP_ENTRIES,
|
||||
@@ -652,7 +671,15 @@ function buildReportModel(workbook, worksheet, loaded, args = {}) {
|
||||
const validation = { valid: errors.length === 0, errors, warnings };
|
||||
return {
|
||||
schemaVersion: 1,
|
||||
source: { path: args.path, sheet: worksheet.name, headerRow: loaded.headerRow, rows: loaded.rows.length },
|
||||
source: {
|
||||
path: args.path,
|
||||
sheet: worksheet.name,
|
||||
headerRow: loaded.headerRow,
|
||||
rows: loaded.rows.length,
|
||||
...(args.source_download_url
|
||||
? { downloadUrl: safeSourceDownloadUrl(args.source_download_url) }
|
||||
: {}),
|
||||
},
|
||||
fields: { metric, profitMetric, quantityMetric, orderId, dimensions },
|
||||
totals: {
|
||||
metric: roundNumber(totalMetric),
|
||||
@@ -699,7 +726,11 @@ export function createExcelAnalysisEngine({ workspaceRoot, maxFileBytes = DEFAUL
|
||||
const { workbook, source, sizeBytes } = await loadWorkbook(args.path);
|
||||
const sampleRows = asPositiveInteger(args.sample_rows, DEFAULT_SAMPLE_ROWS, 20);
|
||||
const sheets = workbook.worksheets.map((worksheet) => {
|
||||
const loaded = loadRows(worksheet, { headerRow: args.header_row });
|
||||
const loaded = loadRows(worksheet, {
|
||||
headerRow: args.header_row,
|
||||
maxRows: args.max_rows,
|
||||
maxCells: args.max_cells,
|
||||
});
|
||||
const profiles = loaded.headers.map((name) => profileColumn(loaded.rows, name));
|
||||
return {
|
||||
name: worksheet.name,
|
||||
@@ -725,7 +756,11 @@ export function createExcelAnalysisEngine({ workspaceRoot, maxFileBytes = DEFAUL
|
||||
async analyze(args = {}) {
|
||||
const { workbook, source } = await loadWorkbook(args.path);
|
||||
const worksheet = worksheetByName(workbook, args.sheet);
|
||||
const loaded = loadRows(worksheet, { headerRow: args.header_row });
|
||||
const loaded = loadRows(worksheet, {
|
||||
headerRow: args.header_row,
|
||||
maxRows: args.max_rows,
|
||||
maxCells: args.max_cells,
|
||||
});
|
||||
const operation = normalizeText(args.operation || 'summary').toLowerCase();
|
||||
let result;
|
||||
if (operation === 'summary') {
|
||||
@@ -754,7 +789,11 @@ export function createExcelAnalysisEngine({ workspaceRoot, maxFileBytes = DEFAUL
|
||||
async chart(args = {}) {
|
||||
const { workbook, source } = await loadWorkbook(args.path);
|
||||
const worksheet = worksheetByName(workbook, args.sheet);
|
||||
const loaded = loadRows(worksheet, { headerRow: args.header_row });
|
||||
const loaded = loadRows(worksheet, {
|
||||
headerRow: args.header_row,
|
||||
maxRows: args.max_rows,
|
||||
maxCells: args.max_cells,
|
||||
});
|
||||
const type = normalizeText(args.type || 'bar').toLowerCase();
|
||||
if (!['bar', 'line'].includes(type)) {
|
||||
throw Object.assign(new Error('Phase 1 图表仅支持 bar 或 line'), { code: 'INVALID_CHART_TYPE' });
|
||||
@@ -784,7 +823,11 @@ export function createExcelAnalysisEngine({ workspaceRoot, maxFileBytes = DEFAUL
|
||||
async report(args = {}) {
|
||||
const { workbook, source } = await loadWorkbook(args.path);
|
||||
const worksheet = worksheetByName(workbook, args.sheet);
|
||||
const loaded = loadRows(worksheet, { headerRow: args.header_row });
|
||||
const loaded = loadRows(worksheet, {
|
||||
headerRow: args.header_row,
|
||||
maxRows: args.max_rows,
|
||||
maxCells: args.max_cells,
|
||||
});
|
||||
const model = buildReportModel(workbook, worksheet, loaded, { ...args, path: source.relative });
|
||||
if (!model.validation.valid) {
|
||||
throw Object.assign(new Error('Excel 报告事实校验失败,未生成报告'), {
|
||||
@@ -818,6 +861,7 @@ export const excelAnalysisInternals = {
|
||||
safeRelativeWorkbookPath,
|
||||
safeChartOutputPath,
|
||||
safeReportOutputPath,
|
||||
safeSourceDownloadUrl,
|
||||
normalizeMainNamespacePrefixes,
|
||||
validateZipBudget,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user