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
+12 -4
View File
@@ -11,7 +11,7 @@ from bs4 import BeautifulSoup
import httpx
from openpyxl import load_workbook
from .core import route_path, save_entry, ensure_public_url, slug_of
from . import db,wordpress_compat,safe_http
from . import db,wordpress_compat,safe_http,wp_complex_v820
KEY_ALIASES={
'title':('title','name','标题','名称','产品名称','post_title'),
@@ -114,7 +114,10 @@ def import_wxr(site_id,content,conflict='skip'):
title=it.findtext('title') or slug
link=it.findtext('link') or ''
path=urlparse(link).path if link.startswith(('http://','https://')) else ''
if not path: path=f'/{slug}/' if typ=='page' else f'/{typ}/{slug}/'
if urlparse(link).query or urlparse(link).fragment:
raise ValueError(f'WXR 记录 {title[:80]} 使用动态参数 URL;请先做 V8.20 预检和显式路由映射')
if not path:
raise ValueError(f'WXR 记录 {title[:80]} 缺少原始 URL,无法保证旧路径不变')
wpmeta={}
for pm in it.findall('wp:postmeta',ns):
key=pm.findtext('wp:meta_key',namespaces=ns)
@@ -133,11 +136,16 @@ def import_wxr(site_id,content,conflict='skip'):
('_stock_status','stock_status'),('_weight','weight')):
if wpmeta.get(source):fields[dest]=str(wpmeta[source])[:1000]
fields.update(taxonomy)
acf=wp_complex_v820.extract_acf(wpmeta)
for key,value in acf['fields'].items():
fields.setdefault(key,value)
for key,value in acf['repeaters'].items():
fields[key]=value
wp_featured=media_index.get(wpmeta.get('_thumbnail_id',''),'')
gallery_ids=[x.strip() for x in wpmeta.get('_product_image_gallery','').split(',') if x.strip()]
gallery=[media_index[x] for x in gallery_ids if x in media_index]
if gallery:fields['gallery']=gallery
warnings=[]
warnings=list(acf['warnings'])
if wpmeta.get('_elementor_data'):
elementor=wordpress_compat.elementor_to_html(wpmeta['_elementor_data'])
if not body.strip() and elementor['html']:
@@ -155,7 +163,7 @@ def import_wxr(site_id,content,conflict='skip'):
'canonical':wpmeta.get('_yoast_wpseo_canonical') or wpmeta.get('rank_math_canonical_url') or '',
'robots':'noindex,follow' if wpmeta.get('_yoast_wpseo_meta-robots-noindex')=='1' or wpmeta.get('rank_math_robots')=='noindex' else 'index,follow',
'status':'published' if status=='publish' else 'draft',
'meta':{'elementor_widgets':elementor['widgets'] if wpmeta.get('_elementor_data') else 0,'wp_post_id':it.findtext('wp:post_id',default='',namespaces=ns),'wp_type':typ,'wp_metadata':wpmeta,'fields':fields,'migration_warnings':warnings}})
'meta':{'elementor_widgets':elementor['widgets'] if wpmeta.get('_elementor_data') else 0,'wp_post_id':it.findtext('wp:post_id',default='',namespaces=ns),'wp_type':typ,'wp_metadata':wpmeta,'fields':fields,'acf_repeaters':acf['repeaters'],'migration_warnings':warnings}})
return import_records(site_id,entries,'wordpress-wxr',conflict)
def safe_get(client,url,max_bytes=3_000_000):