@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.
Files changed (92) hide show
  1. package/AGENTS.md +53 -41
  2. package/CHANGELOG.md +379 -0
  3. package/README.md +6 -6
  4. package/dist/assets-B0yseju_.d.ts +115 -0
  5. package/dist/brand-DTctcDmf.js +202 -0
  6. package/dist/brand-plugin-CHhcei-j.d.ts +32 -0
  7. package/dist/{theme-css.d.ts → brand.d.ts} +15 -4
  8. package/dist/brand.js +4 -0
  9. package/dist/compiler.d.ts +236 -18
  10. package/dist/compiler.js +1315 -13
  11. package/dist/{email/components.js → components-Bl6DT21n.js} +4 -4
  12. package/dist/diagnostic-model-DJtqbETd.js +38 -0
  13. package/dist/diagnostic-model-DxthUZBT.d.ts +31 -0
  14. package/dist/{email/diagnostics.js → diagnostics-CnKFefKz.js} +2 -2
  15. package/dist/diagnostics.d.ts +406 -27
  16. package/dist/diagnostics.js +402 -34
  17. package/dist/edit.d.ts +234 -0
  18. package/dist/{channel-edit.js → edit.js} +341 -3
  19. package/dist/email/jsx-dev-runtime.d.ts +1 -1
  20. package/dist/email/jsx-dev-runtime.js +1 -2
  21. package/dist/email/jsx-runtime.d.ts +1 -1
  22. package/dist/email/jsx-runtime.js +1 -85
  23. package/dist/{email/components.d.ts → email.d.ts} +2 -2
  24. package/dist/email.js +2 -0
  25. package/dist/html-DjPxwjLi.js +1637 -0
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/{cascade-t6kxlgEI.js → ir-De6pTH51.js} +8 -4
  29. package/dist/jsx-runtime-CBlexoeR.js +98 -0
  30. package/dist/{elements-cqldpaJt.d.ts → jsx-runtime-ylIOnaSf.d.ts} +74 -74
  31. package/dist/{profile-BH-OtdDA.js → profile-PUfi3GPH.js} +166 -32
  32. package/dist/{email/render.js → render-D2ht57ZZ.js} +103 -8
  33. package/dist/render-DQKz4Aco.d.ts +548 -0
  34. package/dist/render.d.ts +35 -0
  35. package/dist/render.js +49 -0
  36. package/dist/sms.d.ts +1 -1
  37. package/dist/sms.js +1 -1
  38. package/dist/{template-_QE_rQND.d.ts → template-DMPwxMSq.d.ts} +2 -2
  39. package/dist/{template.js → template-zLUjcwfF.js} +2 -2
  40. package/dist/{theme-css-hFqcPSar.js → theme-css-CEUrDpqq.js} +3 -3
  41. package/dist/whatsapp.d.ts +1 -1
  42. package/dist/whatsapp.js +1 -1
  43. package/generated/contract.json +18 -12
  44. package/generated/reference.md +19 -12
  45. package/package.json +39 -122
  46. package/samva-brand.d.ts +4 -4
  47. package/skills/sml/SKILL.md +2 -2
  48. package/dist/assets-C5duwTya.d.ts +0 -40
  49. package/dist/brand-B-0bN_Ir.d.ts +0 -88
  50. package/dist/channel-edit.d.ts +0 -121
  51. package/dist/channel-specs-B6M_HlaV.js +0 -29
  52. package/dist/compatibility-DbISoFIq.js +0 -589
  53. package/dist/css-BVQKICKC.d.ts +0 -109
  54. package/dist/diagnostic-codes.d.ts +0 -403
  55. package/dist/diagnostic-codes.js +0 -399
  56. package/dist/email/assets.d.ts +0 -2
  57. package/dist/email/assets.js +0 -63
  58. package/dist/email/brand-footer.d.ts +0 -34
  59. package/dist/email/brand-footer.js +0 -63
  60. package/dist/email/brand.d.ts +0 -2
  61. package/dist/email/brand.js +0 -148
  62. package/dist/email/diagnostics.d.ts +0 -2
  63. package/dist/email/elements.d.ts +0 -2
  64. package/dist/email/elements.js +0 -299
  65. package/dist/email/html.d.ts +0 -2
  66. package/dist/email/html.js +0 -321
  67. package/dist/email/render.d.ts +0 -40
  68. package/dist/email/text.d.ts +0 -7
  69. package/dist/email/text.js +0 -100
  70. package/dist/html-BJepOwZ4.d.ts +0 -56
  71. package/dist/ir-aaqNE987.d.ts +0 -252
  72. package/dist/ir.d.ts +0 -2
  73. package/dist/ir.js +0 -7
  74. package/dist/preview-Ekdg1dXG.d.ts +0 -100
  75. package/dist/preview.d.ts +0 -2
  76. package/dist/preview.js +0 -160
  77. package/dist/render-ir.d.ts +0 -19
  78. package/dist/render-ir.js +0 -826
  79. package/dist/sms-segments.d.ts +0 -13
  80. package/dist/sms-segments.js +0 -46
  81. package/dist/source-edit.d.ts +0 -116
  82. package/dist/source-edit.js +0 -343
  83. package/dist/source-locations.d.ts +0 -2
  84. package/dist/source-locations.js +0 -16
  85. package/dist/static.d.ts +0 -57
  86. package/dist/static.js +0 -69
  87. package/dist/tailwind-CMYpZf1v.js +0 -614
  88. package/dist/template.d.ts +0 -2
  89. package/dist/theme-css.js +0 -2
  90. package/dist/theme-imports-lvBw3AD1.js +0 -109
  91. package/dist/transform-Ct2OMkFN.d.ts +0 -153
  92. package/dist/unsubscribe-link-CoesnQDU.js +0 -26
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,2 +0,0 @@
1
- import { i as completeJsxSourceLocation, n as JsxSource, r as JsxSourceLocation, t as JSX_SOURCE_LOCATION_PROP } from "./source-locations-C8wCsiAy.js";
2
- export { JSX_SOURCE_LOCATION_PROP, JsxSource, JsxSourceLocation, completeJsxSourceLocation };
@@ -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