@bettercms-ai/convert 0.26.0 → 0.26.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/CHANGELOG.md +8 -0
- package/dist/cli.js +54 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @bettercms-ai/convert
|
|
2
2
|
|
|
3
|
+
## 0.26.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- c83b686: The codemod routes an image through `bcmsImage` where a call site already declares its field in `bcmsBindings` and still passes the raw import (the element was stamped, and the page kept rendering the file its code imports), and names an image's asset from an optimizer url (`/_image?href=…`). The components skill gains "Read a published value where the app renders the page".
|
|
8
|
+
- Updated dependencies [1a52ee6]
|
|
9
|
+
- @bettercms-ai/types@1.36.0
|
|
10
|
+
|
|
3
11
|
## 0.26.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
package/dist/cli.js
CHANGED
|
@@ -4090,6 +4090,13 @@ var IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".avif", ".gif", ".svg
|
|
|
4090
4090
|
var ASSET_COMPONENTS = /* @__PURE__ */ new Set(["Image", "Picture"]);
|
|
4091
4091
|
var ASSET_MODULE = "astro:assets";
|
|
4092
4092
|
function baseName(url) {
|
|
4093
|
+
const inner = /[?&](?:href|url)=([^&#]+)/.exec(url)?.[1];
|
|
4094
|
+
if (inner) {
|
|
4095
|
+
try {
|
|
4096
|
+
url = decodeURIComponent(inner);
|
|
4097
|
+
} catch {
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4093
4100
|
const file = url.split(/[?#]/)[0].split("/").pop() ?? "";
|
|
4094
4101
|
const base = file.split(".")[0] ?? "";
|
|
4095
4102
|
return base === "" ? null : base.toLowerCase();
|
|
@@ -4115,6 +4122,29 @@ function passedTo(content, parsed, binding) {
|
|
|
4115
4122
|
for (const root of parsed.roots) visit(root);
|
|
4116
4123
|
return out;
|
|
4117
4124
|
}
|
|
4125
|
+
function declaredAt(content, parsed, binding, path) {
|
|
4126
|
+
const out = [];
|
|
4127
|
+
const names = [`"${path}"`, `'${path}'`];
|
|
4128
|
+
const visit = (node) => {
|
|
4129
|
+
if (node.type === "expr") {
|
|
4130
|
+
for (const child of node.children) visit(child);
|
|
4131
|
+
return;
|
|
4132
|
+
}
|
|
4133
|
+
if (node.type !== "element" || node.raw) return;
|
|
4134
|
+
const bindings = node.attrs.find((a) => a.name === "bcmsBindings");
|
|
4135
|
+
const declared = bindings?.expressionRange ? content.slice(bindings.expressionRange.start, bindings.expressionRange.end) : "";
|
|
4136
|
+
if (/^[A-Z]/.test(node.tag) && !ASSET_COMPONENTS.has(node.tag) && names.some((name) => declared.includes(name))) {
|
|
4137
|
+
for (const attr of node.attrs) {
|
|
4138
|
+
if (!attr.expressionRange) continue;
|
|
4139
|
+
const inner = content.slice(attr.expressionRange.start, attr.expressionRange.end).replace(/^\{|\}$/g, "").trim();
|
|
4140
|
+
if (inner === binding) out.push({ node, prop: attr.name, attr });
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
for (const child of node.children) visit(child);
|
|
4144
|
+
};
|
|
4145
|
+
for (const root of parsed.roots) visit(root);
|
|
4146
|
+
return out;
|
|
4147
|
+
}
|
|
4118
4148
|
function assetCandidates(parsed, dialect, original) {
|
|
4119
4149
|
const wanted = baseName(original);
|
|
4120
4150
|
if (wanted === null || parsed.error) return [];
|
|
@@ -4162,6 +4192,18 @@ async function bindImages(ctx, targets) {
|
|
|
4162
4192
|
const rendered = binding === null ? [] : renderedBy(ctx.content, ctx.parsed, ctx.dialect, binding);
|
|
4163
4193
|
const free = rendered.filter((hit2) => !claimed.has(hit2.node.attrInsertAt));
|
|
4164
4194
|
const args = (b) => [ctx.snapshotName(target.slug), JSON.stringify(target.path), b, ...ctx.slug ? [ctx.slug] : []];
|
|
4195
|
+
if (free.length === 0 && binding !== null) {
|
|
4196
|
+
const declared = declaredAt(ctx.content, ctx.parsed, binding, target.path).filter((c) => !claimed.has(c.attr.range.start));
|
|
4197
|
+
if (declared.length === 1) {
|
|
4198
|
+
const call = declared[0];
|
|
4199
|
+
claimed.add(call.attr.range.start);
|
|
4200
|
+
out.splices.push({ start: call.attr.range.start, end: call.attr.range.end, text: rule.attr(call.prop, `${ctx.image}(${args(binding).join(", ")})`) });
|
|
4201
|
+
out.slugs.push(target.slug);
|
|
4202
|
+
out.bound.push(target.key);
|
|
4203
|
+
continue;
|
|
4204
|
+
}
|
|
4205
|
+
}
|
|
4206
|
+
if (target.declared) continue;
|
|
4165
4207
|
if (free.length === 0 && binding !== null && ctx.drill) {
|
|
4166
4208
|
const calls = passedTo(ctx.content, ctx.parsed, binding).filter((c) => !claimed.has(c.node.attrInsertAt));
|
|
4167
4209
|
const call = calls.length === 1 ? calls[0] : null;
|
|
@@ -10184,10 +10226,13 @@ async function convertSources(briefIn, sources, options) {
|
|
|
10184
10226
|
async function convertFile(file, content, targets, parsed, declared, llmFallback, drill) {
|
|
10185
10227
|
const outcomes = /* @__PURE__ */ new Map();
|
|
10186
10228
|
const pendingTargets = [];
|
|
10229
|
+
let unreadImages = false;
|
|
10187
10230
|
const dialect = dialectOf(file, content);
|
|
10188
10231
|
for (const target of targets) {
|
|
10189
10232
|
if (parsed && dialect && convertedHere(content, parsed, dialect, target, file)) {
|
|
10190
10233
|
outcomes.set(target.key, "declared");
|
|
10234
|
+
if (target.kind === "image" && !content.includes("bcmsImage(")) unreadImages = true;
|
|
10235
|
+
else if (target.kind === "image" && !new RegExp(`bcmsImage\\([^)]*["']${target.path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']`).test(content)) unreadImages = true;
|
|
10191
10236
|
} else pendingTargets.push(target);
|
|
10192
10237
|
}
|
|
10193
10238
|
const empty = {
|
|
@@ -10202,7 +10247,7 @@ async function convertFile(file, content, targets, parsed, declared, llmFallback
|
|
|
10202
10247
|
snapshots: [],
|
|
10203
10248
|
notes: []
|
|
10204
10249
|
};
|
|
10205
|
-
if (pendingTargets.length === 0) return empty;
|
|
10250
|
+
if (pendingTargets.length === 0 && !unreadImages) return empty;
|
|
10206
10251
|
if (!dialect) {
|
|
10207
10252
|
if (isTolerantDialect(file)) {
|
|
10208
10253
|
const already = declaredByScan(content);
|
|
@@ -10403,15 +10448,15 @@ async function plan(file, content, dialect, targets, sites, importAt, outcomes,
|
|
|
10403
10448
|
continue;
|
|
10404
10449
|
}
|
|
10405
10450
|
const declares = (d) => d.scope === target.scope && (d.path === target.path || d.path === shapeOfPath(target.path));
|
|
10406
|
-
const
|
|
10451
|
+
const declaredAt2 = new Set(declarations.filter(declares).map((d) => d.at));
|
|
10407
10452
|
const claimedAt = new Set(declarations.filter((d) => !declares(d) && d.via === "field").map((d) => d.at));
|
|
10408
10453
|
const writesField = (site) => site.where !== "attr" || target.kind === "image";
|
|
10409
10454
|
const fresh = eligible.filter(
|
|
10410
|
-
(site) => !
|
|
10455
|
+
(site) => !declaredAt2.has(site.attrInsertAt) && !(writesField(site) && claimedAt.has(site.attrInsertAt))
|
|
10411
10456
|
);
|
|
10412
10457
|
located += eligible.length;
|
|
10413
10458
|
const blocked = eligible.filter(
|
|
10414
|
-
(site) => !
|
|
10459
|
+
(site) => !declaredAt2.has(site.attrInsertAt) && writesField(site) && claimedAt.has(site.attrInsertAt)
|
|
10415
10460
|
);
|
|
10416
10461
|
if (blocked.length > 0) {
|
|
10417
10462
|
outcomes.set(target.key, "AMBIGUOUS_LITERAL");
|
|
@@ -10420,7 +10465,7 @@ async function plan(file, content, dialect, targets, sites, importAt, outcomes,
|
|
|
10420
10465
|
if (fresh.length === 0) {
|
|
10421
10466
|
outcomes.set(
|
|
10422
10467
|
target.key,
|
|
10423
|
-
eligible.some((site) =>
|
|
10468
|
+
eligible.some((site) => declaredAt2.has(site.attrInsertAt)) ? "declared" : "AMBIGUOUS_LITERAL"
|
|
10424
10469
|
);
|
|
10425
10470
|
continue;
|
|
10426
10471
|
}
|
|
@@ -10606,8 +10651,8 @@ async function plan(file, content, dialect, targets, sites, importAt, outcomes,
|
|
|
10606
10651
|
},
|
|
10607
10652
|
targets.filter((t) => {
|
|
10608
10653
|
const outcome = outcomes.get(t.key);
|
|
10609
|
-
return t.kind === "image" && t.scope === "page" && (outcome === void 0 || UNPLACED.has(outcome));
|
|
10610
|
-
}).map((t) => ({ key: t.key, path: t.path, kind: t.kind, original: t.fallback, slug: t.slug }))
|
|
10654
|
+
return t.kind === "image" && t.scope === "page" && (outcome === void 0 || outcome === "declared" || UNPLACED.has(outcome));
|
|
10655
|
+
}).map((t) => ({ key: t.key, path: t.path, kind: t.kind, original: t.fallback, slug: t.slug, declared: outcomes.get(t.key) === "declared" }))
|
|
10611
10656
|
);
|
|
10612
10657
|
for (const key of images.bound) outcomes.set(key, "rewritten");
|
|
10613
10658
|
for (const refusal of images.refused) outcomes.set(refusal.key, refusal.reason);
|
|
@@ -10897,7 +10942,7 @@ function styleEdits(before, after) {
|
|
|
10897
10942
|
// package.json
|
|
10898
10943
|
var package_default = {
|
|
10899
10944
|
name: "@bettercms-ai/convert",
|
|
10900
|
-
version: "0.26.
|
|
10945
|
+
version: "0.26.1",
|
|
10901
10946
|
type: "module",
|
|
10902
10947
|
description: "Deterministic codemod that turns an imported site's text-matched bindings into declared ones \u2014 parser offsets in, one magic-string pass out, plus a receipt of everything it could not do.",
|
|
10903
10948
|
bin: {
|
|
@@ -10930,7 +10975,7 @@ var package_default = {
|
|
|
10930
10975
|
dependencies: {
|
|
10931
10976
|
"@astrojs/compiler": "^2.13.1",
|
|
10932
10977
|
"@babel/parser": "^7.29.2",
|
|
10933
|
-
"@bettercms-ai/types": "^1.
|
|
10978
|
+
"@bettercms-ai/types": "^1.36.0",
|
|
10934
10979
|
"@vue/compiler-sfc": "^3.5.0",
|
|
10935
10980
|
"magic-string": "^0.30.21",
|
|
10936
10981
|
parse5: "^7.3.0",
|