fix: make MindSpace docx repair script runtime-safe
Avoid importing mindspace-public-finish-sync in production runtime so release link repair can copy oa docx files or strip broken anchors without extra deps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -235,10 +235,6 @@ async function copyMindspacePublicLinkTools() {
|
|||||||
path.join(root, 'scripts', 'repair-mindspace-public-downloads.mjs'),
|
path.join(root, 'scripts', 'repair-mindspace-public-downloads.mjs'),
|
||||||
path.join(runtimeRoot, 'scripts', 'repair-mindspace-public-downloads.mjs'),
|
path.join(runtimeRoot, 'scripts', 'repair-mindspace-public-downloads.mjs'),
|
||||||
);
|
);
|
||||||
await fs.copyFile(
|
|
||||||
path.join(root, 'mindspace-public-finish-sync.mjs'),
|
|
||||||
path.join(runtimeRoot, 'mindspace-public-finish-sync.mjs'),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeMetadata() {
|
async function writeMetadata() {
|
||||||
|
|||||||
@@ -1,29 +1,32 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
/**
|
/**
|
||||||
* Repair missing public/*.docx download targets before release link checks.
|
* Repair missing public/*.docx download targets before release link checks.
|
||||||
* 1) sync from oa/ when a safe source exists
|
* Runtime-safe: no imports from the full MindSpace finish-sync graph.
|
||||||
* 2) remove broken docx anchors from HTML when no source is available
|
|
||||||
*/
|
*/
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { syncPublicDocxDownloads } from '../mindspace-public-finish-sync.mjs';
|
|
||||||
|
|
||||||
const DOCX_HREF_RE = /<a\b[^>]*\bhref=(["'])([^"']+\.docx)\1[^>]*>[\s\S]*?<\/a>/gi;
|
const DOCX_HREF_RE = /<a\b[^>]*\bhref=(["'])([^"']+\.docx)\1[^>]*>[\s\S]*?<\/a>/gi;
|
||||||
const DOCX_HREF_CAPTURE_RE = /<a\b[^>]*\bhref=(["'])([^"']+\.docx)\1/gi;
|
const DOCX_HREF_CAPTURE_RE = /<a\b[^>]*\bhref=(["'])([^"']+\.docx)\1/gi;
|
||||||
|
|
||||||
function listMissingDocxTargets(html, publicDir) {
|
function listDocxHrefBasenames(html) {
|
||||||
const missing = new Set();
|
const basenames = new Set();
|
||||||
let match = DOCX_HREF_CAPTURE_RE.exec(html);
|
let match = DOCX_HREF_CAPTURE_RE.exec(html);
|
||||||
while (match) {
|
while (match) {
|
||||||
const docxPath = path.resolve(publicDir, path.basename(String(match[2] ?? '')));
|
const base = path.basename(String(match[2] ?? '')).trim();
|
||||||
if (!fs.existsSync(docxPath)) {
|
if (base) basenames.add(base);
|
||||||
missing.add(path.basename(docxPath).toLowerCase());
|
|
||||||
}
|
|
||||||
match = DOCX_HREF_CAPTURE_RE.exec(html);
|
match = DOCX_HREF_CAPTURE_RE.exec(html);
|
||||||
}
|
}
|
||||||
DOCX_HREF_CAPTURE_RE.lastIndex = 0;
|
DOCX_HREF_CAPTURE_RE.lastIndex = 0;
|
||||||
return [...missing];
|
return [...basenames];
|
||||||
|
}
|
||||||
|
|
||||||
|
function listMissingDocxTargets(html, publicDir) {
|
||||||
|
return listDocxHrefBasenames(html).filter((basename) => {
|
||||||
|
const docxPath = path.join(publicDir, basename);
|
||||||
|
return !fs.existsSync(docxPath);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function stripBrokenDocxAnchors(html, missingBasenames) {
|
function stripBrokenDocxAnchors(html, missingBasenames) {
|
||||||
@@ -39,23 +42,39 @@ function stripBrokenDocxAnchors(html, missingBasenames) {
|
|||||||
return { html: next, changed };
|
return { html: next, changed };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tryCopyDocxFromOa(userRoot, publicDir, basename) {
|
||||||
|
const destination = path.join(publicDir, basename);
|
||||||
|
if (fs.existsSync(destination)) return false;
|
||||||
|
const source = path.join(userRoot, 'oa', basename);
|
||||||
|
if (!fs.existsSync(source) || !fs.statSync(source).isFile()) return false;
|
||||||
|
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
||||||
|
fs.copyFileSync(source, destination);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export function repairMindspacePublicDownloads({ publishDir } = {}) {
|
export function repairMindspacePublicDownloads({ publishDir } = {}) {
|
||||||
const root = path.resolve(String(publishDir ?? ''));
|
const root = path.resolve(String(publishDir ?? ''));
|
||||||
if (!root || !fs.existsSync(root)) {
|
if (!root || !fs.existsSync(root)) {
|
||||||
return { synced: [], missing: [], stripped: [] };
|
return { synced: [], stripped: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncResult = syncPublicDocxDownloads({ publishDir: root, minCompleteSize: 1024 });
|
const synced = [];
|
||||||
const stripped = [];
|
const stripped = [];
|
||||||
|
|
||||||
for (const userDir of fs.readdirSync(root, { withFileTypes: true })) {
|
for (const userDir of fs.readdirSync(root, { withFileTypes: true })) {
|
||||||
if (!userDir.isDirectory()) continue;
|
if (!userDir.isDirectory()) continue;
|
||||||
const publicDir = path.join(root, userDir.name, 'public');
|
const userRoot = path.join(root, userDir.name);
|
||||||
|
const publicDir = path.join(userRoot, 'public');
|
||||||
if (!fs.existsSync(publicDir)) continue;
|
if (!fs.existsSync(publicDir)) continue;
|
||||||
for (const entry of fs.readdirSync(publicDir, { withFileTypes: true })) {
|
for (const entry of fs.readdirSync(publicDir, { withFileTypes: true })) {
|
||||||
if (!entry.isFile() || !entry.name.endsWith('.html')) continue;
|
if (!entry.isFile() || !entry.name.endsWith('.html')) continue;
|
||||||
const htmlPath = path.join(publicDir, entry.name);
|
const htmlPath = path.join(publicDir, entry.name);
|
||||||
const html = fs.readFileSync(htmlPath, 'utf8');
|
const html = fs.readFileSync(htmlPath, 'utf8');
|
||||||
|
for (const basename of listDocxHrefBasenames(html)) {
|
||||||
|
if (tryCopyDocxFromOa(userRoot, publicDir, basename)) {
|
||||||
|
synced.push(path.join(userDir.name, 'public', basename));
|
||||||
|
}
|
||||||
|
}
|
||||||
const missingBasenames = listMissingDocxTargets(html, publicDir);
|
const missingBasenames = listMissingDocxTargets(html, publicDir);
|
||||||
const { html: nextHtml, changed } = stripBrokenDocxAnchors(html, missingBasenames);
|
const { html: nextHtml, changed } = stripBrokenDocxAnchors(html, missingBasenames);
|
||||||
if (!changed) continue;
|
if (!changed) continue;
|
||||||
@@ -64,11 +83,7 @@ export function repairMindspacePublicDownloads({ publishDir } = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return { synced, stripped };
|
||||||
synced: syncResult.synced,
|
|
||||||
missing: syncResult.missing,
|
|
||||||
stripped,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
|
|||||||
Reference in New Issue
Block a user