@samva/vite 0.3.0 → 0.4.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 +7 -1
- package/dist/index.js +87 -36
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -13,7 +13,13 @@ the fetched `origin` default branch. Publish creates an immutable receipt; it do
|
|
|
13
13
|
rewrite history.
|
|
14
14
|
|
|
15
15
|
Template discovery is `.tsx` only. The local email editor writes the canonical TSX entry with a
|
|
16
|
-
revision check and a fresh comparison against disk; SMS and WhatsApp stay read-only.
|
|
16
|
+
revision check and a fresh comparison against disk; SMS and WhatsApp stay read-only. A change
|
|
17
|
+
published without a client is what the editor reads as a foreign edit, so nothing the editor itself
|
|
18
|
+
did may reach it that way. A save and a fixture switch both run in the store's mutation queue, so no
|
|
19
|
+
rebuild can straddle one and publish the difference anonymously; the editor's write comes back
|
|
20
|
+
through the file watcher like any other edit, and a changed file that still holds the bytes its
|
|
21
|
+
entry was built from re-evaluates nothing. Watcher events arrive in bursts and one rebuild re-reads
|
|
22
|
+
the whole project, so a rebuild that has not started yet covers every event queued before it. Every discovered entry is imported once through the dev
|
|
17
23
|
server's own SSR graph, and a default export that is a template definition takes the email lane —
|
|
18
24
|
compiled and rendered through `compileEmailProject`, so a local render cannot drift from a published
|
|
19
25
|
one. Email requires an executable definition. Structured SMS/WhatsApp entries emit SML, and
|
package/dist/index.js
CHANGED
|
@@ -248,12 +248,39 @@ var EditorFileStore = class {
|
|
|
248
248
|
async catalog() {
|
|
249
249
|
return this.ensureLoaded();
|
|
250
250
|
}
|
|
251
|
-
/**
|
|
251
|
+
/**
|
|
252
|
+
* Re-evaluate discovery and every TSX entry after template/dependency/theme HMR.
|
|
253
|
+
* `changed` names the file whose change asked for this refresh.
|
|
254
|
+
*
|
|
255
|
+
* This store writes template files itself, so an editor save comes back as a
|
|
256
|
+
* change event indistinguishable from an author's own edit in another program.
|
|
257
|
+
* Re-running the project for it costs a second full evaluation — the price of
|
|
258
|
+
* one save is the whole catalog, twice — and republishes the document with no
|
|
259
|
+
* client attached, which is exactly how a foreign edit reaches the editor. A
|
|
260
|
+
* changed file that still holds the bytes its current entry was built from is
|
|
261
|
+
* already evaluated, so there is nothing to publish and nothing to rebuild.
|
|
262
|
+
* Every other path, and every file whose bytes have moved, is a real change.
|
|
263
|
+
*/
|
|
252
264
|
async refreshAll(options = {}) {
|
|
253
265
|
const loading = this.initialLoad;
|
|
254
266
|
if (loading !== void 0) await loading;
|
|
255
267
|
else if (!this.loaded) return this.ensureLoaded();
|
|
256
|
-
return this.serialize(() => this.rebuild(options.documents ?? true));
|
|
268
|
+
return this.serialize(async () => await this.alreadyBuilt(options.changed) ? this.catalogSnapshot() : this.rebuild(options.documents ?? true));
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* True when `path` is a discovered entry whose file still holds the exact bytes
|
|
272
|
+
* that entry was evaluated from. Read inside the mutation queue, so a save's own
|
|
273
|
+
* rebuild has already landed by the time its change event asks.
|
|
274
|
+
*/
|
|
275
|
+
async alreadyBuilt(path) {
|
|
276
|
+
if (path === void 0) return false;
|
|
277
|
+
const id = this.idFor(path);
|
|
278
|
+
if (id === void 0) return false;
|
|
279
|
+
const entry = this.entries.get(id);
|
|
280
|
+
if (entry === void 0 || entry.template.stale || entry.authoredSource === void 0) return false;
|
|
281
|
+
const resolved = await this.resolvePath(id);
|
|
282
|
+
if (resolved === void 0) return false;
|
|
283
|
+
return await readFile(resolved, "utf8").catch(() => void 0) === entry.authoredSource;
|
|
257
284
|
}
|
|
258
285
|
async list() {
|
|
259
286
|
return (await this.catalog()).templates;
|
|
@@ -355,43 +382,51 @@ var EditorFileStore = class {
|
|
|
355
382
|
* Show another declared fixture of an email document. It re-renders the same
|
|
356
383
|
* build, so the revision does not move; the resulting document reaches every
|
|
357
384
|
* watcher on the change stream, attributed to the client that asked.
|
|
385
|
+
*
|
|
386
|
+
* It runs in the mutation queue for the same reason a save does: the viewed
|
|
387
|
+
* fixture is part of the entry, and a rebuild that straddled the switch would
|
|
388
|
+
* take its own snapshot from before it and its evaluation from after it, then
|
|
389
|
+
* publish the difference with no client — the reader's own fixture switch,
|
|
390
|
+
* returned to them as somebody else's edit.
|
|
358
391
|
*/
|
|
359
392
|
async viewFixture(id, fixture, client) {
|
|
360
393
|
if (!this.loaded) await this.ensureLoaded();
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
kind: "refused",
|
|
370
|
-
error: `A ${entry.content.channel} template renders no fixture.`
|
|
371
|
-
};
|
|
372
|
-
if (!entry.content.fixtures.includes(fixture)) return {
|
|
373
|
-
ok: false,
|
|
374
|
-
kind: "refused",
|
|
375
|
-
error: `Template declares no fixture named ${JSON.stringify(fixture)}.`
|
|
376
|
-
};
|
|
377
|
-
const meta = this.session.get(id);
|
|
378
|
-
this.session.set(id, {
|
|
379
|
-
...meta,
|
|
380
|
-
metadata: meta?.metadata ?? {},
|
|
381
|
-
fixture
|
|
382
|
-
});
|
|
383
|
-
const loaded = await this.load(id, entry);
|
|
384
|
-
if (loaded.entry === void 0) {
|
|
385
|
-
if (loaded.diagnostics.length > 0) this.diagnostics.set(id, loaded.diagnostics);
|
|
386
|
-
return {
|
|
394
|
+
return this.serialize(async () => {
|
|
395
|
+
const entry = this.entries.get(id);
|
|
396
|
+
if (entry === void 0) return {
|
|
397
|
+
ok: false,
|
|
398
|
+
kind: "missing",
|
|
399
|
+
error: "template not found"
|
|
400
|
+
};
|
|
401
|
+
if (entry.content.channel !== "email") return {
|
|
387
402
|
ok: false,
|
|
388
403
|
kind: "refused",
|
|
389
|
-
error: `
|
|
404
|
+
error: `A ${entry.content.channel} template renders no fixture.`
|
|
390
405
|
};
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
406
|
+
if (!entry.content.fixtures.includes(fixture)) return {
|
|
407
|
+
ok: false,
|
|
408
|
+
kind: "refused",
|
|
409
|
+
error: `Template declares no fixture named ${JSON.stringify(fixture)}.`
|
|
410
|
+
};
|
|
411
|
+
const meta = this.session.get(id);
|
|
412
|
+
this.session.set(id, {
|
|
413
|
+
...meta,
|
|
414
|
+
metadata: meta?.metadata ?? {},
|
|
415
|
+
fixture
|
|
416
|
+
});
|
|
417
|
+
const loaded = await this.load(id, entry);
|
|
418
|
+
if (loaded.entry === void 0) {
|
|
419
|
+
if (loaded.diagnostics.length > 0) this.diagnostics.set(id, loaded.diagnostics);
|
|
420
|
+
return {
|
|
421
|
+
ok: false,
|
|
422
|
+
kind: "refused",
|
|
423
|
+
error: `Template ${id} could not be re-rendered.`
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
this.entries.set(id, loaded.entry);
|
|
427
|
+
this.publishEntry(id, loaded.entry, client);
|
|
428
|
+
return { ok: true };
|
|
429
|
+
});
|
|
395
430
|
}
|
|
396
431
|
publishEntry(id, entry, client) {
|
|
397
432
|
const change = {
|
|
@@ -740,6 +775,9 @@ const samvaEditor = (options = {}) => {
|
|
|
740
775
|
const refreshTheme = () => {
|
|
741
776
|
queueStoreRefresh(server, () => store.refreshAll({ documents: true }));
|
|
742
777
|
};
|
|
778
|
+
let burst = /* @__PURE__ */ new Set();
|
|
779
|
+
let burstShape = false;
|
|
780
|
+
let burstQueued = false;
|
|
743
781
|
const refreshTsxDependents = (path, shapeChange) => {
|
|
744
782
|
const ssr = server.environments.ssr;
|
|
745
783
|
if (ssr === void 0) return;
|
|
@@ -747,18 +785,31 @@ const samvaEditor = (options = {}) => {
|
|
|
747
785
|
const directTemplate = path.endsWith(".tsx") && store.idFor(path) !== void 0;
|
|
748
786
|
const potentialModuleShapeChange = shapeChange && changedModules.size === 0 && MODULE_EXTENSIONS.has(extname(path));
|
|
749
787
|
if (!directTemplate && changedModules.size === 0 && !potentialModuleShapeChange) return;
|
|
788
|
+
burst.add(path);
|
|
789
|
+
burstShape ||= shapeChange;
|
|
790
|
+
if (burstQueued) return;
|
|
791
|
+
burstQueued = true;
|
|
750
792
|
queueStoreRefresh(server, async () => {
|
|
793
|
+
const paths = burst;
|
|
794
|
+
const shape = burstShape;
|
|
795
|
+
burst = /* @__PURE__ */ new Set();
|
|
796
|
+
burstShape = false;
|
|
797
|
+
burstQueued = false;
|
|
751
798
|
const invalidated = /* @__PURE__ */ new Set();
|
|
752
799
|
const collectImporters = (module) => {
|
|
753
800
|
if (invalidated.has(module)) return;
|
|
754
801
|
invalidated.add(module);
|
|
755
802
|
for (const importer of module.importers) collectImporters(importer);
|
|
756
803
|
};
|
|
757
|
-
for (const module of
|
|
758
|
-
if (
|
|
804
|
+
for (const changed of paths) for (const module of ssr.moduleGraph.getModulesByFile(changed) ?? []) collectImporters(module);
|
|
805
|
+
if (shape) ssr.moduleGraph.invalidateAll();
|
|
759
806
|
else for (const module of invalidated) ssr.moduleGraph.invalidateModule(module);
|
|
760
807
|
moduleRunner.evaluatedModules.clear();
|
|
761
|
-
|
|
808
|
+
const only = paths.size === 1 ? [...paths][0] : void 0;
|
|
809
|
+
return store.refreshAll({
|
|
810
|
+
documents: true,
|
|
811
|
+
changed: only
|
|
812
|
+
});
|
|
762
813
|
});
|
|
763
814
|
};
|
|
764
815
|
const onChange = (path) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@samva/vite",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Unified Vite editor for Samva email, SMS, and WhatsApp templates",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"email",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
72
|
"@babel/parser": "^8.0.4",
|
|
73
|
-
"@samva/markup": "^0.
|
|
73
|
+
"@samva/markup": "^0.4.0",
|
|
74
74
|
"caniemail": "2.0.2",
|
|
75
75
|
"css-tree": "3.2.1",
|
|
76
76
|
"effect": "4.0.0-rc.112",
|