@samva/vite 0.7.0 → 0.8.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 +6 -1
- package/README.md +29 -3
- package/dist/build.js +31 -8
- package/dist/{theme-CBF6Czjm.js → discovery-D2eR-rzi.js} +4 -19
- package/dist/editor/assets/index-6Qlbkdhz.js +36 -0
- package/dist/editor/assets/index-Ed_V5wEW.css +2 -0
- package/dist/editor/index.html +2 -2
- package/dist/index.js +60 -21
- package/dist/project-RON2Ei7d.js +603 -0
- package/dist/project.d.ts +62 -3
- package/dist/project.js +2 -294
- package/package.json +2 -2
- package/dist/editor/assets/index-BeVA-Ur2.js +0 -32
- package/dist/editor/assets/index-Dz2CE64u.css +0 -2
package/AGENTS.md
CHANGED
|
@@ -56,6 +56,10 @@ version your previews, checks, and builds use.
|
|
|
56
56
|
entry's canonical `.tsx` source after checking that the file on disk is still the revision it
|
|
57
57
|
read, so a change you saved elsewhere is not overwritten; reload the entry and retry. SMS and
|
|
58
58
|
WhatsApp entries are read-only in the editor.
|
|
59
|
+
- **Committing or hand-editing `.samva/brands`.** `samva:brand` resolves live first and falls
|
|
60
|
+
back to the snapshot `samva brands pull` writes there, warning when it is older than the live
|
|
61
|
+
brand. The API owns the brand, so the directory is gitignored and refreshed with `pull`; a
|
|
62
|
+
project with neither credentials nor a snapshot fails with `brand-unavailable`.
|
|
59
63
|
|
|
60
64
|
## Rules
|
|
61
65
|
|
|
@@ -84,4 +88,5 @@ version your previews, checks, and builds use.
|
|
|
84
88
|
- `vite` with `samvaEditor()` configured serves the editor at the configured `route`; every entry
|
|
85
89
|
should appear with its fixtures.
|
|
86
90
|
- `exportEmailProject({ dir, out })` writes browsable HTML and text for every fixture and a
|
|
87
|
-
`catalog.json` with the asset mapping and findings, without Samva credentials
|
|
91
|
+
`catalog.json` with the asset mapping and findings, without Samva credentials unless the project
|
|
92
|
+
imports `samva:brand` and has no snapshot.
|
package/README.md
CHANGED
|
@@ -102,7 +102,9 @@ client-specific proof.
|
|
|
102
102
|
|
|
103
103
|
## Theme, check, and publish
|
|
104
104
|
|
|
105
|
-
A root `theme.css` uses Tailwind v4 `@theme` syntax
|
|
105
|
+
A root `theme.css` uses Tailwind v4 `@theme` syntax and may `@import` other project `.css` files
|
|
106
|
+
and `samva:brand`; any other import is a compile error. Changes to the theme or a stylesheet it
|
|
107
|
+
imports recompile
|
|
106
108
|
local previews while preserving the last valid theme when an edit is temporarily
|
|
107
109
|
invalid.
|
|
108
110
|
|
|
@@ -118,7 +120,27 @@ per rebuild and `--json` is one-shot. A successful fixture check does not prove
|
|
|
118
120
|
input branch.
|
|
119
121
|
|
|
120
122
|
Imported email assets require `assetBase` in `buildTemplates()` or `--asset-base` in CLI checks
|
|
121
|
-
and builds, naming their actual HTTPS serving base.
|
|
123
|
+
and builds, naming their actual HTTPS serving base.
|
|
124
|
+
|
|
125
|
+
## The organization brand
|
|
126
|
+
|
|
127
|
+
A project that imports `samva:brand` (the default brand) or `samva:brand/<slug>` compiles against
|
|
128
|
+
its organization's brand. The editor, `buildTemplates()`, and `exportEmailProject()` resolve it in
|
|
129
|
+
this order:
|
|
130
|
+
|
|
131
|
+
1. **Live.** With `SAMVA_API_KEY` in the environment or the project's `.env` (and `SAMVA_API_URL`
|
|
132
|
+
for another API), the brand is fetched directly. Without one, the `samva` CLI on `PATH` is asked
|
|
133
|
+
(`samva brands get --json`) and answers with your `samva login` session, which stays in the
|
|
134
|
+
operating-system keyring the CLI owns.
|
|
135
|
+
2. **Snapshot.** `.samva/brands/<slug>.json`, written by `samva brands pull [slug]`, with a
|
|
136
|
+
`brand-offline` warning.
|
|
137
|
+
3. **Neither.** The build fails with `brand-unavailable`, naming both fixes.
|
|
138
|
+
|
|
139
|
+
When the live brand's digest differs from the snapshot's, the build warns with
|
|
140
|
+
`brand-snapshot-stale`; run `samva brands pull` again. Warnings are build diagnostics and editor
|
|
141
|
+
log lines; they never fail a build. The editor and `check --watch` reuse a resolution briefly,
|
|
142
|
+
so a brand edited elsewhere arrives within seconds rather than on the next save. Keep `.samva` in
|
|
143
|
+
`.gitignore`. A project that imports no brand needs no credentials for local work.
|
|
122
144
|
|
|
123
145
|
For a hosted project, commit and push the checked TSX tree with ordinary Git, then publish one exact
|
|
124
146
|
commit:
|
|
@@ -145,9 +167,13 @@ publication by default; later source edits do not change that publication.
|
|
|
145
167
|
```ts
|
|
146
168
|
import { exportEmailProject } from "@samva/vite/project";
|
|
147
169
|
|
|
148
|
-
const receipt = await exportEmailProject({ dir: ".", out: "dist/email" });
|
|
170
|
+
const receipt = await exportEmailProject({ dir: "emails", root: ".", out: "dist/email" });
|
|
149
171
|
```
|
|
150
172
|
|
|
173
|
+
`dir` holds the templates and `root` (default `dir`) is the project: its `theme.css` themes the
|
|
174
|
+
export, as in `buildTemplates()`, `theme` names another file or `false` disables it, and
|
|
175
|
+
`samva:brand` resolves as described above.
|
|
176
|
+
|
|
151
177
|
Export writes `templates/<id>/<fixture>.html` and `.txt`, content-addressed files under `assets/`,
|
|
152
178
|
and `catalog.json` with the asset mapping and findings. The default `../../assets` base resolves
|
|
153
179
|
from each exported page. For delivery, supply the actual HTTPS `assetBase` and serve those bytes
|
package/dist/build.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as projectThemePath, f as brandDiagnostics, i as isTemplateDefinition, l as readProjectThemeCss, m as loadBrandEnv, o as readEmailProject, p as createBrandResolver, s as renderEmailFixtures, u as resolveThemePath } from "./project-RON2Ei7d.js";
|
|
2
|
+
import { t as discoverTemplateFiles } from "./discovery-D2eR-rzi.js";
|
|
2
3
|
import { COMPILER_VERSION, emitModulePreview } from "./emit.js";
|
|
3
4
|
import { createEmailRenderer } from "./email.js";
|
|
4
|
-
import { isTemplateDefinition, readEmailProject, renderEmailFixtures } from "./project.js";
|
|
5
5
|
import { dirname, parse, relative, resolve, sep } from "node:path";
|
|
6
6
|
//#region src/build.ts
|
|
7
7
|
/** Execute each email definition's fixtures; compile structured SMS/WhatsApp through their channel compiler. */
|
|
@@ -38,17 +38,30 @@ const createTemplateBuildSession = async (dir, options = {}) => {
|
|
|
38
38
|
let closed = false;
|
|
39
39
|
let emailRenderer;
|
|
40
40
|
let previousTheme;
|
|
41
|
+
let previousBrand;
|
|
42
|
+
const brands = createBrandResolver({
|
|
43
|
+
root,
|
|
44
|
+
env: await loadBrandEnv(root)
|
|
45
|
+
});
|
|
41
46
|
const build = async () => {
|
|
42
47
|
server.moduleGraph.invalidateAll();
|
|
43
48
|
const themeCss = await readProjectThemeCss(themePath);
|
|
44
|
-
|
|
49
|
+
const files = await discoverTemplateFiles(templatesDir);
|
|
50
|
+
const sourceFiles = await readEmailProject(root);
|
|
51
|
+
const themeFile = themePath === void 0 ? void 0 : projectThemePath(root, themePath);
|
|
52
|
+
const brandResolution = await brands.resolve(themeFile === void 0 || themeCss === void 0 ? sourceFiles : {
|
|
53
|
+
...sourceFiles,
|
|
54
|
+
[themeFile]: themeCss
|
|
55
|
+
}, themeFile);
|
|
56
|
+
const brand = brandResolution.ok ? brandResolution.brand : void 0;
|
|
57
|
+
const brandFindings = brandDiagnostics(brandResolution);
|
|
58
|
+
if (emailRenderer !== void 0 && (themeCss !== previousTheme || brand?.digest !== previousBrand)) {
|
|
45
59
|
await emailRenderer.close();
|
|
46
60
|
emailRenderer = void 0;
|
|
47
61
|
}
|
|
48
62
|
previousTheme = themeCss;
|
|
63
|
+
previousBrand = brand?.digest;
|
|
49
64
|
emailRenderer?.invalidate();
|
|
50
|
-
const files = await discoverTemplateFiles(templatesDir);
|
|
51
|
-
const sourceFiles = await readEmailProject(root);
|
|
52
65
|
const entries = [];
|
|
53
66
|
const emailIds = /* @__PURE__ */ new Map();
|
|
54
67
|
for (const file of files) {
|
|
@@ -84,7 +97,11 @@ const createTemplateBuildSession = async (dir, options = {}) => {
|
|
|
84
97
|
const preview = await (async () => {
|
|
85
98
|
emailRenderer ??= await createEmailRenderer({
|
|
86
99
|
assetBase: options.assetBase ?? "",
|
|
87
|
-
...themeCss === void 0 ? {} : { tailwind: {
|
|
100
|
+
...themeCss === void 0 || themeFile === void 0 ? {} : { tailwind: {
|
|
101
|
+
css: themeCss,
|
|
102
|
+
cssPath: themeFile
|
|
103
|
+
} },
|
|
104
|
+
...brand === void 0 ? {} : { brand }
|
|
88
105
|
});
|
|
89
106
|
const loaded = await emailRenderer.load(sourceFiles, entryPath, entryPath);
|
|
90
107
|
const template = loaded.template;
|
|
@@ -106,7 +123,7 @@ const createTemplateBuildSession = async (dir, options = {}) => {
|
|
|
106
123
|
},
|
|
107
124
|
diagnostics: fixture.diagnostics
|
|
108
125
|
}));
|
|
109
|
-
const diagnostics = [...loaded.compiled.diagnostics];
|
|
126
|
+
const diagnostics = [...brandFindings, ...loaded.compiled.diagnostics];
|
|
110
127
|
if (fixtures.length === 0) diagnostics.push(emailFinding("missing-fixtures", `${template.id} must declare at least one fixture for check and build.`));
|
|
111
128
|
if (loaded.compiled.assets.length > 0 && options.assetBase === void 0) diagnostics.push(emailFinding("missing-asset-base", "Imported email assets require --asset-base with their HTTPS serving origin."));
|
|
112
129
|
if (loaded.compiled.assets.length > 0 && options.assetBase !== void 0 && !/^https:\/\//i.test(options.assetBase)) diagnostics.push(emailFinding("invalid-asset-base", "Email check and build require an HTTPS --asset-base; relative URLs belong to templates export."));
|
|
@@ -126,7 +143,13 @@ const createTemplateBuildSession = async (dir, options = {}) => {
|
|
|
126
143
|
fixtures
|
|
127
144
|
}]
|
|
128
145
|
};
|
|
129
|
-
})().catch(
|
|
146
|
+
})().catch((cause) => {
|
|
147
|
+
const failure = emailFailure(cause);
|
|
148
|
+
return {
|
|
149
|
+
...failure,
|
|
150
|
+
diagnostics: [...brandFindings, ...failure.diagnostics]
|
|
151
|
+
};
|
|
152
|
+
});
|
|
130
153
|
entries.push({
|
|
131
154
|
id: file.id,
|
|
132
155
|
kind: file.kind,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
3
|
//#region src/discovery.ts
|
|
4
4
|
const isTemplateFile = (name) => name.endsWith(".tsx");
|
|
5
5
|
const toPosix = (value) => sep === "/" ? value : value.split(sep).join("/");
|
|
@@ -29,22 +29,7 @@ const discoverTemplateFiles = async (dir) => {
|
|
|
29
29
|
await walk(dir);
|
|
30
30
|
return found.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
31
31
|
};
|
|
32
|
-
/**
|
|
33
|
-
* Resolves the `theme` plugin option to an absolute path, or `undefined` when
|
|
34
|
-
* theming is explicitly disabled (`theme: false`). A string is taken relative to
|
|
35
|
-
* `root` (absolute strings pass through); the default is `<root>/theme.css`.
|
|
36
|
-
*/
|
|
37
|
-
const resolveThemePath = (root, option) => {
|
|
38
|
-
if (option === false) return void 0;
|
|
39
|
-
const file = option ?? "theme.css";
|
|
40
|
-
return isAbsolute(file) ? file : resolve(root, file);
|
|
41
|
-
};
|
|
42
|
-
/** Read authored Tailwind CSS verbatim; the email compiler validates its syntax. */
|
|
43
|
-
const readProjectThemeCss = async (path) => path === void 0 ? void 0 : readFile(path, "utf8").catch((cause) => {
|
|
44
|
-
if (cause.code === "ENOENT") return void 0;
|
|
45
|
-
throw cause;
|
|
46
|
-
});
|
|
47
32
|
//#endregion
|
|
48
|
-
export {
|
|
33
|
+
export { discoverTemplateFiles as t };
|
|
49
34
|
|
|
50
|
-
//# sourceMappingURL=
|
|
35
|
+
//# sourceMappingURL=discovery-D2eR-rzi.js.map
|