fix(page-data): isolate invalid workspace bindings
This commit is contained in:
@@ -47,13 +47,17 @@ export function detectPageDataDatasetUsageFromHtml(html) {
|
||||
// this API must therefore only require the safe, default capability.
|
||||
remember(match[1], { softDelete: true });
|
||||
}
|
||||
for (const match of text.matchAll(/\.insertRow\(\s*([A-Za-z_$][\w$]*)/g)) {
|
||||
// `${...}` inside an HTML template literal is a row-id interpolation, not a
|
||||
// JavaScript identifier passed as the dataset argument. Without this guard,
|
||||
// snippets such as `onclick="deleteRow(${row.id})"` are detected as a `$`
|
||||
// dataset and can make the whole workspace binding pass fail.
|
||||
for (const match of text.matchAll(/\.insertRow\(\s*(?!\$\{)([A-Za-z_$][\w$]*)/g)) {
|
||||
remember(constants.get(match[1]) ?? match[1], { insert: true });
|
||||
}
|
||||
for (const match of text.matchAll(/\.listRows\(\s*([A-Za-z_$][\w$]*)/g)) {
|
||||
for (const match of text.matchAll(/\.listRows\(\s*(?!\$\{)([A-Za-z_$][\w$]*)/g)) {
|
||||
remember(constants.get(match[1]) ?? match[1], { read: true });
|
||||
}
|
||||
for (const match of text.matchAll(/\.deleteRow\(\s*([A-Za-z_$][\w$]*)/g)) {
|
||||
for (const match of text.matchAll(/\.deleteRow\(\s*(?!\$\{)([A-Za-z_$][\w$]*)/g)) {
|
||||
remember(constants.get(match[1]) ?? match[1], { softDelete: true });
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,17 @@ test('detectPageDataDatasetUsageFromHtml resolves dataset constants', () => {
|
||||
assert.deepEqual(usage.get('reading_survey'), { read: true });
|
||||
});
|
||||
|
||||
test('detectPageDataDatasetUsageFromHtml ignores template-literal row id interpolation', () => {
|
||||
const html = `
|
||||
const DATASET = 'daily_log';
|
||||
await client.listRows(DATASET, { limit: 200 });
|
||||
rows.map((row) => \`<button onclick="window.deleteRow(\${row.id})">删除</button>\`);
|
||||
`;
|
||||
const usage = detectPageDataDatasetUsageFromHtml(html);
|
||||
assert.deepEqual([...usage.entries()], [['daily_log', { read: true }]]);
|
||||
assert.equal(usage.has('$'), false);
|
||||
});
|
||||
|
||||
test('assertPolicyMatchesHtmlDatasets rejects mismatched dataset names', () => {
|
||||
const html = `await c.insertRow('tkmind_exp_survey', {});`;
|
||||
assert.throws(
|
||||
|
||||
@@ -137,20 +137,23 @@ export async function ensurePageDataHtmlPagesBound({
|
||||
continue;
|
||||
}
|
||||
|
||||
const assessment = await assessPageDataHtmlBinding({
|
||||
pool,
|
||||
userId,
|
||||
publishDir: workspaceRoot,
|
||||
relativePath: file.relativePath,
|
||||
html: file.content,
|
||||
findPageByRelativePath,
|
||||
});
|
||||
if (assessment.bound) {
|
||||
skipped.push({ relativePath: file.relativePath, reason: 'already_bound' });
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// Keep every page's assessment and repair isolated. A malformed legacy
|
||||
// page must be reported in `errors`, not reject the entire MindSpace page
|
||||
// listing that invokes this best-effort maintenance pass.
|
||||
const assessment = await assessPageDataHtmlBinding({
|
||||
pool,
|
||||
userId,
|
||||
publishDir: workspaceRoot,
|
||||
relativePath: file.relativePath,
|
||||
html: file.content,
|
||||
findPageByRelativePath,
|
||||
});
|
||||
if (assessment.bound) {
|
||||
skipped.push({ relativePath: file.relativePath, reason: 'already_bound' });
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const datasetName of usage.keys()) {
|
||||
await ensureRegisteredDatasetFromHtml({
|
||||
workspaceRoot,
|
||||
|
||||
Reference in New Issue
Block a user