@bettercms-ai/convert 0.7.0 → 0.9.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 +81 -0
- package/README.md +103 -6
- package/dist/cli.js +872 -61
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +178 -4
- package/dist/index.js +786 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,86 @@
|
|
|
1
1
|
# @bettercms-ai/convert
|
|
2
2
|
|
|
3
|
+
## 0.9.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 94a00c3: `--forms`: point an imported site's own `<form>` at the CMS form behind it.
|
|
8
|
+
|
|
9
|
+
After an import the release hook derives a draft form row per `<form>` in the built site, so the
|
|
10
|
+
Forms tab fills up while the repository's markup still posts wherever it always did — to nothing,
|
|
11
|
+
or to somebody else's endpoint. Nothing joined the two halves, because the id that does is the
|
|
12
|
+
platform's and lives in the brief.
|
|
13
|
+
|
|
14
|
+
`npx @bettercms-ai/convert --forms --brief brief.json --root . --receipt receipt.json` reads the
|
|
15
|
+
brief's new `forms` array and writes, in one `magic-string` pass: `action`, `method`,
|
|
16
|
+
`data-bcms-form`, `data-bcms-form-success`, a `data-bcms-form-field` per field (on the control's
|
|
17
|
+
wrapper where it has one), the CMS key as each control's `name`, the `<p class="bcms-form-msg">`
|
|
18
|
+
the runtime writes into, and the submit script — the same bytes the hosted renderer ships, held
|
|
19
|
+
byte-for-byte by a test, because the `cf-turnstile-response` hoist in the middle of it is what
|
|
20
|
+
keeps enabling Turnstile from 403ing every submission.
|
|
21
|
+
|
|
22
|
+
The match is strict: a `<form>` is the CMS form's only when the names its controls post cover
|
|
23
|
+
every field key, and exactly one `<form>` answers. Everything else is receipted by name —
|
|
24
|
+
`FORM_NOT_IN_SOURCE`, `FORM_AMBIGUOUS`, `FIELD_UNMATCHED`, `ACTION_IS_EXPRESSION`,
|
|
25
|
+
`DIALECT_UNSUPPORTED`, `PARSE_ERROR` — because a form wired to the wrong id sends a customer's
|
|
26
|
+
leads into another form's inbox, silently, forever. A form already carrying `data-bcms-form` is
|
|
27
|
+
`alreadyWired`, so a second run writes nothing.
|
|
28
|
+
|
|
29
|
+
The receipt rides on `ConversionReceipt.forms` and claims no brief path, so
|
|
30
|
+
`submit_conversion_receipt` takes it unchanged. A form the brief lists as a DRAFT is wired AND
|
|
31
|
+
noted: it rejects every submission with a 403 until somebody publishes it in the Forms tab.
|
|
32
|
+
|
|
33
|
+
Also: `--version` now reports the package's real version. The constant had been stale at `0.4.0`
|
|
34
|
+
for four releases.
|
|
35
|
+
|
|
36
|
+
## 0.8.0
|
|
37
|
+
|
|
38
|
+
### Minor Changes
|
|
39
|
+
|
|
40
|
+
- 8a4e961: Bind array literals written inline in the markup, and drill a repeater row's leaf into the
|
|
41
|
+
component that renders it.
|
|
42
|
+
|
|
43
|
+
`{['Offer', 'Structure', 'RTL'].map((item) => <li>…</li>)}` had no lane at all — the list lane
|
|
44
|
+
searches the module for a NAMED array, and an inline literal has no name — so every one of its
|
|
45
|
+
items came back `IN_EXPRESSION` with the copy in plain sight. The `]` immediately before the
|
|
46
|
+
`.map(` is the literal's close, which is what makes it safe without a name: there is exactly one
|
|
47
|
+
loop over it and nothing else in the file can hold a second reference. A value rendered beside an
|
|
48
|
+
icon (`<li><span>✓</span>{item}</li>`) gets a `<span>` of its own instead of declaring on the
|
|
49
|
+
parent, because a publish writing the value over the parent deletes the icon.
|
|
50
|
+
|
|
51
|
+
`demos.map((demo) => <DemoCard title={demo.title} />)` refused the whole group with
|
|
52
|
+
`PROP_TARGET_NOT_FOUND`: the value is rendered in the component's file, not the page's. It now
|
|
53
|
+
follows the same drill the scalar lane has always used — a row's leaf is that journey with an index
|
|
54
|
+
in the path. The call site declares one `bcmsBindings={{ title: `cards[${i}].title` }}` per element
|
|
55
|
+
and the component gets `data-bcms-field={bcmsBindings?.title}`. `{...row}` counts as passing the
|
|
56
|
+
prop. Every leaf placed or none, as before: a drill that misses refuses the group by its own reason.
|
|
57
|
+
|
|
58
|
+
Also: `tsconfig.json` / `jsconfig.json` are now opened by the scan gate. `aliasesFrom` reads them
|
|
59
|
+
and nothing ever handed it one, so every `paths` alias resolved to nothing and every prop drilled
|
|
60
|
+
through an `@components/*` import refused with the component sitting right there.
|
|
61
|
+
|
|
62
|
+
`bindDataLiterals` is async for this; it is exported, and a caller outside this package has to
|
|
63
|
+
await it.
|
|
64
|
+
|
|
65
|
+
- 07f6648: Bind arrays of primitives and tuples rendered through `.map`.
|
|
66
|
+
|
|
67
|
+
`const promise = [['Once', 'One message'], …]` iterated as `promise.map(([title, text], i) => …)`
|
|
68
|
+
is the same shape as a row of objects one rung simpler, and the derive lane describes it as a
|
|
69
|
+
repeater group all the same. The declaration becomes `bcmsTuples(page, [[…paths…], …], [ …the
|
|
70
|
+
original array, verbatim… ])` — `bcmsList` for a list one wide — and each rendered element carries
|
|
71
|
+
the same templated `data-bcms-field` a repeater writes, so a second run reads it back and changes
|
|
72
|
+
nothing. The committed helper is v6; an existing v5 helper upgrades in place.
|
|
73
|
+
|
|
74
|
+
The read maps over the path list rather than over the CMS array, so every row's text is editable
|
|
75
|
+
and the number of rows is still the template's: the receipt adds one `PRIMITIVE_LIST` note per
|
|
76
|
+
array saying a row added in the CMS does not appear and a deleted one falls back to the template's
|
|
77
|
+
own copy. A list whose paths are unrelated scalars, one used anywhere but that single `.map` or
|
|
78
|
+
`export`ed, one rendered inside a larger expression, one whose tuple position changes kind between
|
|
79
|
+
rows, and one the brief covers only part of are all refused by name.
|
|
80
|
+
|
|
81
|
+
On the Shatter acceptance repository this moves 16 paths from `IN_EXPRESSION` to `rewritten`
|
|
82
|
+
(219 → 235 of 735).
|
|
83
|
+
|
|
3
84
|
## 0.7.0
|
|
4
85
|
|
|
5
86
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -88,6 +88,58 @@ inherits row 0's styling). Each row element then declares
|
|
|
88
88
|
`` data-bcms-field={`cards[${i}].<leaf>`} ``, and the callback gains an index parameter if it had
|
|
89
89
|
none. An imported data module stays `IN_DATA_FILE`.
|
|
90
90
|
|
|
91
|
+
**An array of PRIMITIVES or TUPLES is converted through its paths, not through its name.** The
|
|
92
|
+
same shape one rung simpler: `const deliverables = ["…", "…", "…"]`, or
|
|
93
|
+
`const faqs = [["q", "a"], …]`, iterated by `.map` with the callback rendering the item (or its
|
|
94
|
+
destructured positions) on one element each. There is no row object to key, so every element's
|
|
95
|
+
path is passed to the read:
|
|
96
|
+
|
|
97
|
+
```js
|
|
98
|
+
const faqs = bcmsTuples(bcmsLanding, [["faqs[0].dt-field", "faqs[0].dd-field"], …],
|
|
99
|
+
[ …the original array, verbatim… ]);
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`bcmsList` is the same for a list one wide. Each rendered element declares
|
|
103
|
+
`` data-bcms-field={`faqs[${i}].<leaf>`} `` — the same templated path a repeater writes, so a
|
|
104
|
+
second run reads it back and changes nothing — and the callback gains an index parameter, and a
|
|
105
|
+
jsx row its `key`, if it had none.
|
|
106
|
+
|
|
107
|
+
These arrays render exactly the paths this run wrote. Unlike `bcmsRows`, which reads the array
|
|
108
|
+
itself, `bcmsList`/`bcmsTuples` map over that fixed path list — so every row's TEXT is editable and
|
|
109
|
+
the NUMBER of rows is still the template's: a row added in the CMS has no path and never appears,
|
|
110
|
+
and a row deleted falls back to the copy the template shipped with. The receipt says so out loud,
|
|
111
|
+
once per array, as a `PRIMITIVE_LIST` note. Reading the array itself is the derive lane's job, not a
|
|
112
|
+
fourth shape here.
|
|
113
|
+
|
|
114
|
+
The lane refuses, by name: `IN_EXPRESSION` when the identifier is used anywhere but that one `.map`
|
|
115
|
+
or is `export`ed (the read replaces the value everywhere, including in files this run cannot see),
|
|
116
|
+
when the callback renders the item inside a larger expression, when an element is not a string
|
|
117
|
+
literal, when one tuple position is text in one row and richtext in the next (one element has one
|
|
118
|
+
sink), when the brief's paths do not cover EVERY element of the array, and when the paths are
|
|
119
|
+
unrelated scalars rather than one group's rows — that last one could only be declared as an
|
|
120
|
+
expression nothing downstream can verify; `REPEATER_AMBIGUOUS` when two loops iterate the array.
|
|
121
|
+
|
|
122
|
+
**An array literal written INLINE in the markup is the same lane, with no name between the two
|
|
123
|
+
halves.** `{['Offer', 'Structure', 'RTL'].map((item) => <li>…</li>)}` is what a template reaches
|
|
124
|
+
for whenever the list is four words long — nobody lifts four bullet points into a `const` — and the
|
|
125
|
+
copy then sits inside an expression the matcher is blind to by construction. The `]` immediately
|
|
126
|
+
before the `.map(` IS the literal's close, so there is exactly one loop over it and nothing else in
|
|
127
|
+
the file can hold a second reference to it: no "used anywhere but that one `.map`" test is needed,
|
|
128
|
+
and everything else — the paths, the templated declaration, the index parameter, the refusals — is
|
|
129
|
+
the named lane's. A value rendered BESIDE something else (`<li><span>✓</span>{item}</li>`) gets a
|
|
130
|
+
`<span>` of its own rather than declaring on the parent, because a publish writing the value over
|
|
131
|
+
the parent deletes the icon; the same wrap the scalar lane writes for a mixed element.
|
|
132
|
+
|
|
133
|
+
**A row's leaf handed to a component is drilled, exactly as a scalar prop is.**
|
|
134
|
+
`demos.map((demo) => <DemoCard title={demo.title} />)` renders the value in the COMPONENT's file,
|
|
135
|
+
so the page's call site declares `` bcmsBindings={{ title: `cards[${i}].title` }} `` — one object
|
|
136
|
+
per element, merged when a row hands two leaves to one component — and the component's element gets
|
|
137
|
+
`data-bcms-field={bcmsBindings?.title}` with the prop added to its destructuring and its props
|
|
138
|
+
type. `{...row}` counts as passing the prop: it is how most templates hand a row over. The group
|
|
139
|
+
still binds whole or not at all, so a drill that misses (the component cannot be resolved, nothing
|
|
140
|
+
in it renders the prop, this conversion already rewrites that file, or the call site already
|
|
141
|
+
carries a `bcmsBindings`) refuses the WHOLE group by the drill's own reason.
|
|
142
|
+
|
|
91
143
|
**An image is bound through its import, not through its url.** An image's `original` is the name
|
|
92
144
|
the bundler minted (`/_astro/hero.do77EGgx_ZNfVA8.jpg`) — it exists in no file, and the asset
|
|
93
145
|
itself is not a source candidate, so a text search can never find it. What survives the build is
|
|
@@ -468,14 +520,59 @@ the now-unused `bcms`/snapshot imports P2 added, and a page whose registered cal
|
|
|
468
520
|
`<Sections>` keeps the imports of the components it used to call. They are harmless at build time
|
|
469
521
|
and removing them would mean editing statements no section asked about.
|
|
470
522
|
|
|
523
|
+
## Forms
|
|
524
|
+
|
|
525
|
+
The third mode, and the shortest. The release-time derive lane reads every `<form>` in the built
|
|
526
|
+
site and creates a DRAFT form per distinct shape, so the Forms tab fills on import while the
|
|
527
|
+
repository's own markup goes on posting wherever it always did. `--forms` joins the two halves.
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
# after the import; the brief carries the project's forms
|
|
531
|
+
npx @bettercms-ai/convert --forms --brief brief.json --root . --receipt forms-receipt.json
|
|
532
|
+
git diff
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
```
|
|
536
|
+
modify src/pages/contact.astro
|
|
537
|
+
|
|
538
|
+
1 forms wired (0 already wired, 0 pending)
|
|
539
|
+
todo publish form Contact in the Forms tab — a draft form rejects every submission with 403.
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
It writes six things onto a matched `<form>` and nothing else: `action="<submitUrl>"`,
|
|
543
|
+
`method="post"`, `data-bcms-form="<id>"` (plus `data-bcms-form-success` when the CMS form has a
|
|
544
|
+
success message), `data-bcms-form-field="<key>"` on each matched control, a
|
|
545
|
+
`<p class="bcms-form-msg" hidden>` before `</form>` when there is none, and the submit script once
|
|
546
|
+
per file — the same bytes the hosted renderer ships, pinned by a test. No helper, no snapshot, no
|
|
547
|
+
import: a form posts to an absolute URL at runtime, so there is nothing to read at build time.
|
|
548
|
+
|
|
549
|
+
The match is the FIELD KEYS: a `<form>` is a CMS form's only when the names its controls post are a
|
|
550
|
+
superset of that form's field keys and exactly one `<form>` answers. Everything else is pending with
|
|
551
|
+
a reason — `FORM_NOT_IN_SOURCE`, `FORM_AMBIGUOUS`, `FIELD_UNMATCHED`, `ACTION_IS_EXPRESSION`,
|
|
552
|
+
`DIALECT_UNSUPPORTED`, `PARSE_ERROR` — because a form wired to the wrong id sends the customer's
|
|
553
|
+
leads into another form's inbox, silently, forever. A `<form>` that already carries
|
|
554
|
+
`data-bcms-form` is `alreadyWired` and is left alone, so a second run is a no-op.
|
|
555
|
+
|
|
556
|
+
`--receipt <file>` writes this run's own receipt (`forms-receipt.json` — a separate file from the
|
|
557
|
+
P2 run's, since the two runs describe different work), and `--strict` exits 2 on any pending form.
|
|
558
|
+
`--forms` and `--componentize` are separate runs; doing both in one command is refused.
|
|
559
|
+
|
|
560
|
+
**Publish the drafts afterwards.** The lane creates forms as drafts and the public submit route
|
|
561
|
+
403s a draft, so a wired draft form is an inviting form on a live site that rejects every
|
|
562
|
+
submission. The receipt carries one `notes` line per draft naming the form to publish in the Forms
|
|
563
|
+
tab; that is the step no codemod can do for you.
|
|
564
|
+
|
|
471
565
|
## Not here yet
|
|
472
566
|
|
|
473
|
-
An array of
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
567
|
+
GROWABLE lists. An array of primitives or tuples is bound (above) but renders the number of rows
|
|
568
|
+
the template shipped with, because the read maps over a fixed path list rather than over the CMS
|
|
569
|
+
array — and when the brief gave its elements unrelated scalar paths (`li-field`, `li-rtl`, …)
|
|
570
|
+
rather than one group's rows, it is not bound at all: the only declaration that could carry a
|
|
571
|
+
different path per row is an expression, and one nothing downstream can verify is worse than the
|
|
572
|
+
honest `IN_EXPRESSION`. Both are the same fix on the derive side — minting the group — not a
|
|
573
|
+
further shape here. Svelte and vue data literals are not converted
|
|
574
|
+
either; all three lanes are written against the two dialects that keep their module and their
|
|
575
|
+
markup in one file with an AST this package already holds.
|
|
479
576
|
|
|
480
577
|
SHARED CHROME the brief still calls page copy. A header rendered by one component that nine routes
|
|
481
578
|
each declare a `nav[0].label` for is nine targets on one element, and there is no honest binding: a
|