release: prepare 0629001 portal updates

This commit is contained in:
john
2026-06-29 22:20:04 +08:00
parent 18ea4f82fd
commit a40e340a41
84 changed files with 17516 additions and 2475 deletions
+3
View File
@@ -16,6 +16,8 @@ const skipBuild = process.argv.includes('--skip-build');
const skipNodeModules = process.argv.includes('--skip-node-modules');
const externalPackages = [
'@img/sharp-darwin-arm64',
'@img/sharp-libvips-darwin-arm64',
'@node-rs/argon2',
'@resvg/resvg-js',
'debug',
@@ -26,6 +28,7 @@ const externalPackages = [
'mysql2/promise',
'qrcode',
'redis',
'sharp',
'undici',
];
+56
View File
@@ -0,0 +1,56 @@
import http from 'node:http';
import { request as httpRequest } from 'node:http';
const listenHost = process.env.IMGPROXY_COMPAT_HOST || '10.10.0.2';
const listenPort = Number(process.env.IMGPROXY_COMPAT_PORT || 20081);
const upstream = new URL(process.env.IMGPROXY_UPSTREAM || 'http://127.0.0.1:20082');
function convertPath(url) {
if (url === '/health') return '/health';
const pathname = url.split('?')[0] || '/';
const match = pathname.match(/^(?:\/[^/]+)?\/unsafe\/(\d+)x(\d+)\/(\d+)\/([a-z0-9]+)\/(.+)$/i);
if (!match) return pathname;
const [, width, height, quality, format, encodedSource] = match;
let source;
source = encodedSource;
for (let i = 0; i < 3; i += 1) {
try {
const decoded = decodeURIComponent(source);
if (decoded === source) break;
source = decoded;
} catch {
break;
}
}
if (source.startsWith('local://') && !source.startsWith('local:///')) {
source = `local:///${source.slice('local://'.length)}`;
}
return `/unsafe/rs:fit:${width}:${height}/q:${quality}/plain/${source}@${format}`;
}
const server = http.createServer((req, res) => {
const upstreamPath = convertPath(req.url || '/');
const options = {
hostname: upstream.hostname,
port: upstream.port || 80,
method: req.method,
path: upstreamPath,
headers: {
...req.headers,
host: upstream.host,
},
};
const proxy = httpRequest(options, (upstreamRes) => {
res.writeHead(upstreamRes.statusCode || 502, upstreamRes.headers);
upstreamRes.pipe(res);
});
proxy.on('error', (err) => {
res.writeHead(502, { 'content-type': 'text/plain' });
res.end(`imgproxy compat proxy error: ${err.message}`);
});
req.pipe(proxy);
});
server.listen(listenPort, listenHost, () => {
console.log(`imgproxy compat proxy listening on ${listenHost}:${listenPort}, upstream ${upstream.href}`);
});
+7
View File
@@ -121,6 +121,7 @@ if [[ "${SKIP_TESTS}" -ne 1 ]]; then
(
cd "${ROOT}"
npm test -- --test-name-pattern='publish|space|billing' >/dev/null
node --test mindspace-public-finish-sync.test.mjs >/dev/null
)
fi
@@ -153,6 +154,12 @@ verify_runtime_artifact() {
verify_runtime_artifact
say "验证公开 HTML 完成同步守卫"
(
cd "${ROOT}"
npm run verify:public-finish-sync-runtime
)
verify_remote_goosed_dependency() {
say "检查 103 goosed 依赖"
ssh -o BatchMode=yes "${HOST}" 'bash -s' <<'REMOTE'
@@ -0,0 +1,79 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
hasRecentOwnPublicHtmlReference,
materializeMissingPublicHtmlWrites,
} from '../mindspace-public-finish-sync.mjs';
const root = path.resolve(new URL('..', import.meta.url).pathname);
const runtimeServer = path.join(root, '.runtime', 'portal', 'server.mjs');
const currentUser = { id: 'a6fb1e97-2b0f-447b-b138-4561d8e5c53e', username: 'john' };
const toolRequestOnlyMessages = [
{
role: 'assistant',
content: [
{
type: 'toolRequest',
toolCall: {
value: {
name: 'write',
arguments: {
path: 'public/guarded-release.html',
content: '<!doctype html><title>Guarded release</title>',
},
},
},
},
],
},
...Array.from({ length: 12 }, (_, index) => ({
role: index % 2 === 0 ? 'assistant' : 'user',
content: [{ type: 'text', text: `validation step ${index + 1}` }],
})),
];
assert.equal(
hasRecentOwnPublicHtmlReference(toolRequestOnlyMessages, currentUser),
true,
'finish sync must detect textless write tool requests beyond the old eight-message window',
);
const publishDir = fs.mkdtempSync(path.join(os.tmpdir(), 'public-finish-release-guard-'));
try {
const result = materializeMissingPublicHtmlWrites({
messages: toolRequestOnlyMessages,
publishDir,
});
assert.deepEqual(result.materialized, ['public/guarded-release.html']);
assert.equal(
fs.readFileSync(path.join(publishDir, 'public/guarded-release.html'), 'utf8'),
'<!doctype html><title>Guarded release</title>',
);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
if (!fs.existsSync(runtimeServer)) {
throw new Error(`runtime server bundle is missing: ${runtimeServer}`);
}
const runtimeSource = fs.readFileSync(runtimeServer, 'utf8');
const requiredRuntimeSnippets = [
'normalizedName === "write"',
'normalizedName.endsWith("__write")',
'recentCount = 80',
'if (extractPublicHtmlWriteArtifacts([message]).length > 0) {',
];
for (const snippet of requiredRuntimeSnippets) {
assert.ok(
runtimeSource.includes(snippet),
`runtime server bundle is missing public finish sync guard: ${snippet}`,
);
}
console.log('public finish sync runtime guard ok');