@bettercms-ai/convert 0.2.1 → 0.3.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/CHANGELOG.md +105 -0
- package/README.md +88 -9
- package/dist/cli.js +626 -164
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +78 -2
- package/dist/index.js +629 -166
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,110 @@
|
|
|
1
1
|
# @bettercms-ai/convert
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Componentize: a section the repository has ALREADY componentised is REGISTERED, not refused.
|
|
8
|
+
|
|
9
|
+
A page whose `index.astro` imports `Hero.astro` and renders `<Hero />` has made the cut
|
|
10
|
+
this lane exists to make. There is nothing in the page to extract, and refusing left the
|
|
11
|
+
CMS naming a section no component in the repo rendered. The existing file is now
|
|
12
|
+
registered under the section's slug — the registry imports it where it lives — and the
|
|
13
|
+
page keeps its call site. It has to be ONE file, one the page IMPORTS, and one the
|
|
14
|
+
registry's own dialect can render; anything else is `SECTION_ROOT_AMBIGUOUS` with the
|
|
15
|
+
reason named.
|
|
16
|
+
|
|
17
|
+
New receipt bucket, in the arithmetic:
|
|
18
|
+
`plan sections === extracted + inlineOnly + alreadyExtracted + registered + pending.length`.
|
|
19
|
+
`registered` is orthogonal to placement — nothing was written for those sections, so
|
|
20
|
+
counting them `extracted` would claim authorship of bytes this package never produced.
|
|
21
|
+
|
|
22
|
+
A page with a registered section is ALL OR NOTHING: `<Sections>` renders every placement
|
|
23
|
+
the page has, so the run is replaced by one `<Sections page="…" />` only when every
|
|
24
|
+
section of the page resolved and every call site is a bare tag in that run. Otherwise the
|
|
25
|
+
call sites stay exactly where their author put them — replacing a subset would render a
|
|
26
|
+
registered section twice, once in place and once out of the registry. The registry
|
|
27
|
+
read-back also resolves ANY relative import now, not only `./Name`: without that a second
|
|
28
|
+
run dropped the registered entry and the page rendered blank.
|
|
29
|
+
|
|
30
|
+
- Repeaters: a row that holds a list of its own becomes a loop inside the loop.
|
|
31
|
+
|
|
32
|
+
`cards[i].items[j].label` is emitted as a nested `.map` in astro and jsx, reading its rows
|
|
33
|
+
off the ROW — `bcmsRows(card, "items", [ …this card's own items… ])` — so a bullet added in
|
|
34
|
+
the dashboard renders. The committed helper needed nothing: `bcmsRows` already passes a
|
|
35
|
+
nested array through untouched and `bcmsSection` hands a section its group whole.
|
|
36
|
+
|
|
37
|
+
The bindings are `` data-bcms-field={`cards[${i}].items[${j}].label`} `` and the shape is
|
|
38
|
+
`cards[*].items[*].label`; the loop now REPORTS the bindings it wrote, both loop variables
|
|
39
|
+
included, instead of the caller re-deriving a literal the file does not contain. ONE level
|
|
40
|
+
of nesting — what the plan mints and what the component prop validator accepts — so a leaf
|
|
41
|
+
indexed twice stays `REPEATER_FIXED_LENGTH`, and so does the html dialect, which has no
|
|
42
|
+
loop form at all.
|
|
43
|
+
|
|
44
|
+
- A hand-written declaration counts as `alreadyDeclared`, never `NO_ORIGINAL`.
|
|
45
|
+
|
|
46
|
+
An element that names the target (`data-bcms-field`, `data-bcms-layout-field`, an address
|
|
47
|
+
in `data-bcms-props`) but gets its copy some other way is DECLARED: the publisher injects
|
|
48
|
+
into it and the editor makes it click-to-edit. It is simply not reading through the
|
|
49
|
+
committed helper. The receipt gains one row per already-declared path —
|
|
50
|
+
`paths.declarations[] { route, scope, path, kind, file, readsFromCms }` — and the
|
|
51
|
+
arithmetic is unchanged. The evidence has to be on the file the ROUTER says serves that
|
|
52
|
+
route: without a read there is nothing in a declaration that says which route it is for,
|
|
53
|
+
and two routes legitimately carry `hero.title`.
|
|
54
|
+
|
|
55
|
+
README: the "declared by hand" paragraph, and the address grammar in one place —
|
|
56
|
+
`data-bcms-field` / `data-bcms-layout-field="layout:<section>:<field>"`, attribute values
|
|
57
|
+
through `data-bcms-props="<address>|<kind>|<attr>"`, and `data-bcms-block` on a section root.
|
|
58
|
+
|
|
59
|
+
- Review hardening for all three of the above.
|
|
60
|
+
|
|
61
|
+
**Componentize.** A section the page renders BOTH inline and through a component it imports is
|
|
62
|
+
`SECTION_ROOT_AMBIGUOUS`: cutting the inline copy left `<Sections>` rendering the band out of
|
|
63
|
+
the registry while the untouched `<Hero />` beside it rendered it again. So is a section two
|
|
64
|
+
imported components both render, and a namespace the page imports but never calls. A registered
|
|
65
|
+
component is imported in the shape its module actually exports — default, `import { Hero }`, or
|
|
66
|
+
`import * as Bands` plus the member the call site names — because a default import of a named
|
|
67
|
+
export binds `undefined`. Contiguity now requires whitespace or comments between the roots, not
|
|
68
|
+
merely consecutive element siblings: `<Hero />Read this first<Features />` had no element in the
|
|
69
|
+
way and the run splice deleted the sentence. The registry read-back goes through the dialect's
|
|
70
|
+
parser instead of a line-shaped regex, so a formatter cannot make it forget the components it
|
|
71
|
+
names — and when the tables genuinely cannot be reconstructed the run refuses with the new
|
|
72
|
+
`REGISTRY_UNREADABLE` rather than rebuilding a registry without them.
|
|
73
|
+
|
|
74
|
+
**Repeaters.** A nested list the repository ships ONE row of still becomes a loop; the plan
|
|
75
|
+
already says `items` is an array, and reading one bullet as "not a list" is how the second one an
|
|
76
|
+
editor adds never renders.
|
|
77
|
+
|
|
78
|
+
**Declarations.** The route rule now covers LOCATED paths too — a hand-written declaration on
|
|
79
|
+
another route's page file, or on an element two routes both claim, is not evidence for either —
|
|
80
|
+
and every declarations row names the file it was found on. `aliasesFrom` reads JSONC (block
|
|
81
|
+
comments, trailing commas), because a tsconfig this package could not parse silently declared no
|
|
82
|
+
aliases and every aliased import then read as "not imported"; one it still cannot read is a
|
|
83
|
+
`TSCONFIG_UNREADABLE` note on the receipt.
|
|
84
|
+
|
|
85
|
+
- A second review round, over the same three.
|
|
86
|
+
|
|
87
|
+
**The registry read-back.** An import must resolve to a file that is actually there —
|
|
88
|
+
composing the path from our own naming convention meant a component somebody DELETED still
|
|
89
|
+
"resolved", and the run re-emitted a registry importing it. Both tables are now parsed
|
|
90
|
+
property by property, with strings and comments skipped by the brace matcher, so a one-line
|
|
91
|
+
table, a wrapped one and a comment containing a `}` are all read whole; anything that cannot
|
|
92
|
+
be reconstructed in full is `REGISTRY_UNREADABLE` rather than a quiet partial rebuild.
|
|
93
|
+
|
|
94
|
+
**Bindings.** `import Default, { Hero } from "./bands"` binds one module twice; the binding
|
|
95
|
+
the page actually CALLS is the one registered, instead of whichever came first.
|
|
96
|
+
|
|
97
|
+
**Declarations.** The route gate now also applies to a declaration that reads from the CMS —
|
|
98
|
+
a home page rendering a teaser of `/about`'s headline reads `/about`'s snapshot legitimately,
|
|
99
|
+
and counting it left `/about`'s own page hardcoded and unreported. "Another route's page" means
|
|
100
|
+
a route THE BRIEF names: `routeOfFile` reads the repository's convention and the two disagree
|
|
101
|
+
about a dynamic route (`/[slug]` vs `/post/[slug]`), and refusing on a name the brief never
|
|
102
|
+
issued rejected every dynamic route's own page.
|
|
103
|
+
|
|
104
|
+
**Configs.** A tsconfig whose `baseUrl`/`paths` are valid JSON of the wrong type threw a
|
|
105
|
+
`TypeError` out of a pure function; the shape is checked and a bad one is
|
|
106
|
+
`TSCONFIG_UNREADABLE`, which the componentize receipt now carries too.
|
|
107
|
+
|
|
3
108
|
## 0.2.0
|
|
4
109
|
|
|
5
110
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -71,6 +71,49 @@ A path counts as rewritten only when EVERY located occurrence of it was. A value
|
|
|
71
71
|
the home page and a shared header is two places a reader sees it, and converting one of them reads
|
|
72
72
|
as a finished job.
|
|
73
73
|
|
|
74
|
+
**A declaration this package did not write still counts.** An element that names the target —
|
|
75
|
+
`data-bcms-field`, `data-bcms-layout-field` or an address in `data-bcms-props` — but gets its copy
|
|
76
|
+
some other way (a literal somebody left there, the project's own data module) is DECLARED: the
|
|
77
|
+
publisher injects into that element and the editor makes it click-to-edit. It is simply not reading
|
|
78
|
+
through the committed helper. Those land in `alreadyDeclared` with a receipt row of their own,
|
|
79
|
+
`readsFromCms: false`, rather than in `NO_ORIGINAL`:
|
|
80
|
+
|
|
81
|
+
```jsonc
|
|
82
|
+
"declarations": [{ "route": "/", "scope": "page", "path": "hero.title",
|
|
83
|
+
"kind": "text", "file": "src/pages/index.astro", "readsFromCms": false }]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The evidence may never be ANOTHER ROUTE'S PAGE FILE — one the brief itself names, since
|
|
87
|
+
`routeOfFile` reads the repository's convention and the two legitimately disagree about a dynamic
|
|
88
|
+
route — and it may never be an element two routes both claim. The same rule gates a declaration
|
|
89
|
+
that DOES read from the CMS: a home page rendering a teaser of `/about`'s headline reads `/about`'s
|
|
90
|
+
snapshot legitimately, and counting it left `/about`'s own page hardcoded and unreported. Without a read there is nothing in a declaration that says which route it is for, and two
|
|
91
|
+
routes legitimately carry `hero.title` — so "some file declares that string" would mark the other
|
|
92
|
+
route bound and leave it hardcoded forever. A path with no original at all is stricter still: the
|
|
93
|
+
only evidence available is the file's identity, so it has to be the one file the router says serves
|
|
94
|
+
that route. A layout path has no route by construction, and any file may carry it.
|
|
95
|
+
|
|
96
|
+
**A tsconfig is JSONC.** Comments and trailing commas are read (that is what `tsc --init` writes),
|
|
97
|
+
because a config this package cannot parse declares no `paths` aliases as far as it is concerned —
|
|
98
|
+
and then every `@/components/Hero` resolves to nothing and the refusal that follows names the wrong
|
|
99
|
+
cause. Its alias fields are shape-checked too: `"paths": "src/*"` is valid JSON and the wrong type,
|
|
100
|
+
and reading it threw a `TypeError` out of a pure function naming nothing. One it still cannot read
|
|
101
|
+
is a receipt NOTE rather than a silence — on the componentize receipt as well, which resolves a
|
|
102
|
+
page's `<Hero />` through exactly those aliases:
|
|
103
|
+
|
|
104
|
+
```jsonc
|
|
105
|
+
"notes": [{ "code": "TSCONFIG_UNREADABLE", "file": "tsconfig.json", "message": "…" }]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**The address grammar, in one place.** Page copy is `data-bcms-field="<path>"` plus
|
|
109
|
+
`data-bcms-kind="<kind>"`. The shared chrome is a DIFFERENT attribute carrying a prefixed address,
|
|
110
|
+
`data-bcms-layout-field="layout:<section>:<field>"`, so the block lane's own `[data-bcms-field]`
|
|
111
|
+
query cannot reach a chrome marker. A value that lives in an ATTRIBUTE (a promoted nav link's
|
|
112
|
+
`href`) has no field attribute of its own and rides `data-bcms-props="<address>|<kind>|<attr>;…"` —
|
|
113
|
+
where `<address>` carries the same `layout:` prefix when it is chrome. A section root additionally
|
|
114
|
+
carries `data-bcms-block="<blockId>"`, which is what the editor hit-tests for section chrome and
|
|
115
|
+
reorder.
|
|
116
|
+
|
|
74
117
|
## As a library
|
|
75
118
|
|
|
76
119
|
```ts
|
|
@@ -92,6 +135,17 @@ proposer re-derives them from its own scan rather than believing the receipt. A
|
|
|
92
135
|
become a loop — non-consecutive siblings, one row shaped differently, the html dialect — is still
|
|
93
136
|
declared and read per index and reported `REPEATER_FIXED_LENGTH`.
|
|
94
137
|
|
|
138
|
+
**A row that holds a list of its own becomes a loop inside the loop.** `cards[i].items[j].label` is
|
|
139
|
+
a nested `.map` in astro and jsx, reading its rows off the ROW — `bcmsRows(card, "items", […])`,
|
|
140
|
+
the same helper one level down, because `bcmsRows` passes a nested array through untouched and
|
|
141
|
+
`bcmsSection` hands a section its group whole. The binding is
|
|
142
|
+
`` data-bcms-field={`cards[${i}].items[${j}].label`} `` and the shape is `cards[*].items[*].label`.
|
|
143
|
+
ONE level: that is what the plan mints and what the component prop validator accepts, so a leaf
|
|
144
|
+
indexed twice keeps the length the template gives it. One inner row is enough to make the loop —
|
|
145
|
+
the plan already says `items` is an array, and reading a card that ships a single bullet as "not a
|
|
146
|
+
list" is how the second bullet an editor adds never renders. The html dialect has no loop form at all, so
|
|
147
|
+
it stays fixed-length there — every index declared, the number of rows the template's.
|
|
148
|
+
|
|
95
149
|
**A prop-drilled literal is converted in two hops, in one proposal.** `<Hero title="…" />` becomes
|
|
96
150
|
`title={bcms(…)}` plus `bcmsBindings={{ title: "hero.title" }}`, and the `<h1>{title}</h1>` inside
|
|
97
151
|
`Hero` — resolved through the file's own imports, relative or through the project's tsconfig
|
|
@@ -142,7 +196,7 @@ modify src/pages/index.astro
|
|
|
142
196
|
modify bcms-content/home.json
|
|
143
197
|
add bcms-content/components.json
|
|
144
198
|
|
|
145
|
-
5 sections extracted (0 inline-only, 0 already extracted, 0 pending)
|
|
199
|
+
5 sections extracted (0 registered, 0 inline-only, 0 already extracted, 0 pending)
|
|
146
200
|
```
|
|
147
201
|
|
|
148
202
|
`--plan <file>` is required and is only read in this mode. `--dry-run`, `--receipt`, `--strict` and
|
|
@@ -166,8 +220,22 @@ add bcms-content/components.json
|
|
|
166
220
|
ran, with nothing in the diff to explain it. The fallback is decided on the **raw** block list:
|
|
167
221
|
a page whose blocks all name components this repo does not have has still been *arranged*, and
|
|
168
222
|
putting the shipped bands back would undo an editor's work on the next build.
|
|
223
|
+
- **Nothing at all, for a section the repository has ALREADY componentised.** A page that renders
|
|
224
|
+
`<Hero />` from a file it imports has made this cut itself: the existing file is REGISTERED under
|
|
225
|
+
the section's slug — the registry imports it where it lives, **in the shape its module actually
|
|
226
|
+
exports**: a default import, `import { Hero }` for a named export, or `import * as Bands` plus the
|
|
227
|
+
member the page's own call site names — and the page keeps calling it. When one module is bound
|
|
228
|
+
twice (`import Default, { Hero } from "./bands"`), the binding the page actually CALLS is the one
|
|
229
|
+
registered. It has to be a file this page imports, the only one it imports that renders the
|
|
230
|
+
group, and one the registry's dialect can render; a section the page ALSO renders inline is two
|
|
231
|
+
answers and is refused. Everything else is `SECTION_ROOT_AMBIGUOUS` with the reason spelled out.
|
|
169
232
|
- **The page** — the contiguous run of section roots replaced by one `<Sections page="<slug>" />`,
|
|
170
|
-
with the import added.
|
|
233
|
+
with the import added. Contiguous means consecutive element siblings **and** nothing but
|
|
234
|
+
whitespace or comments in the bytes between them: `<Hero />Read this first<Features />` has no
|
|
235
|
+
element in the way, and one splice over that run would delete the sentence. A page with a REGISTERED section is all or nothing: `<Sections>` renders
|
|
236
|
+
every placement the page has, so the run is replaced only when every one of the page's sections
|
|
237
|
+
resolved and every call site is a bare tag in that run. Otherwise the call sites stay exactly
|
|
238
|
+
where their author put them — replacing a subset would render a registered section twice.
|
|
171
239
|
- **The stubs** — `blocks: []` added to each page's snapshot (keys it already has are kept), and
|
|
172
240
|
`bcms-content/components.json` = `[]`.
|
|
173
241
|
|
|
@@ -211,11 +279,12 @@ import travels as `import * as X`, not as a named one.
|
|
|
211
279
|
|
|
212
280
|
| reason | |
|
|
213
281
|
|---|---|
|
|
214
|
-
| `SECTION_ROOT_AMBIGUOUS` | the group's fields sit under no single element, one section's root contains another's, or
|
|
282
|
+
| `SECTION_ROOT_AMBIGUOUS` | the group's fields sit under no single element, one section's root contains another's, the page renders it BOTH inline and through a component it imports, two imported components render it, or it is rendered somewhere this cannot register — a file this page does not import, a component of another dialect, a namespace the page never calls. The message says which. |
|
|
215
283
|
| `SECTION_NOT_CONTIGUOUS` | the page's section roots share no container at all. **The components are still written**; the page is left exactly as its author wrote it. |
|
|
216
284
|
| `SECTION_FREE_IDENTIFIERS` | the markup uses a name the component file cannot be given. The identifier is named in the message. |
|
|
217
285
|
| `COMPONENT_CONFLICT` | a file already sits at `src/components/bcms/<Name>` and this package did not write it. Decided on our marker, never on the path — that is a name a person may reasonably have chosen first. |
|
|
218
286
|
| `REGISTRY_CONFLICT` | the same, for `Sections.astro`/`Sections.tsx` or `src/lib/bcms-sections.ts`. Both are rebuilt whole on every run, so an unmarked file there would be destroyed. `--overwrite-helper` proceeds. |
|
|
287
|
+
| `REGISTRY_UNREADABLE` | the registry IS ours and its `REGISTRY`/`SHIPPED` tables cannot be read back — a component it names is GONE from the tree, a row is not a property this can read, or the tables are missing. Every property is parsed structurally and all of them must convert; a partial read is what silently drops a page's bands. It is rebuilt whole every run, so re-emitting it from what this run happened to see would blank those pages. No flag overrides this one: deleting the file is a decision only a person can take. |
|
|
219
288
|
| `ROUTE_FILE_AMBIGUOUS` | two files serve one route. |
|
|
220
289
|
| `DIALECT_UNSUPPORTED` | the page is svelte, vue or html. Nothing is written for it. |
|
|
221
290
|
| `HELPER_CONFLICT` | `src/bcms-content.ts` is a module this package did not write. `--overwrite-helper` proceeds. |
|
|
@@ -236,7 +305,14 @@ brand-new page untouched.
|
|
|
236
305
|
The registry and the sections library are rebuilt WHOLE each run, so they are first read back:
|
|
237
306
|
components an earlier run wrote are reused rather than rewritten, and the shipped order of every
|
|
238
307
|
page already served is carried forward. Without that, componentizing one new page emitted a
|
|
239
|
-
registry that had never heard of the others, and they rendered blank.
|
|
308
|
+
registry that had never heard of the others, and they rendered blank. The read-back goes through
|
|
309
|
+
the dialect's own PARSER, so a formatter that rewrote our imports (single quotes, a wrapped line)
|
|
310
|
+
changes nothing; the two tables are brace-matched with strings and comments skipped and every
|
|
311
|
+
property is parsed structurally, so a one-line table, a wrapped one, or a comment containing a `}`
|
|
312
|
+
are all read whole. An import must resolve to a file that is actually there — composing the path
|
|
313
|
+
from our own naming convention re-emitted a registry importing a component somebody had deleted.
|
|
314
|
+
When any of that cannot be reconstructed the run refuses by name rather than rebuilding from what
|
|
315
|
+
it happens to see.
|
|
240
316
|
|
|
241
317
|
A run whose roots DO share a container but have a foreign sibling between them is not refused: each
|
|
242
318
|
root becomes its own `<Hero …/>` where its markup was, counted `inlineOnly`. The markup is
|
|
@@ -245,17 +321,20 @@ componentised and the ORDER stays the repository's — looping that page would m
|
|
|
245
321
|
The arithmetic is checked rather than hoped for:
|
|
246
322
|
|
|
247
323
|
```
|
|
248
|
-
plan sections === extracted + inlineOnly + alreadyExtracted + pending.length
|
|
324
|
+
plan sections === extracted + inlineOnly + alreadyExtracted + registered + pending.length
|
|
249
325
|
```
|
|
250
326
|
|
|
251
|
-
`
|
|
252
|
-
|
|
327
|
+
`registered` is orthogonal to placement: nothing was written for those sections — the file was
|
|
328
|
+
somebody's before the run and stays theirs — so counting them `extracted` would claim authorship of
|
|
329
|
+
bytes this package never produced. `alreadyExtracted` is the bucket the plan document does not
|
|
330
|
+
name, and it is what makes the round trip observable: a second run over an already-componentised tree reports `extracted: 0,
|
|
253
331
|
pending: []` **and** still accounts for every section. Our own output is recognised by a marker
|
|
254
332
|
comment, never by its path — `src/components/bcms/` is a directory anybody may put a component in.
|
|
255
333
|
|
|
256
334
|
Known leftovers, stated rather than hidden: a page whose every read moved into a component keeps
|
|
257
|
-
the now-unused `bcms`/snapshot imports P2 added
|
|
258
|
-
|
|
335
|
+
the now-unused `bcms`/snapshot imports P2 added, and a page whose registered call sites became one
|
|
336
|
+
`<Sections>` keeps the imports of the components it used to call. They are harmless at build time
|
|
337
|
+
and removing them would mean editing statements no section asked about.
|
|
259
338
|
|
|
260
339
|
## Not here yet
|
|
261
340
|
|