@samva/markup 0.9.0 → 0.11.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/AGENTS.md +53 -41
- package/CHANGELOG.md +379 -0
- package/README.md +6 -6
- package/dist/assets-B0yseju_.d.ts +115 -0
- package/dist/brand-DTctcDmf.js +202 -0
- package/dist/brand-plugin-CHhcei-j.d.ts +32 -0
- package/dist/{theme-css.d.ts → brand.d.ts} +15 -4
- package/dist/brand.js +4 -0
- package/dist/compiler.d.ts +236 -18
- package/dist/compiler.js +1315 -13
- package/dist/{email/components.js → components-Bl6DT21n.js} +4 -4
- package/dist/diagnostic-model-DJtqbETd.js +38 -0
- package/dist/diagnostic-model-DxthUZBT.d.ts +31 -0
- package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +2 -2
- package/dist/diagnostics.d.ts +406 -27
- package/dist/diagnostics.js +402 -34
- package/dist/edit.d.ts +234 -0
- package/dist/{channel-edit.js → edit.js} +341 -3
- package/dist/email/jsx-dev-runtime.d.ts +1 -1
- package/dist/email/jsx-dev-runtime.js +1 -2
- package/dist/email/jsx-runtime.d.ts +1 -1
- package/dist/email/jsx-runtime.js +1 -85
- package/dist/{email/components.d.ts → email.d.ts} +2 -2
- package/dist/email.js +2 -0
- package/dist/html-DjPxwjLi.js +1637 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/{cascade-t6kxlgEI.js → ir-De6pTH51.js} +8 -4
- package/dist/jsx-runtime-CBlexoeR.js +98 -0
- package/dist/{elements-cqldpaJt.d.ts → jsx-runtime-ylIOnaSf.d.ts} +74 -74
- package/dist/{profile-BH-OtdDA.js → profile-PUfi3GPH.js} +166 -32
- package/dist/{email/render.js → render-D2ht57ZZ.js} +103 -8
- package/dist/render-DQKz4Aco.d.ts +548 -0
- package/dist/render.d.ts +35 -0
- package/dist/render.js +49 -0
- package/dist/sms.d.ts +1 -1
- package/dist/sms.js +1 -1
- package/dist/{template-_QE_rQND.d.ts → template-DMPwxMSq.d.ts} +2 -2
- package/dist/{template.js → template-zLUjcwfF.js} +2 -2
- package/dist/{theme-css-hFqcPSar.js → theme-css-CEUrDpqq.js} +3 -3
- package/dist/whatsapp.d.ts +1 -1
- package/dist/whatsapp.js +1 -1
- package/generated/contract.json +18 -12
- package/generated/reference.md +19 -12
- package/package.json +39 -122
- package/samva-brand.d.ts +4 -4
- package/skills/sml/SKILL.md +2 -2
- package/dist/assets-C5duwTya.d.ts +0 -40
- package/dist/brand-B-0bN_Ir.d.ts +0 -88
- package/dist/channel-edit.d.ts +0 -121
- package/dist/channel-specs-B6M_HlaV.js +0 -29
- package/dist/compatibility-DbISoFIq.js +0 -589
- package/dist/css-BVQKICKC.d.ts +0 -109
- package/dist/diagnostic-codes.d.ts +0 -403
- package/dist/diagnostic-codes.js +0 -399
- package/dist/email/assets.d.ts +0 -2
- package/dist/email/assets.js +0 -63
- package/dist/email/brand-footer.d.ts +0 -34
- package/dist/email/brand-footer.js +0 -63
- package/dist/email/brand.d.ts +0 -2
- package/dist/email/brand.js +0 -148
- package/dist/email/diagnostics.d.ts +0 -2
- package/dist/email/elements.d.ts +0 -2
- package/dist/email/elements.js +0 -299
- package/dist/email/html.d.ts +0 -2
- package/dist/email/html.js +0 -321
- package/dist/email/render.d.ts +0 -40
- package/dist/email/text.d.ts +0 -7
- package/dist/email/text.js +0 -100
- package/dist/html-BJepOwZ4.d.ts +0 -56
- package/dist/ir-aaqNE987.d.ts +0 -252
- package/dist/ir.d.ts +0 -2
- package/dist/ir.js +0 -7
- package/dist/preview-Ekdg1dXG.d.ts +0 -100
- package/dist/preview.d.ts +0 -2
- package/dist/preview.js +0 -160
- package/dist/render-ir.d.ts +0 -19
- package/dist/render-ir.js +0 -826
- package/dist/sms-segments.d.ts +0 -13
- package/dist/sms-segments.js +0 -46
- package/dist/source-edit.d.ts +0 -116
- package/dist/source-edit.js +0 -343
- package/dist/source-locations.d.ts +0 -2
- package/dist/source-locations.js +0 -16
- package/dist/static.d.ts +0 -57
- package/dist/static.js +0 -69
- package/dist/tailwind-CMYpZf1v.js +0 -614
- package/dist/template.d.ts +0 -2
- package/dist/theme-css.js +0 -2
- package/dist/theme-imports-lvBw3AD1.js +0 -109
- package/dist/transform-Ct2OMkFN.d.ts +0 -153
- package/dist/unsubscribe-link-CoesnQDU.js +0 -26
package/dist/sms-segments.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
//#region src/sms-segments.d.ts
|
|
2
|
-
/** The encoding a message needs: GSM-7 when every character fits, UCS-2 otherwise. */
|
|
3
|
-
type SmsEncoding = "gsm7" | "ucs2";
|
|
4
|
-
interface SmsSegmentInfo {
|
|
5
|
-
readonly encoding: SmsEncoding;
|
|
6
|
-
readonly characterCount: number;
|
|
7
|
-
readonly segmentCount: number;
|
|
8
|
-
}
|
|
9
|
-
declare const smsSegmentInfo: (text: string) => SmsSegmentInfo;
|
|
10
|
-
declare const hasSmsOptOut: (text: string) => boolean;
|
|
11
|
-
//#endregion
|
|
12
|
-
export { SmsEncoding, SmsSegmentInfo, hasSmsOptOut, smsSegmentInfo };
|
|
13
|
-
//# sourceMappingURL=sms-segments.d.ts.map
|
package/dist/sms-segments.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
//#region src/sms-segments.ts
|
|
2
|
-
const GSM7_EXTENDED = /* @__PURE__ */ new Set([
|
|
3
|
-
"[",
|
|
4
|
-
"]",
|
|
5
|
-
"{",
|
|
6
|
-
"}",
|
|
7
|
-
"\\",
|
|
8
|
-
"~",
|
|
9
|
-
"|",
|
|
10
|
-
"€",
|
|
11
|
-
"^",
|
|
12
|
-
"\f"
|
|
13
|
-
]);
|
|
14
|
-
const GSM7_BASIC_PATTERN = /^[@£$¥èéùìòÇ\nØø\rÅåΔ_ΦΓΛΩΠΨΣΘΞÆæßÉ !"#¤%&'()*+,\-./:;<=>?¡0-9ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÑܧ¿abcdefghijklmnopqrstuvwxyzäöñüà]*$/;
|
|
15
|
-
const containsNonGsmCharacters = (text) => {
|
|
16
|
-
for (const char of text) {
|
|
17
|
-
if (GSM7_EXTENDED.has(char)) continue;
|
|
18
|
-
if (!GSM7_BASIC_PATTERN.test(char)) return true;
|
|
19
|
-
}
|
|
20
|
-
return false;
|
|
21
|
-
};
|
|
22
|
-
const smsSegmentInfo = (text) => {
|
|
23
|
-
if (containsNonGsmCharacters(text)) {
|
|
24
|
-
const codePoints = [...text].length;
|
|
25
|
-
const segmentCount = codePoints <= 70 ? 1 : Math.ceil(codePoints / 67);
|
|
26
|
-
return {
|
|
27
|
-
encoding: "ucs2",
|
|
28
|
-
characterCount: text.length,
|
|
29
|
-
segmentCount
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
let encodedLength = 0;
|
|
33
|
-
for (const char of text) encodedLength += GSM7_EXTENDED.has(char) ? 2 : 1;
|
|
34
|
-
const segmentCount = encodedLength <= 160 ? 1 : Math.ceil(encodedLength / 153);
|
|
35
|
-
return {
|
|
36
|
-
encoding: "gsm7",
|
|
37
|
-
characterCount: text.length,
|
|
38
|
-
segmentCount
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
const SMS_OPT_OUT_PATTERN = /\b(stop|unsubscribe|opt[\s-]?out|cancel|quit)\b/i;
|
|
42
|
-
const hasSmsOptOut = (text) => SMS_OPT_OUT_PATTERN.test(text);
|
|
43
|
-
//#endregion
|
|
44
|
-
export { hasSmsOptOut, smsSegmentInfo };
|
|
45
|
-
|
|
46
|
-
//# sourceMappingURL=sms-segments.js.map
|
package/dist/source-edit.d.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { r as JsxSourceLocation } from "./source-locations-C8wCsiAy.js";
|
|
2
|
-
//#region src/source-edit.d.ts
|
|
3
|
-
/** One exact source replacement; before text guards against concurrent changes. */
|
|
4
|
-
interface SourceReplacement {
|
|
5
|
-
readonly start: number;
|
|
6
|
-
readonly end: number;
|
|
7
|
-
readonly before: string;
|
|
8
|
-
readonly after: string;
|
|
9
|
-
}
|
|
10
|
-
type SourceEditResult = {
|
|
11
|
-
readonly ok: true;
|
|
12
|
-
readonly source: string;
|
|
13
|
-
} | {
|
|
14
|
-
readonly ok: false;
|
|
15
|
-
readonly reason: string;
|
|
16
|
-
};
|
|
17
|
-
interface SourceProperty {
|
|
18
|
-
readonly name: string;
|
|
19
|
-
readonly kind: "literal" | "binding" | "computed";
|
|
20
|
-
readonly value: string;
|
|
21
|
-
readonly literalType: "string" | "number" | "boolean";
|
|
22
|
-
readonly start: number;
|
|
23
|
-
readonly end: number;
|
|
24
|
-
readonly syntax: "attribute" | "child" | "value";
|
|
25
|
-
}
|
|
26
|
-
interface SourceElement {
|
|
27
|
-
readonly start: number;
|
|
28
|
-
readonly end: number;
|
|
29
|
-
readonly contentStart: number | null;
|
|
30
|
-
readonly contentEnd: number | null;
|
|
31
|
-
readonly tag: string;
|
|
32
|
-
readonly attributeInsertion: number;
|
|
33
|
-
readonly hasSpread: boolean;
|
|
34
|
-
readonly properties: readonly SourceProperty[];
|
|
35
|
-
readonly structural: boolean;
|
|
36
|
-
/** The JSX element sibling directly before / after this one, when both share a JSX parent. */
|
|
37
|
-
readonly previous: SourceRange | null;
|
|
38
|
-
readonly next: SourceRange | null;
|
|
39
|
-
}
|
|
40
|
-
interface SourceRange {
|
|
41
|
-
readonly start: number;
|
|
42
|
-
readonly end: number;
|
|
43
|
-
}
|
|
44
|
-
/** Resolve a renderer origin against the unchanged authored entry, never against HTML offsets. */
|
|
45
|
-
declare const inspectSourceElement: (source: string, file: string, origin: JsxSourceLocation) => SourceElement | null;
|
|
46
|
-
/** Literal controls cannot overwrite a binding or computed expression. */
|
|
47
|
-
declare const literalReplacement: (source: string, field: SourceProperty, value: string) => SourceReplacement | null;
|
|
48
|
-
/** Explicit expression changes are proposals: callers show before/after and require Apply. */
|
|
49
|
-
declare const expressionReplacement: (source: string, field: SourceProperty, expression: string) => SourceReplacement;
|
|
50
|
-
/**
|
|
51
|
-
* Where an edited file sits in its template project. Every edit of a template is checked against
|
|
52
|
-
* the static profile: one that would leave it is refused, so a visual edit can never produce a
|
|
53
|
-
* template the compiler rejects.
|
|
54
|
-
*/
|
|
55
|
-
interface ProfileGuard {
|
|
56
|
-
/** The template entry the profile check reads. */
|
|
57
|
-
readonly entry: string;
|
|
58
|
-
/** The file the edit changes; the entry when omitted. A partial names itself here. */
|
|
59
|
-
readonly file?: string | undefined;
|
|
60
|
-
/** The project's other files, when the edited file is not the entry. */
|
|
61
|
-
readonly files?: Readonly<Record<string, string>> | undefined;
|
|
62
|
-
}
|
|
63
|
-
interface ApplyOptions {
|
|
64
|
-
readonly profile?: ProfileGuard | undefined;
|
|
65
|
-
}
|
|
66
|
-
/** Apply exact, nonoverlapping edits and refuse syntax damage without changing the original. */
|
|
67
|
-
declare const applySourceReplacements: (source: string, edits: readonly SourceReplacement[], options?: ApplyOptions) => SourceEditResult;
|
|
68
|
-
type SourceProposal = {
|
|
69
|
-
readonly ok: true;
|
|
70
|
-
readonly edits: readonly SourceReplacement[];
|
|
71
|
-
} | {
|
|
72
|
-
readonly ok: false;
|
|
73
|
-
readonly reason: string;
|
|
74
|
-
};
|
|
75
|
-
/** Structural edits name a whole authored block; loop iterations are never source nodes. */
|
|
76
|
-
declare const structuralReplacements: (source: string, element: SourceElement, operation: {
|
|
77
|
-
readonly kind: "delete";
|
|
78
|
-
} | {
|
|
79
|
-
readonly kind: "duplicate";
|
|
80
|
-
} | {
|
|
81
|
-
readonly kind: "reorder";
|
|
82
|
-
readonly direction: "before" | "after";
|
|
83
|
-
} | {
|
|
84
|
-
readonly kind: "insert";
|
|
85
|
-
readonly tsx: string;
|
|
86
|
-
} | {
|
|
87
|
-
readonly kind: "move";
|
|
88
|
-
readonly destination: SourceElement;
|
|
89
|
-
}) => readonly SourceReplacement[] | null;
|
|
90
|
-
interface SourceFixture {
|
|
91
|
-
readonly name: string;
|
|
92
|
-
readonly start: number;
|
|
93
|
-
readonly end: number;
|
|
94
|
-
readonly source: string;
|
|
95
|
-
}
|
|
96
|
-
/** Declared fixture values are separate source targets from body bindings and literals. */
|
|
97
|
-
declare const inspectSourceFixtures: (source: string) => readonly SourceFixture[];
|
|
98
|
-
type ClassNameOperation = {
|
|
99
|
-
readonly kind: "set";
|
|
100
|
-
readonly value: string;
|
|
101
|
-
} | {
|
|
102
|
-
readonly kind: "add";
|
|
103
|
-
readonly tokens: string;
|
|
104
|
-
} | {
|
|
105
|
-
readonly kind: "remove";
|
|
106
|
-
readonly tokens: string;
|
|
107
|
-
};
|
|
108
|
-
/**
|
|
109
|
-
* Edit an element's class list where it is written. A literal `className` is replaced, extended or
|
|
110
|
-
* trimmed in place; a missing one is added; one that is a condition or any other expression is
|
|
111
|
-
* refused, because rewriting it would discard the choice the author encoded.
|
|
112
|
-
*/
|
|
113
|
-
declare const classNameEdit: (source: string, element: SourceElement, operation: ClassNameOperation) => SourceProposal;
|
|
114
|
-
//#endregion
|
|
115
|
-
export { ApplyOptions, ClassNameOperation, ProfileGuard, SourceEditResult, SourceElement, SourceFixture, SourceProperty, SourceProposal, SourceRange, SourceReplacement, applySourceReplacements, classNameEdit, expressionReplacement, inspectSourceElement, inspectSourceFixtures, literalReplacement, structuralReplacements };
|
|
116
|
-
//# sourceMappingURL=source-edit.d.ts.map
|
package/dist/source-edit.js
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
import { n as isObject } from "./guards-eV1qLbf2.js";
|
|
2
|
-
import { buildLineMap } from "./diagnostics.js";
|
|
3
|
-
import { n as introducedProfileErrors, t as checkStaticProfile } from "./profile-BH-OtdDA.js";
|
|
4
|
-
import { parse } from "@babel/parser";
|
|
5
|
-
//#region src/source-edit.ts
|
|
6
|
-
const node = (value) => isObject(value) && "type" in value && typeof value.type === "string" && "start" in value && typeof value.start === "number" && "end" in value && typeof value.end === "number";
|
|
7
|
-
const children = (value) => Array.isArray(value) ? value.filter(node) : [];
|
|
8
|
-
const walk = (value, visit, parent = null) => {
|
|
9
|
-
if (Array.isArray(value)) {
|
|
10
|
-
for (const item of value) walk(item, visit, parent);
|
|
11
|
-
return;
|
|
12
|
-
}
|
|
13
|
-
if (!node(value)) return;
|
|
14
|
-
visit(value, parent);
|
|
15
|
-
for (const [key, child] of Object.entries(value)) if (key !== "loc" && key !== "extra" && key !== "comments") walk(child, visit, value);
|
|
16
|
-
};
|
|
17
|
-
const parsed = (source) => {
|
|
18
|
-
try {
|
|
19
|
-
const result = parse(source, {
|
|
20
|
-
sourceType: "module",
|
|
21
|
-
plugins: ["typescript", "jsx"]
|
|
22
|
-
});
|
|
23
|
-
return node(result) ? result : null;
|
|
24
|
-
} catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
const binding = (value) => value.type === "Identifier" || value.type === "MemberExpression" && value.computed === false && node(value.object) && binding(value.object);
|
|
29
|
-
/** JSX trims each line of text and joins the non-empty lines with one space. */
|
|
30
|
-
const collapseJsxText = (value) => value.split(/\r\n|\n|\r|\u2028|\u2029/).map((line) => line.trim()).filter((line) => line !== "").join(" ");
|
|
31
|
-
const property = (name, value, source, syntax) => {
|
|
32
|
-
const expression = value.type === "JSXExpressionContainer" && node(value.expression) ? value.expression : value;
|
|
33
|
-
const literal = expression.type === "StringLiteral" || expression.type === "NumericLiteral" || expression.type === "BooleanLiteral" || expression.type === "JSXText";
|
|
34
|
-
const raw = source.slice(expression.start, expression.end);
|
|
35
|
-
const inset = expression.type === "JSXText" ? {
|
|
36
|
-
start: expression.start + (raw.length - raw.trimStart().length),
|
|
37
|
-
end: expression.end - (raw.length - raw.trimEnd().length)
|
|
38
|
-
} : {
|
|
39
|
-
start: value.start,
|
|
40
|
-
end: value.end
|
|
41
|
-
};
|
|
42
|
-
return {
|
|
43
|
-
name,
|
|
44
|
-
literalType: expression.type === "NumericLiteral" ? "number" : expression.type === "BooleanLiteral" ? "boolean" : "string",
|
|
45
|
-
kind: literal ? "literal" : binding(expression) ? "binding" : "computed",
|
|
46
|
-
value: literal && expression.type === "JSXText" && typeof expression.value === "string" ? collapseJsxText(expression.value) : literal && (typeof expression.value === "string" || typeof expression.value === "number" || typeof expression.value === "boolean") ? String(expression.value) : source.slice(expression.start, expression.end),
|
|
47
|
-
start: inset.start,
|
|
48
|
-
end: inset.end,
|
|
49
|
-
syntax
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
/** The JSX elements directly before and after `element` in its parent's child list. */
|
|
53
|
-
const siblingsOf = (element, parent, source) => {
|
|
54
|
-
if (parent?.type !== "JSXElement" && parent?.type !== "JSXFragment") return {
|
|
55
|
-
previous: null,
|
|
56
|
-
next: null
|
|
57
|
-
};
|
|
58
|
-
const list = children(parent.children).filter((child) => child.type !== "JSXText" || source.slice(child.start, child.end).trim() !== "");
|
|
59
|
-
const index = list.findIndex((child) => child.start === element.start);
|
|
60
|
-
const range = (child) => child?.type === "JSXElement" ? {
|
|
61
|
-
start: child.start,
|
|
62
|
-
end: child.end
|
|
63
|
-
} : null;
|
|
64
|
-
return {
|
|
65
|
-
previous: range(list[index - 1]),
|
|
66
|
-
next: range(list[index + 1])
|
|
67
|
-
};
|
|
68
|
-
};
|
|
69
|
-
/** Resolve a renderer origin against the unchanged authored entry, never against HTML offsets. */
|
|
70
|
-
const inspectSourceElement = (source, file, origin) => {
|
|
71
|
-
if (file !== origin.fileName) return null;
|
|
72
|
-
const tree = parsed(source);
|
|
73
|
-
if (tree === null) return null;
|
|
74
|
-
const lines = buildLineMap(source);
|
|
75
|
-
const lineStart = lines[origin.lineNumber - 1];
|
|
76
|
-
if (lineStart === void 0 || !Number.isInteger(origin.columnNumber) || origin.columnNumber < 1) return null;
|
|
77
|
-
const offset = lineStart + origin.columnNumber - 1;
|
|
78
|
-
if (offset >= (lines[origin.lineNumber] ?? source.length)) return null;
|
|
79
|
-
let found = null;
|
|
80
|
-
walk(tree, (element, parent) => {
|
|
81
|
-
if (element.type !== "JSXElement" || element.start !== offset || !node(element.openingElement)) return;
|
|
82
|
-
const opening = element.openingElement;
|
|
83
|
-
const properties = [];
|
|
84
|
-
for (const attribute of children(opening.attributes)) if (attribute.type === "JSXAttribute" && node(attribute.name) && typeof attribute.name.name === "string" && node(attribute.value)) {
|
|
85
|
-
const value = attribute.value;
|
|
86
|
-
const expression = value.type === "JSXExpressionContainer" && node(value.expression) ? value.expression : null;
|
|
87
|
-
if (attribute.name.name === "style" && expression?.type === "ObjectExpression" && !children(expression.properties).some((member) => member.type === "SpreadElement")) for (const member of children(expression.properties)) {
|
|
88
|
-
if (member.type !== "ObjectProperty" || member.computed === true || !node(member.key) || !node(member.value)) continue;
|
|
89
|
-
const key = member.key.type === "Identifier" ? member.key.name : member.key.value;
|
|
90
|
-
if (typeof key === "string") properties.push(property(`style.${key}`, member.value, source, "value"));
|
|
91
|
-
}
|
|
92
|
-
else properties.push(property(attribute.name.name, value, source, "attribute"));
|
|
93
|
-
}
|
|
94
|
-
const meaningful = children(element.children).filter((child) => child.type !== "JSXText" || source.slice(child.start, child.end).trim() !== "");
|
|
95
|
-
if (meaningful.length === 1 && meaningful[0] !== void 0 && meaningful[0].type !== "JSXElement" && meaningful[0].type !== "JSXFragment") properties.unshift(property("children", meaningful[0], source, "child"));
|
|
96
|
-
else if (meaningful.length > 1) {
|
|
97
|
-
const texts = meaningful.flatMap((child, index) => child.type === "JSXText" ? [property(`text.${index}`, child, source, "child")] : []);
|
|
98
|
-
properties.unshift(...texts);
|
|
99
|
-
}
|
|
100
|
-
found = {
|
|
101
|
-
start: element.start,
|
|
102
|
-
end: element.end,
|
|
103
|
-
contentStart: opening.selfClosing === true ? null : opening.end,
|
|
104
|
-
contentEnd: node(element.closingElement) ? element.closingElement.start : null,
|
|
105
|
-
tag: node(opening.name) ? source.slice(opening.name.start, opening.name.end) : "element",
|
|
106
|
-
properties,
|
|
107
|
-
attributeInsertion: (() => {
|
|
108
|
-
const last = children(opening.attributes).at(-1);
|
|
109
|
-
if (last !== void 0) return last.end;
|
|
110
|
-
return node(opening.name) ? opening.name.end : opening.end - 1;
|
|
111
|
-
})(),
|
|
112
|
-
hasSpread: children(opening.attributes).some((attribute) => attribute.type === "JSXSpreadAttribute"),
|
|
113
|
-
structural: parent?.type === "JSXElement" || parent?.type === "JSXFragment",
|
|
114
|
-
...siblingsOf(element, parent, source)
|
|
115
|
-
};
|
|
116
|
-
});
|
|
117
|
-
return found;
|
|
118
|
-
};
|
|
119
|
-
const PLAIN_JSX_TEXT = /^[^\s{}<>&](?:[^{}<>&\r\n\u2028\u2029]*[^\s{}<>&])?$/;
|
|
120
|
-
/** Literal controls cannot overwrite a binding or computed expression. */
|
|
121
|
-
const literalReplacement = (source, field, value) => {
|
|
122
|
-
if (field.kind !== "literal") return null;
|
|
123
|
-
if (field.literalType === "number" && (value.trim() === "" || !Number.isFinite(Number(value)))) return null;
|
|
124
|
-
if (field.literalType === "boolean" && value !== "true" && value !== "false") return null;
|
|
125
|
-
const literal = field.literalType === "string" ? JSON.stringify(value) : field.literalType === "number" ? String(Number(value)) : value;
|
|
126
|
-
const before = source.slice(field.start, field.end);
|
|
127
|
-
const plainText = field.syntax === "child" && !before.startsWith("{") && PLAIN_JSX_TEXT.test(value);
|
|
128
|
-
return {
|
|
129
|
-
start: field.start,
|
|
130
|
-
end: field.end,
|
|
131
|
-
before,
|
|
132
|
-
after: plainText ? value : field.syntax === "child" || field.syntax === "attribute" && field.literalType !== "string" ? `{${literal}}` : literal
|
|
133
|
-
};
|
|
134
|
-
};
|
|
135
|
-
/** Explicit expression changes are proposals: callers show before/after and require Apply. */
|
|
136
|
-
const expressionReplacement = (source, field, expression) => ({
|
|
137
|
-
start: field.start,
|
|
138
|
-
end: field.end,
|
|
139
|
-
before: source.slice(field.start, field.end),
|
|
140
|
-
after: field.syntax === "value" ? expression : `{${expression}}`
|
|
141
|
-
});
|
|
142
|
-
const profileRefusal = (source, result, guard) => {
|
|
143
|
-
const file = guard.file ?? guard.entry;
|
|
144
|
-
const project = (content) => ({
|
|
145
|
-
...guard.files,
|
|
146
|
-
[file]: content
|
|
147
|
-
});
|
|
148
|
-
const introduced = introducedProfileErrors(checkStaticProfile(project(source), guard.entry), checkStaticProfile(project(result), guard.entry));
|
|
149
|
-
const first = introduced[0];
|
|
150
|
-
if (first === void 0) return null;
|
|
151
|
-
const more = introduced.length > 1 ? ` (and ${introduced.length - 1} more)` : "";
|
|
152
|
-
return `This change leaves the static profile: ${first.message}${first.fix === void 0 ? "" : ` ${first.fix}`}${more}`;
|
|
153
|
-
};
|
|
154
|
-
/** Apply exact, nonoverlapping edits and refuse syntax damage without changing the original. */
|
|
155
|
-
const applySourceReplacements = (source, edits, options = {}) => {
|
|
156
|
-
const sorted = [...edits].sort((a, b) => b.start - a.start);
|
|
157
|
-
let boundary = source.length;
|
|
158
|
-
let result = source;
|
|
159
|
-
for (const edit of sorted) {
|
|
160
|
-
if (edit.start < 0 || edit.end < edit.start || edit.end > boundary || source.slice(edit.start, edit.end) !== edit.before) return {
|
|
161
|
-
ok: false,
|
|
162
|
-
reason: "Source changed at this target. Select it again before applying the edit."
|
|
163
|
-
};
|
|
164
|
-
result = result.slice(0, edit.start) + edit.after + result.slice(edit.end);
|
|
165
|
-
boundary = edit.start;
|
|
166
|
-
}
|
|
167
|
-
if (parsed(result) === null) return {
|
|
168
|
-
ok: false,
|
|
169
|
-
reason: "This change is not valid TSX. Review it in Source."
|
|
170
|
-
};
|
|
171
|
-
if (options.profile !== void 0 && result !== source) {
|
|
172
|
-
const refusal = profileRefusal(source, result, options.profile);
|
|
173
|
-
if (refusal !== null) return {
|
|
174
|
-
ok: false,
|
|
175
|
-
reason: refusal
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
return {
|
|
179
|
-
ok: true,
|
|
180
|
-
source: result
|
|
181
|
-
};
|
|
182
|
-
};
|
|
183
|
-
const indentation = (source, offset) => {
|
|
184
|
-
const lineStart = Math.max(source.lastIndexOf("\n", offset - 1), source.lastIndexOf("\r", offset - 1));
|
|
185
|
-
const lead = source.slice(lineStart + 1, offset);
|
|
186
|
-
return /^[ \t]*$/.test(lead) ? lead : null;
|
|
187
|
-
};
|
|
188
|
-
/** The whole lines an element alone occupies, so removing it leaves no blank line behind. */
|
|
189
|
-
const ownLines = (source, element) => {
|
|
190
|
-
if (indentation(source, element.start) === null) return element;
|
|
191
|
-
const trailing = /^[ \t]*(\r\n|\n|\r)/.exec(source.slice(element.end));
|
|
192
|
-
if (trailing === null) return element;
|
|
193
|
-
return {
|
|
194
|
-
start: element.start - (indentation(source, element.start) ?? "").length,
|
|
195
|
-
end: element.end + trailing[0].length
|
|
196
|
-
};
|
|
197
|
-
};
|
|
198
|
-
/** Structural edits name a whole authored block; loop iterations are never source nodes. */
|
|
199
|
-
const structuralReplacements = (source, element, operation) => {
|
|
200
|
-
if (operation.kind === "insert") {
|
|
201
|
-
if (element.contentEnd === null) return null;
|
|
202
|
-
return [{
|
|
203
|
-
start: element.contentEnd,
|
|
204
|
-
end: element.contentEnd,
|
|
205
|
-
before: "",
|
|
206
|
-
after: operation.tsx
|
|
207
|
-
}];
|
|
208
|
-
}
|
|
209
|
-
if (!element.structural) return null;
|
|
210
|
-
const text = source.slice(element.start, element.end);
|
|
211
|
-
if (operation.kind === "duplicate") {
|
|
212
|
-
const indent = indentation(source, element.start);
|
|
213
|
-
return [{
|
|
214
|
-
start: element.end,
|
|
215
|
-
end: element.end,
|
|
216
|
-
before: "",
|
|
217
|
-
after: indent === null ? text : `\n${indent}${text}`
|
|
218
|
-
}];
|
|
219
|
-
}
|
|
220
|
-
if (operation.kind === "reorder") {
|
|
221
|
-
const neighbour = operation.direction === "before" ? element.previous : element.next;
|
|
222
|
-
if (neighbour === null) return null;
|
|
223
|
-
return [{
|
|
224
|
-
start: neighbour.start,
|
|
225
|
-
end: neighbour.end,
|
|
226
|
-
before: source.slice(neighbour.start, neighbour.end),
|
|
227
|
-
after: text
|
|
228
|
-
}, {
|
|
229
|
-
start: element.start,
|
|
230
|
-
end: element.end,
|
|
231
|
-
before: text,
|
|
232
|
-
after: source.slice(neighbour.start, neighbour.end)
|
|
233
|
-
}];
|
|
234
|
-
}
|
|
235
|
-
const removal = ownLines(source, element);
|
|
236
|
-
const removed = {
|
|
237
|
-
start: removal.start,
|
|
238
|
-
end: removal.end,
|
|
239
|
-
before: source.slice(removal.start, removal.end),
|
|
240
|
-
after: ""
|
|
241
|
-
};
|
|
242
|
-
if (operation.kind === "delete") return [removed];
|
|
243
|
-
const destination = operation.destination.contentEnd;
|
|
244
|
-
if (destination === null || destination >= element.start && destination <= element.end) return null;
|
|
245
|
-
return [removed, {
|
|
246
|
-
start: destination,
|
|
247
|
-
end: destination,
|
|
248
|
-
before: "",
|
|
249
|
-
after: text
|
|
250
|
-
}];
|
|
251
|
-
};
|
|
252
|
-
/** The object literal a top-level `const name = { ... }` holds. */
|
|
253
|
-
const topLevelObject = (tree, name) => {
|
|
254
|
-
const program = tree.program;
|
|
255
|
-
if (!node(program)) return null;
|
|
256
|
-
for (const item of children(program.body)) {
|
|
257
|
-
const declaration = item.type === "ExportNamedDeclaration" && node(item.declaration) ? item.declaration : item;
|
|
258
|
-
if (declaration.type !== "VariableDeclaration") continue;
|
|
259
|
-
for (const declarator of children(declaration.declarations)) if (node(declarator.id) && declarator.id.name === name && node(declarator.init) && declarator.init.type === "ObjectExpression") return declarator.init;
|
|
260
|
-
}
|
|
261
|
-
return null;
|
|
262
|
-
};
|
|
263
|
-
/** Declared fixture values are separate source targets from body bindings and literals. */
|
|
264
|
-
const inspectSourceFixtures = (source) => {
|
|
265
|
-
const tree = parsed(source);
|
|
266
|
-
if (tree === null) return [];
|
|
267
|
-
const found = [];
|
|
268
|
-
walk(tree, (call) => {
|
|
269
|
-
if (call.type !== "CallExpression" || !node(call.callee) || call.callee.type !== "Identifier" || call.callee.name !== "defineTemplate") return;
|
|
270
|
-
const definition = children(call.arguments)[0];
|
|
271
|
-
if (definition?.type !== "ObjectExpression") return;
|
|
272
|
-
const fixtures = children(definition.properties).find((field) => field.type === "ObjectProperty" && node(field.key) && field.key.name === "fixtures" && field.computed !== true);
|
|
273
|
-
if (fixtures === void 0 || !node(fixtures.value) || fixtures.value.type !== "ObjectExpression") return;
|
|
274
|
-
for (const field of children(fixtures.value.properties)) {
|
|
275
|
-
if (field.type !== "ObjectProperty" || field.computed === true || !node(field.key) || !node(field.value)) continue;
|
|
276
|
-
const name = field.key.type === "Identifier" ? field.key.name : field.key.value;
|
|
277
|
-
const value = field.value.type === "Identifier" && typeof field.value.name === "string" ? topLevelObject(tree, field.value.name) ?? field.value : field.value;
|
|
278
|
-
if (typeof name === "string") found.push({
|
|
279
|
-
name,
|
|
280
|
-
start: value.start,
|
|
281
|
-
end: value.end,
|
|
282
|
-
source: source.slice(value.start, value.end)
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
return found;
|
|
287
|
-
};
|
|
288
|
-
const tokens = (value) => value.split(/\s+/).filter((item) => item !== "");
|
|
289
|
-
/**
|
|
290
|
-
* Edit an element's class list where it is written. A literal `className` is replaced, extended or
|
|
291
|
-
* trimmed in place; a missing one is added; one that is a condition or any other expression is
|
|
292
|
-
* refused, because rewriting it would discard the choice the author encoded.
|
|
293
|
-
*/
|
|
294
|
-
const classNameEdit = (source, element, operation) => {
|
|
295
|
-
const field = element.properties.find((item) => item.name === "className");
|
|
296
|
-
const next = (current) => {
|
|
297
|
-
if (operation.kind === "set") return tokens(operation.value).join(" ");
|
|
298
|
-
const wanted = tokens(operation.tokens);
|
|
299
|
-
return operation.kind === "add" ? [...current, ...wanted.filter((item) => !current.includes(item))].join(" ") : current.filter((item) => !wanted.includes(item)).join(" ");
|
|
300
|
-
};
|
|
301
|
-
if (field === void 0) {
|
|
302
|
-
if (element.hasSpread) return {
|
|
303
|
-
ok: false,
|
|
304
|
-
reason: "A spread attribute can supply className here, so a class list cannot be added safely. Edit it in Source."
|
|
305
|
-
};
|
|
306
|
-
if (operation.kind === "remove") return {
|
|
307
|
-
ok: true,
|
|
308
|
-
edits: []
|
|
309
|
-
};
|
|
310
|
-
const value = next([]);
|
|
311
|
-
if (value === "") return {
|
|
312
|
-
ok: true,
|
|
313
|
-
edits: []
|
|
314
|
-
};
|
|
315
|
-
return {
|
|
316
|
-
ok: true,
|
|
317
|
-
edits: [{
|
|
318
|
-
start: element.attributeInsertion,
|
|
319
|
-
end: element.attributeInsertion,
|
|
320
|
-
before: "",
|
|
321
|
-
after: ` className=${JSON.stringify(value)}`
|
|
322
|
-
}]
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
if (field.kind !== "literal" || field.literalType !== "string") return {
|
|
326
|
-
ok: false,
|
|
327
|
-
reason: `className is ${field.kind === "binding" ? "bound to" : "computed from"} \`${field.value}\`, so editing it would discard that choice. Change the literal classes inside it in Source.`
|
|
328
|
-
};
|
|
329
|
-
const value = next(tokens(field.value));
|
|
330
|
-
return {
|
|
331
|
-
ok: true,
|
|
332
|
-
edits: [{
|
|
333
|
-
start: field.start,
|
|
334
|
-
end: field.end,
|
|
335
|
-
before: source.slice(field.start, field.end),
|
|
336
|
-
after: JSON.stringify(value)
|
|
337
|
-
}]
|
|
338
|
-
};
|
|
339
|
-
};
|
|
340
|
-
//#endregion
|
|
341
|
-
export { applySourceReplacements, classNameEdit, expressionReplacement, inspectSourceElement, inspectSourceFixtures, literalReplacement, structuralReplacements };
|
|
342
|
-
|
|
343
|
-
//# sourceMappingURL=source-edit.js.map
|
package/dist/source-locations.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
//#region src/source-locations.ts
|
|
2
|
-
/** The JSX prop that carries a {@link JsxSource} to the email JSX runtime. */
|
|
3
|
-
const JSX_SOURCE_LOCATION_PROP = "__samva_source_location";
|
|
4
|
-
/** Normalize the optional payload a JSX transform attaches. */
|
|
5
|
-
const completeJsxSourceLocation = (source) => {
|
|
6
|
-
if (source?.fileName === void 0 || source.lineNumber === void 0 || source.columnNumber === void 0) return;
|
|
7
|
-
return Object.freeze({
|
|
8
|
-
fileName: source.fileName,
|
|
9
|
-
lineNumber: source.lineNumber,
|
|
10
|
-
columnNumber: source.columnNumber
|
|
11
|
-
});
|
|
12
|
-
};
|
|
13
|
-
//#endregion
|
|
14
|
-
export { JSX_SOURCE_LOCATION_PROP, completeJsxSourceLocation };
|
|
15
|
-
|
|
16
|
-
//# sourceMappingURL=source-locations.js.map
|
package/dist/static.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { a as TemplateDiagnostic, r as EmailDiagnostic } from "./diagnostics-B-Gym34R.js";
|
|
2
|
-
import { E as TemplateIr } from "./ir-aaqNE987.js";
|
|
3
|
-
import { g as EmailClientTarget, n as TransformOptions } from "./transform-Ct2OMkFN.js";
|
|
4
|
-
import "./css-BVQKICKC.js";
|
|
5
|
-
import { n as AssetEntry } from "./assets-C5duwTya.js";
|
|
6
|
-
//#region src/sml/lower-project.d.ts
|
|
7
|
-
interface CompiledTemplate {
|
|
8
|
-
/** The IR, or `undefined` when a finding blocks it. */
|
|
9
|
-
readonly ir: TemplateIr | undefined;
|
|
10
|
-
/** The fixtures read from the entry, with imported assets resolved to their URLs. */
|
|
11
|
-
readonly fixtures: Readonly<Record<string, unknown>>;
|
|
12
|
-
readonly assets: readonly AssetEntry[];
|
|
13
|
-
/** Errors block the IR; warnings ride along. */
|
|
14
|
-
readonly diagnostics: readonly TemplateDiagnostic[];
|
|
15
|
-
}
|
|
16
|
-
//#endregion
|
|
17
|
-
//#region src/sml/compile.d.ts
|
|
18
|
-
interface CompileTemplateOptions extends TransformOptions {
|
|
19
|
-
/** The project's files by path. Binary files are addressed as assets. */
|
|
20
|
-
readonly files: Readonly<Record<string, string | Uint8Array>>;
|
|
21
|
-
/** The entry: a `.tsx` file that default-exports `defineTemplate({ ... })`. */
|
|
22
|
-
readonly entry: string;
|
|
23
|
-
/**
|
|
24
|
-
* The portable JSON Schema, for an entry whose `schema` is not a literal `jsonSchema(...)`
|
|
25
|
-
* (a Zod or Effect schema needs a library to become JSON Schema). A host that has evaluated the
|
|
26
|
-
* schema passes its conversion here; a literal schema is read from source and this is ignored.
|
|
27
|
-
*/
|
|
28
|
-
readonly schema?: Readonly<Record<string, unknown>> | undefined;
|
|
29
|
-
}
|
|
30
|
-
/** Compile a project's entry template to IR without evaluating any of it. */
|
|
31
|
-
declare const compileTemplate: (options: CompileTemplateOptions) => Promise<CompiledTemplate>;
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region src/sml/compatibility.d.ts
|
|
34
|
-
/**
|
|
35
|
-
* Check what each declared fixture delivers against the pinned client matrix. The rules run on the
|
|
36
|
-
* rendered document, because what a client drops is what the compiler produced, and every finding
|
|
37
|
-
* is located at the source of the element that produced it.
|
|
38
|
-
*/
|
|
39
|
-
declare const checkTemplateCompatibility: (ir: TemplateIr, fixtures: Readonly<Record<string, unknown>>, options?: {
|
|
40
|
-
readonly clients?: readonly EmailClientTarget[] | undefined;
|
|
41
|
-
}) => readonly EmailDiagnostic[];
|
|
42
|
-
//#endregion
|
|
43
|
-
//#region src/sml/profile.d.ts
|
|
44
|
-
/**
|
|
45
|
-
* Check an entry against the static profile. Pass the entry's source alone, or the project's files
|
|
46
|
-
* and the entry's path. Imports of files that are absent are trusted to exist, and the brand,
|
|
47
|
-
* Tailwind and theme are not resolved, so the result holds only findings the source itself owns.
|
|
48
|
-
*/
|
|
49
|
-
declare const checkStaticProfile: (files: string | Readonly<Record<string, string | Uint8Array>>, entry?: string) => readonly TemplateDiagnostic[];
|
|
50
|
-
/**
|
|
51
|
-
* The errors in `after` that `before` did not already have, compared by code and message so a
|
|
52
|
-
* finding that only moved does not count as new.
|
|
53
|
-
*/
|
|
54
|
-
declare const introducedProfileErrors: (before: readonly TemplateDiagnostic[], after: readonly TemplateDiagnostic[]) => readonly TemplateDiagnostic[];
|
|
55
|
-
//#endregion
|
|
56
|
-
export { type CompileTemplateOptions, type CompiledTemplate, checkStaticProfile, checkTemplateCompatibility, compileTemplate, introducedProfileErrors };
|
|
57
|
-
//# sourceMappingURL=static.d.ts.map
|
package/dist/static.js
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { assetUrl } from "./email/assets.js";
|
|
2
|
-
import { EmailCompileError } from "./email/diagnostics.js";
|
|
3
|
-
import { c as transformProject, i as checkEmailCompatibility } from "./compatibility-DbISoFIq.js";
|
|
4
|
-
import { a as lowerProject, i as completed, n as introducedProfileErrors, o as Findings, r as loadProject, t as checkStaticProfile } from "./profile-BH-OtdDA.js";
|
|
5
|
-
import { renderIr } from "./render-ir.js";
|
|
6
|
-
//#region src/sml/compile.ts
|
|
7
|
-
/** Compile a project's entry template to IR without evaluating any of it. */
|
|
8
|
-
const compileTemplate = async (options) => {
|
|
9
|
-
const findings = new Findings();
|
|
10
|
-
const project = loadProject(options.files, options.entry, findings);
|
|
11
|
-
if (project === void 0) return completed(findings, void 0);
|
|
12
|
-
let transformed;
|
|
13
|
-
try {
|
|
14
|
-
transformed = await transformProject(options.files, options);
|
|
15
|
-
} catch (error) {
|
|
16
|
-
if (!(error instanceof EmailCompileError)) throw error;
|
|
17
|
-
findings.items.push(...error.diagnostics);
|
|
18
|
-
return completed(findings, void 0);
|
|
19
|
-
}
|
|
20
|
-
findings.items.push(...transformed.diagnostics.filter((item) => item.code !== "tailwind-dynamic-class"));
|
|
21
|
-
const assetsByPath = new Map(transformed.assets.map((asset) => [asset.path, asset]));
|
|
22
|
-
return lowerProject(options, project, findings, {
|
|
23
|
-
assets: transformed.assets,
|
|
24
|
-
stylesheets: transformed.stylesheets,
|
|
25
|
-
tailwindSheet: transformed.tailwindSheet,
|
|
26
|
-
urlFor: (path) => {
|
|
27
|
-
const entry = assetsByPath.get(path);
|
|
28
|
-
return entry === void 0 ? void 0 : assetUrl(entry, options.assetBase);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
};
|
|
32
|
-
//#endregion
|
|
33
|
-
//#region src/sml/compatibility.ts
|
|
34
|
-
/**
|
|
35
|
-
* Check what each declared fixture delivers against the pinned client matrix. The rules run on the
|
|
36
|
-
* rendered document, because what a client drops is what the compiler produced, and every finding
|
|
37
|
-
* is located at the source of the element that produced it.
|
|
38
|
-
*/
|
|
39
|
-
const checkTemplateCompatibility = (ir, fixtures, options = {}) => {
|
|
40
|
-
if (ir.email === void 0) return [];
|
|
41
|
-
const sources = ir.sources ?? [];
|
|
42
|
-
const findings = [];
|
|
43
|
-
for (const [fixture, input] of Object.entries(fixtures)) {
|
|
44
|
-
const rendered = renderIr(ir, input, { positions: true });
|
|
45
|
-
const positions = (rendered.positions ?? []).map((position, index) => ({
|
|
46
|
-
start: position.start,
|
|
47
|
-
end: position.end,
|
|
48
|
-
tag: position.tag,
|
|
49
|
-
instancePath: String(index),
|
|
50
|
-
authored: true,
|
|
51
|
-
origins: position.src === void 0 ? [] : [{
|
|
52
|
-
fileName: sources[position.src[2] ?? 0] ?? "",
|
|
53
|
-
lineNumber: position.src[0],
|
|
54
|
-
columnNumber: position.src[1]
|
|
55
|
-
}]
|
|
56
|
-
}));
|
|
57
|
-
findings.push(...checkEmailCompatibility({
|
|
58
|
-
html: rendered.html,
|
|
59
|
-
fixture,
|
|
60
|
-
positions,
|
|
61
|
-
...options.clients === void 0 ? {} : { clients: options.clients }
|
|
62
|
-
}));
|
|
63
|
-
}
|
|
64
|
-
return findings;
|
|
65
|
-
};
|
|
66
|
-
//#endregion
|
|
67
|
-
export { checkStaticProfile, checkTemplateCompatibility, compileTemplate, introducedProfileErrors };
|
|
68
|
-
|
|
69
|
-
//# sourceMappingURL=static.js.map
|