@caretcms/caretize 0.1.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.
Files changed (102) hide show
  1. package/README.md +70 -0
  2. package/dist/backup.d.ts +30 -0
  3. package/dist/backup.d.ts.map +1 -0
  4. package/dist/backup.js +89 -0
  5. package/dist/backup.js.map +1 -0
  6. package/dist/bind-collection.d.ts +56 -0
  7. package/dist/bind-collection.d.ts.map +1 -0
  8. package/dist/bind-collection.js +140 -0
  9. package/dist/bind-collection.js.map +1 -0
  10. package/dist/bind-route.d.ts +40 -0
  11. package/dist/bind-route.d.ts.map +1 -0
  12. package/dist/bind-route.js +150 -0
  13. package/dist/bind-route.js.map +1 -0
  14. package/dist/cli-args.d.ts +30 -0
  15. package/dist/cli-args.d.ts.map +1 -0
  16. package/dist/cli-args.js +118 -0
  17. package/dist/cli-args.js.map +1 -0
  18. package/dist/cli.d.ts +11 -0
  19. package/dist/cli.d.ts.map +1 -0
  20. package/dist/cli.js +356 -0
  21. package/dist/cli.js.map +1 -0
  22. package/dist/detect.d.ts +76 -0
  23. package/dist/detect.d.ts.map +1 -0
  24. package/dist/detect.js +237 -0
  25. package/dist/detect.js.map +1 -0
  26. package/dist/discover.d.ts +13 -0
  27. package/dist/discover.d.ts.map +1 -0
  28. package/dist/discover.js +84 -0
  29. package/dist/discover.js.map +1 -0
  30. package/dist/frontmatter.d.ts +26 -0
  31. package/dist/frontmatter.d.ts.map +1 -0
  32. package/dist/frontmatter.js +52 -0
  33. package/dist/frontmatter.js.map +1 -0
  34. package/dist/identifiers.d.ts +26 -0
  35. package/dist/identifiers.d.ts.map +1 -0
  36. package/dist/identifiers.js +34 -0
  37. package/dist/identifiers.js.map +1 -0
  38. package/dist/import-wrap.d.ts +56 -0
  39. package/dist/import-wrap.d.ts.map +1 -0
  40. package/dist/import-wrap.js +149 -0
  41. package/dist/import-wrap.js.map +1 -0
  42. package/dist/index.d.ts +18 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +18 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/name.d.ts +49 -0
  47. package/dist/name.d.ts.map +1 -0
  48. package/dist/name.js +164 -0
  49. package/dist/name.js.map +1 -0
  50. package/dist/output.d.ts +30 -0
  51. package/dist/output.d.ts.map +1 -0
  52. package/dist/output.js +110 -0
  53. package/dist/output.js.map +1 -0
  54. package/dist/parse.d.ts +86 -0
  55. package/dist/parse.d.ts.map +1 -0
  56. package/dist/parse.js +92 -0
  57. package/dist/parse.js.map +1 -0
  58. package/dist/plan.d.ts +46 -0
  59. package/dist/plan.d.ts.map +1 -0
  60. package/dist/plan.js +76 -0
  61. package/dist/plan.js.map +1 -0
  62. package/dist/preflight.d.ts +19 -0
  63. package/dist/preflight.d.ts.map +1 -0
  64. package/dist/preflight.js +95 -0
  65. package/dist/preflight.js.map +1 -0
  66. package/dist/prop-hoist.d.ts +67 -0
  67. package/dist/prop-hoist.d.ts.map +1 -0
  68. package/dist/prop-hoist.js +232 -0
  69. package/dist/prop-hoist.js.map +1 -0
  70. package/dist/props.d.ts +38 -0
  71. package/dist/props.d.ts.map +1 -0
  72. package/dist/props.js +116 -0
  73. package/dist/props.js.map +1 -0
  74. package/dist/report.d.ts +42 -0
  75. package/dist/report.d.ts.map +1 -0
  76. package/dist/report.js +35 -0
  77. package/dist/report.js.map +1 -0
  78. package/dist/resolve.d.ts +15 -0
  79. package/dist/resolve.d.ts.map +1 -0
  80. package/dist/resolve.js +35 -0
  81. package/dist/resolve.js.map +1 -0
  82. package/dist/run.d.ts +52 -0
  83. package/dist/run.d.ts.map +1 -0
  84. package/dist/run.js +213 -0
  85. package/dist/run.js.map +1 -0
  86. package/dist/splice.d.ts +43 -0
  87. package/dist/splice.d.ts.map +1 -0
  88. package/dist/splice.js +90 -0
  89. package/dist/splice.js.map +1 -0
  90. package/dist/usage.d.ts +90 -0
  91. package/dist/usage.d.ts.map +1 -0
  92. package/dist/usage.js +249 -0
  93. package/dist/usage.js.map +1 -0
  94. package/dist/wrap.d.ts +72 -0
  95. package/dist/wrap.d.ts.map +1 -0
  96. package/dist/wrap.js +170 -0
  97. package/dist/wrap.js.map +1 -0
  98. package/dist/write.d.ts +28 -0
  99. package/dist/write.d.ts.map +1 -0
  100. package/dist/write.js +37 -0
  101. package/dist/write.js.map +1 -0
  102. package/package.json +54 -0
