@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
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Tier-4 prop hoisting — a guarded REWRITE (not pure insertion).
3
+ *
4
+ * The most common pattern caretize couldn't touch is a static string passed to a
5
+ * shared component as a prop:
6
+ *
7
+ * <PageHero title="ABOUT US" description="Since 2010 …" />
8
+ *
9
+ * The text never appears as a tag-able text leaf (it's an attribute on a
10
+ * component), and `data-caret` can't ride a component boundary. The fix is to
11
+ * hoist each literal into an `editable()` frontmatter const and reference it:
12
+ *
13
+ * const pageHeroTitle = await editable("pages::about::title", "ABOUT US");
14
+ * …
15
+ * <PageHero title={pageHeroTitle} description={pageHeroDesc} />
16
+ *
17
+ * Unlike the other tiers this DELETES bytes (the attribute literal), so it can't
18
+ * use the subsequence gate. Instead it is verified by an INVERSE check: undo
19
+ * every edit (swap the reference back to the literal, strip the const lines) and
20
+ * assert the result is byte-identical to the input — proving the only changes
21
+ * were the intended hoists. A hand-off is rewritten only on positive proof the
22
+ * child renders the prop as text (reusing the Tier-2 cross-file verifier), and
23
+ * only when the exact `prop="value"` token occurs exactly once (unambiguous).
24
+ */
25
+ import { parseAstro, walkTags } from "./parse.js";
26
+ import { deriveScope, isValidField, slugifyText } from "./name.js";
27
+ import { resolveComponentImport } from "./resolve.js";
28
+ import { propLocalName } from "./props.js";
29
+ import { classifyConstUsage, rendersOutsideHead } from "./usage.js";
30
+ import { frontmatterRange } from "./frontmatter.js";
31
+ import { IMPORT_LINE, IMPORT_RE } from "./wrap.js";
32
+ const RICH_RE = (local) => new RegExp(`set:html\\s*=\\s*\\{[^}]*\\b${local}\\b[^}]*\\}`);
33
+ function ucFirst(s) {
34
+ return s ? s[0].toUpperCase() + s.slice(1) : s;
35
+ }
36
+ /** camelCase identifier from parts, e.g. ["PageHero","title"] → "pageHeroTitle". */
37
+ function camelIdent(parts) {
38
+ const words = parts.flatMap((p) => p.split(/[^A-Za-z0-9]+/)).filter(Boolean);
39
+ if (words.length === 0)
40
+ return "field";
41
+ return words
42
+ .map((w, i) => (i === 0 ? w[0].toLowerCase() + w.slice(1) : ucFirst(w)))
43
+ .join("");
44
+ }
45
+ function uniquify(base, used) {
46
+ let name = base;
47
+ let n = 2;
48
+ while (used.has(name))
49
+ name = `${base}_${n++}`;
50
+ used.add(name);
51
+ return name;
52
+ }
53
+ /** Existing frontmatter binding names, so hoisted consts never shadow one. */
54
+ function declaredNames(fmText) {
55
+ const set = new Set();
56
+ const re = /\b(?:const|let|var|import)\s+([A-Za-z_$][\w$]*)/g;
57
+ let m;
58
+ while ((m = re.exec(fmText)))
59
+ set.add(m[1]);
60
+ return set;
61
+ }
62
+ /** Existing data-caret field segments in the file, so keys stay unique. */
63
+ function usedFields(source) {
64
+ const set = new Set();
65
+ const re = /data-caret(?:-rich)?="[^"]*::([A-Za-z0-9_]+)"/g;
66
+ let m;
67
+ while ((m = re.exec(source)))
68
+ set.add(m[1]);
69
+ return set;
70
+ }
71
+ /** The exact `name="value"` (or single-quoted) token, if it occurs exactly once. */
72
+ function uniqueAttrToken(source, name, value) {
73
+ for (const q of ['"', "'"]) {
74
+ if (value.includes(q))
75
+ continue;
76
+ const tok = `${name}=${q}${value}${q}`;
77
+ const first = source.indexOf(tok);
78
+ if (first >= 0 && source.indexOf(tok, first + tok.length) < 0)
79
+ return tok;
80
+ }
81
+ return null;
82
+ }
83
+ /**
84
+ * Detect prop-hoist targets: component invocations with static string props
85
+ * whose child provably renders them as text. `readFile` resolves sibling files.
86
+ */
87
+ export async function detectPropHoistTargets(source, relPath, readFile, root) {
88
+ const scoped = deriveScope(relPath);
89
+ if ("skip" in scoped)
90
+ return [];
91
+ const { collection, id } = scoped.scope;
92
+ const fm = frontmatterRange(source);
93
+ if (!fm)
94
+ return [];
95
+ const fmText = source.slice(fm.start, fm.end);
96
+ const ast = root ?? (await parseAstro(source));
97
+ const usedConsts = declaredNames(fmText);
98
+ const fields = usedFields(source);
99
+ // Cache child source + its parsed AST across repeated component uses.
100
+ const childCache = new Map();
101
+ const readChild = async (rel) => {
102
+ if (!childCache.has(rel)) {
103
+ const src = readFile(rel);
104
+ childCache.set(rel, src === null ? null : { src, ast: await parseAstro(src) });
105
+ }
106
+ return childCache.get(rel) ?? null;
107
+ };
108
+ /** Does the child render `prop` purely as text (incl. set:html), no re-handoff,
109
+ * AND in a body-reachable position the editor can actually click? */
110
+ const childTextSafe = (child, prop) => {
111
+ const local = propLocalName(child.src, prop);
112
+ if (!local)
113
+ return false;
114
+ const verdict = classifyConstUsage(child.ast, local, {
115
+ componentHandoffUnsafe: true,
116
+ htmlDirectivesSafe: true,
117
+ });
118
+ // Safe (never a corrupting attribute) AND has at least one body sink — a
119
+ // prop rendered only into <head> (e.g. a layout's <title>) is editable in
120
+ // name only, so don't hoist it.
121
+ return verdict.safe && rendersOutsideHead(child.ast, [local]);
122
+ };
123
+ const targets = [];
124
+ const componentNodes = [];
125
+ walkTags(ast, (node) => {
126
+ if (node.type === "component")
127
+ componentNodes.push(node);
128
+ });
129
+ for (const node of componentNodes) {
130
+ const childRel = resolveComponentImport(source, node.name, relPath);
131
+ const child = childRel ? await readChild(childRel) : null;
132
+ if (!child)
133
+ continue;
134
+ const props = [];
135
+ for (const attr of node.attributes) {
136
+ if (attr.kind !== "quoted")
137
+ continue;
138
+ const value = attr.value ?? "";
139
+ if (value.trim() === "")
140
+ continue;
141
+ if (!isValidField(attr.name) && !slugifyText(attr.name))
142
+ continue;
143
+ // Positive proof: the child renders this prop as text (no native-attr, no
144
+ // re-handoff). set:html counts as text (stega-safe content sink).
145
+ if (!childTextSafe(child, attr.name))
146
+ continue;
147
+ // Locate an unambiguous source token; skip if absent/duplicated.
148
+ const attrBefore = uniqueAttrToken(source, attr.name, value);
149
+ if (!attrBefore)
150
+ continue;
151
+ const local = propLocalName(child.src, attr.name);
152
+ const isRich = local ? RICH_RE(local).test(child.src) : false;
153
+ const baseField = (isValidField(attr.name) ? attr.name : slugifyText(attr.name));
154
+ const field = uniquify(baseField, fields);
155
+ const constName = uniquify(camelIdent([node.name, attr.name]), usedConsts);
156
+ const key = `${collection}::${id}::${field}`;
157
+ const constInsert = `\nconst ${constName} = await editable(${JSON.stringify(key)}, ${JSON.stringify(value)});`;
158
+ props.push({
159
+ propName: attr.name,
160
+ literalValue: value,
161
+ key,
162
+ constName,
163
+ isRich,
164
+ attrBefore,
165
+ attrAfter: `${attr.name}={${constName}}`,
166
+ constInsert,
167
+ });
168
+ }
169
+ if (props.length)
170
+ targets.push({ componentName: node.name, props });
171
+ }
172
+ return targets;
173
+ }
174
+ /** Apply the hoist rewrite. Self-locating: finds each `attrBefore` token by content. */
175
+ export function hoistPropLiterals(source, targets) {
176
+ const base = {
177
+ output: source, ok: true, propsRewritten: 0, constsDeclared: [], addedImport: false,
178
+ };
179
+ const flat = targets.flatMap((t) => t.props);
180
+ if (flat.length === 0)
181
+ return base;
182
+ let output = source;
183
+ // 1. Rewrite each attribute literal → expression reference (template region).
184
+ for (const p of flat) {
185
+ if (!output.includes(p.attrBefore)) {
186
+ return { ...base, ok: false, reason: `attr token not found: ${p.attrBefore}` };
187
+ }
188
+ output = output.replace(p.attrBefore, p.attrAfter);
189
+ }
190
+ // 2. Insert the editable() consts at the end of the frontmatter, and the
191
+ // import at the top (once). Recompute the range on the rewritten source.
192
+ const fm = frontmatterRange(output);
193
+ if (!fm)
194
+ return { ...base, ok: false, reason: "no frontmatter block" };
195
+ const addedImport = !IMPORT_RE.test(output);
196
+ const constBlock = flat.map((p) => p.constInsert).join("");
197
+ output =
198
+ output.slice(0, fm.start) +
199
+ (addedImport ? `${IMPORT_LINE}\n` : "") +
200
+ output.slice(fm.start, fm.end) +
201
+ constBlock +
202
+ output.slice(fm.end);
203
+ return {
204
+ output,
205
+ ok: true,
206
+ propsRewritten: flat.length,
207
+ constsDeclared: flat.map((p) => p.constName),
208
+ addedImport,
209
+ };
210
+ }
211
+ /**
212
+ * Inverse gate: undo every hoist edit on `output` and assert the result is
213
+ * byte-identical to `intermediate` (the source the hoist was applied to). This
214
+ * proves the ONLY changes were the intended hoists — no collateral edits.
215
+ */
216
+ export function verifyHoistResult(intermediate, output, targets, addedImport) {
217
+ let restored = output;
218
+ if (addedImport)
219
+ restored = restored.replace(`${IMPORT_LINE}\n`, "");
220
+ for (const p of targets.flatMap((t) => t.props)) {
221
+ if (!restored.includes(p.constInsert))
222
+ return false;
223
+ restored = restored.replace(p.constInsert, "");
224
+ if (!restored.includes(p.attrAfter))
225
+ return false;
226
+ // Function replacement: `attrBefore` is a verbatim source token and may hold
227
+ // `$&`/`$$`/etc., which string-replacement would interpret as patterns.
228
+ restored = restored.replace(p.attrAfter, () => p.attrBefore);
229
+ }
230
+ return restored === intermediate;
231
+ }
232
+ //# sourceMappingURL=prop-hoist.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prop-hoist.js","sourceRoot":"","sources":["../src/prop-hoist.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Tier-2 cross-file prop wrapping — detection only.
3
+ *
4
+ * Finds frontmatter array/object literals that a page passes straight into a
5
+ * component as a prop (`<Features items={features} />`), follows the import into
6
+ * that child, and confirms — strictly — that every field of the value renders
7
+ * there as visible TEXT and never as a native-element attribute. Only then is the
8
+ * parent const a safe `editable()` wrap target. The wrap itself stays in the
9
+ * parent file (the same pure-insertion `wrapConst` Tier-1 uses); no child file is
10
+ * ever modified.
11
+ *
12
+ * Conservative by construction — a hand-off is wrapped only on positive proof:
13
+ * - import not a resolvable relative `.astro` → skip
14
+ * - child file unreadable → skip
15
+ * - prop not a simple `Astro.props` destructure → skip
16
+ * - child re-passes the prop to a component → skip (grandchild unverified)
17
+ * - any field used in a native attribute (here or in the child) → skip
18
+ */
19
+ import { type AstroNode } from "./parse.js";
20
+ import type { WrapTarget } from "./wrap.js";
21
+ /** Reads a project-root-relative path, or returns null if it doesn't exist. */
22
+ export type FileReader = (relPath: string) => string | null;
23
+ /**
24
+ * The local identifier a child binds `prop` to via `const { … } = Astro.props`.
25
+ * Returns the destructured name (or its `prop: alias` rename), or null when the
26
+ * child doesn't destructure props simply — in which case we can't trace usage
27
+ * and the caller skips.
28
+ */
29
+ export declare function propLocalName(childSource: string, prop: string): string | null;
30
+ /** Verify a child renders `prop`'s fields as text only (strict: no re-handoff). */
31
+ export declare function childRendersPropAsText(childSource: string, prop: string): Promise<boolean>;
32
+ /**
33
+ * Detect Tier-2 wrap targets: literal consts handed to a component as a prop
34
+ * whose child provably renders them as text. `readFile` resolves sibling files
35
+ * (injected so this stays testable without a real filesystem).
36
+ */
37
+ export declare function detectPropWrapTargets(source: string, relPath: string, readFile: FileReader, root?: AstroNode): Promise<WrapTarget[]>;
38
+ //# sourceMappingURL=props.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAMxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAE5D;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmB9E;AAED,mFAAmF;AACnF,wBAAsB,sBAAsB,CAC1C,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,OAAO,CAAC,CAKlB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,UAAU,EACpB,IAAI,CAAC,EAAE,SAAS,GACf,OAAO,CAAC,UAAU,EAAE,CAAC,CAgDvB"}
package/dist/props.js ADDED
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Tier-2 cross-file prop wrapping — detection only.
3
+ *
4
+ * Finds frontmatter array/object literals that a page passes straight into a
5
+ * component as a prop (`<Features items={features} />`), follows the import into
6
+ * that child, and confirms — strictly — that every field of the value renders
7
+ * there as visible TEXT and never as a native-element attribute. Only then is the
8
+ * parent const a safe `editable()` wrap target. The wrap itself stays in the
9
+ * parent file (the same pure-insertion `wrapConst` Tier-1 uses); no child file is
10
+ * ever modified.
11
+ *
12
+ * Conservative by construction — a hand-off is wrapped only on positive proof:
13
+ * - import not a resolvable relative `.astro` → skip
14
+ * - child file unreadable → skip
15
+ * - prop not a simple `Astro.props` destructure → skip
16
+ * - child re-passes the prop to a component → skip (grandchild unverified)
17
+ * - any field used in a native attribute (here or in the child) → skip
18
+ */
19
+ import { parseAstro } from "./parse.js";
20
+ import { deriveScope, isValidField, slugifyText } from "./name.js";
21
+ import { classifyConstUsage } from "./usage.js";
22
+ import { resolveComponentImport } from "./resolve.js";
23
+ import { frontmatterRange, literalConstNames } from "./frontmatter.js";
24
+ import { IDENT } from "./identifiers.js";
25
+ /**
26
+ * The local identifier a child binds `prop` to via `const { … } = Astro.props`.
27
+ * Returns the destructured name (or its `prop: alias` rename), or null when the
28
+ * child doesn't destructure props simply — in which case we can't trace usage
29
+ * and the caller skips.
30
+ */
31
+ export function propLocalName(childSource, prop) {
32
+ const re = /const\s*\{([^}]*)\}\s*=\s*Astro\.props/g;
33
+ let m;
34
+ while ((m = re.exec(childSource))) {
35
+ for (const part of m[1].split(",")) {
36
+ const seg = part.trim();
37
+ if (!seg || seg.startsWith("..."))
38
+ continue;
39
+ // `prop`, `prop: alias`, or `prop = default` (strip the default).
40
+ const key = seg.split(/[:=]/)[0].trim();
41
+ if (key !== prop)
42
+ continue;
43
+ const colon = seg.indexOf(":");
44
+ if (colon >= 0) {
45
+ const alias = seg.slice(colon + 1).split("=")[0].trim();
46
+ return IDENT.test(alias) ? alias : null;
47
+ }
48
+ return IDENT.test(key) ? key : null;
49
+ }
50
+ }
51
+ return null;
52
+ }
53
+ /** Verify a child renders `prop`'s fields as text only (strict: no re-handoff). */
54
+ export async function childRendersPropAsText(childSource, prop) {
55
+ const local = propLocalName(childSource, prop);
56
+ if (!local)
57
+ return false;
58
+ const root = await parseAstro(childSource);
59
+ return classifyConstUsage(root, local, { componentHandoffUnsafe: true }).safe;
60
+ }
61
+ /**
62
+ * Detect Tier-2 wrap targets: literal consts handed to a component as a prop
63
+ * whose child provably renders them as text. `readFile` resolves sibling files
64
+ * (injected so this stays testable without a real filesystem).
65
+ */
66
+ export async function detectPropWrapTargets(source, relPath, readFile, root) {
67
+ const scoped = deriveScope(relPath);
68
+ if ("skip" in scoped)
69
+ return [];
70
+ const { collection, id } = scoped.scope;
71
+ const fm = frontmatterRange(source);
72
+ if (!fm)
73
+ return [];
74
+ const literals = literalConstNames(source.slice(fm.start, fm.end));
75
+ if (literals.size === 0)
76
+ return [];
77
+ const ast = root ?? (await parseAstro(source));
78
+ const targets = [];
79
+ for (const varName of literals) {
80
+ // Local usage: gather this const's component hand-offs, and bail if a field
81
+ // already lands in a native-element attribute in THIS file.
82
+ const local = classifyConstUsage(ast, varName); // lenient: collect handoffs
83
+ if (!local.safe)
84
+ continue;
85
+ // Only consts actually passed to a component as a prop are Tier-2's business;
86
+ // pure local-loop consts are Tier-1's. Dedup hand-offs by component+prop.
87
+ const seen = new Set();
88
+ const handoffs = local.handoffs.filter((h) => {
89
+ const k = `${h.component}::${h.prop}`;
90
+ if (seen.has(k))
91
+ return false;
92
+ seen.add(k);
93
+ return true;
94
+ });
95
+ if (handoffs.length === 0)
96
+ continue;
97
+ // Every hand-off must resolve to a child that renders the prop as text.
98
+ let allSafe = true;
99
+ for (const h of handoffs) {
100
+ const childRel = resolveComponentImport(source, h.component, relPath);
101
+ const childSrc = childRel ? readFile(childRel) : null;
102
+ if (!childSrc || !(await childRendersPropAsText(childSrc, h.prop))) {
103
+ allSafe = false;
104
+ break;
105
+ }
106
+ }
107
+ if (!allSafe)
108
+ continue;
109
+ const field = isValidField(varName) ? varName : slugifyText(varName);
110
+ if (!field)
111
+ continue;
112
+ targets.push({ varName, key: `${collection}::${id}::${field}`, origin: "prop" });
113
+ }
114
+ return targets;
115
+ }
116
+ //# sourceMappingURL=props.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"props.js","sourceRoot":"","sources":["../src/props.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Machine-readable report (for --report) and the human summary counts. Pure
3
+ * functions over plans + the prepared results so they're easy to test.
4
+ */
5
+ import type { FilePlan } from "./plan.js";
6
+ import type { PreparedFile } from "./run.js";
7
+ export interface CaretizeReport {
8
+ version: 1;
9
+ scope: {
10
+ collection: string;
11
+ id: string;
12
+ } | null;
13
+ files: Array<{
14
+ path: string;
15
+ scope: {
16
+ collection: string;
17
+ id: string;
18
+ } | null;
19
+ scopeSkip?: string;
20
+ tags: Array<{
21
+ binding: string;
22
+ tag: string;
23
+ confidence: string;
24
+ text: string;
25
+ }>;
26
+ skipped: Array<{
27
+ tag: string;
28
+ reason: string;
29
+ }>;
30
+ flags: Array<{
31
+ method: string;
32
+ }>;
33
+ }>;
34
+ totals: {
35
+ tagged: number;
36
+ skipped: number;
37
+ flagged: number;
38
+ filesModified: number;
39
+ };
40
+ }
41
+ export declare function buildReport(plans: FilePlan[], prepared?: PreparedFile[]): CaretizeReport;
42
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,CAAC,CAAC;IACX,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjD,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE;YAAE,UAAU,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,IAAI,CAAC;QACjD,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,KAAK,CAAC;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,GAAG,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAChF,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAChD,KAAK,EAAE,KAAK,CAAC;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;KAClC,CAAC,CAAC;IACH,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;CACrF;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,QAAQ,EAAE,EACjB,QAAQ,GAAE,YAAY,EAAO,GAC5B,cAAc,CAgChB"}
package/dist/report.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Machine-readable report (for --report) and the human summary counts. Pure
3
+ * functions over plans + the prepared results so they're easy to test.
4
+ */
5
+ export function buildReport(plans, prepared = []) {
6
+ const modified = new Set(prepared.filter((p) => p.tagCount > 0).map((p) => p.relPath));
7
+ let tagged = 0;
8
+ let skipped = 0;
9
+ let flagged = 0;
10
+ const files = plans.map((plan) => {
11
+ tagged += plan.tags.length;
12
+ skipped += plan.skipped.length;
13
+ flagged += plan.flags.length;
14
+ return {
15
+ path: plan.relPath,
16
+ scope: plan.scope ?? null,
17
+ ...(plan.scopeSkip ? { scopeSkip: plan.scopeSkip } : {}),
18
+ tags: plan.tags.map((t) => ({
19
+ binding: t.binding,
20
+ tag: t.candidate.tag,
21
+ confidence: t.confidence,
22
+ text: t.candidate.text,
23
+ })),
24
+ skipped: plan.skipped.map((s) => ({ tag: s.tag, reason: s.reason })),
25
+ flags: plan.flags.map((f) => ({ method: f.method })),
26
+ };
27
+ });
28
+ return {
29
+ version: 1,
30
+ scope: plans.find((p) => p.scope)?.scope ?? null,
31
+ files,
32
+ totals: { tagged, skipped, flagged, filesModified: modified.size },
33
+ };
34
+ }
35
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmBH,MAAM,UAAU,WAAW,CACzB,KAAiB,EACjB,WAA2B,EAAE;IAE7B,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAEvF,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,IAAI,OAAO,GAAG,CAAC,CAAC;IAEhB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAC/B,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC;QAC3B,OAAO,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;QAC/B,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAC7B,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,OAAO;YAClB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,IAAI;YACzB,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBAC1B,OAAO,EAAE,CAAC,CAAC,OAAO;gBAClB,GAAG,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG;gBACpB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI;aACvB,CAAC,CAAC;YACH,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;YACpE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;SACrD,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,KAAK,IAAI,IAAI;QAChD,KAAK;QACL,MAAM,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,IAAI,EAAE;KACnE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Minimal component-import resolution for the cross-file prop pass.
3
+ *
4
+ * Given an importer's source and a component name, find its `import … from`
5
+ * specifier and resolve it to a project-root-relative `.astro` path. Only RELATIVE
6
+ * specifiers are resolved: bare/`npm` packages and tsconfig path aliases
7
+ * (`~/…`, `@/…`) need build config we don't read, so they return null and the
8
+ * caller conservatively skips that hand-off rather than guess.
9
+ */
10
+ /**
11
+ * Resolve `componentName`'s source path relative to the project root, or null
12
+ * when the import is absent or not a resolvable relative `.astro` specifier.
13
+ */
14
+ export declare function resolveComponentImport(importerSource: string, componentName: string, importerRelPath: string): string | null;
15
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,EACrB,eAAe,EAAE,MAAM,GACtB,MAAM,GAAG,IAAI,CAoBf"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Minimal component-import resolution for the cross-file prop pass.
3
+ *
4
+ * Given an importer's source and a component name, find its `import … from`
5
+ * specifier and resolve it to a project-root-relative `.astro` path. Only RELATIVE
6
+ * specifiers are resolved: bare/`npm` packages and tsconfig path aliases
7
+ * (`~/…`, `@/…`) need build config we don't read, so they return null and the
8
+ * caller conservatively skips that hand-off rather than guess.
9
+ */
10
+ import { posix } from "node:path";
11
+ import { escapeRe } from "./identifiers.js";
12
+ /**
13
+ * Resolve `componentName`'s source path relative to the project root, or null
14
+ * when the import is absent or not a resolvable relative `.astro` specifier.
15
+ */
16
+ export function resolveComponentImport(importerSource, componentName, importerRelPath) {
17
+ // Default import of the component, tolerating a trailing named group:
18
+ // `import Name from '…'` and `import Name, { X } from '…'`. The `(?![\w$])`
19
+ // bounds the name so `Card` doesn't match `CardList`.
20
+ const re = new RegExp(`import\\s+${escapeRe(componentName)}(?![\\w$])[^'"\\n]*?from\\s*['"]([^'"]+)['"]`);
21
+ const m = re.exec(importerSource);
22
+ if (!m)
23
+ return null;
24
+ let spec = m[1];
25
+ if (!spec.startsWith("./") && !spec.startsWith("../"))
26
+ return null; // alias/bare → unresolvable here
27
+ if (!spec.endsWith(".astro")) {
28
+ if (/\.\w+$/.test(spec))
29
+ return null; // a non-.astro file (e.g. .tsx) — out of scope for v1
30
+ spec += ".astro";
31
+ }
32
+ const dir = posix.dirname(importerRelPath.replace(/\\/g, "/"));
33
+ return posix.normalize(posix.join(dir, spec));
34
+ }
35
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,cAAsB,EACtB,aAAqB,EACrB,eAAuB;IAEvB,sEAAsE;IACtE,4EAA4E;IAC5E,sDAAsD;IACtD,MAAM,EAAE,GAAG,IAAI,MAAM,CACnB,aAAa,QAAQ,CAAC,aAAa,CAAC,8CAA8C,CACnF,CAAC;IACF,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IAClC,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpB,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAChB,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,iCAAiC;IAErG,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC,CAAC,sDAAsD;QAC5F,IAAI,IAAI,QAAQ,CAAC;IACnB,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/D,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AAChD,CAAC"}
package/dist/run.d.ts ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Run orchestration. The atomicity model is "validate everything in memory,
3
+ * then write": every file's tagged output is computed and verified (re-parses +
4
+ * is pure-insertion-reversible) BEFORE a single byte hits disk. If any file
5
+ * fails verification, the run aborts having written nothing. During the write
6
+ * phase, backups are taken per file; if a write throws, everything written so
7
+ * far is restored from those backups.
8
+ */
9
+ import { type TagInsertion } from "./write.js";
10
+ import { type WrapTarget } from "./wrap.js";
11
+ import { type PropHoistTarget } from "./prop-hoist.js";
12
+ export interface PreparedFile {
13
+ relPath: string;
14
+ source: string;
15
+ output: string;
16
+ inserted: string[];
17
+ tagCount: number;
18
+ ok: boolean;
19
+ reason?: string;
20
+ }
21
+ /** Compute and verify a single file's tagged output. No disk access. */
22
+ export declare function prepareFile(relPath: string, source: string, items: TagInsertion[]): Promise<PreparedFile>;
23
+ export type { WrapTarget };
24
+ /**
25
+ * Compute and verify a single file's editable()-wrapped output (Tier-1). Same
26
+ * atomicity contract as prepareFile, so the result feeds commitRun unchanged.
27
+ *
28
+ * Gate 2 here is a SUBSEQUENCE check rather than strip-by-string: the inserted
29
+ * `await editable(…, ` / `)` spans contain characters (`)`, commas) that also
30
+ * occur elsewhere, so verifying "original is a subsequence of output" is the
31
+ * robust way to prove pure insertion (no original char deleted or reordered).
32
+ */
33
+ export declare function prepareWrapFile(relPath: string, source: string, targets: WrapTarget[]): Promise<PreparedFile>;
34
+ /**
35
+ * Compute and verify a file's output with BOTH passes: attribute tags and
36
+ * editable() wraps. Tags are applied first (offset-based, in the template), then
37
+ * wraps (content-scan, in the frontmatter) — so the frontmatter insertions never
38
+ * shift the template tag offsets. Same atomicity gates; one PreparedFile out.
39
+ */
40
+ export declare function prepareFileFull(relPath: string, source: string, items: TagInsertion[], targets: WrapTarget[], hoistTargets?: PropHoistTarget[]): Promise<PreparedFile>;
41
+ export interface CommitResult {
42
+ written: string[];
43
+ backups: string[];
44
+ }
45
+ /**
46
+ * Write verified files to disk with backups. Throws (after rolling back) if any
47
+ * prepared file failed verification or any write fails.
48
+ */
49
+ export declare function commitRun(rootDir: string, prepared: PreparedFile[], stamp: string): CommitResult;
50
+ /** Convenience: read a file from disk for preparation. */
51
+ export declare function readSource(rootDir: string, relPath: string): string;
52
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,EAAa,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EAAwC,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAU7F,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wEAAwE;AACxE,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY,EAAE,GACpB,OAAO,CAAC,YAAY,CAAC,CA8BvB;AAED,YAAY,EAAE,UAAU,EAAE,CAAC;AAW3B;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC,YAAY,CAAC,CA8BvB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,EAAE,UAAU,EAAE,EACrB,YAAY,GAAE,eAAe,EAAO,GACnC,OAAO,CAAC,YAAY,CAAC,CAqEvB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,YAAY,EAAE,EACxB,KAAK,EAAE,MAAM,GACZ,YAAY,CA4Bd;AAED,0DAA0D;AAC1D,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnE"}