@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.
- package/README.md +70 -0
- package/dist/backup.d.ts +30 -0
- package/dist/backup.d.ts.map +1 -0
- package/dist/backup.js +89 -0
- package/dist/backup.js.map +1 -0
- package/dist/bind-collection.d.ts +56 -0
- package/dist/bind-collection.d.ts.map +1 -0
- package/dist/bind-collection.js +140 -0
- package/dist/bind-collection.js.map +1 -0
- package/dist/bind-route.d.ts +40 -0
- package/dist/bind-route.d.ts.map +1 -0
- package/dist/bind-route.js +150 -0
- package/dist/bind-route.js.map +1 -0
- package/dist/cli-args.d.ts +30 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +118 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +356 -0
- package/dist/cli.js.map +1 -0
- package/dist/detect.d.ts +76 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/detect.js +237 -0
- package/dist/detect.js.map +1 -0
- package/dist/discover.d.ts +13 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +84 -0
- package/dist/discover.js.map +1 -0
- package/dist/frontmatter.d.ts +26 -0
- package/dist/frontmatter.d.ts.map +1 -0
- package/dist/frontmatter.js +52 -0
- package/dist/frontmatter.js.map +1 -0
- package/dist/identifiers.d.ts +26 -0
- package/dist/identifiers.d.ts.map +1 -0
- package/dist/identifiers.js +34 -0
- package/dist/identifiers.js.map +1 -0
- package/dist/import-wrap.d.ts +56 -0
- package/dist/import-wrap.d.ts.map +1 -0
- package/dist/import-wrap.js +149 -0
- package/dist/import-wrap.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/name.d.ts +49 -0
- package/dist/name.d.ts.map +1 -0
- package/dist/name.js +164 -0
- package/dist/name.js.map +1 -0
- package/dist/output.d.ts +30 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +110 -0
- package/dist/output.js.map +1 -0
- package/dist/parse.d.ts +86 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +92 -0
- package/dist/parse.js.map +1 -0
- package/dist/plan.d.ts +46 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +76 -0
- package/dist/plan.js.map +1 -0
- package/dist/preflight.d.ts +19 -0
- package/dist/preflight.d.ts.map +1 -0
- package/dist/preflight.js +95 -0
- package/dist/preflight.js.map +1 -0
- package/dist/prop-hoist.d.ts +67 -0
- package/dist/prop-hoist.d.ts.map +1 -0
- package/dist/prop-hoist.js +232 -0
- package/dist/prop-hoist.js.map +1 -0
- package/dist/props.d.ts +38 -0
- package/dist/props.d.ts.map +1 -0
- package/dist/props.js +116 -0
- package/dist/props.js.map +1 -0
- package/dist/report.d.ts +42 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +35 -0
- package/dist/report.js.map +1 -0
- package/dist/resolve.d.ts +15 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +35 -0
- package/dist/resolve.js.map +1 -0
- package/dist/run.d.ts +52 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +213 -0
- package/dist/run.js.map +1 -0
- package/dist/splice.d.ts +43 -0
- package/dist/splice.d.ts.map +1 -0
- package/dist/splice.js +90 -0
- package/dist/splice.js.map +1 -0
- package/dist/usage.d.ts +90 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +249 -0
- package/dist/usage.js.map +1 -0
- package/dist/wrap.d.ts +72 -0
- package/dist/wrap.d.ts.map +1 -0
- package/dist/wrap.js +170 -0
- package/dist/wrap.js.map +1 -0
- package/dist/write.d.ts +28 -0
- package/dist/write.d.ts.map +1 -0
- package/dist/write.js +37 -0
- package/dist/write.js.map +1 -0
- package/package.json +54 -0
package/dist/parse.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper over `@astrojs/compiler` for caretize: parse a `.astro` source,
|
|
3
|
+
* walk its tag nodes, and tag-then-verify an element.
|
|
4
|
+
*
|
|
5
|
+
* Minimal structural node types are declared locally rather than imported from
|
|
6
|
+
* the compiler's type barrels, so we stay decoupled from which entrypoint
|
|
7
|
+
* happens to re-export the AST interfaces. We only need a handful of fields.
|
|
8
|
+
*/
|
|
9
|
+
/** A single attribute on a tag node. `kind` distinguishes a static `quoted`
|
|
10
|
+
* value from a dynamic one (`expression`, `template-literal`, `shorthand`,
|
|
11
|
+
* `spread`) — the usage classifier needs that to know whether a value could
|
|
12
|
+
* carry an editable field reference. */
|
|
13
|
+
export interface TagAttr {
|
|
14
|
+
type: string;
|
|
15
|
+
/** Compiler attribute kind: quoted | expression | template-literal | shorthand | spread | empty. */
|
|
16
|
+
kind?: string;
|
|
17
|
+
name: string;
|
|
18
|
+
value: string;
|
|
19
|
+
/** Original source text (populated for some kinds, e.g. quoted/template). */
|
|
20
|
+
raw?: string;
|
|
21
|
+
}
|
|
22
|
+
/** A tag-like AST node (element / component / custom-element / fragment). */
|
|
23
|
+
export interface TagNode {
|
|
24
|
+
type: "element" | "component" | "custom-element" | "fragment";
|
|
25
|
+
name: string;
|
|
26
|
+
attributes: TagAttr[];
|
|
27
|
+
children: AstroNode[];
|
|
28
|
+
position?: {
|
|
29
|
+
start: {
|
|
30
|
+
offset: number;
|
|
31
|
+
};
|
|
32
|
+
end?: {
|
|
33
|
+
offset: number;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export interface ContainerNode {
|
|
38
|
+
type: string;
|
|
39
|
+
children?: AstroNode[];
|
|
40
|
+
position?: {
|
|
41
|
+
start: {
|
|
42
|
+
offset: number;
|
|
43
|
+
};
|
|
44
|
+
end?: {
|
|
45
|
+
offset: number;
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export type AstroNode = (TagNode | ContainerNode) & {
|
|
50
|
+
type: string;
|
|
51
|
+
children?: AstroNode[];
|
|
52
|
+
};
|
|
53
|
+
export declare function isTagNode(node: AstroNode): node is TagNode;
|
|
54
|
+
/** Parse a `.astro` source into its AST root with byte positions populated. */
|
|
55
|
+
export declare function parseAstro(source: string): Promise<AstroNode>;
|
|
56
|
+
/**
|
|
57
|
+
* Depth-first walk over every tag-like node in document order. The callback
|
|
58
|
+
* receives the node and its chain of ancestor nodes (root-first), so callers
|
|
59
|
+
* can answer "am I inside an expression / iterator / a given scope?" without
|
|
60
|
+
* re-walking.
|
|
61
|
+
*/
|
|
62
|
+
export declare function walkTags(root: AstroNode, visit: (node: TagNode, ancestors: AstroNode[]) => void): void;
|
|
63
|
+
export interface TagResult {
|
|
64
|
+
/** The spliced source (only meaningful when `ok` is true). */
|
|
65
|
+
output: string;
|
|
66
|
+
/** True when the attribute was inserted AND the result re-parses with the
|
|
67
|
+
* attribute present on the intended element. */
|
|
68
|
+
ok: boolean;
|
|
69
|
+
/** Populated when `ok` is false, explaining why the splice was rejected. */
|
|
70
|
+
reason?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Splice ` ${attribute}` into the opening tag of the element at `startOffset`,
|
|
74
|
+
* then re-parse the result to confirm (a) it still parses and (b) the intended
|
|
75
|
+
* element now carries the attribute. This converts a bad offset from "silent
|
|
76
|
+
* file corruption" into a rejected, recoverable result — the safety net the
|
|
77
|
+
* writer wraps every change in.
|
|
78
|
+
*
|
|
79
|
+
* `attribute` is the full attribute text WITHOUT a leading space, e.g.
|
|
80
|
+
* `data-caret="pages::home::headline"`.
|
|
81
|
+
*/
|
|
82
|
+
export declare function tagElementAndVerify(source: string, element: {
|
|
83
|
+
name: string;
|
|
84
|
+
startOffset: number;
|
|
85
|
+
}, attribute: string, attrName?: string): Promise<TagResult>;
|
|
86
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH;;;yCAGyC;AACzC,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,oGAAoG;IACpG,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,6EAA6E;AAC7E,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,SAAS,GAAG,WAAW,GAAG,gBAAgB,GAAG,UAAU,CAAC;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,GAAG,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACpE;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QAAC,GAAG,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE,CAAC;CACpE;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,GAAG,aAAa,CAAC,GAAG;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;CACxB,CAAC;AAIF,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,IAAI,OAAO,CAE1D;AAED,+EAA+E;AAC/E,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAGnE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,SAAS,EACf,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,IAAI,GACrD,IAAI,CAaN;AAED,MAAM,WAAW,SAAS;IACxB,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf;qDACiD;IACjD,EAAE,EAAE,OAAO,CAAC;IACZ,4EAA4E;IAC5E,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,EAC9C,SAAS,EAAE,MAAM,EACjB,QAAQ,SAAe,GACtB,OAAO,CAAC,SAAS,CAAC,CA0CpB"}
|
package/dist/parse.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin wrapper over `@astrojs/compiler` for caretize: parse a `.astro` source,
|
|
3
|
+
* walk its tag nodes, and tag-then-verify an element.
|
|
4
|
+
*
|
|
5
|
+
* Minimal structural node types are declared locally rather than imported from
|
|
6
|
+
* the compiler's type barrels, so we stay decoupled from which entrypoint
|
|
7
|
+
* happens to re-export the AST interfaces. We only need a handful of fields.
|
|
8
|
+
*/
|
|
9
|
+
import { parse } from "@astrojs/compiler";
|
|
10
|
+
import { findOpenTagEnd, spliceAttribute } from "./splice.js";
|
|
11
|
+
const TAG_TYPES = new Set(["element", "component", "custom-element", "fragment"]);
|
|
12
|
+
export function isTagNode(node) {
|
|
13
|
+
return TAG_TYPES.has(node.type);
|
|
14
|
+
}
|
|
15
|
+
/** Parse a `.astro` source into its AST root with byte positions populated. */
|
|
16
|
+
export async function parseAstro(source) {
|
|
17
|
+
const result = await parse(source, { position: true });
|
|
18
|
+
return result.ast;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Depth-first walk over every tag-like node in document order. The callback
|
|
22
|
+
* receives the node and its chain of ancestor nodes (root-first), so callers
|
|
23
|
+
* can answer "am I inside an expression / iterator / a given scope?" without
|
|
24
|
+
* re-walking.
|
|
25
|
+
*/
|
|
26
|
+
export function walkTags(root, visit) {
|
|
27
|
+
const stack = [];
|
|
28
|
+
const recurse = (node) => {
|
|
29
|
+
if (!node || typeof node !== "object")
|
|
30
|
+
return;
|
|
31
|
+
if (isTagNode(node))
|
|
32
|
+
visit(node, [...stack]);
|
|
33
|
+
const children = node.children;
|
|
34
|
+
if (Array.isArray(children)) {
|
|
35
|
+
stack.push(node);
|
|
36
|
+
for (const child of children)
|
|
37
|
+
recurse(child);
|
|
38
|
+
stack.pop();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
recurse(root);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Splice ` ${attribute}` into the opening tag of the element at `startOffset`,
|
|
45
|
+
* then re-parse the result to confirm (a) it still parses and (b) the intended
|
|
46
|
+
* element now carries the attribute. This converts a bad offset from "silent
|
|
47
|
+
* file corruption" into a rejected, recoverable result — the safety net the
|
|
48
|
+
* writer wraps every change in.
|
|
49
|
+
*
|
|
50
|
+
* `attribute` is the full attribute text WITHOUT a leading space, e.g.
|
|
51
|
+
* `data-caret="pages::home::headline"`.
|
|
52
|
+
*/
|
|
53
|
+
export async function tagElementAndVerify(source, element, attribute, attrName = "data-caret") {
|
|
54
|
+
const buf = Buffer.from(source, "utf8");
|
|
55
|
+
const found = findOpenTagEnd(buf, element.startOffset);
|
|
56
|
+
if (!found) {
|
|
57
|
+
return { output: source, ok: false, reason: "could not locate end of opening tag" };
|
|
58
|
+
}
|
|
59
|
+
const spliced = spliceAttribute(buf, found.insertAt, ` ${attribute}`);
|
|
60
|
+
const output = spliced.toString("utf8");
|
|
61
|
+
// Re-parse and confirm the intended element (still at the same start offset,
|
|
62
|
+
// since we inserted strictly after the tag's `<`) now has the attribute.
|
|
63
|
+
let verifiedNode;
|
|
64
|
+
try {
|
|
65
|
+
const reAst = await parseAstro(output);
|
|
66
|
+
walkTags(reAst, (node) => {
|
|
67
|
+
if (verifiedNode)
|
|
68
|
+
return;
|
|
69
|
+
if (node.name === element.name &&
|
|
70
|
+
node.position?.start.offset === element.startOffset &&
|
|
71
|
+
node.attributes.some((a) => a.name === attrName)) {
|
|
72
|
+
verifiedNode = node;
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
return {
|
|
78
|
+
output: source,
|
|
79
|
+
ok: false,
|
|
80
|
+
reason: `re-parse threw: ${err.message}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (!verifiedNode) {
|
|
84
|
+
return {
|
|
85
|
+
output: source,
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: "attribute not present on intended element after re-parse",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return { output, ok: true };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planning: turn a `.astro` source + its path into a reviewable plan — the list
|
|
3
|
+
* of concrete `data-caret` bindings to propose, plus what was skipped and
|
|
4
|
+
* flagged. This is the integration point the CLI and the test harness drive.
|
|
5
|
+
*
|
|
6
|
+
* v1 emits self-contained full-triple bindings (`data-caret="collection::id::
|
|
7
|
+
* field"`) rather than scope-on-wrapper + field-only, because a full triple
|
|
8
|
+
* never depends on locating a common ancestor to host the scope — it is always
|
|
9
|
+
* valid wherever it lands. (Scope-on-wrapper is a later readability pass.)
|
|
10
|
+
*/
|
|
11
|
+
import { type AstroNode } from "./parse.js";
|
|
12
|
+
import { type Candidate, type Confidence, type IteratorFlag, type Skipped } from "./detect.js";
|
|
13
|
+
import { type Scope, type ScopeReason } from "./name.js";
|
|
14
|
+
export interface PlannedTag {
|
|
15
|
+
candidate: Candidate;
|
|
16
|
+
collection: string;
|
|
17
|
+
id: string;
|
|
18
|
+
field: string;
|
|
19
|
+
/** Full binding value, `collection::id::field`. */
|
|
20
|
+
binding: string;
|
|
21
|
+
/** Attribute text without leading space, e.g. `data-caret="a::b::c"`. */
|
|
22
|
+
attribute: string;
|
|
23
|
+
startOffset: number;
|
|
24
|
+
confidence: Confidence;
|
|
25
|
+
}
|
|
26
|
+
export interface FilePlan {
|
|
27
|
+
relPath: string;
|
|
28
|
+
scope?: Scope;
|
|
29
|
+
/** Set when the whole file is skipped (dynamic route / unsupported location). */
|
|
30
|
+
scopeSkip?: ScopeReason;
|
|
31
|
+
tags: PlannedTag[];
|
|
32
|
+
skipped: Skipped[];
|
|
33
|
+
flags: IteratorFlag[];
|
|
34
|
+
}
|
|
35
|
+
export interface PlanOptions {
|
|
36
|
+
/** Minimum confidence to include as a proposed tag (default "high"). */
|
|
37
|
+
minConfidence?: Confidence;
|
|
38
|
+
/** Skip image candidates entirely (--no-images). */
|
|
39
|
+
noImages?: boolean;
|
|
40
|
+
/** Override the derived scope, e.g. { collection: "pages", id: "about" }. */
|
|
41
|
+
scope?: Scope;
|
|
42
|
+
/** Promote sanitizer-safe mixed-content blocks to data-caret-rich (--rich). */
|
|
43
|
+
rich?: boolean;
|
|
44
|
+
}
|
|
45
|
+
export declare function planFile(source: string, relPath: string, options?: PlanOptions, root?: AstroNode): Promise<FilePlan>;
|
|
46
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAwB,KAAK,SAAS,EAAgB,MAAM,YAAY,CAAC;AAChF,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,OAAO,EACb,MAAM,aAAa,CAAC;AACrB,OAAO,EAA6B,KAAK,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAEpF,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,UAAU,CAAC;CACxB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,iFAAiF;IACjF,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,EAAE,CAAC;IACnB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,aAAa,CAAC,EAAE,UAAU,CAAC;IAC3B,oDAAoD;IACpD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,+EAA+E;IAC/E,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAoBD,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,WAAgB,EACzB,IAAI,CAAC,EAAE,SAAS,GACf,OAAO,CAAC,QAAQ,CAAC,CA+CnB"}
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Planning: turn a `.astro` source + its path into a reviewable plan — the list
|
|
3
|
+
* of concrete `data-caret` bindings to propose, plus what was skipped and
|
|
4
|
+
* flagged. This is the integration point the CLI and the test harness drive.
|
|
5
|
+
*
|
|
6
|
+
* v1 emits self-contained full-triple bindings (`data-caret="collection::id::
|
|
7
|
+
* field"`) rather than scope-on-wrapper + field-only, because a full triple
|
|
8
|
+
* never depends on locating a common ancestor to host the scope — it is always
|
|
9
|
+
* valid wherever it lands. (Scope-on-wrapper is a later readability pass.)
|
|
10
|
+
*/
|
|
11
|
+
import { parseAstro, walkTags } from "./parse.js";
|
|
12
|
+
import { detect, } from "./detect.js";
|
|
13
|
+
import { assignFields, deriveScope } from "./name.js";
|
|
14
|
+
const RANK = { high: 3, medium: 2, low: 1 };
|
|
15
|
+
/** Field name carried by an existing data-caret value (full-triple or field-only). */
|
|
16
|
+
function fieldOf(value) {
|
|
17
|
+
const parts = value.split("::");
|
|
18
|
+
return parts.length === 3 ? parts[2] : parts[0];
|
|
19
|
+
}
|
|
20
|
+
/** Collect field names already bound in the file, to seed unique naming. */
|
|
21
|
+
function existingFields(root) {
|
|
22
|
+
const fields = [];
|
|
23
|
+
walkTags(root, (node) => {
|
|
24
|
+
const v = node.attributes.find((a) => a.name === "data-caret")?.value;
|
|
25
|
+
if (v)
|
|
26
|
+
fields.push(fieldOf(v));
|
|
27
|
+
});
|
|
28
|
+
return fields;
|
|
29
|
+
}
|
|
30
|
+
export async function planFile(source, relPath, options = {}, root) {
|
|
31
|
+
const minRank = RANK[options.minConfidence ?? "high"];
|
|
32
|
+
const ast = root ?? (await parseAstro(source));
|
|
33
|
+
const { candidates, skipped, flags } = detect(ast, walkTags, { rich: options.rich });
|
|
34
|
+
// Resolve scope (explicit override wins; otherwise derive from path).
|
|
35
|
+
let scope;
|
|
36
|
+
if (options.scope) {
|
|
37
|
+
scope = options.scope;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const derived = deriveScope(relPath);
|
|
41
|
+
if ("skip" in derived) {
|
|
42
|
+
return { relPath, scopeSkip: derived.skip, tags: [], skipped, flags };
|
|
43
|
+
}
|
|
44
|
+
scope = derived.scope;
|
|
45
|
+
}
|
|
46
|
+
// Filter candidates by confidence + image policy, preserving document order.
|
|
47
|
+
// Rich candidates only exist when --rich was requested, so they're always
|
|
48
|
+
// accepted (the confidence floor doesn't gate the explicit opt-in).
|
|
49
|
+
const accepted = candidates.filter((c) => {
|
|
50
|
+
if (options.noImages && c.kind === "image")
|
|
51
|
+
return false;
|
|
52
|
+
if (c.rich)
|
|
53
|
+
return true;
|
|
54
|
+
return RANK[c.confidence] >= minRank;
|
|
55
|
+
});
|
|
56
|
+
const fields = assignFields(accepted, existingFields(ast));
|
|
57
|
+
const tags = accepted.map((candidate) => {
|
|
58
|
+
const field = fields.get(candidate);
|
|
59
|
+
const binding = `${scope.collection}::${scope.id}::${field}`;
|
|
60
|
+
const attribute = candidate.rich
|
|
61
|
+
? `data-caret="${binding}" data-caret-rich`
|
|
62
|
+
: `data-caret="${binding}"`;
|
|
63
|
+
return {
|
|
64
|
+
candidate,
|
|
65
|
+
collection: scope.collection,
|
|
66
|
+
id: scope.id,
|
|
67
|
+
field,
|
|
68
|
+
binding,
|
|
69
|
+
attribute,
|
|
70
|
+
startOffset: candidate.startOffset,
|
|
71
|
+
confidence: candidate.confidence,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
return { relPath, scope, tags, skipped, flags };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=plan.js.map
|
package/dist/plan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight: cheap sanity checks before scanning. Tagging works perfectly yet
|
|
3
|
+
* nothing is editable if CaretCMS isn't actually wired into the project, so we
|
|
4
|
+
* surface that up front. Git cleanliness is advisory — git is the real undo.
|
|
5
|
+
*/
|
|
6
|
+
export interface Preflight {
|
|
7
|
+
isAstroProject: boolean;
|
|
8
|
+
hasCaretCore: boolean;
|
|
9
|
+
/** caret() actually referenced in astro.config (not just installed). */
|
|
10
|
+
caretWired: boolean;
|
|
11
|
+
/** Output mode parsed from astro.config: "server" | "hybrid" | "static". */
|
|
12
|
+
outputMode: string;
|
|
13
|
+
gitRepo: boolean;
|
|
14
|
+
gitClean: boolean | null;
|
|
15
|
+
errors: string[];
|
|
16
|
+
warnings: string[];
|
|
17
|
+
}
|
|
18
|
+
export declare function preflight(rootDir: string): Preflight;
|
|
19
|
+
//# sourceMappingURL=preflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.d.ts","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,YAAY,EAAE,OAAO,CAAC;IACtB,wEAAwE;IACxE,UAAU,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAuCD,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAyDpD"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Preflight: cheap sanity checks before scanning. Tagging works perfectly yet
|
|
3
|
+
* nothing is editable if CaretCMS isn't actually wired into the project, so we
|
|
4
|
+
* surface that up front. Git cleanliness is advisory — git is the real undo.
|
|
5
|
+
*/
|
|
6
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
7
|
+
import { resolve } from "node:path";
|
|
8
|
+
import { execFileSync } from "node:child_process";
|
|
9
|
+
function readPkg(rootDir) {
|
|
10
|
+
const p = resolve(rootDir, "package.json");
|
|
11
|
+
if (!existsSync(p))
|
|
12
|
+
return null;
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(readFileSync(p, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function deps(pkg) {
|
|
21
|
+
if (!pkg)
|
|
22
|
+
return {};
|
|
23
|
+
return {
|
|
24
|
+
...pkg.dependencies,
|
|
25
|
+
...pkg.devDependencies,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function findAstroConfig(rootDir) {
|
|
29
|
+
try {
|
|
30
|
+
const name = readdirSync(rootDir).find((f) => /^astro\.config\.(m?[jt]s|cjs)$/.test(f));
|
|
31
|
+
return name ? resolve(rootDir, name) : null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function readConfig(rootDir) {
|
|
38
|
+
const path = findAstroConfig(rootDir);
|
|
39
|
+
if (!path)
|
|
40
|
+
return null;
|
|
41
|
+
try {
|
|
42
|
+
return readFileSync(path, "utf8");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function preflight(rootDir) {
|
|
49
|
+
const pkg = readPkg(rootDir);
|
|
50
|
+
const d = deps(pkg);
|
|
51
|
+
const config = readConfig(rootDir);
|
|
52
|
+
const isAstroProject = "astro" in d || config !== null;
|
|
53
|
+
const hasCaretCore = "@caretcms/core" in d;
|
|
54
|
+
// caret() is wired only if the config actually references it (imports the
|
|
55
|
+
// package or calls caret(...) in the integrations array).
|
|
56
|
+
const caretWired = config !== null && (/\bcaret\s*\(/.test(config) || /@caretcms\/core/.test(config));
|
|
57
|
+
// Output mode (Astro defaults to "static" when unset). "hybrid" was removed
|
|
58
|
+
// in Astro 5 — a config still carrying it is treated as unknown/static so
|
|
59
|
+
// the warning below points the user at a mode that exists.
|
|
60
|
+
const outputMatch = config?.match(/output\s*:\s*["'](server|static)["']/);
|
|
61
|
+
const outputMode = outputMatch ? outputMatch[1] : "static";
|
|
62
|
+
let gitRepo = false;
|
|
63
|
+
let gitClean = null;
|
|
64
|
+
try {
|
|
65
|
+
const out = execFileSync("git", ["status", "--porcelain"], {
|
|
66
|
+
cwd: rootDir,
|
|
67
|
+
encoding: "utf8",
|
|
68
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
69
|
+
});
|
|
70
|
+
gitRepo = true;
|
|
71
|
+
gitClean = out.trim() === "";
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
gitRepo = false;
|
|
75
|
+
}
|
|
76
|
+
const errors = [];
|
|
77
|
+
const warnings = [];
|
|
78
|
+
if (!isAstroProject) {
|
|
79
|
+
errors.push("This does not look like an Astro project (no astro dependency or astro.config).");
|
|
80
|
+
}
|
|
81
|
+
if (!hasCaretCore) {
|
|
82
|
+
warnings.push("@caretcms/core is not installed — tags will be added, but nothing is editable until CaretCMS is set up.");
|
|
83
|
+
}
|
|
84
|
+
else if (!caretWired) {
|
|
85
|
+
warnings.push("@caretcms/core is installed but caret() is not in astro.config — tags will be inert until you add it to integrations.");
|
|
86
|
+
}
|
|
87
|
+
if (hasCaretCore && outputMode === "static") {
|
|
88
|
+
warnings.push(`output is "static" — embedded inline editing needs output: "server" plus an SSR adapter (e.g. @astrojs/node).`);
|
|
89
|
+
}
|
|
90
|
+
if (gitRepo && gitClean === false) {
|
|
91
|
+
warnings.push("Working tree has uncommitted changes — commit first so you can `git checkout` to undo.");
|
|
92
|
+
}
|
|
93
|
+
return { isAstroProject, hasCaretCore, caretWired, outputMode, gitRepo, gitClean, errors, warnings };
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=preflight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preflight.js","sourceRoot":"","sources":["../src/preflight.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
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 { type AstroNode } from "./parse.js";
|
|
26
|
+
import { type FileReader } from "./props.js";
|
|
27
|
+
/** One prop literal to hoist, with the exact tokens needed to apply + invert. */
|
|
28
|
+
export interface HoistProp {
|
|
29
|
+
propName: string;
|
|
30
|
+
literalValue: string;
|
|
31
|
+
key: string;
|
|
32
|
+
constName: string;
|
|
33
|
+
isRich: boolean;
|
|
34
|
+
/** Exact source token being replaced, e.g. `title="ABOUT US"`. */
|
|
35
|
+
attrBefore: string;
|
|
36
|
+
/** Replacement token, e.g. `title={pageHeroTitle}`. */
|
|
37
|
+
attrAfter: string;
|
|
38
|
+
/** Exact text inserted into frontmatter (leading newline included). */
|
|
39
|
+
constInsert: string;
|
|
40
|
+
}
|
|
41
|
+
export interface PropHoistTarget {
|
|
42
|
+
componentName: string;
|
|
43
|
+
props: HoistProp[];
|
|
44
|
+
}
|
|
45
|
+
export interface HoistResult {
|
|
46
|
+
output: string;
|
|
47
|
+
ok: boolean;
|
|
48
|
+
reason?: string;
|
|
49
|
+
propsRewritten: number;
|
|
50
|
+
constsDeclared: string[];
|
|
51
|
+
/** True when this transform added the `editable` import. */
|
|
52
|
+
addedImport: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect prop-hoist targets: component invocations with static string props
|
|
56
|
+
* whose child provably renders them as text. `readFile` resolves sibling files.
|
|
57
|
+
*/
|
|
58
|
+
export declare function detectPropHoistTargets(source: string, relPath: string, readFile: FileReader, root?: AstroNode): Promise<PropHoistTarget[]>;
|
|
59
|
+
/** Apply the hoist rewrite. Self-locating: finds each `attrBefore` token by content. */
|
|
60
|
+
export declare function hoistPropLiterals(source: string, targets: PropHoistTarget[]): HoistResult;
|
|
61
|
+
/**
|
|
62
|
+
* Inverse gate: undo every hoist edit on `output` and assert the result is
|
|
63
|
+
* byte-identical to `intermediate` (the source the hoist was applied to). This
|
|
64
|
+
* proves the ONLY changes were the intended hoists — no collateral edits.
|
|
65
|
+
*/
|
|
66
|
+
export declare function verifyHoistResult(intermediate: string, output: string, targets: PropHoistTarget[], addedImport: boolean): boolean;
|
|
67
|
+
//# sourceMappingURL=prop-hoist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prop-hoist.d.ts","sourceRoot":"","sources":["../src/prop-hoist.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAwB,KAAK,SAAS,EAAgB,MAAM,YAAY,CAAC;AAGhF,OAAO,EAAiB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAK5D,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,4DAA4D;IAC5D,WAAW,EAAE,OAAO,CAAC;CACtB;AAuDD;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,UAAU,EACpB,IAAI,CAAC,EAAE,SAAS,GACf,OAAO,CAAC,eAAe,EAAE,CAAC,CAyF5B;AAED,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,WAAW,CAsCzF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,eAAe,EAAE,EAC1B,WAAW,EAAE,OAAO,GACnB,OAAO,CAYT"}
|