@bettercms-ai/convert 0.2.1 → 0.4.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.d.ts CHANGED
@@ -175,6 +175,29 @@ interface PendingPath {
175
175
  reason: PendingReason;
176
176
  message?: string;
177
177
  }
178
+ /**
179
+ * One path that was already DECLARED before this run — and how honestly.
180
+ *
181
+ * 🔴 `readsFromCms: false` IS THE INTERESTING ROW. An element carrying a `data-bcms-field` (or a
182
+ * `data-bcms-props` / `data-bcms-layout-field`) that gets its copy some other way — a hand-written
183
+ * declaration on markup that still renders a literal, or reads it from the project's own data
184
+ * module — is DECLARED: the publisher writes into it and the editor can click it. It is simply not
185
+ * reading through this package's helper. Reporting it `NO_ORIGINAL` said the opposite of the truth
186
+ * — a path nobody had declared — and sent an agent to convert an element that was already done.
187
+ *
188
+ * The row is informative; `paths.alreadyDeclared` is the number the arithmetic uses, and both
189
+ * shapes count towards it.
190
+ */
191
+ interface DeclaredPath {
192
+ route: string;
193
+ scope: "page" | "layout";
194
+ path: string;
195
+ kind: string;
196
+ /** The file the declaration was found on. */
197
+ file?: string;
198
+ /** Does the declaring element read this value through the committed helper? */
199
+ readsFromCms: boolean;
200
+ }
178
201
  /** One file the conversion touched, and the tier that produced it. A file is exactly one tier. */
