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:
+16
-10
@@ -14,7 +14,7 @@ import httpx
|
||||
from . import safe_http
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from markupsafe import Markup
|
||||
from . import db, blueprints, theme_store, static_site, responsive, theme_pro, site_design_v87
|
||||
from . import db, blueprints, theme_store, static_site, responsive, theme_pro, site_design_v87, multilingual
|
||||
from .core import uid, route_path, ensure_public_url, sanitize, THEMES
|
||||
|
||||
ROOT=Path(__file__).resolve().parent.parent
|
||||
@@ -23,6 +23,12 @@ ENV=Environment(loader=FileSystemLoader(str(THEME_DIR)),autoescape=select_autoes
|
||||
|
||||
TYPE_NAMES={'page':'页面','post':'新闻','product':'产品','service':'服务','case':'案例','brand':'品牌','vehicle':'车型','other':'内容'}
|
||||
|
||||
def copy_blocks_css(theme_dir,dist_assets):
|
||||
"""Theme-provided blocks.css wins; platform default is the fallback."""
|
||||
candidate=theme_dir/'blocks.css'
|
||||
src=candidate if candidate.is_file() else ROOT/'app'/'static'/'blocks.css'
|
||||
shutil.copy2(src,dist_assets/'blocks.css')
|
||||
|
||||
def output_path(dist,route):
|
||||
path=route_path(route)
|
||||
if path=='/':return dist/'index.html'
|
||||
@@ -36,12 +42,6 @@ def output_path(dist,route):
|
||||
if not target.is_relative_to(dist.resolve()):raise ValueError('非法生成目标目录')
|
||||
return out
|
||||
|
||||
def copy_blocks_css(theme_dir,dist_assets):
|
||||
"""Theme-provided blocks.css wins; platform default is the fallback."""
|
||||
candidate=theme_dir/'blocks.css'
|
||||
src=candidate if candidate.is_file() else ROOT/'app'/'static'/'blocks.css'
|
||||
shutil.copy2(src,dist_assets/'blocks.css')
|
||||
|
||||
def link(path):
|
||||
return (path or '/').lstrip('/') or './'
|
||||
|
||||
@@ -166,6 +166,9 @@ def build(site_id,mirror=False):
|
||||
site=db.one('SELECT * FROM sites WHERE id=?',(site_id,))
|
||||
if not site:raise ValueError('网站不存在')
|
||||
if site['theme'] not in theme_store.ids():raise ValueError('无效的主题')
|
||||
multilingual.assert_build_safe(site_id)
|
||||
locale=multilingual.site_locale(site_id)
|
||||
language_alternates=multilingual.alternates(site_id)
|
||||
records=db.rows("SELECT * FROM entries WHERE site_id=? AND status='published' ORDER BY type,title",(site_id,))
|
||||
redirects=db.rows('SELECT * FROM redirects WHERE site_id=?',(site_id,))
|
||||
settings=json.loads(site['settings_json'] or '{}')
|
||||
@@ -309,6 +312,7 @@ def build(site_id,mirror=False):
|
||||
# global custom.css. Reused templates can share block IDs safely.
|
||||
html=__import__('re').sub(r'<body(?=[\s>])',
|
||||
'<body data-siteforge-route="'+responsive.route_key(page['route'])+'"',html,count=1,flags=__import__('re').I)
|
||||
html=multilingual.inject_seo(html,locale,language_alternates.get(page['route'],[]) if 'noindex' not in (page.get('robots') or '').lower() else [])
|
||||
target=output_path(dist,page['route'])
|
||||
if target.exists():raise ValueError('多个路由写入同一文件: '+page['route'])
|
||||
target.parent.mkdir(parents=True,exist_ok=True)
|
||||
@@ -385,6 +389,7 @@ def build(site_id,mirror=False):
|
||||
missing_html=env.get_template('pro.html').render(**ctx)
|
||||
else:
|
||||
missing_html=template.render(site=site,page=notfound,mode='notfound',content='',blocks='',blueprint_mode=False,entries=local_records,listing_items=[],type_names=TYPE_NAMES,nav=[],link=link,seo_title='404',seo_description='Page not found',canonical='')
|
||||
missing_html=multilingual.inject_seo(missing_html,locale,[])
|
||||
(dist/'404.html').write_text(missing_html,encoding='utf-8')
|
||||
# A complete static search page is packaged for every theme, not a live API.
|
||||
static_site.write_assets(dist,local_records)
|
||||
@@ -402,13 +407,14 @@ def build(site_id,mirror=False):
|
||||
search_html=env.get_template('pro.html').render(**ctx)
|
||||
else:
|
||||
search_html=static_site.search_page(site)
|
||||
search_html=multilingual.inject_seo(search_html,locale,[])
|
||||
search_file.write_text(search_html,encoding='utf-8')
|
||||
route_set.add('/search/')
|
||||
noindex_routes={e['route'] for e in records if 'noindex' in (e['robots'] or '').lower()} | {'/search/'} | generated_noindex
|
||||
canonical_routes=sorted([x for x in route_set if not x.startswith('/404') and x not in noindex_routes])
|
||||
if site['domain'].startswith(('https://','http://')):
|
||||
urls='\n'.join('<url><loc>'+xml_escape(public_url(site,r))+'</loc></url>' for r in canonical_routes)
|
||||
sitemap='<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'+urls+'</urlset>'
|
||||
urls='\n'.join('<url><loc>'+xml_escape(public_url(site,r))+'</loc>'+multilingual.sitemap_alternates(language_alternates.get(r,[]))+'</url>' for r in canonical_routes)
|
||||
sitemap='<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">'+urls+'</urlset>'
|
||||
(dist/'sitemap.xml').write_text(sitemap,encoding='utf-8')
|
||||
(dist/'robots.txt').write_text('User-agent: *\nAllow: /\n'+(f'Sitemap: {site["domain"].rstrip("/")}/sitemap.xml\n' if site['domain'].startswith('http') else ''),encoding='utf-8')
|
||||
rlist=[]
|
||||
@@ -452,7 +458,7 @@ def build(site_id,mirror=False):
|
||||
if canonical_mismatches:warnings.append(f'{len(canonical_mismatches)} 个页面 canonical 域名与新站域名不一致')
|
||||
if missing:warnings.append(f'{len(missing)} 个本地引用图片缺失')
|
||||
if slashless:warnings.append(f'{len(slashless)} 个无后缀、无尾斜杠 URL 需要按照生成的 Nginx 规则部署')
|
||||
report={'site':site['name'],'version':bid,'pages':totals,'theme':site['theme'],'theme_version':theme_pin['version'] if theme_pin else theme_store.latest(site['theme']),
|
||||
report={'site':site['name'],'locale':locale,'alternate_pages':len(language_alternates),'version':bid,'pages':totals,'theme':site['theme'],'theme_version':theme_pin['version'] if theme_pin else theme_store.latest(site['theme']),
|
||||
'warnings':warnings,'errors':issues,'external_assets':external[:200],
|
||||
'missing_local_images':missing[:200],'broken_links':broken_links[:200],'legacy_links':legacy_links[:200],'canonical_mismatches':canonical_mismatches[:200],'slashless_routes':slashless,
|
||||
'redirects':len(redirects),'mirrored_images':len(remote_cache),
|
||||
|
||||
Reference in New Issue
Block a user