feat(seo-geo): 落盘自动补齐与 143 全量回填支持
Memind CI / Test, build, and release guards (pull_request) Failing after 7m12s
Memind CI / Test, build, and release guards (push) Has been cancelled

Finish/交付写盘前自动回填 mindspace-geo,扩展 storage publications 扫描与 geo meta 解析,并增加 143 生产回填脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-16 10:26:02 +08:00
parent acca846856
commit 357e26ab49
15 changed files with 365 additions and 45 deletions
+5 -1
View File
@@ -3,6 +3,7 @@ import path from 'node:path';
import { buildHealthAssessSummary } from './health-assess-summary.mjs';
import { buildHealthTimeline } from './health-timeline.mjs';
import { buildMindSpacePublicUrlForUser } from './mindspace-runtime-config.mjs';
import { prepareMindspacePublicHtmlForDisk } from './mindspace-seo-geo-disk.mjs';
import { resolvePublishDir } from './user-publish.mjs';
import { looksLikeHealthReportPageRequest } from './health-channel-aliases.mjs';
@@ -49,7 +50,10 @@ export function writeHealthPublicHtmlPage({
const publishDir = resolvePublishDir(h5Root, { id: String(userId) });
const absolutePath = path.join(publishDir, clean);
fs.mkdirSync(path.dirname(absolutePath), { recursive: true });
fs.writeFileSync(absolutePath, html, 'utf8');
const preparedHtml = prepareMindspacePublicHtmlForDisk(String(html ?? ''), {
htmlFilePath: absolutePath,
});
fs.writeFileSync(absolutePath, preparedHtml, 'utf8');
if (!fs.existsSync(absolutePath) || fs.statSync(absolutePath).size < minSize) {
const error = Object.assign(new Error('健康页写入失败'), { code: 'health_page_write_failed' });
throw error;
+17 -10
View File
@@ -22,13 +22,6 @@ import { hasRasterShareImageHint } from './wechat/verify/share-preview.mjs';
export const DEFAULT_RASTER_COVER_URL = 'https://m.tkmind.cn/brand/tkmind-icon.png';
function stripHtml(value) {
return String(value ?? '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
function escapeMetaAttribute(value) {
return String(value ?? '')
.replaceAll('&', '&amp;')
@@ -36,6 +29,13 @@ function escapeMetaAttribute(value) {
.replaceAll('<', '&lt;');
}
function stripHtml(value) {
return String(value ?? '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}
function rawTitleFromHtml(html) {
return String(html ?? '').match(/<title[^>]*>([^<]*)<\/title>/i)?.[1]?.trim() ?? '';
}
@@ -135,7 +135,7 @@ export function upsertMindspaceGeoMeta(html, geoPayload, { forceReplace = false
const topic = String(rawH1FromHtml(html) || rawTitleFromHtml(html) || payload.summary).trim();
payload.faq = [{ q: `${topic}讲什么?`, a: payload.summary.slice(0, 220) }];
}
const metaTag = `<meta name="mindspace-geo" content='${JSON.stringify(payload).replace(/'/g, '&#39;')}'>`;
const metaTag = `<meta name="mindspace-geo" content="${escapeMetaAttribute(JSON.stringify(payload))}">`;
const source = String(html ?? '');
if (hasMeta(source, 'mindspace-geo')) {
@@ -342,8 +342,15 @@ export function backfillMindspaceSeoGeoHtml(html, options = {}) {
next = upsertMindspaceCoverMeta(next, buildMinimalCoverMeta(next, { title }));
}
next = upsertDescriptionMeta(next, geoPayload.summary);
if (!hasMeta(next, 'mindspace-geo') || options.forceGeo) {
next = upsertMindspaceGeoMeta(next, geoPayload);
const existingGeo = parseMindspaceGeoMeta(next);
const needsGeoUpsert =
!hasMeta(next, 'mindspace-geo') ||
options.forceGeo ||
!String(existingGeo.summary ?? '').trim();
if (needsGeoUpsert) {
next = upsertMindspaceGeoMeta(next, geoPayload, {
forceReplace: options.forceGeo || !String(existingGeo.summary ?? '').trim(),
});
}
next = repairMindspaceGeoMeta(next);
if (options.fixWarnings !== false) {
+56 -6
View File
@@ -21,15 +21,65 @@ function sanitizeFaqItem(item) {
return { q, a };
}
function decodeMetaAttribute(value) {
return String(value ?? '').replaceAll('&quot;', '"').replaceAll('&amp;', '&').replaceAll('&lt;', '<');
}
function extractGeoMetaJsonRaw(html) {
const source = String(html ?? '');
const marker = source.match(/<meta[^>]*name=["']mindspace-geo["'][^>]*content\s*=\s*/i);
if (!marker) return null;
let rest = source.slice(marker.index + marker[0].length);
if (rest[0] === '"') {
let raw = '';
for (let i = 1; i < rest.length; i += 1) {
if (rest[i] === '&' && rest.slice(i, i + 6) === '&quot;') {
raw += '"';
i += 5;
continue;
}
if (rest[i] === '"') return raw;
raw += rest[i];
}
return null;
}
const jsonStart = rest.indexOf('{');
if (jsonStart < 0) return null;
let depth = 0;
let inString = false;
let escape = false;
for (let i = jsonStart; i < rest.length; i += 1) {
const ch = rest[i];
if (inString) {
if (escape) {
escape = false;
continue;
}
if (ch === '\\') {
escape = true;
continue;
}
if (ch === '"') inString = false;
continue;
}
if (ch === '"') {
inString = true;
continue;
}
if (ch === '{') depth += 1;
else if (ch === '}') {
depth -= 1;
if (depth === 0) return rest.slice(jsonStart, i + 1);
}
}
return null;
}
export function parseMindspaceGeoMeta(html) {
const tag = String(html ?? '').match(/<meta[^>]*name=["']mindspace-geo["'][^>]*>/i)?.[0];
if (!tag) return {};
const contentMatch =
tag.match(/content=(["'])([\s\S]*?)\1/i) ?? tag.match(/content=["']([^"']+)["']/i);
const raw = contentMatch?.[2] ?? contentMatch?.[1];
const raw = extractGeoMetaJsonRaw(html);
if (!raw) return {};
try {
return JSON.parse(raw.replaceAll('&quot;', '"'));
return JSON.parse(decodeMetaAttribute(raw));
} catch {
return {};
}
+7
View File
@@ -15,6 +15,13 @@ test('parseMindspaceGeoMeta reads author faq and keywords', () => {
assert.equal(meta.faq.length, 1);
});
test('parseMindspaceGeoMeta reads legacy single-quoted json with apostrophes in faq', () => {
const html = `<meta name="mindspace-geo" content='{"summary":"业绩回顾","keywords":["报告"],"faq":[{"q":"费比规则?","a":"单客户费比上限18%,超标须特批"}]}'>`;
const meta = parseMindspaceGeoMeta(html);
assert.equal(meta.summary, '业绩回顾');
assert.equal(meta.faq[0].a, '单客户费比上限18%,超标须特批');
});
test('extractFaqFromHeadings builds faq from h2 and paragraph', () => {
const html = `<body>
<h2>最佳季节</h2><p>春秋两季气候最舒适,适合徒步与摄影。</p>
+5 -1
View File
@@ -4,6 +4,7 @@ import path from 'node:path';
import { extractStaticPageLinks, materializePrivateAssetsInPublicHtmlFiles } from './mindspace-chat-save.mjs';
import { DOWNLOADABLE_FILE_PATTERN } from './mindspace-html-download-links.mjs';
import { scheduleWorkspaceHtmlThumbnailSidecars } from './mindspace-workspace-thumbnails.mjs';
import { prepareMindspacePublicHtmlForDisk } from './mindspace-seo-geo-disk.mjs';
import { repairSharePreviewHtml } from './wechat/verify/share-preview-repair.mjs';
import { verifySharePreviewMeta } from './wechat/verify/share-preview.mjs';
@@ -408,9 +409,12 @@ export function materializeMissingPublicHtmlWrites({ messages, publishDir }) {
try {
fs.mkdirSync(path.dirname(destination), { recursive: true });
const rawContent = String(artifact.content ?? '');
const preparedContent = shouldRepairMaterializedPublicHtml(rawContent)
const repairedContent = shouldRepairMaterializedPublicHtml(rawContent)
? repairSharePreviewHtml(rawContent, {})
: rawContent;
const preparedContent = prepareMindspacePublicHtmlForDisk(repairedContent, {
htmlFilePath: destination,
});
fs.writeFileSync(destination, preparedContent, 'utf8');
materialized.push(artifact.relativePath);
scheduleWorkspaceHtmlThumbnailSidecars(root, artifact.relativePath);
+6 -8
View File
@@ -185,10 +185,9 @@ test('materializeMissingPublicHtmlWrites writes html from sandbox write_file too
];
const result = materializeMissingPublicHtmlWrites({ messages, publishDir });
assert.deepEqual(result.materialized, ['public/summer-essay.html']);
assert.equal(
fs.readFileSync(path.join(publishDir, 'public/summer-essay.html'), 'utf8'),
'<!doctype html><title>Summer</title>',
);
const saved = fs.readFileSync(path.join(publishDir, 'public/summer-essay.html'), 'utf8');
assert.match(saved, /<!doctype html><title>Summer<\/title>/i);
assert.match(saved, /name="mindspace-geo"/);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
@@ -420,10 +419,9 @@ test('materializeMissingPublicHtmlWrites writes html from developer write tool c
];
const result = materializeMissingPublicHtmlWrites({ messages, publishDir });
assert.deepEqual(result.materialized, ['public/guizhou-guide.html']);
assert.equal(
fs.readFileSync(path.join(publishDir, 'public/guizhou-guide.html'), 'utf8'),
'<!doctype html><title>Guizhou</title>',
);
const saved = fs.readFileSync(path.join(publishDir, 'public/guizhou-guide.html'), 'utf8');
assert.match(saved, /<!doctype html><title>Guizhou<\/title>/i);
assert.match(saved, /name="mindspace-geo"/);
} finally {
fs.rmSync(publishDir, { recursive: true, force: true });
}
+31
View File
@@ -163,6 +163,29 @@ export function collectMindspaceHtmlFiles(root, userId = null) {
return files;
}
export function collectStoragePublicationHtmlFiles(storageRoot, userId = null) {
const files = [];
const usersDir = path.join(storageRoot, 'users');
if (!fs.existsSync(usersDir)) return files;
const userDirs = userId
? [path.join(usersDir, userId)]
: fs.readdirSync(usersDir, { withFileTypes: true })
.filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
.map((entry) => path.join(usersDir, entry.name));
for (const userDir of userDirs) {
const publicationsDir = path.join(userDir, 'publications');
if (!fs.existsSync(publicationsDir)) continue;
for (const publication of fs.readdirSync(publicationsDir, { withFileTypes: true })) {
if (!publication.isDirectory() || publication.name.startsWith('.')) continue;
const indexHtml = path.join(publicationsDir, publication.name, 'index.html');
if (fs.existsSync(indexHtml)) files.push(indexHtml);
}
}
return files;
}
export function collectPlatformHtmlFiles(publicRoot) {
const files = [];
if (!fs.existsSync(publicRoot)) return files;
@@ -182,17 +205,25 @@ export function collectHtmlTargets({
scope = 'mindspace',
mindspaceRoot,
publicRoot,
storageRoot = null,
userId = null,
file = null,
} = {}) {
if (file) return [path.resolve(file)];
const normalized = String(scope ?? 'mindspace').trim().toLowerCase();
const storageFiles = storageRoot
? collectStoragePublicationHtmlFiles(storageRoot, userId)
: [];
if (normalized === 'platform') {
return collectPlatformHtmlFiles(publicRoot);
}
if (normalized === 'storage') {
return storageFiles;
}
if (normalized === 'all') {
return [
...collectMindspaceHtmlFiles(mindspaceRoot, userId),
...storageFiles,
...collectPlatformHtmlFiles(publicRoot),
];
}
+44
View File
@@ -0,0 +1,44 @@
import fs from 'node:fs';
import path from 'node:path';
import { backfillMindspaceSeoGeoHtml } from './mindspace-geo-backfill.mjs';
function isPublicHtmlPath(value) {
const normalized = String(value ?? '').replace(/\\/g, '/');
return /(?:^|\/)public\/[^/]+\.html$/i.test(normalized);
}
export function prepareMindspacePublicHtmlForDisk(html, { htmlFilePath = '' } = {}) {
const source = String(html ?? '');
const filePath = String(htmlFilePath ?? '');
if (!source.trim()) return source;
if (filePath && !isPublicHtmlPath(filePath)) return source;
if (!filePath && !/<html[\s>]/i.test(source)) return source;
return backfillMindspaceSeoGeoHtml(source, {
htmlFilePath: filePath,
fixWarnings: true,
});
}
export function materializeMindspaceSeoGeoOnDisk(
absolutePath,
{
forceGeo = false,
fixWarnings = true,
} = {},
) {
const filePath = path.resolve(String(absolutePath ?? ''));
if (!filePath || !/\.html$/i.test(filePath)) {
return { changed: false, html: '' };
}
const before = fs.readFileSync(filePath, 'utf8');
const after = backfillMindspaceSeoGeoHtml(before, {
htmlFilePath: filePath,
forceGeo,
fixWarnings,
});
if (after === before) {
return { changed: false, html: before };
}
fs.writeFileSync(filePath, after, 'utf8');
return { changed: true, html: after };
}
+33
View File
@@ -0,0 +1,33 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import {
materializeMindspaceSeoGeoOnDisk,
prepareMindspacePublicHtmlForDisk,
} from './mindspace-seo-geo-disk.mjs';
test('prepareMindspacePublicHtmlForDisk injects mindspace-geo for public html', () => {
const html = '<html><head><title>Demo</title></head><body><h1>Demo</h1></body></html>';
const next = prepareMindspacePublicHtmlForDisk(html, {
htmlFilePath: '/tmp/user/public/demo.html',
});
assert.match(next, /name="mindspace-geo"/);
assert.match(next, /name="description"/);
});
test('materializeMindspaceSeoGeoOnDisk writes backfilled html to disk', () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'seo-geo-disk-'));
const filePath = path.join(dir, 'public', 'page.html');
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(
filePath,
'<html><head><title>Disk Demo</title></head><body><h1>Disk Demo</h1></body></html>',
'utf8',
);
const result = materializeMindspaceSeoGeoOnDisk(filePath);
assert.equal(result.changed, true);
const saved = fs.readFileSync(filePath, 'utf8');
assert.match(saved, /name="mindspace-geo"/);
});
@@ -6,6 +6,7 @@ import {
materializePrivateAssetsInWorkspaceHtml,
resolveClosestHtmlRelativePath,
} from './mindspace-chat-save.mjs';
import { prepareMindspacePublicHtmlForDisk } from './mindspace-seo-geo-disk.mjs';
import {
scanWorkspaceFilesForProhibitedBrowserStorage,
} from './mindspace-browser-storage-policy.mjs';
@@ -105,9 +106,29 @@ export function createMindSpaceWorkspacePublicationDeliveryService({
scanWorkspaceFilesForProhibitedBrowserStorageFn =
scanWorkspaceFilesForProhibitedBrowserStorage,
readFileFn = fsPromises.readFile,
writeFileFn = fsPromises.writeFile,
statFn = fsPromises.stat,
existsSyncFn = fs.existsSync,
prepareMindspaceSeoGeoForDeliveryFn = null,
} = {}) {
const enrichMindspaceHtmlForDelivery =
prepareMindspaceSeoGeoForDeliveryFn
?? (async ({ html, absolutePath }) => {
const enriched = prepareMindspacePublicHtmlForDisk(html, {
htmlFilePath: absolutePath,
});
if (enriched === html) return html;
try {
await writeFileFn(absolutePath, enriched, 'utf8');
} catch (error) {
logger?.warn?.(
`[MindSpace] SEO/GEO disk backfill failed for ${absolutePath}: ${
error?.message || error
}`,
);
}
return enriched;
});
if (!pool || typeof pool.query !== 'function') {
throw new Error(
'createMindSpaceWorkspacePublicationDeliveryService requires a database pool',
@@ -224,6 +245,13 @@ export function createMindSpaceWorkspacePublicationDeliveryService({
}
}
html = await enrichMindspaceHtmlForDelivery({
html,
absolutePath: readablePath,
readFileFn,
writeFileFn,
});
const thumbnailRelativePath =
relativePath.replace(
/\.html$/i,
@@ -144,18 +144,12 @@ test('resolves workspace HTML into a logical delivery without exposing a physica
requestPath:
'/user-1/public/page.html',
});
assert.deepEqual(result, {
action: 'deliver',
kind: 'html',
ownerId: 'user-1',
relativePath: 'public/page.html',
servedName: 'page.html',
canonicalPath:
'/MindSpace/user-1/public/page.html',
thumbnailName: 'page.thumbnail.png',
html:
'<!doctype html><p>localized</p>',
});
assert.equal(result.action, 'deliver');
assert.equal(result.kind, 'html');
assert.equal(result.ownerId, 'user-1');
assert.equal(result.relativePath, 'public/page.html');
assert.match(result.html, /localized/);
assert.match(result.html, /name="mindspace-geo"/);
assert.equal(
Object.hasOwn(result, 'filePath'),
false,
+2 -1
View File
@@ -58,6 +58,7 @@
"check:mindspace-cover": "node scripts/check-mindspace-cover.mjs --scope all",
"check:mindspace-cover:seo": "node scripts/check-mindspace-cover.mjs --scope all",
"backfill:mindspace-geo": "node scripts/backfill-mindspace-geo.mjs --scope all",
"backfill:mindspace-geo:143": "bash scripts/backfill-mindspace-geo-prod.sh",
"demo:thumbnails": "node scripts/thumbnail-preview-demo.mjs",
"audit:conversation-packages": "node scripts/audit-conversation-packages.mjs",
"audit:memory-v2-shadow": "node scripts/audit-memory-v2-shadow.mjs",
@@ -118,7 +119,7 @@
"verify:mindspace-wechat-mp": "node --test mindspace-wechat-mp-config.test.mjs mindspace-wechat-page-draft.test.mjs wechat-draft-publication-standard.test.mjs mindspace-chat-wechat-draft.test.mjs mindspace-public-share-widget.test.mjs server/portal-mindspace-wechat-routes.test.mjs server/portal-mindspace-chat-share-routes.test.mjs",
"verify:portal-access-policy": "node scripts/verify-portal-access-policy.mjs",
"verify:seo-discovery": "node scripts/verify-seo-discovery.mjs",
"verify:seo-geo": "node --test mindspace-index-policy.test.mjs mindspace-seo-tags.test.mjs mindspace-geo-meta.test.mjs mindspace-geo-backfill.test.mjs mindspace-geo-tags.test.mjs mindspace-seo-geo-delivery.test.mjs mindspace-seo-discovery-service.test.mjs mindspace-seo-notify.test.mjs mindspace-config.test.mjs server/portal-seo-discovery-routes.test.mjs plaza-public.test.mjs",
"verify:seo-geo": "node --test mindspace-index-policy.test.mjs mindspace-seo-tags.test.mjs mindspace-geo-meta.test.mjs mindspace-geo-backfill.test.mjs mindspace-geo-tags.test.mjs mindspace-seo-geo-disk.test.mjs mindspace-seo-geo-delivery.test.mjs mindspace-seo-discovery-service.test.mjs mindspace-seo-notify.test.mjs mindspace-config.test.mjs server/portal-seo-discovery-routes.test.mjs plaza-public.test.mjs",
"verify:public-page-interaction": "node scripts/verify-public-page-interaction.mjs",
"verify:page-data": "node --test mindspace-userdata-postgres.test.mjs postgres-user-data-space-service.test.mjs page-data-acceptance.test.mjs page-data-integration.test.mjs page-data-public-service.test.mjs page-data-ops.test.mjs page-data-delivery-assess.test.mjs page-data-delivery-code-review.test.mjs mindspace-page-data-finish-guard.test.mjs mindspace-page-data-finish-guard.integration.test.mjs",
"verify:excel-analyst": "node --test excel-analyst.test.mjs skills-registry.test.mjs skill-runtime-policy.test.mjs capabilities.test.mjs chat-skills.test.mjs chat-intent-router.test.mjs session-reconcile.test.mjs tkmind-proxy-attachment.test.mjs",
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
set -euo pipefail
# Batch backfill SEO/GEO for all MindSpace HTML on production host 143.
# 143 运行的是 release runtime 包(无 .git),因此从本机 rsync 工具脚本后再执行回填。
#
# Usage (from laptop):
# bash scripts/backfill-mindspace-geo-prod.sh
# bash scripts/backfill-mindspace-geo-prod.sh --dry-run
HOST="${MEMIND_PROD_HOST:-john@180.159.29.143}"
LOCAL_REPO="$(cd "$(dirname "$0")/.." && pwd)"
REMOTE_REPO="${MEMIND_PROD_REPO:-/Users/john/Project/Memind}"
REMOTE_MINDSPACE_ROOT="${MEMIND_PROD_MINDSPACE_ROOT:-${REMOTE_REPO}/MindSpace}"
REMOTE_STORAGE_ROOT="${MEMIND_PROD_STORAGE_ROOT:-/Users/john/MindSpace/data/mindspace}"
REMOTE_PUBLIC_ROOT="${MEMIND_PROD_PUBLIC_ROOT:-${REMOTE_REPO}/public}"
REMOTE_NODE="${MEMIND_PROD_NODE:-/opt/homebrew/Cellar/node@22/22.22.3/bin/node}"
EXTRA_ARGS=("$@")
echo "==> 143 SEO/GEO backfill"
echo " host=${HOST}"
echo " local=${LOCAL_REPO}"
echo " remote=${REMOTE_REPO}"
echo " mindspace=${REMOTE_MINDSPACE_ROOT}"
echo " storage=${REMOTE_STORAGE_ROOT}"
echo " public=${REMOTE_PUBLIC_ROOT}"
echo " node=${REMOTE_NODE}"
SEO_FILES=(
chat-image-materialize.mjs
chat-image-turn-scope.mjs
html-document-injection.mjs
mindspace-cover-meta.mjs
mindspace-geo-backfill.mjs
mindspace-geo-meta.mjs
mindspace-og-tags.mjs
mindspace-page-tag.mjs
mindspace-seo-audit.mjs
mindspace-seo-tags.mjs
mindspace-thumbnail-png.mjs
mindspace-thumbnails.mjs
mindspace-workspace-thumbnails.mjs
scripts/backfill-mindspace-geo.mjs
scripts/check-mindspace-cover.mjs
wechat/verify/page-artifact.mjs
wechat/verify/share-preview.mjs
)
ssh "${HOST}" "mkdir -p '${REMOTE_REPO}/scripts' '${REMOTE_REPO}/wechat/verify'"
for rel in "${SEO_FILES[@]}"; do
rsync -az "${LOCAL_REPO}/${rel}" "${HOST}:${REMOTE_REPO}/${rel}"
done
ssh "${HOST}" "set -eo pipefail
NODE_BIN='${REMOTE_NODE}'
if ! test -x \"\${NODE_BIN}\"; then
NODE_BIN=\$(ls /opt/homebrew/Cellar/node@22/*/bin/node 2>/dev/null | head -1)
fi
if ! test -x \"\${NODE_BIN}\"; then
NODE_BIN=\$(ls /Users/john/.local/share/fnm/node-versions/*/installation/bin/node 2>/dev/null | head -1)
fi
test -x \"\${NODE_BIN}\" || { echo 'node not found on 143' >&2; exit 127; }
cd '${REMOTE_REPO}'
\"\${NODE_BIN}\" scripts/backfill-mindspace-geo.mjs \\
--scope all \\
--root '${REMOTE_MINDSPACE_ROOT}' \\
--storage-root '${REMOTE_STORAGE_ROOT}' \\
--public-root '${REMOTE_PUBLIC_ROOT}' \\
${EXTRA_ARGS[*]:-}
\"\${NODE_BIN}\" scripts/check-mindspace-cover.mjs \\
--scope all \\
--root '${REMOTE_MINDSPACE_ROOT}' \\
--storage-root '${REMOTE_STORAGE_ROOT}' \\
--public-root '${REMOTE_PUBLIC_ROOT}'
"
echo "==> done"
+34 -3
View File
@@ -20,6 +20,7 @@ function parseArgs(argv) {
let scope = 'all';
let root = path.join(repoRoot, DEFAULT_PUBLISH_ROOT);
let publicRoot = path.join(repoRoot, 'public');
let storageRoot = null;
let userId = null;
let file = null;
let dryRun = false;
@@ -40,6 +41,9 @@ function parseArgs(argv) {
} else if (arg === '--public-root' && argv[i + 1]) {
publicRoot = path.resolve(argv[i + 1]);
i += 1;
} else if (arg === '--storage-root' && argv[i + 1]) {
storageRoot = path.resolve(argv[i + 1]);
i += 1;
} else if (arg === '--user' && argv[i + 1]) {
userId = argv[i + 1];
i += 1;
@@ -52,11 +56,38 @@ function parseArgs(argv) {
}
}
return { scope, root, publicRoot, userId, file, dryRun, forceGeo, fixWarnings };
return {
scope,
root,
publicRoot,
storageRoot,
userId,
file,
dryRun,
forceGeo,
fixWarnings,
};
}
const { scope, root, publicRoot, userId, file, dryRun, forceGeo, fixWarnings } = parseArgs(process.argv);
const targets = collectHtmlTargets({ scope, mindspaceRoot: root, publicRoot, userId, file });
const {
scope,
root,
publicRoot,
storageRoot,
userId,
file,
dryRun,
forceGeo,
fixWarnings,
} = parseArgs(process.argv);
const targets = collectHtmlTargets({
scope,
mindspaceRoot: root,
publicRoot,
storageRoot,
userId,
file,
});
let changed = 0;
let skipped = 0;
+14 -3
View File
@@ -23,6 +23,7 @@ function parseArgs(argv) {
let scope = 'all';
let root = path.join(repoRoot, DEFAULT_PUBLISH_ROOT);
let publicRoot = path.join(repoRoot, 'public');
let storageRoot = null;
let userId = null;
let file = null;
let seo = true;
@@ -36,6 +37,9 @@ function parseArgs(argv) {
} else if (argv[i] === '--public-root' && argv[i + 1]) {
publicRoot = path.resolve(argv[i + 1]);
i += 1;
} else if (argv[i] === '--storage-root' && argv[i + 1]) {
storageRoot = path.resolve(argv[i + 1]);
i += 1;
} else if (argv[i] === '--user' && argv[i + 1]) {
userId = argv[i + 1];
i += 1;
@@ -51,7 +55,7 @@ function parseArgs(argv) {
process.exit(0);
}
}
return { scope, root, publicRoot, userId, file, seo };
return { scope, root, publicRoot, storageRoot, userId, file, seo };
}
function isPlatformOnlyPath(htmlPath, publicRoot) {
@@ -60,8 +64,15 @@ function isPlatformOnlyPath(htmlPath, publicRoot) {
return resolved.startsWith(`${resolvedPublicRoot}${path.sep}`);
}
const { scope, root, publicRoot, userId, file, seo } = parseArgs(process.argv);
const targets = collectHtmlTargets({ scope, mindspaceRoot: root, publicRoot, userId, file });
const { scope, root, publicRoot, storageRoot, userId, file, seo } = parseArgs(process.argv);
const targets = collectHtmlTargets({
scope,
mindspaceRoot: root,
publicRoot,
storageRoot,
userId,
file,
});
const results = [];
for (const htmlPath of targets) {