179
202
  interface ReceiptFile {
180
203
  path: string;
@@ -187,6 +210,13 @@ interface ConversionReceipt {
187
210
  declared: number;
188
211
  rewritten: number;
189
212
  alreadyDeclared: number;
213
+ /**
214
+ * One row per already-declared path, saying whether it reads from the CMS. @see DeclaredPath
215
+ *
216
+ * OPTIONAL, because a receipt written by an older version of this package does not carry it and
217
+ * a reader must not require it. The COUNT above is the contract; this is the detail.
218
+ */
219
+ declarations?: DeclaredPath[];
190
220
  pending: PendingPath[];
191
221
  };
192
222
  occurrences: {
@@ -205,6 +235,8 @@ interface ConversionReceipt {
205
235
  * a reviewer can see what to look for, and so the scan has somewhere to disagree with.
206
236
  */
207
237
  dynamicBindings: DynamicBinding[];
238
+ /** Facts about the repository that shaped this run. OPTIONAL: an older receipt carries none. */
239
+ notes?: ReceiptNote[];
208
240
  }
209
241
  /**
210
242
  * One binding whose `data-bcms-field` is an expression, and where its value is written literally.
@@ -226,6 +258,19 @@ interface DynamicBinding {
226
258
  literal: string;
227
259
  }[];
228
260
  }
261
+ /**
262
+ * Something about the REPOSITORY that changed what this run could see, named rather than swallowed.
263
+ *
264
+ * Not a path and not a refusal: a note is about a file the conversion READ, and it explains why a
265
+ * later reason may name the wrong cause. `TSCONFIG_UNREADABLE` is the first — a project config this
266
+ * package could not parse declares no `paths` aliases as far as it is concerned, so every aliased
267
+ * import resolves to nothing and the component behind it reads as one the page does not import.
268
+ */
269
+ interface ReceiptNote {
270
+ code: "TSCONFIG_UNREADABLE";
271
+ file: string;
272
+ message: string;
273
+ }
229
274
  /** Raised when the receipt does not add up. Never caught inside this package. */
230
275
  declare class ReceiptInvariantError extends Error {
231
276
  readonly code = "RECEIPT_INVARIANT";
@@ -653,7 +698,7 @@ declare function attrsFor(path: string, kind: string, props?: AttrBinding[], sco
653
698
  declare function propsAttribute(props: AttrBinding[]): string;
654
699
 
655
700
  /** Bumped whenever the emitted source changes. `isKnownHelper` upgrades anything older. */
656
- declare const HELPER_VERSION = 3;
701
+ declare const HELPER_VERSION = 4;
657
702
  /** Where each dialect's helper lives. Null = this dialect reads nothing at build time. */
658
703
  declare const HELPER_PATH: Record<Dialect, string | null>;
659
704
  /** The directory the snapshots live in, at the repository root. */
@@ -712,6 +757,14 @@ declare function isSourceCandidate(path: string): boolean;
712
757
  * specifiers only, which is a smaller conversion and a true one.
713
758
  */
714
759
  declare function aliasesFrom(sources: SourceFile[]): Map<string, string[]>;
760
+ /**
761
+ * The project configs this package could not read, by path — a receipt NOTE, never a silent skip.
762
+ *
763
+ * A config that fails even the tolerant reader is one whose `paths` aliases nobody can resolve, so
764
+ * an aliased import reads as "not imported" and the refusal that follows names the wrong cause.
765
+ * The note says which file to look at. @see ConversionReceipt.notes
766
+ */
767
+ declare function unreadableConfigs(sources: SourceFile[]): string[];
715
768
  /** The repository path a specifier names, or null when it is not among the files we were given. */
716
769
  declare function resolveSpecifier(fromFile: string, specifier: string, files: Set<string>, aliases: Map<string, string[]>): string | null;
717
770
  /** What one local name was imported from, and WHICH export of it. */
@@ -926,6 +979,15 @@ interface ComponentizePlanSection {
926
979
  componentId?: string;
927
980
  proposedSlug?: string;
928
981
  };
982
+ /**
983
+ * WHERE THIS PLACEMENT'S COPY LIVES — Phase B's one new field.
984
+ *
985
+ * `"instance"` says the server moved the group's values onto the instance's `overrides`, so the
986
+ * placement carries no `bind` and its elements declare the BLOCK lane. ABSENT MEANS `"bind"`:
987
+ * a plan written by a Phase A server says nothing here, and a codemod that required the field
988
+ * would refuse every plan older than itself. @see readPlan
989
+ */
990
+ copy?: string;
929
991
  /** The SERVER already decided it cannot place this one. Not this codemod's to extract. */
930
992
  pending?: {
931
993
  reason?: string;
@@ -963,15 +1025,29 @@ interface ComponentizePlan {
963
1025
  * `COMPONENT_CONFLICT` a file already sits at the component's path and this package did
964
1026
  * not write it.
965
1027
  * `REGISTRY_CONFLICT` the same, for the registry or the sections library.
1028
+ * `REGISTRY_UNREADABLE` the registry IS ours, and its tables cannot be read back. It is
1029
+ * rewritten whole every run, so rebuilding it from what this run
1030
+ * happened to see would drop the components it already names.
966
1031
  * `ROUTE_FILE_AMBIGUOUS` two files serve one route; the repository's own router has to
967
1032
  * pick, and a codemod that picks differently edits a dead page.
968
1033
  * `DIALECT_UNSUPPORTED` the page is svelte / vue / html; nothing is written for it.
969
1034
  * `HELPER_CONFLICT` `src/bcms-content.ts` is a module this package did not write.
970
1035
  * `SNAPSHOT_INVALID` `bcms-content/<page>.json` is not JSON this can add `blocks` to.
1036
+ * `COPY_MODE_MISMATCH` `--copy instance` was asked of a plan whose placements are all
1037
+ * still bound to page field groups. @see ComponentizeOptions
1038
+ * `PROP_MAP_INCONSISTENT` the plan's `fields[].path` is not `<one prefix> + fields[].key`,
1039
+ * so a page path cannot be turned into a prop key losslessly.
1040
+ * `DUPLICATE_FIELD_BINDING` one element in the section carries more than one
1041
+ * `data-bcms-field`, and there is no single address to rewrite.
1042
+ * `INLINE_NEEDS_INSTANCE` the page cannot be looped, and the in-place call site the codemod
1043
+ * would write carries no `blockId`/`overrides` — so a placement
1044
+ * whose copy has MOVED could neither render nor address it.
1045
+ * `SECTION_UNMIGRATABLE` an OLDER generated component at this section's path cannot be
1046
+ * upgraded to the current format. @see migrateSections
971
1047
  * `NOT_IN_SOURCE` no file we were given renders the group, or the route names no
972
1048
  * page file this package can find.
973
1049
  */
974
- type SectionPendingReason = "SECTION_ROOT_AMBIGUOUS" | "SECTION_NOT_CONTIGUOUS" | "SECTION_FREE_IDENTIFIERS" | "COMPONENT_CONFLICT" | "REGISTRY_CONFLICT" | "ROUTE_FILE_AMBIGUOUS" | "DIALECT_UNSUPPORTED" | "HELPER_CONFLICT" | "SNAPSHOT_INVALID" | "NOT_IN_SOURCE";
1050
+ type SectionPendingReason = "SECTION_ROOT_AMBIGUOUS" | "SECTION_NOT_CONTIGUOUS" | "SECTION_FREE_IDENTIFIERS" | "COMPONENT_CONFLICT" | "REGISTRY_CONFLICT" | "REGISTRY_UNREADABLE" | "ROUTE_FILE_AMBIGUOUS" | "DIALECT_UNSUPPORTED" | "HELPER_CONFLICT" | "SNAPSHOT_INVALID" | "COPY_MODE_MISMATCH" | "PROP_MAP_INCONSISTENT" | "DUPLICATE_FIELD_BINDING" | "INLINE_NEEDS_INSTANCE" | "SECTION_UNMIGRATABLE" | "NOT_IN_SOURCE";
975
1051
  interface PendingSection {
976
1052
  route: string;
977
1053
  groupKey: string;
@@ -998,11 +1074,31 @@ interface SectionsReceipt {
998
1074
  * account for every section of the plan if "done earlier" is a bucket of its own.
999
1075
  */
1000
1076
  alreadyExtracted: number;
1077
+ /**
1078
+ * The repository ALREADY has the component: the page imports it, and the registry now names it.
1079
+ *
1080
+ * 🔴 ITS OWN BUCKET, AND ORTHOGONAL TO PLACEMENT. Nothing was written for these — the file was
1081
+ * somebody's before this run and stays theirs — so counting them `extracted` would claim
1082
+ * authorship of bytes this package never produced, and counting them `pending` would say the CMS
1083
+ * has no component for a section it plainly does. Whether the page's call sites were then
1084
+ * replaced by the loop is a separate question, answered by `extracted` for the sections this run
1085
+ * did cut.
1086
+ */
1087
+ registered: number;
1001
1088
  pending: PendingSection[];
1002
1089
  }
1003
1090
  interface ComponentizeReceipt {
1004
1091
  /** The plan this run was made against, carried so a receipt can be matched to one. */
1005
1092
  planDigest: string;
1093
+ /**
1094
+ * Which copy model this run was asked for — the same word the server's receipt carries.
1095
+ *
1096
+ * It does NOT change the bytes of an extracted component: the lane an element declares is
1097
+ * decided at render time from the placement's own props, so one file serves both. What it
1098
+ * changes is what the run will accept — `"instance"` against a plan that moved no copy is
1099
+ * `COPY_MODE_MISMATCH` — and what the registry's shipped fallback binds. @see bcmsFields
1100
+ */
1101
+ copy: "bind" | "instance";
1006
1102
  sections: SectionsReceipt;
1007
1103
  /** The component slugs this run wrote a file for, sorted. */
1008
1104
  components: string[];
@@ -1013,6 +1109,15 @@ interface ComponentizeReceipt {
1013
1109
  * that does not place them has done exactly what it was told.
1014
1110
  */
1015
1111
  planPending: number;
1112
+ /**
1113
+ * Facts about the repository that shaped this run — the same list the conversion receipt carries.
1114
+ *
1115
+ * A tsconfig this package cannot read declares no `paths` aliases as far as it is concerned, and
1116
+ * THIS lane resolves a page's `<Hero />` through exactly those aliases: the section then reads as
1117
+ * one the page does not import, and `SECTION_ROOT_AMBIGUOUS` names the wrong cause. The note is
1118
+ * what points at the real one.
1119
+ */
1120
+ notes?: ReceiptNote[];
1016
1121
  }
1017
1122
  /** Raised when the receipt does not account for every section. Never caught inside this package. */
1018
1123
  declare class SectionInvariantError extends Error {
@@ -1030,9 +1135,27 @@ declare function assertSections(total: number, sections: SectionsReceipt): Secti
1030
1135
  interface ComponentizeOptions {
1031
1136
  /** Replace a helper module this package did not write, instead of refusing. The CLI's flag. */
1032
1137
  overwriteHelper?: boolean;
1138
+ /**
1139
+ * WHICH COPY MODEL THE OPERATOR MEANT — `"bind"` (Phase A, the default) or `"instance"`.
1140
+ *
1141
+ * 🔴 AN ASSERTION, NOT A SWITCH. The extracted files are identical either way, so this flag
1142
+ * exists to catch the order-of-operations mistake that would otherwise be silent: running
1143
+ * `--copy instance` in the repository BEFORE the server has moved the copy leaves a tree that
1144
+ * looks converted and renders every section out of page fields the dashboard is about to hide.
1145
+ * A plan whose placements are all still bound says so, and the run refuses by name.
1146
+ */
1147
+ copy?: "bind" | "instance";
1033
1148
  }
1034
- /** The marker that says a file is OURS. Idempotence is decided on it, never on a path. */
1035
- declare const SECTION_MARKER = "// @bettercms-ai/convert section v1";
1149
+ /**
1150
+ * The marker that says a file is OURS — and WHICH FORMAT it is in.
1151
+ *
1152
+ * 🔴 THE VERSION IS LOAD-BEARING, NOT DECORATION. Idempotence is decided on this marker, so a v1
1153
+ * component (0.3: page-path bindings, no render-time lane) read as "already ours, nothing to do"
1154
+ * was skipped forever — every repository converted before Phase B stayed on the page lane, and
1155
+ * `--copy instance` there reported five sections done and moved no address at all. A format change
1156
+ * bumps the version, and anything older is MIGRATED before it is called done. @see migrateSections
1157
+ */
1158
+ declare const SECTION_MARKER = "// @bettercms-ai/convert section v2";
1036
1159
  /** The same, for the two modules that hold the registry rather than a section. */
1037
1160
  declare const REGISTRY_MARKER = "// @bettercms-ai/convert sections v1";
1038
1161
  /** Where the generated components live. Fixed, so a second run knows its own output on sight. */
@@ -1126,4 +1249,4 @@ declare function convertSources(briefIn: Brief, sources: SourceFile[], options?:
1126
1249
  receipt: ConversionReceipt;
1127
1250
  }>;
1128
1251
 
1129
- export { type AstNode, type AttrBinding, type Brief, type BriefPage, type BriefPath, COMPONENT_DIR, CONTENT_DIR, type Carried, type ComponentizeOptions, type ComponentizePlan, type ComponentizePlanComponent, type ComponentizePlanField, type ComponentizePlanPage, type ComponentizePlanSection, type ComponentizeReceipt, type ConversionReceipt, ConvertError, type ConvertErrorCode, type ConvertOptions, DIALECT_RULES, type Dialect, type DynamicBinding, type FileDeclaration, type FindSitesResult, HELPER_PATH, HELPER_VERSION, type ImportedFrom, type LlmFallback, type LocatedPath, type Node, PARSE_FILE, type ParsedFile, type ParserError, type PathLocator, type PendingPath, type PendingReason, type PendingSection, type PlanFile, REGISTRY_MARKER, type ReceiptFile, ReceiptInvariantError, type Rewrite, SECTIONS_LIB, SECTION_MARKER, SKIP_DIRS, SKIP_FILES, SOURCE_EXTENSIONS, STUB_CONTENT, SectionInvariantError, type SectionPendingReason, type SectionsReceipt, type Site, type SiteWhere, type SourceFile, type Splice, type TargetIdentity, type UnlocatedPath, type ValidationCode, ValidationError, aliasesFrom, assertReceipt, assertSections, attrsFor, briefDigest, carryFor, componentizeSources, convertSources, convertedHere, coverageOf, declaringElements, dialectOf, exportedFunction, findSites, findSitesTolerant, flat, helperSource, importsIn, isDynamicRoute, isKnownHelper, isSourceCandidate, locate, overlapping, pageFilesFor, parseFile, propsAttribute, readBrief, readComponentizePlan, readDeclarations, readExpr, readPlan, relativeImport, resolveSpecifier, rewriteFile, routeOfFile, stripTags, walkAst };
1252
+ export { type AstNode, type AttrBinding, type Brief, type BriefPage, type BriefPath, COMPONENT_DIR, CONTENT_DIR, type Carried, type ComponentizeOptions, type ComponentizePlan, type ComponentizePlanComponent, type ComponentizePlanField, type ComponentizePlanPage, type ComponentizePlanSection, type ComponentizeReceipt, type ConversionReceipt, ConvertError, type ConvertErrorCode, type ConvertOptions, DIALECT_RULES, type DeclaredPath, type Dialect, type DynamicBinding, type FileDeclaration, type FindSitesResult, HELPER_PATH, HELPER_VERSION, type ImportedFrom, type LlmFallback, type LocatedPath, type Node, PARSE_FILE, type ParsedFile, type ParserError, type PathLocator, type PendingPath, type PendingReason, type PendingSection, type PlanFile, REGISTRY_MARKER, type ReceiptFile, ReceiptInvariantError, type ReceiptNote, type Rewrite, SECTIONS_LIB, SECTION_MARKER, SKIP_DIRS, SKIP_FILES, SOURCE_EXTENSIONS, STUB_CONTENT, SectionInvariantError, type SectionPendingReason, type SectionsReceipt, type Site, type SiteWhere, type SourceFile, type Splice, type TargetIdentity, type UnlocatedPath, type ValidationCode, ValidationError, aliasesFrom, assertReceipt, assertSections, attrsFor, briefDigest, carryFor, componentizeSources, convertSources, convertedHere, coverageOf, declaringElements, dialectOf, exportedFunction, findSites, findSitesTolerant, flat, helperSource, importsIn, isDynamicRoute, isKnownHelper, isSourceCandidate, locate, overlapping, pageFilesFor, parseFile, propsAttribute, readBrief, readComponentizePlan, readDeclarations, readExpr, readPlan, relativeImport, resolveSpecifier, rewriteFile, routeOfFile, stripTags, unreadableConfigs, walkAst };