- ProductArchiveImageSearchDialog:弹窗打开期间监听 document 级 paste,页面任意位置 Ctrl+V 直接粘贴图片(点击上传区仍是文件选择);搜索结果小图可点击,复用 ProductArchiveImagePreview 放大核对 - yydd-prod-release-guards.sh:prepare_standalone_runtime 补齐 Turbopack standalone 产物遗漏的 onnxruntime-node 原生二进制(libonnxruntime.so.*),根治部署后本地图索层加载失败;符号链接场景自动跳过
122 lines
4.1 KiB
Bash
122 lines
4.1 KiB
Bash
# Embedded by both official deployment entry points; no remote helper dependency.
|
|
assert_release_ready() {
|
|
local app_root="$1"
|
|
local runtime="$app_root/.next/standalone"
|
|
if [ ! -s "$runtime/server.js" ] || [ ! -d "$runtime/.next/static" ]; then
|
|
echo "Incomplete standalone release: $app_root" >&2
|
|
return 1
|
|
fi
|
|
if [ -z "$(find "$runtime/.next/static" -type f -size +0c -print -quit)" ]; then
|
|
echo "Empty standalone static assets: $app_root" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
prepare_standalone_runtime() {
|
|
local app_root="$1"
|
|
local runtime="$app_root/.next/standalone"
|
|
if [ ! -s "$runtime/server.js" ] || [ ! -d "$app_root/.next/static" ]; then
|
|
echo "Missing build output: $app_root" >&2
|
|
return 1
|
|
fi
|
|
if [ -z "$(find "$app_root/.next/static" -type f -size +0c -print -quit)" ]; then
|
|
echo "Empty build static assets: $app_root" >&2
|
|
return 1
|
|
fi
|
|
mkdir -p "$runtime/.next"
|
|
rm -rf -- "$runtime/.next/static" "$runtime/public"
|
|
cp -a "$app_root/.next/static" "$runtime/.next/static"
|
|
if [ -d "$app_root/public" ]; then
|
|
cp -a "$app_root/public" "$runtime/public"
|
|
fi
|
|
# Turbopack standalone tracing may omit onnxruntime-node native binaries;
|
|
# restore them or @huggingface/transformers fails to load libonnxruntime at runtime.
|
|
local ort_real="$app_root/node_modules/onnxruntime-node"
|
|
local ort_runtime="$runtime/node_modules/onnxruntime-node"
|
|
if [ -d "$ort_real/bin" ] && [ ! -L "$ort_runtime" ]; then
|
|
mkdir -p "$ort_runtime"
|
|
rm -rf -- "$ort_runtime/bin"
|
|
cp -a "$ort_real/bin" "$ort_runtime/bin"
|
|
fi
|
|
assert_release_ready "$app_root"
|
|
}
|
|
|
|
acquire_deploy_lock() {
|
|
command -v python3 >/dev/null || return 1
|
|
exec 9>"$BASE/.deploy.lock"
|
|
if ! flock -n 9; then
|
|
echo "Another production deployment is running" >&2
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
verify_release_http() {
|
|
python3 - "$1" <<'PY'
|
|
import json
|
|
import sys
|
|
import time
|
|
from html.parser import HTMLParser
|
|
from urllib.parse import urljoin, urlparse
|
|
from urllib.request import urlopen
|
|
|
|
base = sys.argv[1].rstrip('/')
|
|
|
|
def get(url):
|
|
with urlopen(url, timeout=15) as response:
|
|
if response.status != 200:
|
|
raise RuntimeError('HTTP %s: %s' % (response.status, url))
|
|
return response.read(), response.headers.get_content_type(), response.url
|
|
|
|
for attempt in range(10):
|
|
try:
|
|
payload, _, _ = get(base + '/api/health')
|
|
result = json.loads(payload)
|
|
health = result.get('data', {}).get('services', {})
|
|
if not result.get('success'):
|
|
raise RuntimeError('Health request failed')
|
|
if health.get('database') != 'connected' or health.get('api') != 'running':
|
|
raise RuntimeError('Unhealthy database or API')
|
|
break
|
|
except Exception:
|
|
if attempt == 9:
|
|
raise
|
|
time.sleep(2)
|
|
|
|
class Assets(HTMLParser):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.urls = set()
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
attrs = dict(attrs)
|
|
if tag == 'script' and attrs.get('src'):
|
|
self.urls.add(attrs['src'])
|
|
if tag == 'link' and 'stylesheet' in attrs.get('rel', '').split() and attrs.get('href'):
|
|
self.urls.add(attrs['href'])
|
|
|
|
assets = set()
|
|
for path in ['/', '/installation-demo-workbench']:
|
|
content, kind, final_url = get(base + path)
|
|
if kind != 'text/html':
|
|
raise RuntimeError('Expected HTML: ' + path)
|
|
parser = Assets()
|
|
parser.feed(content.decode('utf-8'))
|
|
found = {urljoin(final_url, item) for item in parser.urls}
|
|
found = {item for item in found if urlparse(item).path.startswith('/_next/static/')}
|
|
if not found:
|
|
raise RuntimeError('No Next static references: ' + path)
|
|
assets.update(found)
|
|
|
|
for url in sorted(assets):
|
|
content, kind, _ = get(url)
|
|
if not content or kind == 'text/html':
|
|
raise RuntimeError('Invalid static asset: ' + url)
|
|
print('200 ' + url, flush=True)
|
|
if not any(urlparse(url).path.endswith('.css') for url in assets):
|
|
raise RuntimeError('No stylesheet verified')
|
|
if not any(urlparse(url).path.endswith('.js') for url in assets):
|
|
raise RuntimeError('No JavaScript verified')
|
|
print('Health and %d static assets verified at %s' % (len(assets), base), flush=True)
|
|
PY
|
|
}
|