@nuasite/cli 0.56.0 → 0.56.2

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
@@ -7368,6 +7368,10 @@ function withoutBlankArrayItems(frontmatter) {
7368
7368
  out[key] = prunedValue(value);
7369
7369
  return out;
7370
7370
  }
7371
+ var NON_TEXT_SLUG_TYPES;
7372
+ var init_field_values = __esm(() => {
7373
+ NON_TEXT_SLUG_TYPES = new Set(["array", "object", "reference", "image", "file", "boolean", "number", "date", "datetime"]);
7374
+ });
7371
7375
  // ../cms-types/src/index.ts
7372
7376
  function isFieldType(value) {
7373
7377
  return FIELD_TYPES.includes(value);
@@ -7377,6 +7381,7 @@ function isDerivedTransform(value) {
7377
7381
  }
7378
7382
  var FIELD_TYPES, DERIVED_TRANSFORMS;
7379
7383
  var init_src = __esm(() => {
7384
+ init_field_values();
7380
7385
  FIELD_TYPES = [
7381
7386
  "text",
7382
7387
  "textarea",
@@ -22482,8 +22487,23 @@ function parseSource(source) {
22482
22487
  return null;
22483
22488
  }
22484
22489
  }
22490
+ async function classifyEmptyContentConfig(fs) {
22491
+ let found = false;
22492
+ for (const configPath of CONTENT_CONFIG_PATHS) {
22493
+ try {
22494
+ await fs.stat(configPath);
22495
+ } catch {
22496
+ continue;
22497
+ }
22498
+ found = true;
22499
+ if (parseSource(await fs.readFile(configPath)) === null) {
22500
+ return "unparseable";
22501
+ }
22502
+ }
22503
+ return found ? "no-collections" : "missing";
22504
+ }
22485
22505
  async function parseContentConfig(fs, cache) {
22486
- for (const configPath of ["src/content/config.ts", "src/content.config.ts"]) {
22506
+ for (const configPath of CONTENT_CONFIG_PATHS) {
22487
22507
  let stat;
22488
22508
  try {
22489
22509
  stat = await fs.stat(configPath);
@@ -23104,6 +23124,10 @@ function parseFieldLayoutFromObject(obj) {
23104
23124
  if (value.type === "BooleanLiteral")
23105
23125
  layout.sidebar = value.value;
23106
23126
  break;
23127
+ case "position":
23128
+ if (value.type === "StringLiteral" && (value.value === "sidebar" || value.value === "header"))
23129
+ layout.position = value.value;
23130
+ break;
23107
23131
  case "hidden":
23108
23132
  if (value.type === "BooleanLiteral")
23109
23133
  layout.hidden = value.value;
@@ -23166,7 +23190,7 @@ function dropNestedDerivedFrom(collectionName, path2, fields) {
23166
23190
  dropNestedDerivedFrom(collectionName, fieldPath, field.fields);
23167
23191
  }
23168
23192
  }
23169
- var import_parser, FIELD_HELPER_TYPES, VALID_HINT_KEYS, WRAPPER_METHODS;
23193
+ var import_parser, FIELD_HELPER_TYPES, VALID_HINT_KEYS, WRAPPER_METHODS, CONTENT_CONFIG_PATHS;
23170
23194
  var init_content_config_ast = __esm(() => {
23171
23195
  init_src();
23172
23196
  import_parser = __toESM(require_lib(), 1);
@@ -23198,6 +23222,7 @@ var init_content_config_ast = __esm(() => {
23198
23222
  "accept"
23199
23223
  ]);
23200
23224
  WRAPPER_METHODS = new Set(["optional", "nullable", "nullish", "default"]);
23225
+ CONTENT_CONFIG_PATHS = ["src/content/config.ts", "src/content.config.ts"];
23201
23226
  });
23202
23227
 
23203
23228
  // ../cms-core/src/shared.ts
@@ -23362,12 +23387,15 @@ async function checkContent(fs, options = {}) {
23362
23387
  const findings = [];
23363
23388
  const config = await parseContentConfig(fs, new Map);
23364
23389
  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
- });
23390
+ const reason = await classifyEmptyContentConfig(fs);
23391
+ if (reason !== "no-collections") {
23392
+ findings.push({
23393
+ severity: "error",
23394
+ code: reason === "missing" ? "config/missing" : "config/unreadable",
23395
+ file: "src/content.config.ts",
23396
+ message: reason === "missing" ? `No content config found \u2014 expected one of ${CONTENT_CONFIG_PATHS.join(" or ")}.` : "The content config could not be parsed."
23397
+ });
23398
+ }
23371
23399
  return { findings, collections: 0, entries: 0 };
23372
23400
  }
23373
23401
  const collections = await loadCollections(fs, config);
@@ -23576,13 +23604,13 @@ function assignFieldMetadata(fields, directives) {
23576
23604
  for (const field of fields) {
23577
23605
  if (SIDEBAR_FIELD_NAMES.has(normalizeFieldName(field.name)) || field.type === "image" || field.type === "boolean") {
23578
23606
  field.position = "sidebar";
23579
- } else {
23580
- field.position = "header";
23581
23607
  }
23582
23608
  const directive = directives[field.name];
23583
23609
  if (directive) {
23584
- if (directive.position)
23610
+ if (directive.position) {
23585
23611
  field.position = directive.position;
23612
+ field.positionDeclared = true;
23613
+ }
23586
23614
  if (directive.group)
23587
23615
  field.group = directive.group;
23588
23616
  }
@@ -23922,8 +23950,14 @@ function applyParsedFieldLayout(field, pf) {
23922
23950
  field.width = layout.width;
23923
23951
  if (layout.order !== undefined)
23924
23952
  field.order = layout.order;
23925
- if (layout.sidebar)
23953
+ if (layout.sidebar) {
23926
23954
  field.position = "sidebar";
23955
+ field.positionDeclared = true;
23956
+ }
23957
+ if (layout.position) {
23958
+ field.position = layout.position;
23959
+ field.positionDeclared = true;
23960
+ }
23927
23961
  if (layout.derivedFrom) {
23928
23962
  field.derivedFrom = layout.derivedFrom;
23929
23963
  field.derivedDeclared = true;
@@ -33552,7 +33586,7 @@ async function applyImageChange(content, change, absFilePath, originUrl) {
33552
33586
  break;
33553
33587
  }
33554
33588
  }
33555
- if (replacedIndex < 0 && change.sourceSnippet) {
33589
+ if (replacedIndex < 0 && change.sourceSnippet && isYamlValueSource(newContent, change.sourceSnippet, change.sourcePath)) {
33556
33590
  for (const srcToFind of srcCandidates) {
33557
33591
  const yamlResult = tryYamlValueReplacement(change.sourceSnippet, srcToFind, newSrc);
33558
33592
  if (yamlResult !== null) {
@@ -33859,7 +33893,7 @@ function replaceLiteralInSnippet(content, snippet, oldValue, newValue) {
33859
33893
  continue;
33860
33894
  const updated = snippet.replace(pattern, `${quote}${safeNewValue}${quote}`);
33861
33895
  if (updated !== snippet)
33862
- return content.replace(snippet, updated);
33896
+ return content.replace(snippet, escapeReplacement(updated));
33863
33897
  }
33864
33898
  return;
33865
33899
  }
@@ -33953,35 +33987,66 @@ function applyTextChange(content, change, manifest) {
33953
33987
  if (!content.includes(sourceSnippet)) {
33954
33988
  return { success: false, error: "Source snippet not found in file" };
33955
33989
  }
33956
- const stylingAllowed = manifest.entries[change.cmsId]?.allowStyling !== false;
33990
+ const entry = manifest.entries[change.cmsId];
33991
+ const insideYaml = isYamlValueSource(content, sourceSnippet, change.sourcePath);
33992
+ const stylingAllowed = entry?.allowStyling !== false && !insideYaml;
33957
33993
  const newText = stylingAllowed ? htmlValue ?? newValue : newValue;
33958
33994
  const placeholderPattern = /\{\{cms:[^}]+\}\}/g;
33959
33995
  if (placeholderPattern.test(originalValue)) {
33960
- return applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue, newText);
33996
+ return applyTextChangeWithPlaceholders(insideYaml, content, sourceSnippet, originalValue, newText);
33961
33997
  }
33962
33998
  const resolvedNewText = resolveCmsPlaceholders(newText, manifest);
33963
33999
  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) };
34000
+ const write = (updatedSnippet2) => writeSnippet(content, sourceSnippet, updatedSnippet2, insideYaml, resolvedNewText);
34001
+ const snippetIsJavaScript = isJavaScriptSource(change.sourcePath) && (!!entry?.variableName || !looksLikeMarkup(sourceSnippet));
34002
+ if (snippetIsJavaScript) {
34003
+ const literalResult = tryJsStringLiteralChange(sourceSnippet, resolvedOriginal, resolvedNewText);
34004
+ if (literalResult !== null) {
34005
+ return write(literalResult);
34006
+ }
34007
+ }
34008
+ if (insideYaml) {
34009
+ const frontmatterResult = tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewText, true);
34010
+ if (frontmatterResult !== null) {
34011
+ return write(frontmatterResult);
33969
34012
  }
34013
+ }
34014
+ const updatedSnippet = sourceSnippet.replace(resolvedOriginal, escapeReplacement(resolvedNewText));
34015
+ if (updatedSnippet === sourceSnippet) {
33970
34016
  const brResult = tryBrNormalizedChange(sourceSnippet, resolvedOriginal, resolvedNewText);
33971
34017
  if (brResult !== null) {
33972
- return { success: true, content: content.replace(sourceSnippet, brResult) };
34018
+ return write(brResult);
34019
+ }
34020
+ if (snippetIsJavaScript) {
34021
+ const literalResult = tryJsStringLiteralChange(sourceSnippet, resolvedOriginal, resolvedNewText);
34022
+ if (literalResult !== null) {
34023
+ return write(literalResult);
34024
+ }
33973
34025
  }
33974
34026
  const matchedText = findTextInSnippet(sourceSnippet, resolvedOriginal);
33975
34027
  if (matchedText) {
33976
- const updatedWithEntity = sourceSnippet.replace(matchedText, resolvedNewText);
33977
- return { success: true, content: content.replace(sourceSnippet, updatedWithEntity) };
34028
+ const spliced = /<[^>]+>/.test(resolvedNewText) ? null : spliceIntoEntitySource(matchedText, resolvedOriginal, resolvedNewText);
34029
+ const replacement = spliced ?? (/<[^>]+>/.test(resolvedNewText) ? encodeNbspLike(resolvedNewText, matchedText) : encodeEntitiesLike(resolvedNewText, matchedText));
34030
+ const updatedWithEntity = sourceSnippet.replace(matchedText, escapeReplacement(replacement));
34031
+ return write(updatedWithEntity);
33978
34032
  }
33979
34033
  const innerMatch = sourceSnippet.match(/^(\s*<(\w+)\b[^>]*>)([\s\S]*)(<\/\2>\s*)$/);
33980
34034
  if (innerMatch) {
33981
34035
  const [, openTag, , innerContent, closeTag] = innerMatch;
33982
34036
  const textOnly = innerContent.replace(/<[^>]+>/g, "");
33983
34037
  if (textOnly === resolvedOriginal) {
33984
- return { success: true, content: content.replace(sourceSnippet, openTag + resolvedNewText + closeTag) };
34038
+ const isHtmlReplacement = /<[^>]+>/.test(resolvedNewText);
34039
+ if (isHtmlReplacement || !/<[^>]+>/.test(innerContent)) {
34040
+ return write(openTag + resolvedNewText + closeTag);
34041
+ }
34042
+ const splicedInner = spliceTextAcrossInlineMarkup(innerContent, resolvedOriginal, resolvedNewText);
34043
+ if (splicedInner !== null) {
34044
+ return write(openTag + splicedInner + closeTag);
34045
+ }
34046
+ return {
34047
+ success: false,
34048
+ error: "Cannot apply this edit without dropping the inline markup inside the element " + "\u2014 edit the styled part separately"
34049
+ };
33985
34050
  }
33986
34051
  }
33987
34052
  return {
@@ -33989,9 +34054,9 @@ function applyTextChange(content, change, manifest) {
33989
34054
  error: `Original text "${resolvedOriginal.substring(0, 50)}..." not found in source snippet`
33990
34055
  };
33991
34056
  }
33992
- return { success: true, content: content.replace(sourceSnippet, updatedSnippet) };
34057
+ return write(updatedSnippet);
33993
34058
  }
33994
- function applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue, newText) {
34059
+ function applyTextChangeWithPlaceholders(insideYaml, content, sourceSnippet, originalValue, newText) {
33995
34060
  const placeholderPattern = /\{\{cms:[^}]+\}\}/g;
33996
34061
  const originalParts = originalValue.split(placeholderPattern);
33997
34062
  const newParts = newText.split(placeholderPattern);
@@ -34011,7 +34076,7 @@ function applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue,
34011
34076
  if (matchedText !== oldPart) {
34012
34077
  newPart = encodeEntitiesLike(newPart, matchedText);
34013
34078
  }
34014
- updatedSnippet = updatedSnippet.replace(matchedText, newPart);
34079
+ updatedSnippet = updatedSnippet.replace(matchedText, escapeReplacement(newPart));
34015
34080
  anyChange = true;
34016
34081
  } else {
34017
34082
  return {
@@ -34023,27 +34088,192 @@ function applyTextChangeWithPlaceholders(content, sourceSnippet, originalValue,
34023
34088
  if (!anyChange) {
34024
34089
  return { success: false, error: "No text changes detected between original and new values" };
34025
34090
  }
34026
- return { success: true, content: content.replace(sourceSnippet, updatedSnippet) };
34091
+ return writeSnippet(content, sourceSnippet, updatedSnippet, insideYaml, newText);
34092
+ }
34093
+ function isJavaScriptSource(sourcePath) {
34094
+ return !!sourcePath && JAVASCRIPT_SOURCE.test(sourcePath);
34095
+ }
34096
+ function isYamlValueSource(content, sourceSnippet, sourcePath) {
34097
+ if (!sourcePath)
34098
+ return false;
34099
+ if (YAML_DOCUMENT_SOURCE.test(sourcePath))
34100
+ return true;
34101
+ if (!YAML_FRONTMATTER_SOURCE.test(sourcePath))
34102
+ return false;
34103
+ const block = frontmatterBlock(content);
34104
+ if (!block)
34105
+ return false;
34106
+ const snippetStart = content.indexOf(sourceSnippet);
34107
+ return snippetStart >= block.start && snippetStart + sourceSnippet.length <= block.end;
34108
+ }
34109
+ function frontmatterBlock(content) {
34110
+ const opening = /^(?:\uFEFF)?(?:[ \t]*\r?\n)*(---|\+\+\+)[ \t]*\r?\n/.exec(content);
34111
+ if (!opening || opening[1] !== "---")
34112
+ return null;
34113
+ const start = opening[0].length;
34114
+ const closing = /\n(?:---|\+\+\+)[ \t]*(\r?\n|$)/.exec(content.slice(start));
34115
+ return closing ? { start, end: start + closing.index } : null;
34116
+ }
34117
+ function looksLikeMarkup(text) {
34118
+ return /<[a-zA-Z/!]/.test(text);
34119
+ }
34120
+ function decodeJsString(raw) {
34121
+ let out = "";
34122
+ for (let i2 = 0;i2 < raw.length; i2++) {
34123
+ const char = raw[i2];
34124
+ if (char !== "\\") {
34125
+ out += char;
34126
+ continue;
34127
+ }
34128
+ const next = raw[++i2];
34129
+ if (next === undefined)
34130
+ break;
34131
+ if (next === "u") {
34132
+ if (raw[i2 + 1] === "{") {
34133
+ const close = raw.indexOf("}", i2 + 2);
34134
+ if (close !== -1) {
34135
+ const code2 = parseInt(raw.slice(i2 + 2, close), 16);
34136
+ if (!Number.isNaN(code2)) {
34137
+ out += String.fromCodePoint(code2);
34138
+ i2 = close;
34139
+ continue;
34140
+ }
34141
+ }
34142
+ }
34143
+ const code = parseInt(raw.slice(i2 + 1, i2 + 5), 16);
34144
+ if (!Number.isNaN(code)) {
34145
+ out += String.fromCharCode(code);
34146
+ i2 += 4;
34147
+ continue;
34148
+ }
34149
+ }
34150
+ if (next === "x") {
34151
+ const code = parseInt(raw.slice(i2 + 1, i2 + 3), 16);
34152
+ if (!Number.isNaN(code)) {
34153
+ out += String.fromCharCode(code);
34154
+ i2 += 2;
34155
+ continue;
34156
+ }
34157
+ }
34158
+ if (next === `
34159
+ `)
34160
+ continue;
34161
+ out += JS_ESCAPES[next] ?? next;
34162
+ }
34163
+ return out;
34164
+ }
34165
+ function encodeJsString(value, quote, escapeNbsp) {
34166
+ let out = value.replace(/\\/g, "\\\\").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replaceAll(quote, `\\${quote}`);
34167
+ if (escapeNbsp)
34168
+ out = out.replace(/\u00A0/g, "\\u00A0");
34169
+ if (quote === "`")
34170
+ out = out.replace(/\$\{/g, "\\${");
34171
+ return quote + out + quote;
34172
+ }
34173
+ function findStringLiterals(snippet) {
34174
+ const literals = [];
34175
+ for (let i2 = 0;i2 < snippet.length; i2++) {
34176
+ const quote = snippet[i2];
34177
+ if (quote !== "'" && quote !== '"' && quote !== "`")
34178
+ continue;
34179
+ let j = i2 + 1;
34180
+ while (j < snippet.length) {
34181
+ if (snippet[j] === "\\")
34182
+ j += 2;
34183
+ else if (snippet[j] === quote)
34184
+ break;
34185
+ else
34186
+ j++;
34187
+ }
34188
+ if (j >= snippet.length)
34189
+ break;
34190
+ const raw = snippet.slice(i2 + 1, j);
34191
+ literals.push({ start: i2, end: j + 1, quote, value: decodeJsString(raw) });
34192
+ i2 = j;
34193
+ }
34194
+ return literals;
34195
+ }
34196
+ function tryJsStringLiteralChange(sourceSnippet, resolvedOriginal, resolvedNewText) {
34197
+ const literals = findStringLiterals(sourceSnippet);
34198
+ if (literals.length === 0)
34199
+ return null;
34200
+ const splice = (start, end, replacement) => sourceSnippet.slice(0, start) + replacement + sourceSnippet.slice(end);
34201
+ for (const literal of literals) {
34202
+ if (literal.value !== resolvedOriginal)
34203
+ continue;
34204
+ const escapeNbsp = /\\u00[aA]0/.test(sourceSnippet.slice(literal.start, literal.end));
34205
+ return splice(literal.start, literal.end, encodeJsString(resolvedNewText, literal.quote, escapeNbsp));
34206
+ }
34207
+ for (let first = 0;first < literals.length; first++) {
34208
+ let combined = "";
34209
+ for (let last = first;last < literals.length; last++) {
34210
+ if (last > first) {
34211
+ const between = sourceSnippet.slice(literals[last - 1].end, literals[last].start);
34212
+ if (!/^\s*\+\s*$/.test(between))
34213
+ break;
34214
+ }
34215
+ combined += literals[last].value;
34216
+ if (last === first || combined !== resolvedOriginal)
34217
+ continue;
34218
+ const start = literals[first].start;
34219
+ const end = literals[last].end;
34220
+ const escapeNbsp = /\\u00[aA]0/.test(sourceSnippet.slice(start, end));
34221
+ return splice(start, end, encodeJsString(resolvedNewText, literals[first].quote, escapeNbsp));
34222
+ }
34223
+ }
34224
+ return null;
34225
+ }
34226
+ function pickRunAtSeam(tokens, earlier, later) {
34227
+ const seam = tokens.slice(earlier.index + 1, later.index).find((token) => token.startsWith("<"));
34228
+ return seam?.startsWith("</") ? later : earlier;
34229
+ }
34230
+ function spliceTextAcrossInlineMarkup(innerContent, originalText, newText) {
34231
+ const tokens = innerContent.split(/(<[^>]+>)/);
34232
+ const isTag = (token2) => token2.startsWith("<");
34233
+ const runs = [];
34234
+ let plain = "";
34235
+ for (let i2 = 0;i2 < tokens.length; i2++) {
34236
+ const token2 = tokens[i2];
34237
+ if (isTag(token2) || !token2)
34238
+ continue;
34239
+ runs.push({ index: i2, start: plain.length, end: plain.length + token2.length });
34240
+ plain += token2;
34241
+ }
34242
+ if (plain !== originalText)
34243
+ return null;
34244
+ let start = 0;
34245
+ while (start < originalText.length && start < newText.length && originalText[start] === newText[start])
34246
+ start++;
34247
+ let tail = 0;
34248
+ while (tail < originalText.length - start && tail < newText.length - start && originalText[originalText.length - 1 - tail] === newText[newText.length - 1 - tail])
34249
+ tail++;
34250
+ const originalEnd = originalText.length - tail;
34251
+ const newEnd = newText.length - tail;
34252
+ if (start === originalEnd && start === newEnd)
34253
+ return innerContent;
34254
+ if (start === originalEnd) {
34255
+ const inserted = newText.slice(start, newEnd);
34256
+ if (start === 0)
34257
+ return inserted + innerContent;
34258
+ if (start === originalText.length)
34259
+ return innerContent + inserted;
34260
+ }
34261
+ const fitting = runs.filter((r) => start >= r.start && originalEnd <= r.end);
34262
+ const run = start === originalEnd && fitting.length > 1 ? pickRunAtSeam(tokens, fitting[0], fitting[fitting.length - 1]) : fitting[0];
34263
+ if (!run)
34264
+ return null;
34265
+ const token = tokens[run.index];
34266
+ tokens[run.index] = token.slice(0, start - run.start) + newText.slice(start, newEnd) + token.slice(originalEnd - run.start);
34267
+ return tokens.join("");
34027
34268
  }
34028
34269
  function findTextInSnippet(snippet, decodedText) {
34029
34270
  if (snippet.includes(decodedText)) {
34030
34271
  return decodedText;
34031
34272
  }
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})`);
34273
+ let pattern = "";
34274
+ for (const char of decodedText) {
34275
+ const alternatives = ENTITY_ALTERNATIVES.get(char);
34276
+ pattern += alternatives ? `(?:${[char, ...alternatives].map(escapeRegex).join("|")})` : escapeRegex(char);
34047
34277
  }
34048
34278
  const regex = new RegExp(pattern);
34049
34279
  const match = snippet.match(regex);
@@ -34055,11 +34285,70 @@ function findTextInSnippet(snippet, decodedText) {
34055
34285
  const brMatch = snippet.match(brRegex);
34056
34286
  return brMatch && brMatch[0] !== decodedText ? brMatch[0] : null;
34057
34287
  }
34288
+ function sameCharacter(a, b2) {
34289
+ if (a === b2)
34290
+ return true;
34291
+ const spaceLike = (c) => c === " " || c === "\xA0";
34292
+ return spaceLike(a) && spaceLike(b2);
34293
+ }
34294
+ function decodeEntitiesWithOffsets(source) {
34295
+ const entityPattern = /&(?:#x([0-9a-f]+)|#(\d+)|([a-z]+));/iy;
34296
+ let text = "";
34297
+ const offsets = [];
34298
+ let i2 = 0;
34299
+ while (i2 < source.length) {
34300
+ entityPattern.lastIndex = i2;
34301
+ const match = entityPattern.exec(source);
34302
+ const decoded = match ? decodeEntityMatch(match) : undefined;
34303
+ offsets.push(i2);
34304
+ if (decoded !== undefined) {
34305
+ text += decoded;
34306
+ i2 += match[0].length;
34307
+ } else {
34308
+ text += source[i2];
34309
+ i2++;
34310
+ }
34311
+ }
34312
+ offsets.push(source.length);
34313
+ return { text, offsets };
34314
+ }
34315
+ function decodeEntityMatch(match) {
34316
+ const [, hex, dec, name] = match;
34317
+ if (hex)
34318
+ return String.fromCodePoint(parseInt(hex, 16));
34319
+ if (dec)
34320
+ return String.fromCodePoint(parseInt(dec, 10));
34321
+ return name ? NAMED_ENTITIES[name.toLowerCase()] : undefined;
34322
+ }
34323
+ function spliceIntoEntitySource(source, original, replacement) {
34324
+ const { text: decoded, offsets } = decodeEntitiesWithOffsets(source);
34325
+ if (decoded.length !== original.length)
34326
+ return null;
34327
+ for (let i2 = 0;i2 < decoded.length; i2++) {
34328
+ if (!sameCharacter(decoded[i2], original[i2]))
34329
+ return null;
34330
+ }
34331
+ let start = 0;
34332
+ while (start < original.length && start < replacement.length && sameCharacter(original[start], replacement[start])) {
34333
+ start++;
34334
+ }
34335
+ let tail = 0;
34336
+ while (tail < original.length - start && tail < replacement.length - start && sameCharacter(original[original.length - 1 - tail], replacement[replacement.length - 1 - tail]))
34337
+ tail++;
34338
+ const originalEnd = original.length - tail;
34339
+ const newEnd = replacement.length - tail;
34340
+ if (start === originalEnd && start === newEnd)
34341
+ return source;
34342
+ const head = source.slice(0, offsets[start]);
34343
+ const rest = source.slice(offsets[originalEnd]);
34344
+ return head + encodeEntitiesLike(replacement.slice(start, newEnd), source) + rest;
34345
+ }
34058
34346
  function encodeEntitiesLike(text, reference) {
34059
34347
  let result = text;
34060
34348
  if (reference.includes("&amp;")) {
34061
34349
  result = result.replace(/&/g, "&amp;");
34062
34350
  }
34351
+ result = encodeNbspLike(result, reference);
34063
34352
  if (reference.includes("&lt;")) {
34064
34353
  result = result.replace(/</g, "&lt;");
34065
34354
  }
@@ -34074,6 +34363,13 @@ function encodeEntitiesLike(text, reference) {
34074
34363
  }
34075
34364
  return result;
34076
34365
  }
34366
+ function encodeNbspLike(text, reference) {
34367
+ if (reference.includes("&nbsp;"))
34368
+ return text.replace(/\u00A0/g, "&nbsp;");
34369
+ if (reference.includes("&#160;"))
34370
+ return text.replace(/\u00A0/g, "&#160;");
34371
+ return text;
34372
+ }
34077
34373
  function resolveCmsPlaceholders(text, manifest) {
34078
34374
  const placeholderPattern = /\{\{cms:([^}]+)\}\}/g;
34079
34375
  return text.replace(placeholderPattern, (match, cmsId) => {
@@ -34135,24 +34431,164 @@ function getVisibleText(html) {
34135
34431
  `);
34136
34432
  return text.trim();
34137
34433
  }
34138
- function tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewText) {
34139
- const keyMatch = sourceSnippet.match(/^(\s*([\w][\w-]*):\s*)/);
34140
- if (!keyMatch)
34434
+ function findYamlTargets(sourceSnippet, allowQuotedKey) {
34435
+ let doc;
34436
+ try {
34437
+ doc = import_yaml6.parseDocument(sourceSnippet);
34438
+ if (doc.errors.length > 0)
34439
+ return [];
34440
+ } catch {
34441
+ return [];
34442
+ }
34443
+ const targets = [];
34444
+ import_yaml6.visit(doc, {
34445
+ Scalar(key, node, path13) {
34446
+ if (key === "key" || !node.range)
34447
+ return;
34448
+ if (typeof node.value !== "string" && typeof node.value !== "number")
34449
+ return;
34450
+ const parent = path13[path13.length - 1];
34451
+ const pair = key === "value" && parent && "key" in parent ? parent : null;
34452
+ if (!allowQuotedKey && (pair ? pair.key?.type !== "PLAIN" : node.type !== "PLAIN"))
34453
+ return;
34454
+ const ownerStart = pair ? pair.key.range[0] : dashBefore(sourceSnippet, node.range[0]);
34455
+ const [start, end] = node.range;
34456
+ const value = node.value;
34457
+ const trailingCr = typeof value === "string" && value.endsWith("\r") && sourceSnippet[end - 1] === "\r";
34458
+ targets.push({
34459
+ start,
34460
+ end: trailingCr ? end - 1 : end,
34461
+ value: trailingCr ? value.slice(0, -1) : value,
34462
+ key: pair ? String(pair.key.value) : null,
34463
+ ownerColumn: columnOf(sourceSnippet, ownerStart)
34464
+ });
34465
+ }
34466
+ });
34467
+ return targets;
34468
+ }
34469
+ function dashBefore(sourceSnippet, valueStart) {
34470
+ const dash = sourceSnippet.lastIndexOf("-", valueStart);
34471
+ return dash < 0 ? valueStart : dash;
34472
+ }
34473
+ function columnOf(sourceSnippet, offset) {
34474
+ return offset - (sourceSnippet.lastIndexOf(`
34475
+ `, offset - 1) + 1);
34476
+ }
34477
+ function tryYamlValueReplacement(sourceSnippet, resolvedOriginal, resolvedNewText, allowQuotedKey = false) {
34478
+ const matches = findYamlTargets(sourceSnippet, allowQuotedKey).filter((candidate) => String(candidate.value) === resolvedOriginal);
34479
+ if (matches.length !== 1)
34480
+ return null;
34481
+ const target = matches[0];
34482
+ const fieldIsTyped = resolvesAsNonString(sourceSnippet.slice(target.start, target.end));
34483
+ const replacement = typeof target.value === "number" && YAML_PLAIN_NUMBER.test(resolvedNewText) ? resolvedNewText : serializeYamlValue(resolvedNewText, target.key, target.ownerColumn, fieldIsTyped);
34484
+ if (replacement === null)
34141
34485
  return null;
34486
+ const updated = sourceSnippet.slice(0, target.start) + replacement + sourceSnippet.slice(target.end);
34487
+ return yamlValueRoundTrips(updated, resolvedNewText) ? updated : null;
34488
+ }
34489
+ function yamlValueRoundTrips(updatedSnippet, expected) {
34490
+ return findYamlTargets(normalizeCr(updatedSnippet), true).some((target) => scalarSays(target.value, expected));
34491
+ }
34492
+ function scalarSays(actual, expected) {
34493
+ if (String(actual) === expected)
34494
+ return true;
34495
+ if (typeof actual !== "number")
34496
+ return false;
34497
+ const parsed = tryParseYaml(expected);
34498
+ return typeof parsed === "number" && parsed === actual;
34499
+ }
34500
+ function writeSnippet(content, sourceSnippet, updatedSnippet, insideYaml, expected) {
34501
+ if (insideYaml && !yamlSnippetSurvives(sourceSnippet, updatedSnippet, expected)) {
34502
+ return {
34503
+ success: false,
34504
+ error: `"${expected.substring(0, 50)}" cannot be written into this frontmatter field without breaking it`
34505
+ };
34506
+ }
34507
+ return { success: true, content: content.replace(sourceSnippet, escapeReplacement(updatedSnippet)) };
34508
+ }
34509
+ function yamlSnippetSurvives(sourceSnippet, updatedSnippet, expected) {
34510
+ const before = yamlScalars(sourceSnippet);
34511
+ const after = yamlScalars(updatedSnippet);
34512
+ if (!before || !after || before.length !== after.length)
34513
+ return false;
34514
+ let changed = 0;
34515
+ for (let i2 = 0;i2 < before.length; i2++) {
34516
+ if (before[i2].owner !== after[i2].owner)
34517
+ return false;
34518
+ const wrote = after[i2].value;
34519
+ if (String(before[i2].value) === String(wrote))
34520
+ continue;
34521
+ changed++;
34522
+ const saysIt = expected === "" ? wrote === "" : scalarSays(wrote, expected) || String(wrote).includes(expected);
34523
+ if (!saysIt)
34524
+ return false;
34525
+ }
34526
+ return changed > 0;
34527
+ }
34528
+ function yamlScalars(snippet) {
34142
34529
  try {
34143
- const parsed = import_yaml6.parse(sourceSnippet);
34144
- if (parsed == null || typeof parsed !== "object")
34145
- return null;
34146
- const value = parsed[keyMatch[2]];
34147
- if (typeof value !== "string" && typeof value !== "number")
34530
+ const doc = import_yaml6.parseDocument(normalizeCr(snippet));
34531
+ if (doc.errors.length > 0)
34148
34532
  return null;
34149
- if (String(value) !== resolvedOriginal)
34533
+ const scalars = [];
34534
+ import_yaml6.visit(doc, {
34535
+ Scalar(key, node) {
34536
+ scalars.push({ owner: String(key), value: node.value });
34537
+ }
34538
+ });
34539
+ return scalars;
34540
+ } catch {
34541
+ return null;
34542
+ }
34543
+ }
34544
+ function normalizeCr(snippet) {
34545
+ return snippet.replace(/\r(?=\n|$)/g, "");
34546
+ }
34547
+ function serializeYamlValue(value, key, ownerColumn, fieldIsTyped = false) {
34548
+ let body = renderYamlValue(value, key, undefined);
34549
+ if (!fieldIsTyped && body !== null && !body.includes(`
34550
+ `) && resolvesAsNonString(body)) {
34551
+ body = renderYamlValue(value, key, "QUOTE_DOUBLE");
34552
+ }
34553
+ if (body === null || !body.includes(`
34554
+ `))
34555
+ return body;
34556
+ const pad = " ".repeat(ownerColumn);
34557
+ const [header, ...rest] = body.split(`
34558
+ `);
34559
+ return [header, ...rest.map((line) => line === "" ? line : pad + line)].join(`
34560
+ `);
34561
+ }
34562
+ function renderYamlValue(value, key, defaultStringType) {
34563
+ const options = { lineWidth: 0, ...defaultStringType ? { defaultStringType, defaultKeyType: "PLAIN" } : {} };
34564
+ const rendered = key === null ? import_yaml6.stringify([value], options) : import_yaml6.stringify({ [key]: value }, options);
34565
+ let valueStart;
34566
+ try {
34567
+ const doc = import_yaml6.parseDocument(rendered);
34568
+ const first = doc.contents?.items?.[0];
34569
+ const node = key === null ? first : first?.value;
34570
+ if (!node?.range)
34150
34571
  return null;
34572
+ valueStart = node.range[0];
34151
34573
  } catch {
34152
34574
  return null;
34153
34575
  }
34154
- const serialized = import_yaml6.stringify(resolvedNewText, { lineWidth: 0 }).trimEnd();
34155
- return `${keyMatch[1]}${serialized}`;
34576
+ const text = rendered.slice(valueStart);
34577
+ return /^[|>]\d*\+/.test(text) ? text : text.replace(/\n$/, "");
34578
+ }
34579
+ function resolvesAsNonString(scalar) {
34580
+ try {
34581
+ return typeof import_yaml6.parse(scalar, { version: "1.1" }) !== "string";
34582
+ } catch {
34583
+ return false;
34584
+ }
34585
+ }
34586
+ function tryParseYaml(text) {
34587
+ try {
34588
+ return import_yaml6.parse(text);
34589
+ } catch {
34590
+ return;
34591
+ }
34156
34592
  }
34157
34593
  function tryDataFileValueReplacement(content, sourceSnippet, originalValue, newValue, sourceLine) {
34158
34594
  const doubleQuoted = `"${originalValue}"`;
@@ -34168,7 +34604,7 @@ function tryDataFileValueReplacement(content, sourceSnippet, originalValue, newV
34168
34604
  } else {
34169
34605
  return null;
34170
34606
  }
34171
- const updatedSnippet = sourceSnippet.replace(quotedOriginal, quotedNew);
34607
+ const updatedSnippet = sourceSnippet.replace(quotedOriginal, escapeReplacement(quotedNew));
34172
34608
  if (updatedSnippet === sourceSnippet)
34173
34609
  return null;
34174
34610
  let searchStart = 0;
@@ -34232,14 +34668,14 @@ function tryBrNormalizedChange(sourceSnippet, resolvedOriginal, resolvedNewText)
34232
34668
  const leadingWs = raw.slice(0, raw.indexOf(trimmed));
34233
34669
  const trailingWs = raw.slice(raw.indexOf(trimmed) + trimmed.length);
34234
34670
  const newRaw = leadingWs + newSegment + trailingWs;
34235
- result = result.replace(raw, newRaw);
34671
+ result = result.replace(raw, escapeReplacement(newRaw));
34236
34672
  break;
34237
34673
  }
34238
34674
  }
34239
34675
  }
34240
34676
  return result !== sourceSnippet ? result : null;
34241
34677
  }
34242
- var import_node_html_parser, import_yaml6, ASSET_IMPORT_EXT_RE, REMOTE_FETCH_TIMEOUT_MS = 15000, REMOTE_FETCH_MAX_BYTES, QUOTED_LITERAL_DELIMITERS;
34678
+ 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
34679
  var init_source_writer = __esm(() => {
34244
34680
  init_astro_image_paths();
34245
34681
  init_config();
@@ -34250,6 +34686,37 @@ var init_source_writer = __esm(() => {
34250
34686
  ASSET_IMPORT_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|ico|bmp|tiff?)$/i;
34251
34687
  REMOTE_FETCH_MAX_BYTES = 50 * 1024 * 1024;
34252
34688
  QUOTED_LITERAL_DELIMITERS = [`'`, `"`, "`"];
34689
+ JAVASCRIPT_SOURCE = /\.(astro|[cm]?[jt]sx?)$/i;
34690
+ YAML_DOCUMENT_SOURCE = /\.ya?ml$/i;
34691
+ YAML_FRONTMATTER_SOURCE = /\.(md|mdx|markdown)$/i;
34692
+ JS_ESCAPES = {
34693
+ n: `
34694
+ `,
34695
+ r: "\r",
34696
+ t: "\t",
34697
+ b: "\b",
34698
+ f: "\f",
34699
+ v: "\v",
34700
+ 0: "\x00"
34701
+ };
34702
+ ENTITY_ALTERNATIVES = new Map([
34703
+ ["&", ["&amp;"]],
34704
+ ["\xA0", ["&nbsp;", "&#160;", " "]],
34705
+ [" ", ["&nbsp;", "&#160;"]],
34706
+ ["<", ["&lt;"]],
34707
+ [">", ["&gt;"]],
34708
+ ['"', ["&quot;"]],
34709
+ ["'", ["&#39;", "&apos;"]]
34710
+ ]);
34711
+ NAMED_ENTITIES = {
34712
+ amp: "&",
34713
+ lt: "<",
34714
+ gt: ">",
34715
+ quot: '"',
34716
+ apos: "'",
34717
+ nbsp: "\xA0"
34718
+ };
34719
+ YAML_PLAIN_NUMBER = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
34253
34720
  });
34254
34721
 
34255
34722
  // ../cms/src/handlers/api-routes.ts