@nuasite/cli 0.35.0 → 0.36.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.
Files changed (2) hide show
  1. package/dist/index.js +165 -34
  2. package/package.json +3 -3
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 {
@@ -30965,6 +30977,7 @@ var init_snippet_utils = __esm(() => {
30965
30977
 
30966
30978
  // ../cms/src/handlers/source-writer.ts
30967
30979
  import fs14 from "fs/promises";
30980
+ import path13 from "path";
30968
30981
  async function handleUpdate(request, manifestWriter) {
30969
30982
  const { changes, meta } = request;
30970
30983
  const errors = [];
@@ -30995,11 +31008,15 @@ async function handleUpdate(request, manifestWriter) {
30995
31008
  const release = await acquireFileLock(fullPath);
30996
31009
  try {
30997
31010
  const currentContent = await fs14.readFile(fullPath, "utf-8");
30998
- const { newContent, appliedCount, failedChanges } = applyChanges(currentContent, fileChanges, manifest);
31011
+ const { newContent, appliedCount, failedChanges, fileOps } = await applyChanges(currentContent, fileChanges, manifest, fullPath, meta.url);
30999
31012
  if (failedChanges.length > 0) {
31000
31013
  errors.push(...failedChanges);
31001
31014
  }
31002
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
+ }
31003
31020
  await fs14.writeFile(fullPath, newContent, "utf-8");
31004
31021
  updated += appliedCount;
31005
31022
  }
@@ -31016,16 +31033,19 @@ async function handleUpdate(request, manifestWriter) {
31016
31033
  errors: errors.length > 0 ? errors : undefined
31017
31034
  };
31018
31035
  }
31019
- function applyChanges(content, changes, manifest) {
31036
+ async function applyChanges(content, changes, manifest, absFilePath, originUrl) {
31020
31037
  let newContent = content;
31021
31038
  let appliedCount = 0;
31022
31039
  const failedChanges = [];
31040
+ const fileOps = [];
31023
31041
  const sortedChanges = [...changes].sort((a, b2) => (b2.sourceLine ?? 0) - (a.sourceLine ?? 0));
31024
31042
  for (const change of sortedChanges) {
31025
31043
  if (change.imageChange) {
31026
- const result2 = applyImageChange(newContent, change);
31044
+ const result2 = await applyImageChange(newContent, change, absFilePath, originUrl);
31027
31045
  if (result2.success) {
31028
31046
  newContent = result2.content;
31047
+ if (result2.fileOp)
31048
+ fileOps.push(result2.fileOp);
31029
31049
  appliedCount++;
31030
31050
  } else {
31031
31051
  failedChanges.push({ cmsId: change.cmsId, error: result2.error });
@@ -31059,9 +31079,9 @@ function applyChanges(content, changes, manifest) {
31059
31079
  failedChanges.push({ cmsId: change.cmsId, error: result.error });
31060
31080
  }
31061
31081
  }
31062
- return { newContent, appliedCount, failedChanges };
31082
+ return { newContent, appliedCount, failedChanges, fileOps };
31063
31083
  }
31064
- function applyImageChange(content, change) {
31084
+ async function applyImageChange(content, change, absFilePath, originUrl) {
31065
31085
  const { newSrc, newAlt } = change.imageChange;
31066
31086
  const originalSrc = change.originalValue;
31067
31087
  if (!originalSrc) {
@@ -31153,6 +31173,7 @@ function applyImageChange(content, change) {
31153
31173
  }
31154
31174
  }
31155
31175
  }
31176
+ let pendingFileOp;
31156
31177
  if (replacedIndex < 0 && change.sourceLine > 0) {
31157
31178
  const lines = newContent.split(`
31158
31179
  `);
@@ -31166,7 +31187,20 @@ function applyImageChange(content, change) {
31166
31187
  const exprMatch = findExpressionSrcAttribute(regionText);
31167
31188
  if (exprMatch) {
31168
31189
  const exprContent = regionText.slice(exprMatch.index + regionText.slice(exprMatch.index).indexOf("{") + 1, exprMatch.index + exprMatch.length - 1).trim();
31169
- 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
+ }
31170
31204
  }
31171
31205
  }
31172
31206
  }
@@ -31206,7 +31240,101 @@ function applyImageChange(content, change) {
31206
31240
  newContent = newContent.slice(0, altAbsoluteIndex) + `alt=${altQuote}${escapedAlt}${altQuote}` + newContent.slice(altAbsoluteIndex + altLength);
31207
31241
  }
31208
31242
  }
31209
- 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);
31210
31338
  }
31211
31339
  function applyColorChange(content, change) {
31212
31340
  const { oldClass, newClass } = change.styleChange;
@@ -31698,18 +31826,21 @@ function tryBrNormalizedChange(sourceSnippet, resolvedOriginal, resolvedNewText)
31698
31826
  }
31699
31827
  return result !== sourceSnippet ? result : null;
31700
31828
  }
31701
- 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;
31702
31830
  var init_source_writer = __esm(() => {
31831
+ init_astro_image_paths();
31703
31832
  init_config();
31704
31833
  init_snippet_utils();
31705
31834
  init_utils2();
31706
31835
  import_node_html_parser = __toESM(require_dist2(), 1);
31707
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;
31708
31839
  QUOTED_LITERAL_DELIMITERS = [`'`, `"`, "`"];
31709
31840
  });
31710
31841
 
31711
31842
  // ../cms/src/handlers/api-routes.ts
31712
- import path13 from "path";
31843
+ import path14 from "path";
31713
31844
  function requireMedia(ctx) {
31714
31845
  if (!ctx.mediaAdapter) {
31715
31846
  sendError(ctx.res, "Media storage not configured", 501);
@@ -31801,7 +31932,7 @@ var init_api_routes = __esm(() => {
31801
31932
  }),
31802
31933
  custom("POST", "markdown/delete", async ({ req, res, manifestWriter, contentDir }) => {
31803
31934
  const body = await parseJsonBody(req);
31804
- const fullPath = path13.resolve(getProjectRoot(), body.filePath?.replace(/^\//, "") ?? "");
31935
+ const fullPath = path14.resolve(getProjectRoot(), body.filePath?.replace(/^\//, "") ?? "");
31805
31936
  expectedDeletions.add(fullPath);
31806
31937
  const result = await handleDeleteMarkdown(body);
31807
31938
  if (result.success) {
@@ -31893,7 +32024,7 @@ var init_api_routes = __esm(() => {
31893
32024
  const body = await parseJsonBody(req);
31894
32025
  const result = await handleDeletePage(body);
31895
32026
  if (result.success && result.filePath) {
31896
- expectedDeletions.add(path13.resolve(getProjectRoot(), result.filePath));
32027
+ expectedDeletions.add(path14.resolve(getProjectRoot(), result.filePath));
31897
32028
  }
31898
32029
  if (result.success && body.createRedirect && body.redirectTo) {
31899
32030
  await handleAddRedirect({ source: body.pagePath, destination: body.redirectTo, statusCode: 307 });
@@ -31998,7 +32129,7 @@ var init_color_patterns = __esm(() => {
31998
32129
 
31999
32130
  // ../cms/src/tailwind-colors.ts
32000
32131
  import fs15 from "fs/promises";
32001
- import path14 from "path";
32132
+ import path15 from "path";
32002
32133
  async function parseTailwindConfig(projectRoot = getProjectRoot()) {
32003
32134
  const cssFiles = [
32004
32135
  "src/styles/global.css",
@@ -32012,7 +32143,7 @@ async function parseTailwindConfig(projectRoot = getProjectRoot()) {
32012
32143
  ];
32013
32144
  let customColors = [];
32014
32145
  for (const cssFile of cssFiles) {
32015
- const fullPath = path14.join(projectRoot, cssFile);
32146
+ const fullPath = path15.join(projectRoot, cssFile);
32016
32147
  try {
32017
32148
  const content = await fs15.readFile(fullPath, "utf-8");
32018
32149
  customColors = extractColorsFromCss(content);
@@ -32085,7 +32216,7 @@ async function parseTextStyles(projectRoot = getProjectRoot()) {
32085
32216
  ];
32086
32217
  let customTextStyles = {};
32087
32218
  for (const cssFile of cssFiles) {
32088
- const fullPath = path14.join(projectRoot, cssFile);
32219
+ const fullPath = path15.join(projectRoot, cssFile);
32089
32220
  try {
32090
32221
  const content = await fs15.readFile(fullPath, "utf-8");
32091
32222
  customTextStyles = extractTextStylesFromCss(content);
@@ -32542,7 +32673,7 @@ var init_dev_middleware = __esm(() => {
32542
32673
 
32543
32674
  // ../cms/src/manifest-writer.ts
32544
32675
  import fs16 from "fs/promises";
32545
- import path15 from "path";
32676
+ import path16 from "path";
32546
32677
 
32547
32678
  class ManifestWriter {
32548
32679
  globalManifest;
@@ -32613,10 +32744,10 @@ class ManifestWriter {
32613
32744
  }
32614
32745
  getPageManifestPath(pagePath) {
32615
32746
  if (pagePath === "/" || pagePath === "") {
32616
- return path15.join(this.outDir, "index.json");
32747
+ return path16.join(this.outDir, "index.json");
32617
32748
  }
32618
32749
  const cleanPath = pagePath.replace(/^\//, "");
32619
- return path15.join(this.outDir, `${cleanPath}.json`);
32750
+ return path16.join(this.outDir, `${cleanPath}.json`);
32620
32751
  }
32621
32752
  addPage(pagePath, entries, components, collection, seo) {
32622
32753
  this.pageManifests.set(pagePath, { entries, components, collection, seo });
@@ -32642,7 +32773,7 @@ class ManifestWriter {
32642
32773
  }
32643
32774
  async writePageManifest(pagePath, entries, components, collection, seo) {
32644
32775
  const manifestPath = this.getPageManifestPath(pagePath);
32645
- const manifestDir = path15.dirname(manifestPath);
32776
+ const manifestDir = path16.dirname(manifestPath);
32646
32777
  await fs16.mkdir(manifestDir, { recursive: true });
32647
32778
  const metadata = {
32648
32779
  version: MANIFEST_VERSION,
@@ -32693,7 +32824,7 @@ class ManifestWriter {
32693
32824
  }
32694
32825
  }
32695
32826
  if (this.outDir) {
32696
- const globalManifestPath = path15.join(this.outDir, this.manifestFile);
32827
+ const globalManifestPath = path16.join(this.outDir, this.manifestFile);
32697
32828
  const globalSettings = {
32698
32829
  componentDefinitions: this.componentDefinitions,
32699
32830
  pages
@@ -32783,7 +32914,7 @@ var init_s3 = __esm(() => {
32783
32914
 
32784
32915
  // ../cms/src/migrate-astro-image.ts
32785
32916
  import fs17 from "fs/promises";
32786
- import path16 from "path";
32917
+ import path17 from "path";
32787
32918
  async function migrateAstroImages(options = {}) {
32788
32919
  const projectRoot = options.projectRoot ?? getProjectRoot();
32789
32920
  const dryRun = options.dryRun ?? false;
@@ -32795,7 +32926,7 @@ async function migrateAstroImages(options = {}) {
32795
32926
  if (astroFields.length === 0 || !def.entries)
32796
32927
  continue;
32797
32928
  for (const entry of def.entries) {
32798
- 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);
32799
32930
  let raw;
32800
32931
  try {
32801
32932
  raw = await fs17.readFile(entryAbs, "utf-8");
@@ -32820,7 +32951,7 @@ async function migrateAstroImages(options = {}) {
32820
32951
  }
32821
32952
  if (!current.startsWith("/") || current.startsWith("//"))
32822
32953
  continue;
32823
- const sourceAbs = path16.join(projectRoot, "public", current.replace(/^\/+/, ""));
32954
+ const sourceAbs = path17.join(projectRoot, "public", current.replace(/^\/+/, ""));
32824
32955
  let sourceBuf;
32825
32956
  try {
32826
32957
  sourceBuf = await fs17.readFile(sourceAbs);
@@ -32831,11 +32962,11 @@ async function migrateAstroImages(options = {}) {
32831
32962
  const target = await pickAstroImageTarget({
32832
32963
  entryAbsPath: entryAbs,
32833
32964
  slug: entry.slug,
32834
- originalFilename: path16.basename(current),
32965
+ originalFilename: path17.basename(current),
32835
32966
  compareBuffer: sourceBuf
32836
32967
  });
32837
32968
  if (!dryRun) {
32838
- await fs17.mkdir(path16.dirname(target.absPath), { recursive: true });
32969
+ await fs17.mkdir(path17.dirname(target.absPath), { recursive: true });
32839
32970
  await fs17.writeFile(target.absPath, sourceBuf);
32840
32971
  }
32841
32972
  doc.set(field.name, target.relPath);
@@ -32890,7 +33021,7 @@ var exports_migrate = {};
32890
33021
  __export(exports_migrate, {
32891
33022
  migrate: () => migrate
32892
33023
  });
32893
- import path17 from "path";
33024
+ import path18 from "path";
32894
33025
  async function migrate(args) {
32895
33026
  if (args.target !== "astro-image") {
32896
33027
  console.error(`Unknown migrate target: ${args.target}`);
@@ -32909,7 +33040,7 @@ async function migrate(args) {
32909
33040
  for (const m of result.migrations) {
32910
33041
  console.log(` ${m.entrySourcePath}`);
32911
33042
  console.log(` ${m.fieldName}: ${m.originalValue} \u2192 ${m.newValue}`);
32912
- 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)}`);
32913
33044
  }
32914
33045
  }
32915
33046
  if (result.skipped.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuasite/cli",
3
- "version": "0.35.0",
3
+ "version": "0.36.1",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "files": [
@@ -23,8 +23,8 @@
23
23
  "prepack": "bun run ../../scripts/workspace-deps/resolve-deps.ts"
24
24
  },
25
25
  "dependencies": {
26
- "@nuasite/agent-summary": "0.35.0",
27
- "@nuasite/cms": "0.35.0",
26
+ "@nuasite/agent-summary": "0.36.1",
27
+ "@nuasite/cms": "0.36.1",
28
28
  "astro": "6.1.4",
29
29
  "stacktracey": "2.2.0"
30
30
  },