@nuasite/cli 0.56.0 → 0.56.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -22482,8 +22482,23 @@ function parseSource(source) {
22482
22482
  return null;
22483
22483
  }
22484
22484
  }
22485
+ async function classifyEmptyContentConfig(fs) {
22486
+ let found = false;
22487
+ for (const configPath of CONTENT_CONFIG_PATHS) {
22488
+ try {
22489
+ await fs.stat(configPath);
22490
+ } catch {
22491
+ continue;
22492
+ }
22493
+ found = true;
22494
+ if (parseSource(await fs.readFile(configPath)) === null) {
22495
+ return "unparseable";
22496
+ }
22497
+ }
22498
+ return found ? "no-collections" : "missing";
22499
+ }
22485
22500
  async function parseContentConfig(fs, cache) {
22486
- for (const configPath of ["src/content/config.ts", "src/content.config.ts"]) {
22501
+ for (const configPath of CONTENT_CONFIG_PATHS) {
22487
22502
  let stat;
22488
22503
  try {
22489
22504
  stat = await fs.stat(configPath);
@@ -23166,7 +23181,7 @@ function dropNestedDerivedFrom(collectionName, path2, fields) {
23166
23181
  dropNestedDerivedFrom(collectionName, fieldPath, field.fields);
23167
23182
  }
23168
23183
  }
23169
- var import_parser, FIELD_HELPER_TYPES, VALID_HINT_KEYS, WRAPPER_METHODS;
23184
+ var import_parser, FIELD_HELPER_TYPES, VALID_HINT_KEYS, WRAPPER_METHODS, CONTENT_CONFIG_PATHS;
23170
23185
  var init_content_config_ast = __esm(() => {
23171
23186
  init_src();
23172
23187
  import_parser = __toESM(require_lib(), 1);
@@ -23198,6 +23213,7 @@ var init_content_config_ast = __esm(() => {
23198
23213
  "accept"
23199
23214
  ]);
23200
23215
  WRAPPER_METHODS = new Set(["optional", "nullable", "nullish", "default"]);
23216
+ CONTENT_CONFIG_PATHS = ["src/content/config.ts", "src/content.config.ts"];
23201
23217
  });
23202
23218
 
23203
23219
  // ../cms-core/src/shared.ts
@@ -23362,12 +23378,15 @@ async function checkContent(fs, options = {}) {
23362
23378
  const findings = [];
23363
23379
  const config = await parseContentConfig(fs, new Map);
23364
23380
  if (config.size === 0) {
23365
- findings.push({
23366
- severity: "error",
23367
- code: "config/no-collections",
23368
- file: "src/content.config.ts",
23369
- message: "No collections found \u2014 the content config is missing, unreadable, or declares nothing."
23370
- });
23381
+ const reason = await classifyEmptyContentConfig(fs);
23382
+ if (reason !== "no-collections") {
23383
+ findings.push({
23384
+ severity: "error",
23385
+ code: reason === "missing" ? "config/missing" : "config/unreadable",
23386
+ file: "src/content.config.ts",
23387
+ message: reason === "missing" ? `No content config found \u2014 expected one of ${CONTENT_CONFIG_PATHS.join(" or ")}.` : "The content config could not be parsed."
23388
+ });
23389
+ }
23371
23390
  return { findings, collections: 0, entries: 0 };
23372
23391
  }
23373
23392
  const collections = await loadCollections(fs, config);
@@ -33552,7 +33571,7 @@ async function applyImageChange(content, change, absFilePath, originUrl) {
33552
33571
  break;
33553
33572
  }
33554
33573
  }
33555
- if (replacedIndex < 0 && change.sourceSnippet) {
33574
+ if (replacedIndex < 0 && change.sourceSnippet && isYamlValueSource(newContent, change.sourceSnippet, change.sourcePath)) {
33556
33575
  for (const srcToFind of srcCandidates) {
33557
33576
  const yamlResult = tryYamlValueReplacement(change.sourceSnippet, srcToFind, newSrc);
33558
33577
  if (yamlResult !== null) {
@@ -33859,7 +33878,7 @@ function replaceLiteralInSnippet(content, snippet, oldValue, newValue) {
33859
33878
  continue;
33860
33879
  const updated = snippet.replace(pattern, `${quote}${safeNewValue}${quote}`);
33861
33880
  if (updated !== snippet)
33862
- return content.replace(snippet, updated);
33881
+ return content.replace(snippet, escapeReplacement(updated));
33863
33882
  }
33864
33883
  return;
33865
33884
  }
@@ -33953,35 +33972,66 @@ function applyTextChange(content, change, manifest) {
33953
33972
  if (!content.includes(sourceSnippet)) {
33954
33973
  return { success: false, error: "Source snippet not found in file" };
33955
33974
  }
33956
- const stylingAllowed = manifest.entries[change.cmsId]?.allowStyling !== false;
33975
+ const entry = manifest.entries[change.cmsId];
33976
+ const insideYaml = isYamlValueSource(content, sourceSnippet, change.sourcePath);
33977
+ const stylingAllowed = entry?.allowStyling !== false && !insideYaml;
33957
33978
  const newText = stylingAllowed ? htmlValue ?? newValue : newValue;
33958
33979
  const placeholderPattern = /\{\{cms:[^}]+\}\}/g;
33959
33980
  if (placeholderPattern.test(originalValue)) {
33960
- return applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue, newText);
33981
+ return applyTextChangeWithPlaceholders(insideYaml, content, sourceSnippet, originalValue, newText);
33961
33982
  }
33962
33983
  const resolvedNewText = resolveCmsPlaceholders(newText, manifest);
33963
33984
  const resolvedOriginal = resolveCmsPlaceholders(originalValue, manifest);
33964
- const updatedSnippet = sourceSnippet.replace(resolvedOriginal, resolvedNewText);
33965
- if (updatedSnippet === sourceSnippet) {
33966
- const yamlResult = tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewText);
33967
- if (yamlResult !== null) {
33968
- return { success: true, content: content.replace(sourceSnippet, yamlResult) };
33985
+ const write = (updatedSnippet2) => writeSnippet(content, sourceSnippet, updatedSnippet2, insideYaml, resolvedNewText);
33986
+ const snippetIsJavaScript = isJavaScriptSource(change.sourcePath) && (!!entry?.variableName || !looksLikeMarkup(sourceSnippet));
33987
+ if (snippetIsJavaScript) {
33988
+ const literalResult = tryJsStringLiteralChange(sourceSnippet, resolvedOriginal, resolvedNewText);
33989
+ if (literalResult !== null) {
33990
+ return write(literalResult);
33991
+ }
33992
+ }
33993
+ if (insideYaml) {
33994
+ const frontmatterResult = tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewText, true);
33995
+ if (frontmatterResult !== null) {
33996
+ return write(frontmatterResult);
33969
33997
  }
33998
+ }
33999
+ const updatedSnippet = sourceSnippet.replace(resolvedOriginal, escapeReplacement(resolvedNewText));
34000
+ if (updatedSnippet === sourceSnippet) {
33970
34001
  const brResult = tryBrNormalizedChange(sourceSnippet, resolvedOriginal, resolvedNewText);
33971
34002
  if (brResult !== null) {
33972
- return { success: true, content: content.replace(sourceSnippet, brResult) };
34003
+ return write(brResult);
34004
+ }
34005
+ if (snippetIsJavaScript) {
34006
+ const literalResult = tryJsStringLiteralChange(sourceSnippet, resolvedOriginal, resolvedNewText);
34007
+ if (literalResult !== null) {
34008
+ return write(literalResult);
34009
+ }
33973
34010
  }
33974
34011
  const matchedText = findTextInSnippet(sourceSnippet, resolvedOriginal);
33975
34012
  if (matchedText) {
33976
- const updatedWithEntity = sourceSnippet.replace(matchedText, resolvedNewText);
33977
- return { success: true, content: content.replace(sourceSnippet, updatedWithEntity) };
34013
+ const spliced = /<[^>]+>/.test(resolvedNewText) ? null : spliceIntoEntitySource(matchedText, resolvedOriginal, resolvedNewText);
34014
+ const replacement = spliced ?? (/<[^>]+>/.test(resolvedNewText) ? encodeNbspLike(resolvedNewText, matchedText) : encodeEntitiesLike(resolvedNewText, matchedText));
34015
+ const updatedWithEntity = sourceSnippet.replace(matchedText, escapeReplacement(replacement));
34016
+ return write(updatedWithEntity);
33978
34017
  }
33979
34018
  const innerMatch = sourceSnippet.match(/^(\s*<(\w+)\b[^>]*>)([\s\S]*)(<\/\2>\s*)$/);
33980
34019
  if (innerMatch) {
33981
34020
  const [, openTag, , innerContent, closeTag] = innerMatch;
33982
34021
  const textOnly = innerContent.replace(/<[^>]+>/g, "");
33983
34022
  if (textOnly === resolvedOriginal) {
33984
- return { success: true, content: content.replace(sourceSnippet, openTag + resolvedNewText + closeTag) };
34023
+ const isHtmlReplacement = /<[^>]+>/.test(resolvedNewText);
34024
+ if (isHtmlReplacement || !/<[^>]+>/.test(innerContent)) {
34025
+ return write(openTag + resolvedNewText + closeTag);
34026
+ }
34027
+ const splicedInner = spliceTextAcrossInlineMarkup(innerContent, resolvedOriginal, resolvedNewText);
34028
+ if (splicedInner !== null) {
34029
+ return write(openTag + splicedInner + closeTag);
34030
+ }
34031
+ return {
34032
+ success: false,
34033
+ error: "Cannot apply this edit without dropping the inline markup inside the element " + "\u2014 edit the styled part separately"
34034
+ };
33985
34035
  }
33986
34036
  }
33987
34037
  return {
@@ -33989,9 +34039,9 @@ function applyTextChange(content, change, manifest) {
33989
34039
  error: `Original text "${resolvedOriginal.substring(0, 50)}..." not found in source snippet`
33990
34040
  };
33991
34041
  }
33992
- return { success: true, content: content.replace(sourceSnippet, updatedSnippet) };
34042
+ return write(updatedSnippet);
33993
34043
  }
33994
- function applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue, newText) {
34044
+ function applyTextChangeWithPlaceholders(insideYaml, content, sourceSnippet, originalValue, newText) {
33995
34045
  const placeholderPattern = /\{\{cms:[^}]+\}\}/g;
33996
34046
  const originalParts = originalValue.split(placeholderPattern);
33997
34047
  const newParts = newText.split(placeholderPattern);
@@ -34011,7 +34061,7 @@ function applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue,
34011
34061
  if (matchedText !== oldPart) {
34012
34062
  newPart = encodeEntitiesLike(newPart, matchedText);
34013
34063
  }
34014
- updatedSnippet = updatedSnippet.replace(matchedText, newPart);
34064
+ updatedSnippet = updatedSnippet.replace(matchedText, escapeReplacement(newPart));
34015
34065
  anyChange = true;
34016
34066
  } else {
34017
34067
  return {
@@ -34023,27 +34073,192 @@ function applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue,
34023
34073
  if (!anyChange) {
34024
34074
  return { success: false, error: "No text changes detected between original and new values" };
34025
34075
  }
34026
- return { success: true, content: content.replace(sourceSnippet, updatedSnippet) };
34076
+ return writeSnippet(content, sourceSnippet, updatedSnippet, insideYaml, newText);
34077
+ }
34078
+ function isJavaScriptSource(sourcePath) {
34079
+ return !!sourcePath && JAVASCRIPT_SOURCE.test(sourcePath);
34080
+ }
34081
+ function isYamlValueSource(content, sourceSnippet, sourcePath) {
34082
+ if (!sourcePath)
34083
+ return false;
34084
+ if (YAML_DOCUMENT_SOURCE.test(sourcePath))
34085
+ return true;
34086
+ if (!YAML_FRONTMATTER_SOURCE.test(sourcePath))
34087
+ return false;
34088
+ const block = frontmatterBlock(content);
34089
+ if (!block)
34090
+ return false;
34091
+ const snippetStart = content.indexOf(sourceSnippet);
34092
+ return snippetStart >= block.start && snippetStart + sourceSnippet.length <= block.end;
34093
+ }
34094
+ function frontmatterBlock(content) {
34095
+ const opening = /^(?:\uFEFF)?(?:[ \t]*\r?\n)*(---|\+\+\+)[ \t]*\r?\n/.exec(content);
34096
+ if (!opening || opening[1] !== "---")
34097
+ return null;
34098
+ const start = opening[0].length;
34099
+ const closing = /\n(?:---|\+\+\+)[ \t]*(\r?\n|$)/.exec(content.slice(start));
34100
+ return closing ? { start, end: start + closing.index } : null;
34101
+ }
34102
+ function looksLikeMarkup(text) {
34103
+ return /<[a-zA-Z/!]/.test(text);
34104
+ }
34105
+ function decodeJsString(raw) {
34106
+ let out = "";
34107
+ for (let i2 = 0;i2 < raw.length; i2++) {
34108
+ const char = raw[i2];
34109
+ if (char !== "\\") {
34110
+ out += char;
34111
+ continue;
34112
+ }
34113
+ const next = raw[++i2];
34114
+ if (next === undefined)
34115
+ break;
34116
+ if (next === "u") {
34117
+ if (raw[i2 + 1] === "{") {
34118
+ const close = raw.indexOf("}", i2 + 2);
34119
+ if (close !== -1) {
34120
+ const code2 = parseInt(raw.slice(i2 + 2, close), 16);
34121
+ if (!Number.isNaN(code2)) {
34122
+ out += String.fromCodePoint(code2);
34123
+ i2 = close;
34124
+ continue;
34125
+ }
34126
+ }
34127
+ }
34128
+ const code = parseInt(raw.slice(i2 + 1, i2 + 5), 16);
34129
+ if (!Number.isNaN(code)) {
34130
+ out += String.fromCharCode(code);
34131
+ i2 += 4;
34132
+ continue;
34133
+ }
34134
+ }
34135
+ if (next === "x") {
34136
+ const code = parseInt(raw.slice(i2 + 1, i2 + 3), 16);
34137
+ if (!Number.isNaN(code)) {
34138
+ out += String.fromCharCode(code);
34139
+ i2 += 2;
34140
+ continue;
34141
+ }
34142
+ }
34143
+ if (next === `
34144
+ `)
34145
+ continue;
34146
+ out += JS_ESCAPES[next] ?? next;
34147
+ }
34148
+ return out;
34149
+ }
34150
+ function encodeJsString(value, quote, escapeNbsp) {
34151
+ let out = value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replaceAll(quote, `\\${quote}`);
34152
+ if (escapeNbsp)
34153
+ out = out.replace(/\u00A0/g, "\\u00A0");
34154
+ if (quote === "`")
34155
+ out = out.replace(/\$\{/g, "\\${");
34156
+ return quote + out + quote;
34157
+ }
34158
+ function findStringLiterals(snippet) {
34159
+ const literals = [];
34160
+ for (let i2 = 0;i2 < snippet.length; i2++) {
34161
+ const quote = snippet[i2];
34162
+ if (quote !== "'" && quote !== '"' && quote !== "`")
34163
+ continue;
34164
+ let j = i2 + 1;
34165
+ while (j < snippet.length) {
34166
+ if (snippet[j] === "\\")
34167
+ j += 2;
34168
+ else if (snippet[j] === quote)
34169
+ break;
34170
+ else
34171
+ j++;
34172
+ }
34173
+ if (j >= snippet.length)
34174
+ break;
34175
+ const raw = snippet.slice(i2 + 1, j);
34176
+ literals.push({ start: i2, end: j + 1, quote, value: decodeJsString(raw) });
34177
+ i2 = j;
34178
+ }
34179
+ return literals;
34180
+ }
34181
+ function tryJsStringLiteralChange(sourceSnippet, resolvedOriginal, resolvedNewText) {
34182
+ const literals = findStringLiterals(sourceSnippet);
34183
+ if (literals.length === 0)
34184
+ return null;
34185
+ const splice = (start, end, replacement) => sourceSnippet.slice(0, start) + replacement + sourceSnippet.slice(end);
34186
+ for (const literal of literals) {
34187
+ if (literal.value !== resolvedOriginal)
34188
+ continue;
34189
+ const escapeNbsp = /\\u00[aA]0/.test(sourceSnippet.slice(literal.start, literal.end));
34190
+ return splice(literal.start, literal.end, encodeJsString(resolvedNewText, literal.quote, escapeNbsp));
34191
+ }
34192
+ for (let first = 0;first < literals.length; first++) {
34193
+ let combined = "";
34194
+ for (let last = first;last < literals.length; last++) {
34195
+ if (last > first) {
34196
+ const between = sourceSnippet.slice(literals[last - 1].end, literals[last].start);
34197
+ if (!/^\s*\+\s*$/.test(between))
34198
+ break;
34199
+ }
34200
+ combined += literals[last].value;
34201
+ if (last === first || combined !== resolvedOriginal)
34202
+ continue;
34203
+ const start = literals[first].start;
34204
+ const end = literals[last].end;
34205
+ const escapeNbsp = /\\u00[aA]0/.test(sourceSnippet.slice(start, end));
34206
+ return splice(start, end, encodeJsString(resolvedNewText, literals[first].quote, escapeNbsp));
34207
+ }
34208
+ }
34209
+ return null;
34210
+ }
34211
+ function pickRunAtSeam(tokens, earlier, later) {
34212
+ const seam = tokens.slice(earlier.index + 1, later.index).find((token) => token.startsWith("<"));
34213
+ return seam?.startsWith("</") ? later : earlier;
34214
+ }
34215
+ function spliceTextAcrossInlineMarkup(innerContent, originalText, newText) {
34216
+ const tokens = innerContent.split(/(<[^>]+>)/);
34217
+ const isTag = (token2) => token2.startsWith("<");
34218
+ const runs = [];
34219
+ let plain = "";
34220
+ for (let i2 = 0;i2 < tokens.length; i2++) {
34221
+ const token2 = tokens[i2];
34222
+ if (isTag(token2) || !token2)
34223
+ continue;
34224
+ runs.push({ index: i2, start: plain.length, end: plain.length + token2.length });
34225
+ plain += token2;
34226
+ }
34227
+ if (plain !== originalText)
34228
+ return null;
34229
+ let start = 0;
34230
+ while (start < originalText.length && start < newText.length && originalText[start] === newText[start])
34231
+ start++;
34232
+ let tail = 0;
34233
+ while (tail < originalText.length - start && tail < newText.length - start && originalText[originalText.length - 1 - tail] === newText[newText.length - 1 - tail])
34234
+ tail++;
34235
+ const originalEnd = originalText.length - tail;
34236
+ const newEnd = newText.length - tail;
34237
+ if (start === originalEnd && start === newEnd)
34238
+ return innerContent;
34239
+ if (start === originalEnd) {
34240
+ const inserted = newText.slice(start, newEnd);
34241
+ if (start === 0)
34242
+ return inserted + innerContent;
34243
+ if (start === originalText.length)
34244
+ return innerContent + inserted;
34245
+ }
34246
+ const fitting = runs.filter((r) => start >= r.start && originalEnd <= r.end);
34247
+ const run = start === originalEnd && fitting.length > 1 ? pickRunAtSeam(tokens, fitting[0], fitting[fitting.length - 1]) : fitting[0];
34248
+ if (!run)
34249
+ return null;
34250
+ const token = tokens[run.index];
34251
+ tokens[run.index] = token.slice(0, start - run.start) + newText.slice(start, newEnd) + token.slice(originalEnd - run.start);
34252
+ return tokens.join("");
34027
34253
  }
34028
34254
  function findTextInSnippet(snippet, decodedText) {
34029
34255
  if (snippet.includes(decodedText)) {
34030
34256
  return decodedText;
34031
34257
  }
34032
- const entityMap = [
34033
- ["&", "&amp;"],
34034
- [" ", "&nbsp;"],
34035
- [" ", "&#160;"],
34036
- ["<", "&lt;"],
34037
- [">", "&gt;"],
34038
- ['"', "&quot;"],
34039
- ["'", "&#39;"],
34040
- ["'", "&apos;"]
34041
- ];
34042
- let pattern = escapeRegex(decodedText);
34043
- for (const [char, entity] of entityMap) {
34044
- const escapedChar = escapeRegex(char);
34045
- const escapedEntity = escapeRegex(entity);
34046
- pattern = pattern.replace(new RegExp(escapedChar, "g"), `(?:${escapedChar}|${escapedEntity})`);
34258
+ let pattern = "";
34259
+ for (const char of decodedText) {
34260
+ const alternatives = ENTITY_ALTERNATIVES.get(char);
34261
+ pattern += alternatives ? `(?:${[char, ...alternatives].map(escapeRegex).join("|")})` : escapeRegex(char);
34047
34262
  }
34048
34263
  const regex = new RegExp(pattern);
34049
34264
  const match = snippet.match(regex);
@@ -34055,11 +34270,70 @@ function findTextInSnippet(snippet, decodedText) {
34055
34270
  const brMatch = snippet.match(brRegex);
34056
34271
  return brMatch && brMatch[0] !== decodedText ? brMatch[0] : null;
34057
34272
  }
34273
+ function sameCharacter(a, b2) {
34274
+ if (a === b2)
34275
+ return true;
34276
+ const spaceLike = (c) => c === " " || c === "\xA0";
34277
+ return spaceLike(a) && spaceLike(b2);
34278
+ }
34279
+ function decodeEntitiesWithOffsets(source) {
34280
+ const entityPattern = /&(?:#x([0-9a-f]+)|#(\d+)|([a-z]+));/iy;
34281
+ let text = "";
34282
+ const offsets = [];
34283
+ let i2 = 0;
34284
+ while (i2 < source.length) {
34285
+ entityPattern.lastIndex = i2;
34286
+ const match = entityPattern.exec(source);
34287
+ const decoded = match ? decodeEntityMatch(match) : undefined;
34288
+ offsets.push(i2);
34289
+ if (decoded !== undefined) {
34290
+ text += decoded;
34291
+ i2 += match[0].length;
34292
+ } else {
34293
+ text += source[i2];
34294
+ i2++;
34295
+ }
34296
+ }
34297
+ offsets.push(source.length);
34298
+ return { text, offsets };
34299
+ }
34300
+ function decodeEntityMatch(match) {
34301
+ const [, hex, dec, name] = match;
34302
+ if (hex)
34303
+ return String.fromCodePoint(parseInt(hex, 16));
34304
+ if (dec)
34305
+ return String.fromCodePoint(parseInt(dec, 10));
34306
+ return name ? NAMED_ENTITIES[name.toLowerCase()] : undefined;
34307
+ }
34308
+ function spliceIntoEntitySource(source, original, replacement) {
34309
+ const { text: decoded, offsets } = decodeEntitiesWithOffsets(source);
34310
+ if (decoded.length !== original.length)
34311
+ return null;
34312
+ for (let i2 = 0;i2 < decoded.length; i2++) {
34313
+ if (!sameCharacter(decoded[i2], original[i2]))
34314
+ return null;
34315
+ }
34316
+ let start = 0;
34317
+ while (start < original.length && start < replacement.length && sameCharacter(original[start], replacement[start])) {
34318
+ start++;
34319
+ }
34320
+ let tail = 0;
34321
+ while (tail < original.length - start && tail < replacement.length - start && sameCharacter(original[original.length - 1 - tail], replacement[replacement.length - 1 - tail]))
34322
+ tail++;
34323
+ const originalEnd = original.length - tail;
34324
+ const newEnd = replacement.length - tail;
34325
+ if (start === originalEnd && start === newEnd)
34326
+ return source;
34327
+ const head = source.slice(0, offsets[start]);
34328
+ const rest = source.slice(offsets[originalEnd]);
34329
+ return head + encodeEntitiesLike(replacement.slice(start, newEnd), source) + rest;
34330
+ }
34058
34331
  function encodeEntitiesLike(text, reference) {
34059
34332
  let result = text;
34060
34333
  if (reference.includes("&amp;")) {
34061
34334
  result = result.replace(/&/g, "&amp;");
34062
34335
  }
34336
+ result = encodeNbspLike(result, reference);
34063
34337
  if (reference.includes("&lt;")) {
34064
34338
  result = result.replace(/</g, "&lt;");
34065
34339
  }
@@ -34074,6 +34348,13 @@ function encodeEntitiesLike(text, reference) {
34074
34348
  }
34075
34349
  return result;
34076
34350
  }
34351
+ function encodeNbspLike(text, reference) {
34352
+ if (reference.includes("&nbsp;"))
34353
+ return text.replace(/\u00A0/g, "&nbsp;");
34354
+ if (reference.includes("&#160;"))
34355
+ return text.replace(/\u00A0/g, "&#160;");
34356
+ return text;
34357
+ }
34077
34358
  function resolveCmsPlaceholders(text, manifest) {
34078
34359
  const placeholderPattern = /\{\{cms:([^}]+)\}\}/g;
34079
34360
  return text.replace(placeholderPattern, (match, cmsId) => {
@@ -34135,24 +34416,164 @@ function getVisibleText(html) {
34135
34416
  `);
34136
34417
  return text.trim();
34137
34418
  }
34138
- function tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewText) {
34139
- const keyMatch = sourceSnippet.match(/^(\s*([\w][\w-]*):\s*)/);
34140
- if (!keyMatch)
34419
+ function findYamlTargets(sourceSnippet, allowQuotedKey) {
34420
+ let doc;
34421
+ try {
34422
+ doc = import_yaml6.parseDocument(sourceSnippet);
34423
+ if (doc.errors.length > 0)
34424
+ return [];
34425
+ } catch {
34426
+ return [];
34427
+ }
34428
+ const targets = [];
34429
+ import_yaml6.visit(doc, {
34430
+ Scalar(key, node, path13) {
34431
+ if (key === "key" || !node.range)
34432
+ return;
34433
+ if (typeof node.value !== "string" && typeof node.value !== "number")
34434
+ return;
34435
+ const parent = path13[path13.length - 1];
34436
+ const pair = key === "value" && parent && "key" in parent ? parent : null;
34437
+ if (!allowQuotedKey && (pair ? pair.key?.type !== "PLAIN" : node.type !== "PLAIN"))
34438
+ return;
34439
+ const ownerStart = pair ? pair.key.range[0] : dashBefore(sourceSnippet, node.range[0]);
34440
+ const [start, end] = node.range;
34441
+ const value = node.value;
34442
+ const trailingCr = typeof value === "string" && value.endsWith("\r") && sourceSnippet[end - 1] === "\r";
34443
+ targets.push({
34444
+ start,
34445
+ end: trailingCr ? end - 1 : end,
34446
+ value: trailingCr ? value.slice(0, -1) : value,
34447
+ key: pair ? String(pair.key.value) : null,
34448
+ ownerColumn: columnOf(sourceSnippet, ownerStart)
34449
+ });
34450
+ }
34451
+ });
34452
+ return targets;
34453
+ }
34454
+ function dashBefore(sourceSnippet, valueStart) {
34455
+ const dash = sourceSnippet.lastIndexOf("-", valueStart);
34456
+ return dash < 0 ? valueStart : dash;
34457
+ }
34458
+ function columnOf(sourceSnippet, offset) {
34459
+ return offset - (sourceSnippet.lastIndexOf(`
34460
+ `, offset - 1) + 1);
34461
+ }
34462
+ function tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewText, allowQuotedKey = false) {
34463
+ const matches = findYamlTargets(sourceSnippet, allowQuotedKey).filter((candidate) => String(candidate.value) === resolvedOriginal);
34464
+ if (matches.length !== 1)
34141
34465
  return null;
34466
+ const target = matches[0];
34467
+ const fieldIsTyped = resolvesAsNonString(sourceSnippet.slice(target.start, target.end));
34468
+ const replacement = typeof target.value === "number" && YAML_PLAIN_NUMBER.test(resolvedNewText) ? resolvedNewText : serializeYamlValue(resolvedNewText, target.key, target.ownerColumn, fieldIsTyped);
34469
+ if (replacement === null)
34470
+ return null;
34471
+ const updated = sourceSnippet.slice(0, target.start) + replacement + sourceSnippet.slice(target.end);
34472
+ return yamlValueRoundTrips(updated, resolvedNewText) ? updated : null;
34473
+ }
34474
+ function yamlValueRoundTrips(updatedSnippet, expected) {
34475
+ return findYamlTargets(normalizeCr(updatedSnippet), true).some((target) => scalarSays(target.value, expected));
34476
+ }
34477
+ function scalarSays(actual, expected) {
34478
+ if (String(actual) === expected)
34479
+ return true;
34480
+ if (typeof actual !== "number")
34481
+ return false;
34482
+ const parsed = tryParseYaml(expected);
34483
+ return typeof parsed === "number" && parsed === actual;
34484
+ }
34485
+ function writeSnippet(content, sourceSnippet, updatedSnippet, insideYaml, expected) {
34486
+ if (insideYaml && !yamlSnippetSurvives(sourceSnippet, updatedSnippet, expected)) {
34487
+ return {
34488
+ success: false,
34489
+ error: `"${expected.substring(0, 50)}" cannot be written into this frontmatter field without breaking it`
34490
+ };
34491
+ }
34492
+ return { success: true, content: content.replace(sourceSnippet, escapeReplacement(updatedSnippet)) };
34493
+ }
34494
+ function yamlSnippetSurvives(sourceSnippet, updatedSnippet, expected) {
34495
+ const before = yamlScalars(sourceSnippet);
34496
+ const after = yamlScalars(updatedSnippet);
34497
+ if (!before || !after || before.length !== after.length)
34498
+ return false;
34499
+ let changed = 0;
34500
+ for (let i2 = 0;i2 < before.length; i2++) {
34501
+ if (before[i2].owner !== after[i2].owner)
34502
+ return false;
34503
+ const wrote = after[i2].value;
34504
+ if (String(before[i2].value) === String(wrote))
34505
+ continue;
34506
+ changed++;
34507
+ const saysIt = expected === "" ? wrote === "" : scalarSays(wrote, expected) || String(wrote).includes(expected);
34508
+ if (!saysIt)
34509
+ return false;
34510
+ }
34511
+ return changed > 0;
34512
+ }
34513
+ function yamlScalars(snippet) {
34142
34514
  try {
34143
- const parsed = import_yaml6.parse(sourceSnippet);
34144
- if (parsed == null || typeof parsed !== "object")
34515
+ const doc = import_yaml6.parseDocument(normalizeCr(snippet));
34516
+ if (doc.errors.length > 0)
34145
34517
  return null;
34146
- const value = parsed[keyMatch[2]];
34147
- if (typeof value !== "string" && typeof value !== "number")
34148
- return null;
34149
- if (String(value) !== resolvedOriginal)
34518
+ const scalars = [];
34519
+ import_yaml6.visit(doc, {
34520
+ Scalar(key, node) {
34521
+ scalars.push({ owner: String(key), value: node.value });
34522
+ }
34523
+ });
34524
+ return scalars;
34525
+ } catch {
34526
+ return null;
34527
+ }
34528
+ }
34529
+ function normalizeCr(snippet) {
34530
+ return snippet.replace(/\r(?=\n|$)/g, "");
34531
+ }
34532
+ function serializeYamlValue(value, key, ownerColumn, fieldIsTyped = false) {
34533
+ let body = renderYamlValue(value, key, undefined);
34534
+ if (!fieldIsTyped && body !== null && !body.includes(`
34535
+ `) && resolvesAsNonString(body)) {
34536
+ body = renderYamlValue(value, key, "QUOTE_DOUBLE");
34537
+ }
34538
+ if (body === null || !body.includes(`
34539
+ `))
34540
+ return body;
34541
+ const pad = " ".repeat(ownerColumn);
34542
+ const [header, ...rest] = body.split(`
34543
+ `);
34544
+ return [header, ...rest.map((line) => line === "" ? line : pad + line)].join(`
34545
+ `);
34546
+ }
34547
+ function renderYamlValue(value, key, defaultStringType) {
34548
+ const options = { lineWidth: 0, ...defaultStringType ? { defaultStringType, defaultKeyType: "PLAIN" } : {} };
34549
+ const rendered = key === null ? import_yaml6.stringify([value], options) : import_yaml6.stringify({ [key]: value }, options);
34550
+ let valueStart;
34551
+ try {
34552
+ const doc = import_yaml6.parseDocument(rendered);
34553
+ const first = doc.contents?.items?.[0];
34554
+ const node = key === null ? first : first?.value;
34555
+ if (!node?.range)
34150
34556
  return null;
34557
+ valueStart = node.range[0];
34151
34558
  } catch {
34152
34559
  return null;
34153
34560
  }
34154
- const serialized = import_yaml6.stringify(resolvedNewText, { lineWidth: 0 }).trimEnd();
34155
- return `${keyMatch[1]}${serialized}`;
34561
+ const text = rendered.slice(valueStart);
34562
+ return /^[|>]\d*\+/.test(text) ? text : text.replace(/\n$/, "");
34563
+ }
34564
+ function resolvesAsNonString(scalar) {
34565
+ try {
34566
+ return typeof import_yaml6.parse(scalar, { version: "1.1" }) !== "string";
34567
+ } catch {
34568
+ return false;
34569
+ }
34570
+ }
34571
+ function tryParseYaml(text) {
34572
+ try {
34573
+ return import_yaml6.parse(text);
34574
+ } catch {
34575
+ return;
34576
+ }
34156
34577
  }
34157
34578
  function tryDataFileValueReplacement(content, sourceSnippet, originalValue, newValue, sourceLine) {
34158
34579
  const doubleQuoted = `"${originalValue}"`;
@@ -34168,7 +34589,7 @@ function tryDataFileValueReplacement(content, sourceSnippet, originalValue, newV
34168
34589
  } else {
34169
34590
  return null;
34170
34591
  }
34171
- const updatedSnippet = sourceSnippet.replace(quotedOriginal, quotedNew);
34592
+ const updatedSnippet = sourceSnippet.replace(quotedOriginal, escapeReplacement(quotedNew));
34172
34593
  if (updatedSnippet === sourceSnippet)
34173
34594
  return null;
34174
34595
  let searchStart = 0;
@@ -34232,14 +34653,14 @@ function tryBrNormalizedChange(sourceSnippet, resolvedOriginal, resolvedNewText)
34232
34653
  const leadingWs = raw.slice(0, raw.indexOf(trimmed));
34233
34654
  const trailingWs = raw.slice(raw.indexOf(trimmed) + trimmed.length);
34234
34655
  const newRaw = leadingWs + newSegment + trailingWs;
34235
- result = result.replace(raw, newRaw);
34656
+ result = result.replace(raw, escapeReplacement(newRaw));
34236
34657
  break;
34237
34658
  }
34238
34659
  }
34239
34660
  }
34240
34661
  return result !== sourceSnippet ? result : null;
34241
34662
  }
34242
- var import_node_html_parser, import_yaml6, ASSET_IMPORT_EXT_RE, REMOTE_FETCH_TIMEOUT_MS = 15000, REMOTE_FETCH_MAX_BYTES, QUOTED_LITERAL_DELIMITERS;
34663
+ var import_node_html_parser, import_yaml6, ASSET_IMPORT_EXT_RE, REMOTE_FETCH_TIMEOUT_MS = 15000, REMOTE_FETCH_MAX_BYTES, QUOTED_LITERAL_DELIMITERS, JAVASCRIPT_SOURCE, YAML_DOCUMENT_SOURCE, YAML_FRONTMATTER_SOURCE, JS_ESCAPES, ENTITY_ALTERNATIVES, NAMED_ENTITIES, YAML_PLAIN_NUMBER;
34243
34664
  var init_source_writer = __esm(() => {
34244
34665
  init_astro_image_paths();
34245
34666
  init_config();
@@ -34250,6 +34671,37 @@ var init_source_writer = __esm(() => {
34250
34671
  ASSET_IMPORT_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|ico|bmp|tiff?)$/i;
34251
34672
  REMOTE_FETCH_MAX_BYTES = 50 * 1024 * 1024;
34252
34673
  QUOTED_LITERAL_DELIMITERS = [`'`, `"`, "`"];
34674
+ JAVASCRIPT_SOURCE = /\.(astro|[cm]?[jt]sx?)$/i;
34675
+ YAML_DOCUMENT_SOURCE = /\.ya?ml$/i;
34676
+ YAML_FRONTMATTER_SOURCE = /\.(md|mdx|markdown)$/i;
34677
+ JS_ESCAPES = {
34678
+ n: `
34679
+ `,
34680
+ r: "\r",
34681
+ t: "\t",
34682
+ b: "\b",
34683
+ f: "\f",
34684
+ v: "\v",
34685
+ 0: "\x00"
34686
+ };
34687
+ ENTITY_ALTERNATIVES = new Map([
34688
+ ["&", ["&amp;"]],
34689
+ ["\xA0", ["&nbsp;", "&#160;", " "]],
34690
+ [" ", ["&nbsp;", "&#160;"]],
34691
+ ["<", ["&lt;"]],
34692
+ [">", ["&gt;"]],
34693
+ ['"', ["&quot;"]],
34694
+ ["'", ["&#39;", "&apos;"]]
34695
+ ]);
34696
+ NAMED_ENTITIES = {
34697
+ amp: "&",
34698
+ lt: "<",
34699
+ gt: ">",
34700
+ quot: '"',
34701
+ apos: "'",
34702
+ nbsp: "\xA0"
34703
+ };
34704
+ YAML_PLAIN_NUMBER = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
34253
34705
  });
34254
34706
 
34255
34707
  // ../cms/src/handlers/api-routes.ts