@nuasite/cli 0.34.0 → 0.36.0

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
@@ -24558,18 +24558,30 @@ async function pickAstroImageTarget(args) {
24558
24558
  if (await isFreeOrMatching(baseAbs, args.compareBuffer)) {
24559
24559
  return { absPath: baseAbs, relPath: `./${baseName}` };
24560
24560
  }
24561
- const hash = shortContentHash(args.compareBuffer);
24562
- const ext = path8.extname(safeFilename);
24561
+ const candidateAbs = await pickHashedSibling(entryDir, baseName, args.compareBuffer);
24562
+ return { absPath: candidateAbs, relPath: `./${path8.basename(candidateAbs)}` };
24563
+ }
24564
+ async function pickSiblingTarget(dir, filename, buf) {
24565
+ const safe = path8.basename(filename);
24566
+ if (!safe || safe === "." || safe === "..") {
24567
+ throw new Error(`Invalid filename: ${filename}`);
24568
+ }
24569
+ const baseAbs = path8.join(dir, safe);
24570
+ if (await isFreeOrMatching(baseAbs, buf))
24571
+ return baseAbs;
24572
+ return pickHashedSibling(dir, safe, buf);
24573
+ }
24574
+ async function pickHashedSibling(dir, baseName, buf) {
24575
+ const hash = shortContentHash(buf);
24576
+ const ext = path8.extname(baseName);
24563
24577
  const stem = path8.basename(baseName, ext);
24564
24578
  for (let attempt = 0;attempt < 5; attempt++) {
24565
24579
  const suffix = attempt === 0 ? hash : `${hash}-${attempt}`;
24566
- const candidateName = `${stem}-${suffix}${ext}`;
24567
- const candidateAbs = path8.join(entryDir, candidateName);
24568
- if (await isFreeOrMatching(candidateAbs, args.compareBuffer)) {
24569
- return { absPath: candidateAbs, relPath: `./${candidateName}` };
24570
- }
24580
+ const candidateAbs = path8.join(dir, `${stem}-${suffix}${ext}`);
24581
+ if (await isFreeOrMatching(candidateAbs, buf))
24582
+ return candidateAbs;
24571
24583
  }
24572
- throw new Error(`Could not pick a unique filename for ${safeFilename} in ${entryDir}`);
24584
+ throw new Error(`Could not pick a unique filename for ${baseName} in ${dir}`);
24573
24585
  }