package/dist/usage.js ADDED
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Usage safety classifier — the single safety primitive both wrap tiers depend on.
3
+ *
4
+ * `editable()` stega-encodes EVERY string leaf of the value it wraps (see
5
+ * `@caretcms/core`'s encodeLeaves). An encoded string is invisible and harmless
6
+ * when it renders as visible TEXT, but it CORRUPTS anything it lands in as an
7
+ * attribute — a broken href/src/class/id/datetime. So a literal const is only
8
+ * safe to wrap when every field that flows out of it reaches the DOM as text and
9
+ * never as a native-element attribute.
10
+ *
11
+ * This module answers exactly that, from the `@astrojs/compiler` AST:
12
+ * - native element (`type:"element"`) + a DYNAMIC attribute that references a
13
+ * field of the value → UNSAFE.
14
+ * - native element text position (`{item.title}`) → safe.
15
+ * - component (`type:"component"`) attribute that references a field → a prop
16
+ * hand-off; reported so a cross-file pass can verify the child (Tier-2),
17
+ * and — depending on `componentHandoffUnsafe` — either tolerated (Tier-1,
18
+ * preserving prior behavior) or treated as unprovable/UNSAFE (Tier-2 child).
19
+ *
20
+ * The bias is conservative throughout: anything we cannot positively prove is
21
+ * text-only is reported UNSAFE. False negatives (a safe const left unwrapped)
22
+ * are acceptable; a false positive (encoding into an attribute) is not. In line
23
+ * with that, the loop-variable scan over-approximates — within an expression
24
+ * that mentions the const, EVERY `.map()`/`.flatMap()` binding is treated as
25
+ * carrying a field of it, so nested and chained loops can never smuggle a field
26
+ * into an attribute unnoticed.
27
+ */
28
+ import { walkTags, isTagNode } from "./parse.js";
29
+ import { IDENT, identRefRe, referencesIdent } from "./identifiers.js";
30
+ const HTML_DIRECTIVES = new Set(["set:html", "set:text"]);
31
+ /** Concatenated raw JS text of an expression node's direct text children. */
32
+ function exprJs(node) {
33
+ let s = "";
34
+ for (const child of node.children ?? []) {
35
+ if (child.type === "text")
36
+ s += child.value ?? "";
37
+ }
38
+ return s;
39
+ }
40
+ /** The first parameter of an arrow/function param list, respecting brackets. */
41
+ function firstParam(paramSrc) {
42
+ let depth = 0;
43
+ let out = "";
44
+ for (const c of paramSrc) {
45
+ if (c === "{" || c === "[" || c === "(")
46
+ depth++;
47
+ else if (c === "}" || c === "]" || c === ")")
48
+ depth--;
49
+ else if (c === "," && depth === 0)
50
+ break;
51
+ out += c;
52
+ }
53
+ return out.trim();
54
+ }
55
+ /** Identifiers bound by a single callback parameter (plain or destructured). */
56
+ function paramIdents(param) {
57
+ if (!param)
58
+ return [];
59
+ if (param.startsWith("{") || param.startsWith("[")) {
60
+ // Destructuring: collect every binding identifier. For `{ a: b }` the bound
61
+ // local is `b`; over-collecting (keeping `a` too) only makes us more
62
+ // conservative, which is safe. Rest elements (`...rest`) included likewise.
63
+ const out = [];
64
+ for (const tok of param.replace(/[[\]{}.]/g, " ").split(/[,\s:=]+/)) {
65
+ const t = tok.trim();
66
+ if (IDENT.test(t))
67
+ out.push(t);
68
+ }
69
+ return out;
70
+ }
71
+ // Plain param, possibly with a default (`item = {}`): keep the binding name.
72
+ const name = param.split("=")[0].trim();
73
+ return IDENT.test(name) ? [name] : [];
74
+ }
75
+ /** Read the callback's first-parameter source starting just after `map(`. */
76
+ function readCallbackFirstParam(js, from) {
77
+ let i = from;
78
+ const skipWs = () => {
79
+ while (i < js.length && /\s/.test(js[i]))
80
+ i++;
81
+ };
82
+ skipWs();
83
+ if (js.startsWith("async", i) && /[\s(]/.test(js[i + 5] ?? ""))
84
+ i += 5; // async arrow
85
+ skipWs();
86
+ if (js.startsWith("function", i)) {
87
+ const paren = js.indexOf("(", i);
88
+ if (paren < 0)
89
+ return "";
90
+ i = paren;
91
+ }
92
+ if (js[i] === "(") {
93
+ let depth = 0;
94
+ let src = "";
95
+ for (; i < js.length; i++) {
96
+ const c = js[i];
97
+ if (c === "(")
98
+ depth++;
99
+ else if (c === ")") {
100
+ depth--;
101
+ if (depth === 0)
102
+ break;
103
+ }
104
+ else if (depth >= 1)
105
+ src += c;
106
+ }
107
+ return firstParam(src);
108
+ }
109
+ // Parenless arrow: `x => …` — read up to `=>`, `,` or `)`.
110
+ let src = "";
111
+ while (i < js.length && !/[=,)\s]/.test(js[i]))
112
+ src += js[i++];
113
+ return src;
114
+ }
115
+ /**
116
+ * Loop-binding identifiers introduced by `.map()`/`.flatMap()` callbacks in a JS
117
+ * fragment. With `receiver`, only loops over that exact variable are read; with
118
+ * no receiver, EVERY map/flatMap is read (the conservative scan used during
119
+ * classification, so chained/nested loops can't hide a field).
120
+ */
121
+ export function mapParamIdents(js, receiver) {
122
+ const idents = new Set();
123
+ const head = receiver
124
+ ? `${identRefRe(receiver).source}\\s*\\.\\s*(?:map|flatMap)\\s*\\(`
125
+ : `\\.\\s*(?:map|flatMap)\\s*\\(`;
126
+ const re = new RegExp(head, "g");
127
+ let m;
128
+ while ((m = re.exec(js))) {
129
+ for (const id of paramIdents(readCallbackFirstParam(js, m.index + m[0].length))) {
130
+ idents.add(id);
131
+ }
132
+ }
133
+ return [...idents];
134
+ }
135
+ /** Attribute kinds that can carry a JS reference (static `quoted`/`empty` cannot). */
136
+ function attrIsDynamic(kind) {
137
+ return kind === "expression" || kind === "template-literal" || kind === "shorthand" || kind === "spread";
138
+ }
139
+ /** Does this attribute reference any of `names` (its value, raw, or shorthand/spread name)? */
140
+ function attrReferences(attr, names) {
141
+ if (attr.kind === "shorthand" || attr.kind === "spread") {
142
+ // `{name}` / `{...name}` — the identifier is the attribute name.
143
+ return names.includes(attr.name) || referencesIdent(attr.name, names);
144
+ }
145
+ return referencesIdent(`${attr.value} ${attr.raw ?? ""}`, names);
146
+ }
147
+ /**
148
+ * Classify how a literal const `varName` is consumed in a parsed template.
149
+ *
150
+ * For every tag node, the field-carrying identifiers in scope are `varName`
151
+ * itself plus the binding variables of any ancestor expression that mentions
152
+ * `varName` and loops. A dynamic attribute referencing one of those names is the
153
+ * encoded value escaping into a sink: a native element → UNSAFE; a component → a
154
+ * recorded prop hand-off (and UNSAFE too under `componentHandoffUnsafe`). Text
155
+ * positions (`{item.x}` as an element's child) are never attributes, so they
156
+ * never trip this — exactly the leaves `editable()` can safely encode.
157
+ */
158
+ export function classifyConstUsage(root, varName, opts = {}) {
159
+ const handoffs = [];
160
+ let unsafe;
161
+ const varRe = identRefRe(varName);
162
+ walkTags(root, (node, ancestors) => {
163
+ const names = [varName];
164
+ for (const anc of ancestors) {
165
+ if (anc.type !== "expression")
166
+ continue;
167
+ const js = exprJs(anc);
168
+ if (varRe.test(js))
169
+ names.push(...mapParamIdents(js));
170
+ }
171
+ for (const attr of node.attributes) {
172
+ if (!attrIsDynamic(attr.kind))
173
+ continue; // static `quoted`/`empty` are inert
174
+ if (!attrReferences(attr, names))
175
+ continue;
176
+ if (node.type === "element") {
177
+ // set:html / set:text render the value as CONTENT, not a real attribute;
178
+ // stega survives there, so (when opted in) it's a safe text sink.
179
+ if (opts.htmlDirectivesSafe && HTML_DIRECTIVES.has(attr.name))
180
+ continue;
181
+ // Encoded field would land in a real DOM attribute → corrupting.
182
+ unsafe ??= `field of "${varName}" used in <${node.name}> attribute "${attr.name}"`;
183
+ }
184
+ else if (node.type === "component") {
185
+ handoffs.push({ component: node.name, prop: attr.name });
186
+ if (opts.componentHandoffUnsafe) {
187
+ unsafe ??= `field of "${varName}" handed to <${node.name}> prop "${attr.name}" (child not verified)`;
188
+ }
189
+ }
190
+ else {
191
+ // custom-element / fragment — unknown rendering; be conservative.
192
+ unsafe ??= `field of "${varName}" used on <${node.name}> (unverifiable)`;
193
+ }
194
+ }
195
+ });
196
+ if (unsafe)
197
+ return { safe: false, reason: unsafe, handoffs };
198
+ return { safe: true, handoffs };
199
+ }
200
+ /** Elements that live in (or feed) the document `<head>`. A field rendered only
201
+ * here is text-safe for stega, but the inline editor's click-to-edit can never
202
+ * surface it — so it isn't a real editable sink. */
203
+ const HEAD_ELEMENTS = new Set(["head", "title", "meta", "base", "link"]);
204
+ function inHead(ancestors, node) {
205
+ if (node && isTagNode(node) && HEAD_ELEMENTS.has(node.name))
206
+ return true;
207
+ return ancestors.some((a) => isTagNode(a) && HEAD_ELEMENTS.has(a.name));
208
+ }
209
+ /**
210
+ * Does the template render any of `names` in a BODY-reachable position — a text
211
+ * expression (`{title}`) or a `set:html`/`set:text` content sink — that is NOT
212
+ * inside the document `<head>`?
213
+ *
214
+ * `classifyConstUsage` proves a field never lands in a corrupting attribute, but
215
+ * a field that renders ONLY into `<head>` (a layout's `<title>{title}</title>`)
216
+ * is text-safe yet has no click-to-edit target. The prop-hoist tier requires a
217
+ * positive body sink so it doesn't mint `editable()` bindings the editor can't
218
+ * surface (and, incidentally, won't hoist a prop the child never renders at all).
219
+ */
220
+ export function rendersOutsideHead(root, names) {
221
+ let found = false;
222
+ const stack = [];
223
+ const visit = (node) => {
224
+ if (found || !node || typeof node !== "object")
225
+ return;
226
+ if (node.type === "expression" && referencesIdent(exprJs(node), names) && !inHead(stack)) {
227
+ found = true;
228
+ return;
229
+ }
230
+ if (isTagNode(node) && !inHead(stack, node)) {
231
+ for (const attr of node.attributes) {
232
+ if (HTML_DIRECTIVES.has(attr.name) && attrReferences(attr, names)) {
233
+ found = true;
234
+ return;
235
+ }
236
+ }
237
+ }
238
+ const children = node.children;
239
+ if (Array.isArray(children)) {
240
+ stack.push(node);
241
+ for (const child of children)
242
+ visit(child);
243
+ stack.pop();
244
+ }
245
+ };
246
+ visit(root);
247
+ return found;
248
+ }
249
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sourceRoot":"","sources":["../src/usage.ts"],"names":[],"mappings":"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"}
package/dist/wrap.d.ts ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Tier-1 editable() wrapping — PURE INSERTION.
3
+ *
4
+ * Wraps a frontmatter `const`/`let`/`var` initializer with
5
+ * `await editable('key', …)` and inserts the import, WITHOUT moving any code:
6
+ * the original initializer is preserved verbatim between two inserted spans, so
7
+ * the transform is provably reversible (strip the inserted text → original
8
+ * restored) — the same safety contract as caretize's attribute insertion.
9
+ *
10
+ * const services = ['a', 'b'];
11
+ * → const services = await editable("pages::home::services", ['a', 'b']);
12
+ * (+ `import { editable } from '@caretcms/core';` at the top of frontmatter)
13
+ *
14
+ * Restructuring cases (an inline array literal inside JSX that must be hoisted
15
+ * to frontmatter) are intentionally NOT handled here — that breaks pure
16
+ * insertion and belongs to a separate, opt-in migrator.
17
+ */
18
+ import { type AstroNode } from "./parse.js";
19
+ /** The `editable()` import line + a detector for it, shared with import-wrap.ts. */
20
+ export declare const IMPORT_LINE = "import { editable } from '@caretcms/core';";
21
+ export declare const IMPORT_RE: RegExp;
22
+ /** A literal const the wrapper could target, before its provenance is known. */
23
+ export interface WrapCandidate {
24
+ /** Frontmatter const/let/var to wrap. */
25
+ varName: string;
26
+ /** Binding key, e.g. `pages::home::services`. */
27
+ key: string;
28
+ }
29
+ /**
30
+ * How a wrap target was found:
31
+ * - `loop` — a same-file literal const iterated in the template (Tier-1)
32
+ * - `prop` — a literal const passed to a component that renders it as text (Tier-2)
33
+ * - `import` — a default JSON/module import iterated in the template (Tier-3)
34
+ */
35
+ export type WrapOrigin = "loop" | "prop" | "import";
36
+ /** A safety-verified wrap target, tagged with how it was found. */
37
+ export interface WrapTarget extends WrapCandidate {
38
+ origin: WrapOrigin;
39
+ }
40
+ export interface WrapResult {
41
+ output: string;
42
+ ok: boolean;
43
+ /** Reason when ok === false. */
44
+ reason?: string;
45
+ /** True when the source already had the wrap (no-op, idempotent). */
46
+ alreadyWrapped?: boolean;
47
+ }
48
+ /**
49
+ * Wrap the initializer of `const <varName> = …` in the frontmatter with
50
+ * `await editable('<key>', …)` and ensure the import is present. Pure insertion.
51
+ */
52
+ export declare function wrapConst(source: string, varName: string, key: string): WrapResult;
53
+ /**
54
+ * Detect Tier-1 wrap targets: frontmatter consts initialized to an array/object
55
+ * LITERAL that are `.map()`/`.flatMap()`'d in the template. Only literal-inited
56
+ * consts qualify — loops over fetched data (getCollection/await) or imports
57
+ * aren't inline content and are left alone. The key is derived from the file's
58
+ * scope (`collection::id`) plus the variable name as the field.
59
+ */
60
+ export declare function detectWrapTargets(source: string, relPath: string): WrapCandidate[];
61
+ /**
62
+ * Safety-checked Tier-1 detection: the literal-const candidates from
63
+ * `detectWrapTargets`, minus any whose mapped fields flow into a native-element
64
+ * attribute (where `editable()`'s stega encoding would corrupt an href/src/class
65
+ * /…). Component prop hand-offs are tolerated here — Tier-1's long-standing
66
+ * behavior — and verified across files by the Tier-2 prop pass instead.
67
+ *
68
+ * Async because the safety check needs the parsed AST; `root` may be passed when
69
+ * the caller has already parsed `source` (the CLI does, to avoid re-parsing).
70
+ */
71
+ export declare function detectWrapTargetsSafe(source: string, relPath: string, root?: AstroNode): Promise<WrapTarget[]>;
72
+ //# sourceMappingURL=wrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAKxD,oFAAoF;AACpF,eAAO,MAAM,WAAW,+CAA+C,CAAC;AACxE,eAAO,MAAM,SAAS,QACkD,CAAC;AAEzE,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEpD,mEAAmE;AACnE,MAAM,WAAW,UAAW,SAAQ,aAAa;IAC/C,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAmCD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAiDlF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,EAAE,CA+BlF;AAED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,SAAS,GACf,OAAO,CAAC,UAAU,EAAE,CAAC,CAOvB"}
package/dist/wrap.js ADDED
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Tier-1 editable() wrapping — PURE INSERTION.
3
+ *
4
+ * Wraps a frontmatter `const`/`let`/`var` initializer with
5
+ * `await editable('key', …)` and inserts the import, WITHOUT moving any code:
6
+ * the original initializer is preserved verbatim between two inserted spans, so
7
+ * the transform is provably reversible (strip the inserted text → original
8
+ * restored) — the same safety contract as caretize's attribute insertion.
9
+ *
10
+ * const services = ['a', 'b'];
11
+ * → const services = await editable("pages::home::services", ['a', 'b']);
12
+ * (+ `import { editable } from '@caretcms/core';` at the top of frontmatter)
13
+ *
14
+ * Restructuring cases (an inline array literal inside JSX that must be hoisted
15
+ * to frontmatter) are intentionally NOT handled here — that breaks pure
16
+ * insertion and belongs to a separate, opt-in migrator.
17
+ */
18
+ import { deriveScope, isValidField, slugifyText } from "./name.js";
19
+ import { parseAstro } from "./parse.js";
20
+ import { classifyConstUsage } from "./usage.js";
21
+ import { frontmatterRange, literalConstNames } from "./frontmatter.js";
22
+ import { isIdentifier } from "./identifiers.js";
23
+ /** The `editable()` import line + a detector for it, shared with import-wrap.ts. */
24
+ export const IMPORT_LINE = `import { editable } from '@caretcms/core';`;
25
+ export const IMPORT_RE = /import\s*\{[^}]*\beditable\b[^}]*\}\s*from\s*['"]@caretcms\/core['"]/;
26
+ /**
27
+ * Given the offset of `=`, return the offset just past the initializer
28
+ * expression by balancing brackets and skipping strings. Stops at a `;` or
29
+ * newline encountered at bracket-depth 0.
30
+ */
31
+ function initializerEnd(source, eq, limit) {
32
+ let i = eq + 1;
33
+ while (i < limit && /\s/.test(source[i]))
34
+ i++;
35
+ if (i >= limit)
36
+ return null;
37
+ let depth = 0;
38
+ let quote = null;
39
+ for (; i < limit; i++) {
40
+ const c = source[i];
41
+ if (quote) {
42
+ if (c === "\\") {
43
+ i++;
44
+ continue;
45
+ }
46
+ if (c === quote)
47
+ quote = null;
48
+ continue;
49
+ }
50
+ if (c === '"' || c === "'" || c === "`") {
51
+ quote = c;
52
+ continue;
53
+ }
54
+ if (c === "[" || c === "(" || c === "{")
55
+ depth++;
56
+ else if (c === "]" || c === ")" || c === "}")
57
+ depth--;
58
+ else if (depth === 0 && (c === ";" || c === "\n"))
59
+ return i;
60
+ }
61
+ return depth === 0 ? limit : null;
62
+ }
63
+ /**
64
+ * Wrap the initializer of `const <varName> = …` in the frontmatter with
65
+ * `await editable('<key>', …)` and ensure the import is present. Pure insertion.
66
+ */
67
+ export function wrapConst(source, varName, key) {
68
+ if (!isIdentifier(varName)) {
69
+ return { output: source, ok: false, reason: `invalid identifier: ${varName}` };
70
+ }
71
+ const fm = frontmatterRange(source);
72
+ if (!fm)
73
+ return { output: source, ok: false, reason: "no frontmatter block" };
74
+ const fmText = source.slice(fm.start, fm.end);
75
+ const declRe = new RegExp(`\\b(?:const|let|var)\\s+${varName}\\b`);
76
+ const m = declRe.exec(fmText);
77
+ if (!m) {
78
+ return { output: source, ok: false, reason: `declaration of ${varName} not found in frontmatter` };
79
+ }
80
+ const eqRel = fmText.indexOf("=", m.index + m[0].length);
81
+ if (eqRel < 0)
82
+ return { output: source, ok: false, reason: "no initializer" };
83
+ const eqAbs = fm.start + eqRel;
84
+ // Idempotent: bail if already wrapped.
85
+ if (/^\s*await\s+editable\s*\(/.test(source.slice(eqAbs + 1, eqAbs + 48))) {
86
+ return { output: source, ok: true, alreadyWrapped: true };
87
+ }
88
+ let initStart = eqAbs + 1;
89
+ while (initStart < fm.end && /\s/.test(source[initStart]))
90
+ initStart++;
91
+ const initEnd = initializerEnd(source, eqAbs, fm.end);
92
+ if (initEnd === null || initStart >= fm.end) {
93
+ return { output: source, ok: false, reason: "could not bound initializer" };
94
+ }
95
+ const prefix = `await editable(${JSON.stringify(key)}, `;
96
+ const suffix = `)`;
97
+ // Pure insertion: keep every original character, add prefix/suffix around the
98
+ // untouched initializer.
99
+ let output = source.slice(0, initStart) +
100
+ prefix +
101
+ source.slice(initStart, initEnd) +
102
+ suffix +
103
+ source.slice(initEnd);
104
+ // Add the import at the top of the frontmatter unless it's already there.
105
+ if (!IMPORT_RE.test(source)) {
106
+ output = output.slice(0, fm.start) + IMPORT_LINE + "\n" + output.slice(fm.start);
107
+ }
108
+ return { output, ok: true };
109
+ }
110
+ /**
111
+ * Detect Tier-1 wrap targets: frontmatter consts initialized to an array/object
112
+ * LITERAL that are `.map()`/`.flatMap()`'d in the template. Only literal-inited
113
+ * consts qualify — loops over fetched data (getCollection/await) or imports
114
+ * aren't inline content and are left alone. The key is derived from the file's
115
+ * scope (`collection::id`) plus the variable name as the field.
116
+ */
117
+ export function detectWrapTargets(source, relPath) {
118
+ const fm = frontmatterRange(source);
119
+ if (!fm)
120
+ return [];
121
+ const fmText = source.slice(fm.start, fm.end);
122
+ const template = source.slice(fm.end);
123
+ // 1. frontmatter consts whose initializer is an array/object literal
124
+ const literalConsts = literalConstNames(fmText);
125
+ if (literalConsts.size === 0)
126
+ return [];
127
+ // 2. of those, the ones actually iterated in the template
128
+ const mapped = new Set();
129
+ const mapRe = /\b([A-Za-z_$][\w$]*)\s*\.\s*(?:map|flatMap)\b/g;
130
+ let mm;
131
+ while ((mm = mapRe.exec(template))) {
132
+ if (literalConsts.has(mm[1]))
133
+ mapped.add(mm[1]);
134
+ }
135
+ if (mapped.size === 0)
136
+ return [];
137
+ // 3. derive the scope + a key per target
138
+ const scoped = deriveScope(relPath);
139
+ if ("skip" in scoped)
140
+ return [];
141
+ const { collection, id } = scoped.scope;
142
+ const candidates = [];
143
+ for (const varName of mapped) {
144
+ const field = isValidField(varName) ? varName : slugifyText(varName);
145
+ if (!field)
146
+ continue;
147
+ candidates.push({ varName, key: `${collection}::${id}::${field}` });
148
+ }
149
+ return candidates;
150
+ }
151
+ /**
152
+ * Safety-checked Tier-1 detection: the literal-const candidates from
153
+ * `detectWrapTargets`, minus any whose mapped fields flow into a native-element
154
+ * attribute (where `editable()`'s stega encoding would corrupt an href/src/class
155
+ * /…). Component prop hand-offs are tolerated here — Tier-1's long-standing
156
+ * behavior — and verified across files by the Tier-2 prop pass instead.
157
+ *
158
+ * Async because the safety check needs the parsed AST; `root` may be passed when
159
+ * the caller has already parsed `source` (the CLI does, to avoid re-parsing).
160
+ */
161
+ export async function detectWrapTargetsSafe(source, relPath, root) {
162
+ const candidates = detectWrapTargets(source, relPath);
163
+ if (candidates.length === 0)
164
+ return [];
165
+ const ast = root ?? (await parseAstro(source));
166
+ return candidates
167
+ .filter((t) => classifyConstUsage(ast, t.varName).safe)
168
+ .map((t) => ({ ...t, origin: "loop" }));
169
+ }
170
+ //# sourceMappingURL=wrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.js","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The multi-tag writer primitive: splice many `data-caret` attributes into one
3
+ * source. Works on a UTF-8 Buffer (byte offsets) and applies insertions
4
+ * right-to-left so each splice leaves the not-yet-applied (smaller) offsets
5
+ * valid. Pure insertion only — never reformats.
6
+ *
7
+ * This is the in-memory core; the CLI layer wraps it with backups + atomic
8
+ * file writes + a re-parse verification gate.
9
+ */
10
+ export interface TagInsertion {
11
+ /** Byte offset of the target element's opening `<`. */
12
+ startOffset: number;
13
+ /** Attribute text WITHOUT a leading space, e.g. `data-caret="a::b::c"`. */
14
+ attribute: string;
15
+ }
16
+ export interface ApplyResult {
17
+ output: string;
18
+ /** The exact strings inserted (each with its leading space), in source
19
+ * order — useful for reversibility checks. */
20
+ inserted: string[];
21
+ /** Insertions that could not be placed (their opening tag end wasn't found). */
22
+ failures: Array<{
23
+ startOffset: number;
24
+ reason: string;
25
+ }>;
26
+ }
27
+ export declare function applyTags(source: string, items: TagInsertion[]): ApplyResult;
28
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf;mDAC+C;IAC/C,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gFAAgF;IAChF,QAAQ,EAAE,KAAK,CAAC;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,GAAG,WAAW,CA8B5E"}
package/dist/write.js ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The multi-tag writer primitive: splice many `data-caret` attributes into one
3
+ * source. Works on a UTF-8 Buffer (byte offsets) and applies insertions
4
+ * right-to-left so each splice leaves the not-yet-applied (smaller) offsets
5
+ * valid. Pure insertion only — never reformats.
6
+ *
7
+ * This is the in-memory core; the CLI layer wraps it with backups + atomic
8
+ * file writes + a re-parse verification gate.
9
+ */
10
+ import { findOpenTagEnd } from "./splice.js";
11
+ export function applyTags(source, items) {
12
+ const buf = Buffer.from(source, "utf8");
13
+ const resolved = [];
14
+ const failures = [];
15
+ for (const item of items) {
16
+ const found = findOpenTagEnd(buf, item.startOffset);
17
+ if (!found) {
18
+ failures.push({ startOffset: item.startOffset, reason: "open tag end not found" });
19
+ continue;
20
+ }
21
+ resolved.push({ insertAt: found.insertAt, text: ` ${item.attribute}` });
22
+ }
23
+ // Apply largest-offset-first so earlier splices don't shift later targets.
24
+ resolved.sort((a, b) => b.insertAt - a.insertAt);
25
+ let out = buf;
26
+ for (const r of resolved) {
27
+ out = Buffer.concat([
28
+ out.subarray(0, r.insertAt),
29
+ Buffer.from(r.text, "utf8"),
30
+ out.subarray(r.insertAt),
31
+ ]);
32
+ }
33
+ // Report inserted strings in source order (resolved is currently desc).
34
+ const inserted = [...resolved].sort((a, b) => a.insertAt - b.insertAt).map((r) => r.text);
35
+ return { output: out.toString("utf8"), inserted, failures };
36
+ }
37
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkB7C,MAAM,UAAU,SAAS,CAAC,MAAc,EAAE,KAAqB;IAC7D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAExC,MAAM,QAAQ,GAA8C,EAAE,CAAC;IAC/D,MAAM,QAAQ,GAA4B,EAAE,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC,CAAC;YACnF,SAAS;QACX,CAAC;QACD,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC1E,CAAC;IAED,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAEjD,IAAI,GAAG,GAAG,GAAG,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;YAClB,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC;YAC3B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC;YAC3B,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;SACzB,CAAC,CAAC;IACL,CAAC;IAED,wEAAwE;IACxE,MAAM,QAAQ,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAE1F,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AAC9D,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@caretcms/caretize",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "CLI that scans an Astro project and interactively adds data-caret attributes, turning a static site into a CaretCMS-editable one.",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "astro",
9
+ "cms",
10
+ "caret",
11
+ "codemod",
12
+ "cli",
13
+ "data-caret",
14
+ "inline-editing"
15
+ ],
16
+ "homepage": "https://github.com/web-stacked/caretcms/tree/main/packages/caretize#readme",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/web-stacked/caretcms.git",
20
+ "directory": "packages/caretize"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/web-stacked/caretcms/issues"
24
+ },
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "bin": {
28
+ "caretize": "./dist/cli.js"
29
+ },
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.build.json && chmod +x dist/cli.js",
41
+ "typecheck": "tsc -p tsconfig.json",
42
+ "clean": "rm -rf dist"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "dependencies": {
48
+ "@astrojs/compiler": "2.13.1"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "25.6.0",
52
+ "typescript": "6.0.3"
53
+ }
54
+ }