upgrade: adopt V8.21.3 (V8.13→8.21.3: P5 SFTP/S3, P6 multilingual, editor overhaul, enterprise hardening)

Overlay ~150 upstream items onto our V8.12 base, preserving trade-pro
theme, registry tests, and design docs. Re-applied our patch set
(discovery/env_for/blocks.css/type-mapping/theme_color/frontend entries,
test_theme_pro_v3 mods) — all anchors survived upstream evolution.

New platform enhancement (fixes upgrade-introduced regression):
multilingual.site_locale now honors settings_json.locale, plus
PUT /api/i18n/sites/{id}/locale — standalone English sites get
<html lang='en'> under the V8.15 locale-rewrite pipeline.

compose: adopt V8.21.3 (image 8.21.3-dev.1, /api/ready probes, MySQL/S3
env) with project name pinned to siteforge-v8-10 (data volumes).

Tests: 356 passed / 11 failed — all 11 confirmed pre-existing in
pristine V8.21.3 on this host (10 upstream Windows env + our build test
now green after locale fix). Patch registry updated in plan doc.

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-10-23 12:31:03 +08:00
parent 257c89e159
commit 601451f22b
218 changed files with 10441 additions and 628 deletions
+20 -2
View File
@@ -192,6 +192,13 @@ def parse_wxr(content):
if len(content)>MAX_UPLOAD:raise ValueError('XML 文件超过限制')
root=ET.fromstring(content)
if root.tag!='rss':raise ValueError('这不是 WordPress WXR RSS 导出')
# Stage only unambiguous permanent paths; dynamic functions remain review items.
from .wp_complex_v820 import inspect_wxr
checks=inspect_wxr(content)
blockers=[x for x in checks['issues'] if x['code'] in ('invalid_permalink','duplicate_route')]
if blockers or (checks['findings_truncated'] and checks['blocking_issues']):
raise ValueError('存在不能安全映射的 WordPress URL,请先运行 V8.20 WXR 预检并修正:'+
', '.join(x['code']+':'+x['route'] for x in blockers[:5]))
rows=[];ids=[];warnings=[]
for it in root.findall('./channel/item'):
get=lambda name:next((str(node.text or '') for node in it if node.tag.endswith('}'+name) or node.tag==name),'')
@@ -216,12 +223,23 @@ def parse_wxr(content):
if k:metas[k]=v[:10000]
if any(k in metas for k in ('_elementor_data','_product_attributes')):
warnings.append({'row':len(rows)+1,'issue':'含复杂 Elementor/WooCommerce 组件,导入后需人工核对'})
rows.append({'标题':title,'路径':route,'正文':get('encoded'),
rows.append({'标题':title,'路径':route,'正文':get('encoded'),'类型':raw_typ,
'SEO标题':metas.get('_yoast_wpseo_title') or metas.get('rank_math_title',''),
'SEO描述':metas.get('_yoast_wpseo_metadesc') or metas.get('rank_math_description',''),
'分类':raw_typ})
ids.append(f'wordpress:{wid}' if wid else f'wordpress:path:{route}')
return rows,ids,warnings
for finding in checks['issues']:
if finding['severity'] not in ('review','blocker'):continue
if finding['code'] in ('invalid_permalink','duplicate_route'):continue
route=finding['route']
index=next((i+1 for i,row in enumerate(rows) if row['路径']==route),0)
if index:
if finding['code'].startswith('elementor') and any(w.get('row')==index and 'Elementor' in w.get('issue','') for w in warnings):
continue
warnings.append({'row':index,'issue':finding['code']+': '+finding['message']})
# Bound warnings written into batch metadata and shown for human review.
return rows,ids,warnings[:200]
@router.post('/{site_id}/staging')
def api_stage(request:Request,site_id:str,payload:dict=Body(...)):