24574
24586
  async function isFreeOrMatching(absPath, compareBuffer) {
24575
24587
  try {
@@ -30939,9 +30951,14 @@ function extractAstroImageOriginalUrl(src) {
30939
30951
  const url = new URL(src, "http://localhost");
30940
30952
  if (url.pathname === "/_image" || url.pathname.startsWith("/_image/")) {
30941
30953
  const href = url.searchParams.get("href");
30942
- if (href && href !== src)
30954
+ if (href)
30943
30955
  return href;
30944
30956
  }
30957
+ if (url.pathname.startsWith("/@image/")) {
30958
+ const f = url.searchParams.get("f");
30959
+ if (f)
30960
+ return f;
30961
+ }
30945
30962
  } catch {}
30946
30963
  return;
30947
30964
  }
@@ -30960,6 +30977,7 @@ var init_snippet_utils = __esm(() => {
30960
30977
 
30961
30978
  // ../cms/src/handlers/source-writer.ts
30962
30979
  import fs14 from "fs/promises";
30980
+ import path13 from "path";
30963
30981
  async function handleUpdate(request, manifestWriter) {
30964
30982
  const { changes, meta } = request;
30965
30983
  const errors = [];
@@ -30990,11 +31008,15 @@ async function handleUpdate(request, manifestWriter) {
30990
31008
  const release = await acquireFileLock(fullPath);
30991
31009
  try {
30992
31010
  const currentContent = await fs14.readFile(fullPath, "utf-8");
30993
- const { newContent, appliedCount, failedChanges } = applyChanges(currentContent, fileChanges, manifest);
31011
+ const { newContent, appliedCount, failedChanges, fileOps } = await applyChanges(currentContent, fileChanges, manifest, fullPath, meta.url);
30994
31012
  if (failedChanges.length > 0) {
30995
31013
  errors.push(...failedChanges);
30996
31014
  }
30997
31015
  if (appliedCount > 0 && newContent !== currentContent) {
31016
+ for (const op of fileOps) {
31017
+ await fs14.mkdir(path13.dirname(op.target), { recursive: true });
31018
+ await fs14.writeFile(op.target, op.bytes);
31019
+ }
30998
31020
  await fs14.writeFile(fullPath, newContent, "utf-8");
30999
31021
  updated += appliedCount;
31000
31022
  }
@@ -31011,16 +31033,19 @@ async function handleUpdate(request, manifestWriter) {
31011
31033
  errors: errors.length > 0 ? errors : undefined
31012
31034
  };
31013
31035
  }
31014
- function applyChanges(content, changes, manifest) {
31036
+ async function applyChanges(content, changes, manifest, absFilePath, originUrl) {
31015
31037
  let newContent = content;
31016
31038
  let appliedCount = 0;
31017
31039
  const failedChanges = [];
31040
+ const fileOps = [];
31018
31041
  const sortedChanges = [...changes].sort((a, b2) => (b2.sourceLine ?? 0) - (a.sourceLine ?? 0));
31019
31042
  for (const change of sortedChanges) {
31020
31043
  if (change.imageChange) {
31021
- const result2 = applyImageChange(newContent, change);
31044
+ const result2 = await applyImageChange(newContent, change, absFilePath, originUrl);
31022
31045
  if (result2.success) {
31023
31046
  newContent = result2.content;
31047
+ if (result2.fileOp)
31048
+ fileOps.push(result2.fileOp);
31024
31049
  appliedCount++;
31025
31050
  } else {
31026
31051
  failedChanges.push({ cmsId: change.cmsId, error: result2.error });
@@ -31054,9 +31079,9 @@ function applyChanges(content, changes, manifest) {
31054
31079
  failedChanges.push({ cmsId: change.cmsId, error: result.error });
31055
31080
  }
31056
31081
  }
31057
- return { newContent, appliedCount, failedChanges };
31082
+ return { newContent, appliedCount, failedChanges, fileOps };
31058
31083
  }
31059
- function applyImageChange(content, change) {
31084
+ async function applyImageChange(content, change, absFilePath, originUrl) {
31060
31085
  const { newSrc, newAlt } = change.imageChange;
31061
31086
  const originalSrc = change.originalValue;
31062
31087
  if (!originalSrc) {
@@ -31148,6 +31173,7 @@ function applyImageChange(content, change) {
31148
31173
  }
31149
31174
  }
31150
31175
  }
31176
+ let pendingFileOp;
31151
31177
  if (replacedIndex < 0 && change.sourceLine > 0) {
31152
31178
  const lines = newContent.split(`
31153
31179
  `);
@@ -31161,7 +31187,20 @@ function applyImageChange(content, change) {
31161
31187
  const exprMatch = findExpressionSrcAttribute(regionText);
31162
31188
  if (exprMatch) {
31163
31189
  const exprContent = regionText.slice(exprMatch.index + regionText.slice(exprMatch.index).indexOf("{") + 1, exprMatch.index + exprMatch.length - 1).trim();
31164
- return { success: false, error: `Image src uses a dynamic expression (src={${exprContent}}) \u2014 edit the data source directly` };
31190
+ const importInfo = /^\w+$/.test(exprContent) ? findFrontmatterAssetImport(content, exprContent) : null;
31191
+ if (!importInfo) {
31192
+ return { success: false, error: `Image src uses a dynamic expression (src={${exprContent}}) \u2014 edit the data source directly` };
31193
+ }
31194
+ const rewrite = absFilePath ? await tryRewriteAssetImport(content, importInfo, newSrc, absFilePath, originUrl) : null;
31195
+ if (rewrite) {
31196
+ newContent = rewrite.content;
31197
+ pendingFileOp = rewrite.fileOp;
31198
+ replacedIndex = rewrite.importSourceIndex;
31199
+ } else {
31200
+ const literalResult = inlineJsxLiteralReplace(newContent, lines, regionStart, exprMatch, newSrc);
31201
+ newContent = literalResult.content;
31202
+ replacedIndex = literalResult.replacedIndex;
31203
+ }
31165
31204
  }
31166
31205
  }
31167
31206
  }
@@ -31201,7 +31240,101 @@ function applyImageChange(content, change) {
31201
31240
  newContent = newContent.slice(0, altAbsoluteIndex) + `alt=${altQuote}${escapedAlt}${altQuote}` + newContent.slice(altAbsoluteIndex + altLength);
31202
31241
  }
31203
31242
  }
31204
- return { success: true, content: newContent };
31243
+ return { success: true, content: newContent, fileOp: pendingFileOp };
31244
+ }
31245
+ function findFrontmatterAssetImport(content, varName) {
31246
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
31247
+ if (!fmMatch)
31248
+ return null;
31249
+ const fmStart = fmMatch[0].indexOf(fmMatch[1]);
31250
+ const importRe = /^\s*import\s+(?!type\b)([\s\S]+?)\s+from\s+(['"])([^'"]+)\2/gm;
31251
+ let m;
31252
+ while ((m = importRe.exec(fmMatch[1])) !== null) {
31253
+ const source = m[3];
31254
+ if (!source.startsWith(".") || !ASSET_IMPORT_EXT_RE.test(source))
31255
+ continue;
31256
+ const tokens = m[1].replace(/[{}]/g, ",").split(",").map((s) => s.trim()).filter((t) => t && !t.startsWith("type "));
31257
+ const matches = tokens.some((tok) => {
31258
+ const aliasMatch = tok.match(/^\S+\s+as\s+(\S+)$/);
31259
+ return (aliasMatch ? aliasMatch[1] : tok) === varName;
31260
+ });
31261
+ if (!matches)
31262
+ continue;
31263
+ const matchStart = fmStart + m.index;
31264
+ const sourceStart = matchStart + m[0].indexOf(m[2]) + 1;
31265
+ return { localName: varName, source, sourceStart, sourceEnd: sourceStart + source.length };
31266
+ }
31267
+ return null;
31268
+ }
31269
+ function inlineJsxLiteralReplace(content, lines, regionStart, exprMatch, newSrc) {
31270
+ let regionStartOffset = 0;
31271
+ for (let i = 0;i < regionStart; i++)
31272
+ regionStartOffset += lines[i].length + 1;
31273
+ const absIndex = regionStartOffset + exprMatch.index;
31274
+ return {
31275
+ content: content.slice(0, absIndex) + `src="${escapeReplacement(newSrc)}"` + content.slice(absIndex + exprMatch.length),
31276
+ replacedIndex: absIndex
31277
+ };
31278
+ }
31279
+ async function tryRewriteAssetImport(content, importInfo, newSrc, absFilePath, originUrl) {
31280
+ const resolved = await resolveNewSrcBytes(newSrc, originUrl);
31281
+ if (!resolved)
31282
+ return null;
31283
+ const originalAssetAbs = path13.resolve(path13.dirname(absFilePath), importInfo.source);
31284
+ const targetAbs = await pickSiblingTarget(path13.dirname(originalAssetAbs), resolved.filename, resolved.bytes);
31285
+ const newRelImport = relativeImportPath(absFilePath, targetAbs);
31286
+ const newContent = content.slice(0, importInfo.sourceStart) + newRelImport + content.slice(importInfo.sourceEnd);
31287
+ return {
31288
+ content: newContent,
31289
+ fileOp: { target: targetAbs, bytes: resolved.bytes },
31290
+ importSourceIndex: importInfo.sourceStart
31291
+ };
31292
+ }
31293
+ async function resolveNewSrcBytes(newSrc, originUrl) {
31294
+ const filenameFromPath = (p) => path13.basename(p.split("?")[0] ?? p);
31295
+ const diskPath = newSrc.startsWith("/src/") ? path13.join(getProjectRoot(), newSrc.slice(1)) : newSrc.startsWith("/") && !newSrc.startsWith("//") ? path13.join(getProjectRoot(), "public", newSrc.replace(/^\/+/, "")) : null;
31296
+ if (diskPath) {
31297
+ try {
31298
+ return { bytes: await fs14.readFile(diskPath), filename: filenameFromPath(newSrc) };
31299
+ } catch {}
31300
+ }
31301
+ try {
31302
+ const isAbsolute = /^https?:\/\//.test(newSrc);
31303
+ if (!isAbsolute && !originUrl)
31304
+ return null;
31305
+ const fetchUrl = isAbsolute ? newSrc : new URL(newSrc, originUrl).toString();
31306
+ const res = await fetch(fetchUrl, { signal: AbortSignal.timeout(REMOTE_FETCH_TIMEOUT_MS) });
31307
+ if (!res.ok)
31308
+ return null;
31309
+ const bytes = await readBoundedBody(res, REMOTE_FETCH_MAX_BYTES);
31310
+ if (!bytes)
31311
+ return null;
31312
+ return { bytes, filename: filenameFromPath(new URL(fetchUrl).pathname) };
31313
+ } catch {
31314
+ return null;
31315
+ }
31316
+ }
31317
+ async function readBoundedBody(res, maxBytes) {
31318
+ const declared = Number(res.headers.get("content-length"));
31319
+ if (declared > maxBytes)
31320
+ return null;
31321
+ if (!res.body)
31322
+ return null;
31323
+ const reader = res.body.getReader();
31324
+ const chunks = [];
31325
+ let total = 0;
31326
+ while (true) {
31327
+ const { done, value } = await reader.read();
31328
+ if (done)
31329
+ break;
31330
+ total += value.byteLength;
31331
+ if (total > maxBytes) {
31332
+ await reader.cancel();
31333
+ return null;
31334
+ }
31335
+ chunks.push(value);
31336
+ }
31337
+ return Buffer.concat(chunks, total);
31205
31338
  }
31206
31339
  function applyColorChange(content, change) {
31207
31340
  const { oldClass, newClass } = change.styleChange;
@@ -31693,18 +31826,21 @@ function tryBrNormalizedChange(sourceSnippet, resolvedOriginal, resolvedNewText)
31693
31826
  }
31694
31827
  return result !== sourceSnippet ? result : null;
31695
31828
  }
31696
- var import_node_html_parser, import_yaml5, QUOTED_LITERAL_DELIMITERS;
31829
+ var import_node_html_parser, import_yaml5, ASSET_IMPORT_EXT_RE, REMOTE_FETCH_TIMEOUT_MS = 15000, REMOTE_FETCH_MAX_BYTES, QUOTED_LITERAL_DELIMITERS;
31697
31830
  var init_source_writer = __esm(() => {
31831
+ init_astro_image_paths();
31698
31832
  init_config();
31699
31833
  init_snippet_utils();
31700
31834
  init_utils2();
31701
31835
  import_node_html_parser = __toESM(require_dist2(), 1);
31702
31836
  import_yaml5 = __toESM(require_dist(), 1);
31837
+ ASSET_IMPORT_EXT_RE = /\.(jpe?g|png|gif|webp|avif|svg|ico|bmp|tiff?)$/i;
31838
+ REMOTE_FETCH_MAX_BYTES = 50 * 1024 * 1024;
31703
31839
  QUOTED_LITERAL_DELIMITERS = [`'`, `"`, "`"];
31704
31840
  });
31705
31841
 
31706
31842
  // ../cms/src/handlers/api-routes.ts
31707
- import path13 from "path";
31843
+ import path14 from "path";
31708
31844
  function requireMedia(ctx) {
31709
31845
  if (!ctx.mediaAdapter) {
31710
31846
  sendError(ctx.res, "Media storage not configured", 501);
@@ -31796,7 +31932,7 @@ var init_api_routes = __esm(() => {
31796
31932
  }),
31797
31933
  custom("POST", "markdown/delete", async ({ req, res, manifestWriter, contentDir }) => {
31798
31934
  const body = await parseJsonBody(req);
31799
- const fullPath = path13.resolve(getProjectRoot(), body.filePath?.replace(/^\//, "") ?? "");
31935
+ const fullPath = path14.resolve(getProjectRoot(), body.filePath?.replace(/^\//, "") ?? "");
31800
31936
  expectedDeletions.add(fullPath);
31801
31937
  const result = await handleDeleteMarkdown(body);
31802
31938
  if (result.success) {
@@ -31888,7 +32024,7 @@ var init_api_routes = __esm(() => {
31888
32024
  const body = await parseJsonBody(req);
31889
32025
  const result = await handleDeletePage(body);
31890
32026
  if (result.success && result.filePath) {
31891
- expectedDeletions.add(path13.resolve(getProjectRoot(), result.filePath));
32027
+ expectedDeletions.add(path14.resolve(getProjectRoot(), result.filePath));
31892
32028
  }
31893
32029
  if (result.success && body.createRedirect && body.redirectTo) {
31894
32030
  await handleAddRedirect({ source: body.pagePath, destination: body.redirectTo, statusCode: 307 });
@@ -31993,7 +32129,7 @@ var init_color_patterns = __esm(() => {
31993
32129
 
31994
32130
  // ../cms/src/tailwind-colors.ts
31995
32131
  import fs15 from "fs/promises";
31996
- import path14 from "path";
32132
+ import path15 from "path";
31997
32133
  async function parseTailwindConfig(projectRoot = getProjectRoot()) {
31998
32134
  const cssFiles = [
31999
32135
  "src/styles/global.css",
@@ -32007,7 +32143,7 @@ async function parseTailwindConfig(projectRoot = getProjectRoot()) {
32007
32143
  ];
32008
32144
  let customColors = [];
32009
32145
  for (const cssFile of cssFiles) {
32010
- const fullPath = path14.join(projectRoot, cssFile);
32146
+ const fullPath = path15.join(projectRoot, cssFile);
32011
32147
  try {
32012
32148
  const content = await fs15.readFile(fullPath, "utf-8");
32013
32149
  customColors = extractColorsFromCss(content);
@@ -32080,7 +32216,7 @@ async function parseTextStyles(projectRoot = getProjectRoot()) {
32080
32216
  ];
32081
32217
  let customTextStyles = {};
32082
32218
  for (const cssFile of cssFiles) {
32083
- const fullPath = path14.join(projectRoot, cssFile);
32219
+ const fullPath = path15.join(projectRoot, cssFile);
32084
32220
  try {
32085
32221
  const content = await fs15.readFile(fullPath, "utf-8");
32086
32222
  customTextStyles = extractTextStylesFromCss(content);
@@ -32537,7 +32673,7 @@ var init_dev_middleware = __esm(() => {
32537
32673
 
32538
32674
  // ../cms/src/manifest-writer.ts
32539
32675
  import fs16 from "fs/promises";
32540
- import path15 from "path";
32676
+ import path16 from "path";
32541
32677
 
32542
32678
  class ManifestWriter {
32543
32679
  globalManifest;
@@ -32608,10 +32744,10 @@ class ManifestWriter {
32608
32744
  }
32609
32745
  getPageManifestPath(pagePath) {
32610
32746
  if (pagePath === "/" || pagePath === "") {
32611
- return path15.join(this.outDir, "index.json");
32747
+ return path16.join(this.outDir, "index.json");
32612
32748
  }
32613
32749
  const cleanPath = pagePath.replace(/^\//, "");
32614
- return path15.join(this.outDir, `${cleanPath}.json`);
32750
+ return path16.join(this.outDir, `${cleanPath}.json`);
32615
32751
  }
32616
32752
  addPage(pagePath, entries, components, collection, seo) {
32617
32753
  this.pageManifests.set(pagePath, { entries, components, collection, seo });
@@ -32637,7 +32773,7 @@ class ManifestWriter {
32637
32773
  }
32638
32774
  async writePageManifest(pagePath, entries, components, collection, seo) {
32639
32775
  const manifestPath = this.getPageManifestPath(pagePath);
32640
- const manifestDir = path15.dirname(manifestPath);
32776
+ const manifestDir = path16.dirname(manifestPath);
32641
32777
  await fs16.mkdir(manifestDir, { recursive: true });
32642
32778
  const metadata = {
32643
32779
  version: MANIFEST_VERSION,
@@ -32688,7 +32824,7 @@ class ManifestWriter {
32688
32824
  }
32689
32825
  }
32690
32826
  if (this.outDir) {
32691
- const globalManifestPath = path15.join(this.outDir, this.manifestFile);
32827
+ const globalManifestPath = path16.join(this.outDir, this.manifestFile);
32692
32828
  const globalSettings = {
32693
32829
  componentDefinitions: this.componentDefinitions,
32694
32830
  pages
@@ -32778,7 +32914,7 @@ var init_s3 = __esm(() => {
32778
32914
 
32779
32915
  // ../cms/src/migrate-astro-image.ts
32780
32916
  import fs17 from "fs/promises";
32781
- import path16 from "path";
32917
+ import path17 from "path";
32782
32918
  async function migrateAstroImages(options = {}) {
32783
32919
  const projectRoot = options.projectRoot ?? getProjectRoot();
32784
32920
  const dryRun = options.dryRun ?? false;
@@ -32790,7 +32926,7 @@ async function migrateAstroImages(options = {}) {
32790
32926
  if (astroFields.length === 0 || !def.entries)
32791
32927
  continue;
32792
32928
  for (const entry of def.entries) {
32793
- const entryAbs = path16.isAbsolute(entry.sourcePath) ? entry.sourcePath : path16.join(projectRoot, entry.sourcePath);
32929
+ const entryAbs = path17.isAbsolute(entry.sourcePath) ? entry.sourcePath : path17.join(projectRoot, entry.sourcePath);
32794
32930
  let raw;
32795
32931
  try {
32796
32932
  raw = await fs17.readFile(entryAbs, "utf-8");
@@ -32815,7 +32951,7 @@ async function migrateAstroImages(options = {}) {
32815
32951
  }
32816
32952
  if (!current.startsWith("/") || current.startsWith("//"))
32817
32953
  continue;
32818
- const sourceAbs = path16.join(projectRoot, "public", current.replace(/^\/+/, ""));
32954
+ const sourceAbs = path17.join(projectRoot, "public", current.replace(/^\/+/, ""));
32819
32955
  let sourceBuf;
32820
32956
  try {
32821
32957
  sourceBuf = await fs17.readFile(sourceAbs);
@@ -32826,11 +32962,11 @@ async function migrateAstroImages(options = {}) {
32826
32962
  const target = await pickAstroImageTarget({
32827
32963
  entryAbsPath: entryAbs,
32828
32964
  slug: entry.slug,
32829
- originalFilename: path16.basename(current),
32965
+ originalFilename: path17.basename(current),
32830
32966
  compareBuffer: sourceBuf
32831
32967
  });
32832
32968
  if (!dryRun) {
32833
- await fs17.mkdir(path16.dirname(target.absPath), { recursive: true });
32969
+ await fs17.mkdir(path17.dirname(target.absPath), { recursive: true });
32834
32970
  await fs17.writeFile(target.absPath, sourceBuf);
32835
32971
  }
32836
32972
  doc.set(field.name, target.relPath);
@@ -32885,7 +33021,7 @@ var exports_migrate = {};
32885
33021
  __export(exports_migrate, {
32886
33022
  migrate: () => migrate
32887
33023
  });
32888
- import path17 from "path";
33024
+ import path18 from "path";
32889
33025
  async function migrate(args) {
32890
33026
  if (args.target !== "astro-image") {
32891
33027
  console.error(`Unknown migrate target: ${args.target}`);
@@ -32904,7 +33040,7 @@ async function migrate(args) {
32904
33040
  for (const m of result.migrations) {
32905
33041
  console.log(` ${m.entrySourcePath}`);
32906
33042
  console.log(` ${m.fieldName}: ${m.originalValue} \u2192 ${m.newValue}`);
32907
- console.log(` copy: ${path17.relative(process.cwd(), m.copiedFrom)} \u2192 ${path17.relative(process.cwd(), m.copiedTo)}`);
33043
+ console.log(` copy: ${path18.relative(process.cwd(), m.copiedFrom)} \u2192 ${path18.relative(process.cwd(), m.copiedTo)}`);
32908
33044
  }
32909
33045
  }
32910
33046
  if (result.skipped.length > 0) {