Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 24 additions & 14 deletions packages/dshmarket/lib/patch.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,23 @@ export function findUserPatchPath(host, profileDir) {
}
return join(profileDir, 'cordis.patch.yml');
}
/** Row ids the market is allowed to write: plain unquoted YAML scalars. */
const ROW_ID_RE = /^[A-Za-z0-9_.-]+$/u;
/** Row ids the market may write unquoted: plain YAML scalars. */
const PLAIN_ROW_ID_RE = /^[A-Za-z0-9_.-]+$/u;
/**
* Serialize a row id as a YAML scalar, double-quoting it when it is not a
* plain scalar. Loader entry ids may be npm package names such as
* `@scope/name`; the leading `@` is a reserved YAML indicator, so a bare
* `- id: @scope/name` is not valid YAML and breaks the profile overlay
* (issue #241). A double-quoted scalar (`"@scope/name"`) is valid YAML and
* is round-tripped by the line scanner below.
*/
function yamlRowId(rowId) {
return PLAIN_ROW_ID_RE.test(rowId) ? rowId : JSON.stringify(rowId);
}
/** Reverse {@link yamlRowId}: strip the double quotes around a quoted scalar. */
function unquoteYamlRowId(raw) {
return raw.startsWith('"') && raw.endsWith('"') ? raw.slice(1, -1) : raw;
}
/**
* Line-wise scan of one patch file — the plugin-hub shapes. Deliberately
* not a YAML parse: the file may hold structures the market's dialect
Expand All @@ -141,19 +156,20 @@ export function readUserPatchState(patchPath) {
if (/^- /u.test(line))
inInsert = false;
if (inInsert) {
const insertRow = /^ {4}- id: ([A-Za-z0-9_.-]+)/u.exec(line);
const insertRow = /^ {4}- id: ((?:[A-Za-z0-9_.-]+)|"(?:[^"]*)")/u.exec(line);
if (insertRow !== null)
inserts.push(insertRow[1]);
inserts.push(unquoteYamlRowId(insertRow[1]));
continue;
}
const disableRow = /^- id: ([A-Za-z0-9_.-]+)\s*$/u.exec(line);
const disableRow = /^- id: ((?:[A-Za-z0-9_.-]+)|"(?:[^"]*)")\s*$/u.exec(line);
if (disableRow === null)
continue;
const rowId = unquoteYamlRowId(disableRow[1]);
const next = lines[index + 1] ?? '';
if (/^ {2}disabled: true\s*$/u.test(next))
disables.push(disableRow[1]);
disables.push(rowId);
else if (/^ {2}disabled: false\s*$/u.test(next))
forced.push(disableRow[1]);
forced.push(rowId);
}
return { disables, forced, inserts };
}
Expand Down Expand Up @@ -374,7 +390,7 @@ function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
}
function rowBlock(rowId, disabled) {
return `- id: ${rowId}\n disabled: ${disabled ? 'true' : 'false'}\n`;
return `- id: ${yamlRowId(rowId)}\n disabled: ${disabled ? 'true' : 'false'}\n`;
}
/**
* Append one top-level patch entry, refusing when the file is not a valid
Expand Down Expand Up @@ -439,9 +455,6 @@ function appendPatchEntry(patchPath, block) {
/** Disable one row: append `- id: X` + `disabled: true` (idempotent). */
export function disableRow(patchPath, rowId) {
return queuedWrite(async () => {
if (!ROW_ID_RE.test(rowId)) {
return { ok: false, reason: `行 id 含特殊字符,不支持写入补丁层 / row id ${rowId} cannot be written to the patch layer` };
}
const state = readUserPatchState(patchPath);
if (state.disables.includes(rowId))
return { ok: true, reason: null };
Expand All @@ -455,9 +468,6 @@ export function disableRow(patchPath, rowId) {
* `disabled: false` when a lower layer (bundle/home patch) holds it down. */
export function enableRow(patchPath, rowId) {
return queuedWrite(async () => {
if (!ROW_ID_RE.test(rowId)) {
return { ok: false, reason: `行 id 含特殊字符,不支持写入补丁层 / row id ${rowId} cannot be written to the patch layer` };
}
const state = readUserPatchState(patchPath);
const blockRe = new RegExp(`^- id: ['\"]?${escapeRegExp(rowId)}['\"]?\\r?\\n disabled: true\\r?\\n`, 'mu');
const text = (() => {
Expand Down
40 changes: 26 additions & 14 deletions packages/dshmarket/src/patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,25 @@ export interface PatchState {
inserts: string[]
}

/** Row ids the market is allowed to write: plain unquoted YAML scalars. */
const ROW_ID_RE = /^[A-Za-z0-9_.-]+$/u
/** Row ids the market may write unquoted: plain YAML scalars. */
const PLAIN_ROW_ID_RE = /^[A-Za-z0-9_.-]+$/u

/**
* Serialize a row id as a YAML scalar, double-quoting it when it is not a
* plain scalar. Loader entry ids may be npm package names such as
* `@scope/name`; the leading `@` is a reserved YAML indicator, so a bare
* `- id: @scope/name` is not valid YAML and breaks the profile overlay
* (issue #241). A double-quoted scalar (`"@scope/name"`) is valid YAML and
* is round-tripped by the line scanner below.
*/
function yamlRowId(rowId: string): string {
return PLAIN_ROW_ID_RE.test(rowId) ? rowId : JSON.stringify(rowId)
}

/** Reverse {@link yamlRowId}: strip the double quotes around a quoted scalar. */
function unquoteYamlRowId(raw: string): string {
return raw.startsWith('"') && raw.endsWith('"') ? raw.slice(1, -1) : raw
}

/**
* Line-wise scan of one patch file — the plugin-hub shapes. Deliberately
Expand All @@ -161,15 +178,16 @@ export function readUserPatchState(patchPath: string): PatchState {
}
if (/^- /u.test(line)) inInsert = false
if (inInsert) {
const insertRow = /^ {4}- id: ([A-Za-z0-9_.-]+)/u.exec(line)
if (insertRow !== null) inserts.push(insertRow[1])
const insertRow = /^ {4}- id: ((?:[A-Za-z0-9_.-]+)|"(?:[^"]*)")/u.exec(line)
if (insertRow !== null) inserts.push(unquoteYamlRowId(insertRow[1]))
continue
}
const disableRow = /^- id: ([A-Za-z0-9_.-]+)\s*$/u.exec(line)
const disableRow = /^- id: ((?:[A-Za-z0-9_.-]+)|"(?:[^"]*)")\s*$/u.exec(line)
if (disableRow === null) continue
const rowId = unquoteYamlRowId(disableRow[1])
const next = lines[index + 1] ?? ''
if (/^ {2}disabled: true\s*$/u.test(next)) disables.push(disableRow[1])
else if (/^ {2}disabled: false\s*$/u.test(next)) forced.push(disableRow[1])
if (/^ {2}disabled: true\s*$/u.test(next)) disables.push(rowId)
else if (/^ {2}disabled: false\s*$/u.test(next)) forced.push(rowId)
}
return { disables, forced, inserts }
}
Expand Down Expand Up @@ -385,7 +403,7 @@ function escapeRegExp(value: string): string {
}

function rowBlock(rowId: string, disabled: boolean): string {
return `- id: ${rowId}\n disabled: ${disabled ? 'true' : 'false'}\n`
return `- id: ${yamlRowId(rowId)}\n disabled: ${disabled ? 'true' : 'false'}\n`
}

/**
Expand Down Expand Up @@ -451,9 +469,6 @@ function appendPatchEntry(patchPath: string, block: string): { ok: boolean; reas
/** Disable one row: append `- id: X` + `disabled: true` (idempotent). */
export function disableRow(patchPath: string, rowId: string): Promise<{ ok: boolean; reason: string | null }> {
return queuedWrite(async () => {
if (!ROW_ID_RE.test(rowId)) {
return { ok: false, reason: `行 id 含特殊字符,不支持写入补丁层 / row id ${rowId} cannot be written to the patch layer` }
}
const state = readUserPatchState(patchPath)
if (state.disables.includes(rowId)) return { ok: true, reason: null }
const result = appendPatchEntry(patchPath, rowBlock(rowId, true))
Expand All @@ -466,9 +481,6 @@ export function disableRow(patchPath: string, rowId: string): Promise<{ ok: bool
* `disabled: false` when a lower layer (bundle/home patch) holds it down. */
export function enableRow(patchPath: string, rowId: string): Promise<{ ok: boolean; reason: string | null }> {
return queuedWrite(async () => {
if (!ROW_ID_RE.test(rowId)) {
return { ok: false, reason: `行 id 含特殊字符,不支持写入补丁层 / row id ${rowId} cannot be written to the patch layer` }
}
const state = readUserPatchState(patchPath)
const blockRe = new RegExp(`^- id: ['\"]?${escapeRegExp(rowId)}['\"]?\\r?\\n disabled: true\\r?\\n`, 'mu')
const text = (() => {
Expand Down