@samva/markup 0.6.0 → 0.7.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 +80 -47
- package/README.md +1 -1
- package/dist/compile-sms.d.ts +1 -1
- package/dist/compile-whatsapp.d.ts +1 -1
- package/dist/compiler.js +27 -6
- package/dist/components.d.ts +2 -2
- package/dist/emit.js +2 -2
- package/dist/footer.d.ts +3 -3
- package/dist/footer.js +2 -2
- package/dist/{parser-CQcASEyy.d.ts → parser-t29cHLVL.d.ts} +2 -2
- package/dist/parser.d.ts +1 -1
- package/dist/parser.js +1 -2
- package/dist/tsx-source-instrumentation.d.ts +9 -0
- package/dist/tsx-source-instrumentation.js +20 -2
- package/dist/tsx-tracer.js +2 -2
- package/package.json +4 -4
package/AGENTS.md
CHANGED
|
@@ -1,47 +1,80 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
intrinsic prop types and the serializer's allowlist, so an
|
|
38
|
-
typecheck and again at compile with a source location.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
# @samva/markup agent guide
|
|
2
|
+
|
|
3
|
+
`@samva/markup` is the authoring language and compiler for Samva templates. Email templates are
|
|
4
|
+
TSX modules that default-export a `defineEmail` definition and render through a JSX runtime that
|
|
5
|
+
lowers to email-safe HTML and plain text. SMS and WhatsApp templates use the structured SML runtime.
|
|
6
|
+
The package is plain TypeScript with no application runtime; the local editor, project builds, and
|
|
7
|
+
exports live in `@samva/vite`.
|
|
8
|
+
|
|
9
|
+
## Entry points
|
|
10
|
+
|
|
11
|
+
- `@samva/markup/template` — `defineEmail`, `TemplateDefinition`, `TemplateInput`, and
|
|
12
|
+
`TEMPLATE_NAME_PATTERN`.
|
|
13
|
+
- `@samva/markup/email` — the JSX runtime. Set `jsxImportSource: "@samva/markup/email"` for email
|
|
14
|
+
entries.
|
|
15
|
+
- `@samva/markup/email/components` — email primitives (`Email`, `Section`, `Columns`, `Button`,
|
|
16
|
+
and the rest of its exports). They generate table layout and classic-Outlook fallbacks.
|
|
17
|
+
- `@samva/markup/email/render` — `renderEmail(template, data)`, which validates input and returns
|
|
18
|
+
subject, preheader, HTML, text, and the derived tree.
|
|
19
|
+
- `@samva/markup/email/diagnostics` — `EmailDiagnostic` and `EmailCompileError`.
|
|
20
|
+
- `@samva/markup/input-schema` — `inputSchema`, which turns any Standard JSON Schema into the
|
|
21
|
+
portable JSON Schema and validator a template's input is checked against.
|
|
22
|
+
- `@samva/markup/compiler` — `compileEmailProject`, the bundler-agnostic compile step. It imports no
|
|
23
|
+
Vite, esbuild, or Node builtins, so it runs in any JavaScript host.
|
|
24
|
+
- `jsxImportSource: "@samva/markup"` and `@samva/markup/components` — the structured SMS and
|
|
25
|
+
WhatsApp runtime and components. `parseSml` (`@samva/markup/parser`) and `compileSml` /
|
|
26
|
+
`compileSource` (`@samva/markup/compile`) work on SML source.
|
|
27
|
+
|
|
28
|
+
The package's `generated/` directory ships the SML and email language references and agent
|
|
29
|
+
contracts; read those before authoring. Import only subpaths declared in `package.json` `exports`.
|
|
30
|
+
|
|
31
|
+
## How you hurt yourself here
|
|
32
|
+
|
|
33
|
+
- **Passing a string where you want markup.** Nothing an author passes as a string becomes markup;
|
|
34
|
+
strings render as escaped text. There is no raw-HTML escape hatch: compatibility markup comes only
|
|
35
|
+
from the email primitives.
|
|
36
|
+
- **Using an element or attribute the serializer does not allow.** `@samva/markup/email/elements` is
|
|
37
|
+
the one table behind both the JSX intrinsic prop types and the serializer's allowlist, so an
|
|
38
|
+
unsupported element or attribute fails at typecheck and again at compile with a source location.
|
|
39
|
+
- **Naming a template or fixture like a path.** A template id and a fixture key are both written
|
|
40
|
+
into file paths, so each must match `TEMPLATE_NAME_PATTERN`, one plain path segment.
|
|
41
|
+
`defineEmail` throws a `TypeError` where the name is declared. Code that builds a path from these
|
|
42
|
+
names imports the pattern instead of restating it.
|
|
43
|
+
- **Reading the clock or randomness in a template.** Samva's hosted render has no ambient clock and
|
|
44
|
+
no entropy source: reading the current instant or a random value there throws unless the
|
|
45
|
+
definition declares the fixed value in its `runtime` (`TemplateRuntime`). A local render does not
|
|
46
|
+
enforce this, so a template that reads `Date.now()` can pass locally and fail once published. Take
|
|
47
|
+
times from validated input, or declare them.
|
|
48
|
+
- **Treating `renderEmail` as a sandbox.** It executes the template's TypeScript in the calling
|
|
49
|
+
process. Run untrusted template source in an isolated host.
|
|
50
|
+
- **Expecting the schema library to do the validation.** A template's input schema is converted to
|
|
51
|
+
a portable JSON Schema and validated by that document, not by the library that produced it. A
|
|
52
|
+
refinement that the converter cannot export is refused, and defaults are never inserted or values
|
|
53
|
+
coerced. The README lists the supported keywords.
|
|
54
|
+
|
|
55
|
+
## Rules
|
|
56
|
+
|
|
57
|
+
- **Email entries default-export a `defineEmail` definition** from a `.tsx` file. The definition
|
|
58
|
+
owns a stable, project-unique `id`, an input schema, named fixtures, and a synchronous `render`
|
|
59
|
+
that receives the validated JSON input unchanged. Helper modules are ordinary imports, not
|
|
60
|
+
catalog entries.
|
|
61
|
+
- **Email templates have no textual SML form.** The derived semantic tree lowers directly to HTML
|
|
62
|
+
and text through `@samva/markup/email/render`; SML is the SMS and WhatsApp language.
|
|
63
|
+
- **Structured SML is a closed grammar.** `parseSml` and `compileSml` reject anything outside it
|
|
64
|
+
rather than passing it through.
|
|
65
|
+
- **Failures are typed.** The SML surface returns a better-result `Result`; branch with
|
|
66
|
+
`Result.isOk` / `Result.isErr` and discriminate the `TaggedError` (`SmlParseError`,
|
|
67
|
+
`SmlCompileError`) by `_tag`. Email compilation reports `EmailDiagnostic` values: errors throw
|
|
68
|
+
`EmailCompileError`, warnings ride back on the render result. A declaration the compiler cannot
|
|
69
|
+
accept at all, such as an invalid name or a nonportable input schema, throws a `TypeError`.
|
|
70
|
+
- **Data models are plain types.** Markup's data models are TypeScript types, not schema
|
|
71
|
+
declarations, so there is no schema library to adopt to consume them. Unions discriminate on
|
|
72
|
+
`type` (an `EmailNode`, an `SmlOp`); only error classes carry `_tag`. The input schema is the one
|
|
73
|
+
place a Standard Schema is accepted, and which library produces it is yours to choose.
|
|
74
|
+
|
|
75
|
+
## Proving your setup
|
|
76
|
+
|
|
77
|
+
- `tsc --noEmit` over the template project catches unsupported elements, attributes, and props.
|
|
78
|
+
- `samva templates check` (from `@samva/cli`) compiles every entry and renders every fixture,
|
|
79
|
+
reporting findings with source locations.
|
|
80
|
+
- `renderEmail(template, fixture)` in a unit test proves one template's output without a build.
|
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ a value at import. `new Date(value)` with an explicit argument always works, so
|
|
|
101
101
|
takes its time from validated input or states it once as configuration. Locale (`en-US`) and time
|
|
102
102
|
zone (UTC) are publication-wide and back every `Intl` constructor and `toLocale*` call.
|
|
103
103
|
|
|
104
|
-
Portable validation uses `@cfworker/json-schema`
|
|
104
|
+
Portable validation uses `@cfworker/json-schema` without runtime code generation. The
|
|
105
105
|
2020-12 meta-schema is checked at every supported schema position, and the explicit profile rejects
|
|
106
106
|
keywords outside its contract before interpretation. The AJV dependency supplies static meta-schema
|
|
107
107
|
JSON; its code generator is not invoked in a loaded template.
|
package/dist/compile-sms.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as Diagnostic } from "./diagnostics-OcQKQYSY.js";
|
|
2
2
|
import { f as ElementNode } from "./ast-BrDtApwS.js";
|
|
3
|
-
import { a as SMS_TAG_SPECS } from "./parser-
|
|
3
|
+
import { a as SMS_TAG_SPECS } from "./parser-t29cHLVL.js";
|
|
4
4
|
import { t as SmsEncoding } from "./sms-segments-y4FpPilJ.js";
|
|
5
5
|
//#region src/compile-sms.d.ts
|
|
6
6
|
/** Documented worst-case rendered width of one `{{variable}}`. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as SmlCompileError, t as Diagnostic } from "./diagnostics-OcQKQYSY.js";
|
|
2
2
|
import { f as ElementNode } from "./ast-BrDtApwS.js";
|
|
3
|
-
import { l as WHATSAPP_TAG_SPECS } from "./parser-
|
|
3
|
+
import { l as WHATSAPP_TAG_SPECS } from "./parser-t29cHLVL.js";
|
|
4
4
|
import { Result } from "better-result";
|
|
5
5
|
//#region src/compile-whatsapp.d.ts
|
|
6
6
|
type WhatsAppSpecs = typeof WHATSAPP_TAG_SPECS;
|
package/dist/compiler.js
CHANGED
|
@@ -7,7 +7,7 @@ import { EmailCompileError, formatEmailDiagnostic, hasBlockingDiagnostic } from
|
|
|
7
7
|
import { collectStylesheets, hasStylesheets, registerStylesheet, resetStylesheets } from "./email/stylesheets.js";
|
|
8
8
|
import { deriveEmailText } from "./email/text.js";
|
|
9
9
|
import { buildLineMap, lineColumn } from "./diagnostics.js";
|
|
10
|
-
import { instrumentTsxSourceForPreview } from "./tsx-source-instrumentation.js";
|
|
10
|
+
import { TsxSourceInstrumentationError, instrumentTsxSourceForPreview } from "./tsx-source-instrumentation.js";
|
|
11
11
|
import { parse } from "@babel/parser";
|
|
12
12
|
import { caniemail, parseHtml, rawData } from "caniemail";
|
|
13
13
|
import { generate, ident, parse as parse$1, walk } from "css-tree";
|
|
@@ -264,11 +264,10 @@ const EMAIL_CLIENT_MATRIX = [
|
|
|
264
264
|
}
|
|
265
265
|
];
|
|
266
266
|
const LABELS = new Map(EMAIL_CLIENT_MATRIX.map((target) => [target.id, target.label]));
|
|
267
|
-
const data = rawData;
|
|
268
267
|
const COMPATIBILITY_PROVENANCE = {
|
|
269
268
|
rules: "caniemail@2.0.2",
|
|
270
|
-
apiVersion:
|
|
271
|
-
lastUpdate:
|
|
269
|
+
apiVersion: rawData.api_version,
|
|
270
|
+
lastUpdate: rawData.last_update_date
|
|
272
271
|
};
|
|
273
272
|
/**
|
|
274
273
|
* The constructs whose loss actually breaks a message: the recipient reads
|
|
@@ -298,7 +297,7 @@ const ESSENTIAL_CONSTRUCTS = /* @__PURE__ */ new Set([
|
|
|
298
297
|
"mailto: links",
|
|
299
298
|
"Local anchors"
|
|
300
299
|
]);
|
|
301
|
-
const KNOWN_TITLES = new Set(
|
|
300
|
+
const KNOWN_TITLES = new Set(rawData.data.map((feature) => feature.title));
|
|
302
301
|
/** The innermost recorded node containing an offset, so a finding names its element. */
|
|
303
302
|
const originsAt = (positions, offset) => {
|
|
304
303
|
let best;
|
|
@@ -1246,6 +1245,28 @@ const stylesheetModule = (id, source, options) => {
|
|
|
1246
1245
|
};
|
|
1247
1246
|
};
|
|
1248
1247
|
/**
|
|
1248
|
+
* Authored TSX the parser refused is a finding about the source, located where
|
|
1249
|
+
* the parser stopped, so every host reports it the way it reports any other
|
|
1250
|
+
* compile error.
|
|
1251
|
+
*/
|
|
1252
|
+
const instrumentAuthoredTsx = (text, id) => {
|
|
1253
|
+
try {
|
|
1254
|
+
return instrumentTsxSourceForPreview(text, id, void 0, null);
|
|
1255
|
+
} catch (error) {
|
|
1256
|
+
if (!(error instanceof TsxSourceInstrumentationError)) throw error;
|
|
1257
|
+
throw new EmailCompileError({ diagnostics: [{
|
|
1258
|
+
code: "tsx-invalid-authored-source",
|
|
1259
|
+
severity: "error",
|
|
1260
|
+
message: error.message,
|
|
1261
|
+
origins: error.location === void 0 ? [] : [{
|
|
1262
|
+
fileName: id,
|
|
1263
|
+
lineNumber: error.location.line,
|
|
1264
|
+
columnNumber: error.location.column
|
|
1265
|
+
}]
|
|
1266
|
+
}] });
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
/**
|
|
1249
1270
|
* Transform one project file into a JavaScript module.
|
|
1250
1271
|
*
|
|
1251
1272
|
* `.tsx`/`.ts` keep their authored semantics and gain source origins. A
|
|
@@ -1270,7 +1291,7 @@ const transformModule = async (id, source, options) => {
|
|
|
1270
1291
|
};
|
|
1271
1292
|
const discovered = options.tailwind === false ? void 0 : discoverClassCandidates(text, id);
|
|
1272
1293
|
return {
|
|
1273
|
-
code: JSX.test(id) ?
|
|
1294
|
+
code: JSX.test(id) ? instrumentAuthoredTsx(text, id) : text,
|
|
1274
1295
|
classes: discovered?.candidates,
|
|
1275
1296
|
diagnostics: discovered?.diagnostics ?? []
|
|
1276
1297
|
};
|
package/dist/components.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { f as ElementNode } from "./ast-BrDtApwS.js";
|
|
2
|
-
import { i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, o as TagSpec, t as AttributeSpec } from "./parser-
|
|
2
|
+
import { i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, o as TagSpec, t as AttributeSpec } from "./parser-t29cHLVL.js";
|
|
3
3
|
import { r as PredicateRef, s as VariableRef } from "./variables-J_L3qxRa.js";
|
|
4
4
|
import { s as SmlChildren } from "./jsx-runtime-I-1nCtl2.js";
|
|
5
5
|
//#region src/components.d.ts
|
|
@@ -244,7 +244,7 @@ declare const WhatsApp: (props: PropsOf<{
|
|
|
244
244
|
readonly category: {
|
|
245
245
|
readonly type: "string";
|
|
246
246
|
readonly required: true;
|
|
247
|
-
readonly values: readonly ["marketing", "utility", "authentication"
|
|
247
|
+
readonly values: readonly ["marketing", "utility", "authentication"];
|
|
248
248
|
};
|
|
249
249
|
};
|
|
250
250
|
readonly content: "whatsapp";
|
package/dist/emit.js
CHANGED
|
@@ -5,8 +5,8 @@ import { renderTemplate } from "./render-template.js";
|
|
|
5
5
|
import { Result } from "better-result";
|
|
6
6
|
//#region src/emit.ts
|
|
7
7
|
/**
|
|
8
|
-
* emitModule — the module-level emit boundary for the TSX authoring layer
|
|
9
|
-
*
|
|
8
|
+
* emitModule — the module-level emit boundary for the TSX authoring layer.
|
|
9
|
+
* The vite plugin does the importing: TSX runs at the
|
|
10
10
|
* USER's build time and this receives the already-evaluated module object (a
|
|
11
11
|
* record of its exports). emitModule stays bundler-agnostic — it never touches
|
|
12
12
|
* the filesystem, esbuild, or vite. It reads only `moduleExports.default`,
|
package/dist/footer.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ interface FooterContent {
|
|
|
29
29
|
readonly customHtml?: string | undefined;
|
|
30
30
|
}
|
|
31
31
|
/** Which compliance-relevant footer fields the effective content is missing,
|
|
32
|
-
* surfaced on the design read path for
|
|
32
|
+
* surfaced on the design read path for the dashboard's footer nudge. */
|
|
33
33
|
interface FooterCompleteness {
|
|
34
34
|
/** No physical mailing address is set anywhere in the cascade (CAN-SPAM). */
|
|
35
35
|
readonly missingAddress: boolean;
|
|
@@ -42,8 +42,8 @@ interface RenderedFooter {
|
|
|
42
42
|
readonly completeness: FooterCompleteness;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Which compliance-relevant fields the footer content lacks — the signal
|
|
46
|
-
* dashboard nudge reads. Derivable from `design.footer` alone (no token table),
|
|
45
|
+
* Which compliance-relevant fields the footer content lacks — the signal the
|
|
46
|
+
* dashboard's footer nudge reads. Derivable from `design.footer` alone (no token table),
|
|
47
47
|
* so the design read path can report it without composing the block. A
|
|
48
48
|
* `customHtml` footer is treated as complete (the org owns its own content).
|
|
49
49
|
*/
|
package/dist/footer.js
CHANGED
|
@@ -23,8 +23,8 @@ const addressLines = (address) => {
|
|
|
23
23
|
].filter((line) => typeof line === "string" && line.trim().length > 0);
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
|
-
* Which compliance-relevant fields the footer content lacks — the signal
|
|
27
|
-
* dashboard nudge reads. Derivable from `design.footer` alone (no token table),
|
|
26
|
+
* Which compliance-relevant fields the footer content lacks — the signal the
|
|
27
|
+
* dashboard's footer nudge reads. Derivable from `design.footer` alone (no token table),
|
|
28
28
|
* so the design read path can report it without composing the block. A
|
|
29
29
|
* `customHtml` footer is treated as complete (the org owns its own content).
|
|
30
30
|
*/
|
|
@@ -312,7 +312,7 @@ declare const WHATSAPP_TAG_SPECS: {
|
|
|
312
312
|
readonly category: {
|
|
313
313
|
readonly type: "string";
|
|
314
314
|
readonly required: true;
|
|
315
|
-
readonly values: readonly ["marketing", "utility", "authentication"
|
|
315
|
+
readonly values: readonly ["marketing", "utility", "authentication"];
|
|
316
316
|
};
|
|
317
317
|
};
|
|
318
318
|
readonly content: "whatsapp";
|
|
@@ -393,4 +393,4 @@ declare const allowedChildren: (model: ContentModel) => ReadonlyArray<SmlTag>;
|
|
|
393
393
|
declare const parseSml: (source: string) => Result<SmlDocument, SmlParseError>;
|
|
394
394
|
//#endregion
|
|
395
395
|
export { SMS_TAG_SPECS as a, UNIVERSAL_ATTRIBUTE_TAGS as c, parseSml as d, SML_PARSER_LIMITS as f, EMAIL_TAG_SPECS as i, WHATSAPP_TAG_SPECS as l, CHANNEL_TAG_SPECS as n, TagSpec as o, ContentModel as r, UNIVERSAL_ATTRIBUTES as s, AttributeSpec as t, allowedChildren as u };
|
|
396
|
-
//# sourceMappingURL=parser-
|
|
396
|
+
//# sourceMappingURL=parser-t29cHLVL.d.ts.map
|
package/dist/parser.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-
|
|
1
|
+
import { a as SMS_TAG_SPECS, c as UNIVERSAL_ATTRIBUTE_TAGS, d as parseSml, f as SML_PARSER_LIMITS, i as EMAIL_TAG_SPECS, l as WHATSAPP_TAG_SPECS, n as CHANNEL_TAG_SPECS, o as TagSpec, r as ContentModel, s as UNIVERSAL_ATTRIBUTES, t as AttributeSpec, u as allowedChildren } from "./parser-t29cHLVL.js";
|
|
2
2
|
export { AttributeSpec, CHANNEL_TAG_SPECS, ContentModel, EMAIL_TAG_SPECS, SML_PARSER_LIMITS, SMS_TAG_SPECS, TagSpec, UNIVERSAL_ATTRIBUTES, UNIVERSAL_ATTRIBUTE_TAGS, WHATSAPP_TAG_SPECS, allowedChildren, parseSml };
|
package/dist/parser.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
//#region src/tsx-source-instrumentation.d.ts
|
|
2
2
|
declare const TsxSourceInstrumentationError_base: import("better-result").TaggedErrorClass<"TsxSourceInstrumentationError">;
|
|
3
|
+
/**
|
|
4
|
+
* The authored TSX could not be instrumented. `location` is the 1-based line
|
|
5
|
+
* and column the refusal points at, when the parser or the offending attribute
|
|
6
|
+
* names one.
|
|
7
|
+
*/
|
|
3
8
|
declare class TsxSourceInstrumentationError extends TsxSourceInstrumentationError_base<{
|
|
4
9
|
readonly message: string;
|
|
10
|
+
readonly location: {
|
|
11
|
+
readonly line: number;
|
|
12
|
+
readonly column: number;
|
|
13
|
+
} | undefined;
|
|
5
14
|
readonly cause?: unknown;
|
|
6
15
|
}> {}
|
|
7
16
|
/**
|
|
@@ -4,6 +4,11 @@ import { buildLineMap, lineColumn } from "./diagnostics.js";
|
|
|
4
4
|
import { TaggedError } from "better-result";
|
|
5
5
|
import { parse } from "@babel/parser";
|
|
6
6
|
//#region src/tsx-source-instrumentation.ts
|
|
7
|
+
/**
|
|
8
|
+
* The authored TSX could not be instrumented. `location` is the 1-based line
|
|
9
|
+
* and column the refusal points at, when the parser or the offending attribute
|
|
10
|
+
* names one.
|
|
11
|
+
*/
|
|
7
12
|
var TsxSourceInstrumentationError = class extends TaggedError("TsxSourceInstrumentationError") {};
|
|
8
13
|
const isAstNode = (value) => isObject(value) && typeof value.type === "string" && typeof value.start === "number" && typeof value.end === "number";
|
|
9
14
|
const walkAst = (value, visit) => {
|
|
@@ -23,6 +28,15 @@ const jsxIdentifier = (value) => {
|
|
|
23
28
|
return typeof value.name === "string" ? value.name : void 0;
|
|
24
29
|
};
|
|
25
30
|
const parseErrorMessage = (error) => isObject(error) && typeof error.message === "string" ? error.message : "The authored TSX could not be parsed for preview instrumentation.";
|
|
31
|
+
/** Babel reports a 1-based line and a 0-based column. */
|
|
32
|
+
const parseErrorLocation = (error) => {
|
|
33
|
+
if (!isObject(error) || !isObject(error.loc)) return void 0;
|
|
34
|
+
const { line, column } = error.loc;
|
|
35
|
+
return typeof line === "number" && typeof column === "number" ? {
|
|
36
|
+
line,
|
|
37
|
+
column: column + 1
|
|
38
|
+
} : void 0;
|
|
39
|
+
};
|
|
26
40
|
const marker = (fileName, source, offset, token) => {
|
|
27
41
|
const position = lineColumn(buildLineMap(source), offset);
|
|
28
42
|
return ` ${JSX_SOURCE_LOCATION_PROP}={${JSON.stringify({
|
|
@@ -53,7 +67,8 @@ const instrumentTsxSourceForPreview = (source, fileName, token, jsxImportSource
|
|
|
53
67
|
} catch (error) {
|
|
54
68
|
throw new TsxSourceInstrumentationError({
|
|
55
69
|
cause: error,
|
|
56
|
-
message: parseErrorMessage(error)
|
|
70
|
+
message: parseErrorMessage(error),
|
|
71
|
+
location: parseErrorLocation(error)
|
|
57
72
|
});
|
|
58
73
|
}
|
|
59
74
|
const edits = [];
|
|
@@ -78,7 +93,10 @@ const instrumentTsxSourceForPreview = (source, fileName, token, jsxImportSource
|
|
|
78
93
|
const attributes = openingElement.attributes;
|
|
79
94
|
if (Array.isArray(attributes)) for (const attribute of attributes) {
|
|
80
95
|
if (!isAstNode(attribute) || attribute.type !== "JSXAttribute") continue;
|
|
81
|
-
if (jsxIdentifier(attribute.name) === "__samva_source_location") throw new TsxSourceInstrumentationError({
|
|
96
|
+
if (jsxIdentifier(attribute.name) === "__samva_source_location") throw new TsxSourceInstrumentationError({
|
|
97
|
+
message: `The reserved JSX prop ${JSX_SOURCE_LOCATION_PROP} cannot be authored directly.`,
|
|
98
|
+
location: lineColumn(buildLineMap(source), attribute.start)
|
|
99
|
+
});
|
|
82
100
|
}
|
|
83
101
|
const closingLength = source.slice(openingElement.end - 2, openingElement.end) === "/>" ? 2 : 1;
|
|
84
102
|
edits.push({
|
package/dist/tsx-tracer.js
CHANGED
|
@@ -138,11 +138,11 @@ const headingText = (node) => {
|
|
|
138
138
|
};
|
|
139
139
|
const parseAuthored = (source) => {
|
|
140
140
|
try {
|
|
141
|
-
const
|
|
141
|
+
const { program } = parse(source, {
|
|
142
142
|
plugins: ["typescript", "jsx"],
|
|
143
143
|
sourceType: "module"
|
|
144
144
|
});
|
|
145
|
-
return Result.ok(parsed.
|
|
145
|
+
return isAstNode(program) ? Result.ok(program) : Result.err(diagnostic("tsx-invalid-authored-source", "The authored TSX parsed without a location."));
|
|
146
146
|
} catch (error) {
|
|
147
147
|
return Result.err(diagnostic("tsx-invalid-authored-source", error instanceof Error ? error.message : "The authored TSX could not be parsed."));
|
|
148
148
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samva/markup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Type-safe authoring and compilation primitives for Samva templates",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -279,16 +279,16 @@
|
|
|
279
279
|
"devDependencies": {
|
|
280
280
|
"@babel/parser": "^8.0.4",
|
|
281
281
|
"@css-inline/css-inline-wasm": "0.15.0",
|
|
282
|
-
"@effect/vitest": "4.0.0-rc.
|
|
282
|
+
"@effect/vitest": "4.0.0-rc.117",
|
|
283
283
|
"@types/css-tree": "3.2.0",
|
|
284
284
|
"caniemail": "2.0.2",
|
|
285
285
|
"css-tree": "3.2.1",
|
|
286
|
-
"effect": "4.0.0-rc.
|
|
286
|
+
"effect": "4.0.0-rc.117",
|
|
287
287
|
"tailwindcss": "^4.3.3",
|
|
288
288
|
"tsdown": "^0.22.13",
|
|
289
289
|
"typescript": "^7.0.2",
|
|
290
290
|
"vitest": "^4.1.11",
|
|
291
|
-
"zod": "4.5
|
|
291
|
+
"zod": "4.6.5"
|
|
292
292
|
},
|
|
293
293
|
"peerDependencies": {
|
|
294
294
|
"@babel/parser": "^8.0.4",
|