@samva/vite 0.1.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 +8 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/build.d.ts +42 -0
- package/dist/build.js +99 -0
- package/dist/build.js.map +1 -0
- package/dist/editor/assets/authoring-editor-CSA7Zv4v-B3TgD9LK.js +145 -0
- package/dist/editor/assets/authoring-editor-CSA7Zv4v-B3TgD9LK.js.map +1 -0
- package/dist/editor/assets/index-C1g6_O2Y.js +86 -0
- package/dist/editor/assets/index-C1g6_O2Y.js.map +1 -0
- package/dist/editor/assets/index-j-Fc0EML.css +2 -0
- package/dist/editor/index.html +13 -0
- package/dist/emit.d.ts +116 -0
- package/dist/emit.js +236 -0
- package/dist/emit.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +760 -0
- package/dist/index.js.map +1 -0
- package/dist/theme-CfJe8s3k.js +138 -0
- package/dist/theme-CfJe8s3k.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/editor-store.ts","../src/editor.ts"],"sourcesContent":["import { createHash, randomUUID } from \"node:crypto\";\nimport { readFile, realpath, rename, writeFile } from \"node:fs/promises\";\nimport { dirname, isAbsolute, join, resolve, sep } from \"node:path\";\n\nimport type { CompileOptions } from \"@samva/markup/compile\";\nimport type { Diagnostic } from \"@samva/markup/diagnostics\";\nimport { variableReferences } from \"@samva/markup/interpolate\";\nimport { applyOps, type SmlOp } from \"@samva/markup/ops\";\nimport type { TemplateVariable } from \"@samva/markup/render-template\";\nimport { Result } from \"effect\";\n\nimport { discoverTemplateFiles, type TemplateFile, type TemplateFileKind } from \"./discovery\";\nimport { compileSourcePreview, emitModulePreview, type EmittedTemplatePreview } from \"./emit\";\n\n// Node-side repository backing the Vite editor surface. `.samva` is the editable\n// source of truth; `.tsx` is evaluated by Vite and exposed as a read-only authored\n// source plus emitted SML. File paths are document identities: one TSX file has\n// exactly one possible template, its default export.\n\ntype EditorTemplateAccess = \"editable\" | \"readonly\";\n\nexport interface EditorTemplate {\n /** POSIX path relative to the templates directory — the document identity. */\n readonly id: string;\n readonly name: string;\n readonly channel: \"email\" | \"sms\" | \"whatsapp\";\n readonly sourceKind: TemplateFileKind;\n readonly access: EditorTemplateAccess;\n}\n\nexport interface EditorCatalogDiagnostic {\n readonly file: string;\n readonly severity: \"warning\" | \"error\";\n readonly code: string;\n readonly message: string;\n readonly span?: { readonly start: number; readonly end: number } | undefined;\n}\n\nexport interface EditorCatalog {\n readonly templates: ReadonlyArray<EditorTemplate>;\n readonly diagnostics: ReadonlyArray<EditorCatalogDiagnostic>;\n}\n\ninterface SnapshotFields {\n readonly id: null;\n readonly name: string;\n readonly channel: \"email\" | \"sms\" | \"whatsapp\";\n /** Emitted/compiled SML. This is always the renderer's input, including TSX. */\n readonly source: string;\n readonly rev: string;\n readonly variables: ReadonlyArray<{ readonly name: string; readonly required: boolean }>;\n readonly metadata: DocumentMetadataValue;\n}\n\nexport type EditorSnapshot =\n | (SnapshotFields & {\n readonly origin: { readonly kind: \"samva\"; readonly file: string };\n readonly access: { readonly kind: \"editable\" };\n })\n | (SnapshotFields & {\n readonly origin: {\n readonly kind: \"tsx\";\n readonly file: string;\n readonly authoredSource: string;\n };\n readonly access: { readonly kind: \"readonly\"; readonly reason: string };\n });\n\ninterface FromDefaultValue {\n readonly email: string;\n readonly name?: string | undefined;\n}\n\ninterface DocumentMetadataValue {\n readonly fromDefault?: FromDefaultValue | undefined;\n readonly replyToDefault?: ReadonlyArray<string> | undefined;\n}\n\nexport type EditorUpdate =\n | { readonly kind: \"ops\"; readonly baseRev: string; readonly ops: ReadonlyArray<SmlOp> }\n | { readonly kind: \"source\"; readonly baseRev: string; readonly source: string }\n | {\n readonly kind: \"metadata\";\n readonly patch: {\n readonly name?: string | undefined;\n readonly fromDefault?: FromDefaultValue | null | undefined;\n readonly replyToDefault?: ReadonlyArray<string> | null | undefined;\n };\n };\n\nexport type SaveOutcome =\n | { readonly ok: true; readonly rev: string }\n | {\n readonly ok: false;\n readonly conflict: { readonly expectedRev: string; readonly actualRev: string };\n }\n | { readonly ok: false; readonly error: string };\n\nexport interface StoreChange {\n readonly id: string;\n readonly rev: string;\n /** Emitted SML; the renderer never consumes authored TSX. */\n readonly source: string;\n /** Present for a TSX refresh so the read-only source panel advances too. */\n readonly authoredSource?: string | undefined;\n readonly sourceClient?: string | undefined;\n}\n\nexport type TsxEvaluation =\n | {\n readonly ok: true;\n readonly source: string;\n readonly channel: \"email\" | \"sms\" | \"whatsapp\";\n readonly variables: ReadonlyArray<TemplateVariable>;\n }\n | { readonly ok: false; readonly diagnostics: ReadonlyArray<EditorCatalogDiagnostic> };\n\nexport type EvaluateTsx = (\n path: string,\n id: string,\n name: string,\n compile: CompileOptions | undefined,\n) => Promise<TsxEvaluation>;\n\ninterface SessionMeta {\n readonly name?: string | undefined;\n readonly metadata: DocumentMetadataValue;\n}\n\ninterface CachedTemplate {\n readonly template: EditorTemplate;\n readonly source: string;\n readonly authoredSource?: string | undefined;\n readonly rev: string;\n readonly variables: ReadonlyArray<{ readonly name: string; readonly required: boolean }>;\n}\n\ninterface LoadedTemplate {\n readonly entry?: CachedTemplate | undefined;\n readonly diagnostics: ReadonlyArray<EditorCatalogDiagnostic>;\n}\n\nexport interface EditorFileStoreOptions {\n readonly evaluateTsx?: EvaluateTsx | undefined;\n /** Read lazily so theme HMR applies to the next evaluation without rebuilding the store. */\n readonly compile?: (() => CompileOptions | undefined) | undefined;\n}\n\nconst noop = (): void => {};\n\nconst hashSource = (source: string): string =>\n createHash(\"sha256\").update(source, \"utf8\").digest(\"hex\").slice(0, 16);\n\nconst deriveName = (id: string): string => {\n const base = id.slice(id.lastIndexOf(\"/\") + 1);\n return base.replace(/\\.(?:samva|tsx)$/, \"\");\n};\n\nconst kindForId = (id: string): TemplateFileKind | undefined => {\n if (id.endsWith(\".samva\")) return \"samva\";\n if (id.endsWith(\".tsx\")) return \"tsx\";\n return undefined;\n};\n\nconst within = (child: string, parent: string): boolean =>\n child === parent || child.startsWith(parent + sep);\n\nconst flattenCompiledDiagnostics = (\n template: EmittedTemplatePreview,\n): ReadonlyArray<Diagnostic> => {\n if (template.compiled.channel === \"email\") {\n return [\n ...template.diagnostics,\n ...template.compiled.bases.flatMap((base) => base.diagnostics ?? []),\n ];\n }\n return [...template.diagnostics, ...(template.compiled.diagnostics ?? [])];\n};\n\nconst diagnostic = (\n file: string,\n severity: EditorCatalogDiagnostic[\"severity\"],\n code: string,\n message: string,\n span?: EditorCatalogDiagnostic[\"span\"],\n): EditorCatalogDiagnostic => ({ file, severity, code, message, span });\n\n/**\n * Enforces the TSX file contract after Vite evaluates the module. Named exports\n * are deliberately never inspected or invoked; only `default` can be a template.\n */\nexport const evaluateDefaultExport = (\n id: string,\n name: string,\n moduleExports: Record<string, unknown>,\n compile?: CompileOptions | undefined,\n): TsxEvaluation => {\n const preview = emitModulePreview(name, moduleExports, compile);\n const template = preview.templates[0];\n if (template === undefined) {\n return {\n ok: false,\n diagnostics: preview.diagnostics.map((problem) =>\n diagnostic(\n id,\n problem.code === \"missing-default-export\" ? \"warning\" : \"error\",\n problem.code,\n problem.message,\n problem.span,\n ),\n ),\n };\n }\n\n if (!template.ok || template.source === undefined) {\n const problems = flattenCompiledDiagnostics(template);\n return {\n ok: false,\n diagnostics:\n problems.length > 0\n ? problems.map((problem) =>\n diagnostic(id, \"error\", problem.code, problem.message, problem.span),\n )\n : [\n diagnostic(\n id,\n \"error\",\n \"invalid-default-export\",\n \"The default-exported template could not emit valid SML.\",\n ),\n ],\n };\n }\n\n return {\n ok: true,\n source: template.source,\n channel: template.channel,\n variables: template.variables,\n };\n};\n\nexport class EditorFileStore {\n private readonly dir: string;\n private readonly evaluateTsx: EvaluateTsx | undefined;\n private readonly compile: () => CompileOptions | undefined;\n private realDir: string | undefined;\n private loaded = false;\n private initialLoad: Promise<EditorCatalog> | undefined;\n private readonly entries = new Map<string, CachedTemplate>();\n private readonly diagnostics = new Map<string, ReadonlyArray<EditorCatalogDiagnostic>>();\n private readonly currentRev = new Map<string, string>();\n private readonly writing = new Set<string>();\n private readonly saveChain = new Map<string, Promise<unknown>>();\n private readonly session = new Map<string, SessionMeta>();\n private readonly listeners = new Set<(change: StoreChange) => void>();\n private readonly catalogListeners = new Set<() => void>();\n\n constructor(templatesDir: string, options: EditorFileStoreOptions = {}) {\n this.dir = templatesDir;\n this.evaluateTsx = options.evaluateTsx;\n this.compile = options.compile ?? (() => undefined);\n }\n\n private async resolvePath(id: string): Promise<string | undefined> {\n if (kindForId(id) === undefined || id.includes(\"\\0\")) return undefined;\n const full = resolve(this.dir, id);\n if (!within(full, this.dir)) return undefined;\n const realDir = await this.resolveRealDir();\n if (realDir === undefined) return undefined;\n const real = await realpath(full).catch(() => undefined);\n if (real === undefined || !within(real, realDir)) return undefined;\n return real;\n }\n\n private async resolveRealDir(): Promise<string | undefined> {\n if (this.realDir === undefined) this.realDir = await realpath(this.dir).catch(() => undefined);\n return this.realDir;\n }\n\n idFor(path: string): string | undefined {\n const full = isAbsolute(path) ? path : join(this.dir, path);\n if (!within(full, this.dir)) return undefined;\n const rel = full\n .slice(this.dir.length + 1)\n .split(\"\\\\\")\n .join(\"/\");\n return kindForId(rel) === undefined ? undefined : rel;\n }\n\n private async load(file: TemplateFile): Promise<LoadedTemplate> {\n const path = await this.resolvePath(file.id);\n if (path === undefined) {\n return {\n diagnostics: [\n diagnostic(\n file.id,\n \"error\",\n \"template-unavailable\",\n \"Template file is missing or resolves outside the configured templates directory.\",\n ),\n ],\n };\n }\n const authoredSource = await readFile(path, \"utf8\").catch(() => undefined);\n if (authoredSource === undefined) {\n return {\n diagnostics: [\n diagnostic(file.id, \"error\", \"template-read-error\", \"Template file could not be read.\"),\n ],\n };\n }\n\n const name = this.session.get(file.id)?.name ?? deriveName(file.id);\n if (file.kind === \"samva\") {\n const preview = compileSourcePreview(name, authoredSource, this.compile());\n if (!preview.ok) {\n const problems = flattenCompiledDiagnostics(preview);\n const previous = this.entries.get(file.id);\n const diagnostics = problems.map((problem) =>\n diagnostic(file.id, \"error\", problem.code, problem.message, problem.span),\n );\n // Keep an already-open editable document addressable while its user is\n // typing through an invalid intermediate state. A malformed file at cold\n // discovery has no trustworthy channel and is excluded instead.\n if (previous?.template.sourceKind !== \"samva\") return { diagnostics };\n const rev = hashSource(authoredSource);\n return {\n entry: {\n ...previous,\n template: { ...previous.template, name },\n source: authoredSource,\n rev,\n variables: [...variableReferences(authoredSource)]\n .sort()\n .map((variableName) => ({ name: variableName, required: false })),\n },\n diagnostics,\n };\n }\n const rev = hashSource(authoredSource);\n return {\n entry: {\n template: {\n id: file.id,\n name,\n channel: preview.channel,\n sourceKind: \"samva\",\n access: \"editable\",\n },\n source: authoredSource,\n rev,\n variables: [...variableReferences(authoredSource)]\n .sort()\n .map((variableName) => ({ name: variableName, required: false })),\n },\n diagnostics: [],\n };\n }\n\n if (this.evaluateTsx === undefined) {\n return {\n diagnostics: [\n diagnostic(\n file.id,\n \"error\",\n \"tsx-evaluator-unavailable\",\n \"TSX templates require the Vite evaluator.\",\n ),\n ],\n };\n }\n const evaluated = await this.evaluateTsx(path, file.id, name, this.compile()).catch(\n (cause: unknown): TsxEvaluation => ({\n ok: false,\n diagnostics: [\n diagnostic(\n file.id,\n \"error\",\n \"module-load-error\",\n cause instanceof Error ? cause.message : String(cause),\n ),\n ],\n }),\n );\n if (!evaluated.ok) return { diagnostics: evaluated.diagnostics };\n const rev = hashSource(evaluated.source);\n return {\n entry: {\n template: {\n id: file.id,\n name,\n channel: evaluated.channel,\n sourceKind: \"tsx\",\n access: \"readonly\",\n },\n source: evaluated.source,\n authoredSource,\n rev,\n variables: evaluated.variables.map((variable) => ({\n name: variable.name,\n required: variable.required,\n })),\n },\n diagnostics: [],\n };\n }\n\n private catalogSnapshot(): EditorCatalog {\n return {\n templates: [...this.entries.values()]\n .map((entry) => entry.template)\n .sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)),\n diagnostics: [...this.diagnostics.values()].flat(),\n };\n }\n\n private ensureLoaded(): Promise<EditorCatalog> {\n if (this.loaded) return Promise.resolve(this.catalogSnapshot());\n if (this.initialLoad !== undefined) return this.initialLoad;\n const loading = this.rebuild(false);\n this.initialLoad = loading;\n void loading.then(\n () => {\n if (this.initialLoad === loading) this.initialLoad = undefined;\n },\n () => {\n if (this.initialLoad === loading) this.initialLoad = undefined;\n },\n );\n return loading;\n }\n\n private async rebuild(emitDocuments: boolean): Promise<EditorCatalog> {\n const previous = new Map(this.entries);\n const files = await discoverTemplateFiles(this.dir);\n const loaded = await Promise.all(\n files.map(async (file) => [file.id, await this.load(file)] as const),\n );\n\n this.entries.clear();\n this.diagnostics.clear();\n for (const [id, result] of loaded) {\n if (result.entry !== undefined) {\n this.entries.set(id, result.entry);\n this.currentRev.set(id, result.entry.rev);\n } else {\n this.currentRev.delete(id);\n }\n if (result.diagnostics.length > 0) this.diagnostics.set(id, result.diagnostics);\n }\n this.loaded = true;\n\n if (emitDocuments) {\n for (const [id, entry] of this.entries) {\n const before = previous.get(id);\n if (\n before === undefined ||\n before.source !== entry.source ||\n before.authoredSource !== entry.authoredSource\n ) {\n this.publish({\n id,\n rev: entry.rev,\n source: entry.source,\n authoredSource: entry.authoredSource,\n });\n }\n }\n }\n for (const listener of this.catalogListeners) listener();\n return this.catalogSnapshot();\n }\n\n async catalog(): Promise<EditorCatalog> {\n return this.ensureLoaded();\n }\n\n /** Re-evaluate discovery and every TSX entry after template/dependency/theme HMR. */\n async refreshAll(\n options: { readonly documents?: boolean | undefined } = {},\n ): Promise<EditorCatalog> {\n const loading = this.initialLoad;\n if (loading !== undefined) await loading;\n else if (!this.loaded) return this.ensureLoaded();\n return this.rebuild(options.documents ?? true);\n }\n\n /** Recompile one changed editable file without re-evaluating unrelated TSX modules. */\n async refreshSamva(id: string): Promise<EditorCatalog> {\n if (kindForId(id) !== \"samva\") return this.catalog();\n if (!this.loaded) return this.ensureLoaded();\n const result = await this.load({ id, path: join(this.dir, id), kind: \"samva\" });\n if (result.entry !== undefined) {\n this.entries.set(id, result.entry);\n this.currentRev.set(id, result.entry.rev);\n } else {\n this.entries.delete(id);\n this.currentRev.delete(id);\n }\n if (result.diagnostics.length > 0) this.diagnostics.set(id, result.diagnostics);\n else this.diagnostics.delete(id);\n for (const listener of this.catalogListeners) listener();\n return this.catalogSnapshot();\n }\n\n async list(): Promise<ReadonlyArray<EditorTemplate>> {\n return (await this.catalog()).templates;\n }\n\n async open(id: string): Promise<EditorSnapshot | undefined> {\n if (!this.loaded) await this.ensureLoaded();\n const entry = this.entries.get(id);\n if (entry === undefined) return undefined;\n const meta = this.session.get(id);\n const fields: SnapshotFields = {\n id: null,\n name: meta?.name ?? entry.template.name,\n channel: entry.template.channel,\n source: entry.source,\n rev: entry.rev,\n variables: entry.variables,\n metadata: meta?.metadata ?? {},\n };\n if (entry.template.sourceKind === \"tsx\") {\n return {\n ...fields,\n origin: {\n kind: \"tsx\",\n file: id,\n authoredSource: entry.authoredSource ?? \"\",\n },\n access: {\n kind: \"readonly\",\n reason: \"TSX templates are code-authored. Edit the default export in your code editor.\",\n },\n };\n }\n return {\n ...fields,\n origin: { kind: \"samva\", file: id },\n access: { kind: \"editable\" },\n };\n }\n\n async save(id: string, update: EditorUpdate, client?: string): Promise<SaveOutcome> {\n if (kindForId(id) === \"tsx\") {\n return {\n ok: false,\n error: \"TSX templates are read-only; edit the default export in source.\",\n };\n }\n const path = await this.resolvePath(id);\n if (path === undefined || kindForId(id) !== \"samva\") {\n return { ok: false, error: \"template not found\" };\n }\n\n if (update.kind === \"metadata\") {\n const prev = this.session.get(id) ?? { metadata: {} };\n const next: SessionMeta = {\n name: update.patch.name ?? prev.name,\n metadata: {\n fromDefault:\n update.patch.fromDefault === undefined\n ? prev.metadata.fromDefault\n : (update.patch.fromDefault ?? undefined),\n replyToDefault:\n update.patch.replyToDefault === undefined\n ? prev.metadata.replyToDefault\n : (update.patch.replyToDefault ?? undefined),\n },\n };\n this.session.set(id, next);\n const entry = this.entries.get(id);\n if (entry !== undefined && next.name !== undefined) {\n this.entries.set(id, { ...entry, template: { ...entry.template, name: next.name } });\n for (const listener of this.catalogListeners) listener();\n }\n const current = await readFile(path, \"utf8\").catch(() => undefined);\n return {\n ok: true,\n rev: current === undefined ? (this.currentRev.get(id) ?? \"\") : hashSource(current),\n };\n }\n\n return this.withLock(id, () => this.commit(id, path, update, client));\n }\n\n private async commit(\n id: string,\n path: string,\n update: Extract<EditorUpdate, { kind: \"ops\" | \"source\" }>,\n client: string | undefined,\n ): Promise<SaveOutcome> {\n const current = await readFile(path, \"utf8\").catch(() => undefined);\n if (current === undefined) return { ok: false, error: \"template not found\" };\n const actualRev = hashSource(current);\n if (update.baseRev !== actualRev) {\n return { ok: false, conflict: { expectedRev: update.baseRev, actualRev } };\n }\n\n let nextSource: string;\n if (update.kind === \"ops\") {\n const applied = applyOps(current, [...update.ops]);\n if (Result.isFailure(applied)) return { ok: false, error: applied.failure.message };\n nextSource = applied.success.source;\n } else {\n nextSource = update.source;\n }\n\n const rev = hashSource(nextSource);\n this.writing.add(id);\n // oxlint-disable-next-line samva/no-try-catch-or-throw -- fs resource guard; plain Node adapter boundary\n try {\n const temp = join(dirname(path), `.${randomUUID()}.samva.tmp`);\n await writeFile(temp, nextSource, \"utf8\");\n await rename(temp, path);\n // Reuse the targeted editable-file loader while the watcher echo is\n // suppressed. It recompiles the new root channel and diagnostics, updates\n // the open snapshot, and publishes the catalog refresh that remounts the\n // browser editor with the new channel/check context.\n await this.refreshSamva(id);\n } finally {\n this.writing.delete(id);\n }\n\n this.publish({ id, rev, source: nextSource, sourceClient: client });\n return { ok: true, rev };\n }\n\n private withLock<A>(id: string, run: () => Promise<A>): Promise<A> {\n const prev = this.saveChain.get(id) ?? Promise.resolve();\n const next = prev.then(run, run);\n this.saveChain.set(id, next.then(noop, noop));\n return next;\n }\n\n async handleFileChange(path: string): Promise<StoreChange | undefined> {\n const id = this.idFor(path);\n if (id === undefined || kindForId(id) !== \"samva\") return undefined;\n if (this.writing.has(id)) return undefined;\n const real = await this.resolvePath(id);\n if (real === undefined) return undefined;\n const source = await readFile(real, \"utf8\").catch(() => undefined);\n if (source === undefined) return undefined;\n const rev = hashSource(source);\n if (rev === this.currentRev.get(id)) return undefined;\n this.currentRev.set(id, rev);\n const previous = this.entries.get(id);\n if (previous !== undefined) {\n this.entries.set(id, {\n ...previous,\n source,\n rev,\n variables: [...variableReferences(source)]\n .sort()\n .map((name) => ({ name, required: false })),\n });\n }\n const change: StoreChange = { id, rev, source, sourceClient: undefined };\n this.publish(change);\n return change;\n }\n\n private publish(change: StoreChange): void {\n for (const listener of this.listeners) listener(change);\n }\n\n subscribe(listener: (change: StoreChange) => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n subscribeCatalog(listener: () => void): () => void {\n this.catalogListeners.add(listener);\n return () => this.catalogListeners.delete(listener);\n }\n}\n","import { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { extname, isAbsolute, join, relative, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type { CompileOptions } from \"@samva/markup/compile\";\nimport { Predicate } from \"effect\";\nimport type { Connect, EnvironmentModuleNode, Plugin, ResolvedConfig, ViteDevServer } from \"vite\";\nimport { createServerModuleRunner, loadEnv } from \"vite\";\n\nimport {\n EditorFileStore,\n evaluateDefaultExport,\n type EditorCatalogDiagnostic,\n type StoreChange,\n} from \"./editor-store\";\nimport { resolveThemePath, ThemeState } from \"./theme\";\n\n// samvaEditor() — the sole Vite dev-server surface for Samva templates. It owns\n// TSX evaluation, theme discovery, mixed-source cataloging, compiled previews,\n// editing, and HMR. `.samva` files are editable; `.tsx` files are evaluated by\n// Vite and shown read-only as authored TSX plus emitted SML.\n//\n// Serving model: the editor UI is a PRE-BUILT static bundle (dist/editor),\n// served by this middleware — the same pattern vite-plugin-inspect and\n// @tanstack/devtools use, and the robust one here specifically because a Samva\n// project sets the automatic JSX runtime to `@samva/markup` for its templates;\n// running the editor's own React JSX through that transform would break it. The\n// bundle ships its own React, Tailwind (baked at build time), and editor chrome.\n//\n// Transport (see editor-store.ts for the revision/echo model):\n// GET {route}/api/capabilities → { authenticated } (API key stays server-side)\n// GET {route}/api/templates → mixed-source catalog + diagnostics/theme\n// GET {route}/api/document?id= → open snapshot { source, rev, variables, … }\n// POST {route}/api/document?id=&client= → save → { rev } | 409 conflict | 422 error\n// GET {route}/api/events?id=&client= → SSE stream of changes (origin per client)\n// GET {route}/api/catalog-events → SSE catalog/theme refresh stream\n\nconst DEFAULT_ROUTE = \"/\";\n\nconst MODULE_EXTENSIONS = new Set([\n \".cjs\",\n \".css\",\n \".cts\",\n \".js\",\n \".json\",\n \".jsx\",\n \".mjs\",\n \".mts\",\n \".ts\",\n \".tsx\",\n]);\n\n// The pre-built editor SPA ships beside the plugin bundle in dist/editor. The\n// source-tree fallback keeps real middleware integration tests and local source\n// execution on the same built artifact.\nconst packagedEditorDir = fileURLToPath(new URL(\"./editor/\", import.meta.url));\nconst EDITOR_DIR = existsSync(packagedEditorDir)\n ? packagedEditorDir\n : fileURLToPath(new URL(\"../dist/editor/\", import.meta.url));\n\nconst MIME: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".json\": \"application/json\",\n \".map\": \"application/json\",\n \".ico\": \"image/x-icon\",\n \".png\": \"image/png\",\n \".woff2\": \"font/woff2\",\n};\n\nexport interface SamvaEditorPluginOptions {\n /** Directory containing `.samva` / `.tsx` templates, relative to project root. Default `emails`. */\n readonly templatesDir?: string | undefined;\n /** Editor UI route. Default `/`; its API and assets are mounted as siblings below it. */\n readonly route?: string | undefined;\n /** Compile options applied to catalog validation and browser previews. */\n readonly compile?: CompileOptions | undefined;\n /** Project theme file relative to root. Default `theme.css`; false disables discovery. */\n readonly theme?: string | false | undefined;\n}\n\n/** Normalize + validate the mount route: absolute, root allowed, no trailing slash. */\nconst normalizeRoute = (raw: string): string => {\n const route = raw === \"/\" ? \"/\" : raw.replace(/\\/+$/, \"\");\n if (\n !route.startsWith(\"/\") ||\n route === \"\" ||\n route.includes(\"//\") ||\n route.includes(\"?\") ||\n route.includes(\"#\")\n ) {\n // oxlint-disable-next-line samva/no-try-catch-or-throw, samva/no-error-constructor -- vite plugin config validation boundary, not Effect domain\n throw new Error(\n `samvaEditor: \\`route\\` must be an absolute URL path (received ${JSON.stringify(raw)})`,\n );\n }\n return route;\n};\n\nconst sendJson = (res: ServerResponse, status: number, body: unknown): void => {\n res.statusCode = status;\n res.setHeader(\"content-type\", \"application/json\");\n res.end(JSON.stringify(body));\n};\n\n/** Serve a file under the built editor dir; returns false (untouched res) if absent/outside. */\nconst serveStatic = async (res: ServerResponse, filePath: string): Promise<boolean> => {\n const resolved = resolve(filePath);\n const root = EDITOR_DIR.replace(/\\/$/, \"\");\n if (resolved !== root && !resolved.startsWith(`${root}/`)) return false;\n const body = await readFile(resolved).catch(() => undefined);\n if (body === undefined) return false;\n res.statusCode = 200;\n res.setHeader(\"content-type\", MIME[extname(resolved)] ?? \"application/octet-stream\");\n res.end(body);\n return true;\n};\n\nconst VITE_INTERNAL_PREFIXES = [\n \"/@vite/\",\n \"/@id/\",\n \"/@fs/\",\n \"/@react-refresh\",\n \"/__vite\",\n \"/node_modules/.vite/\",\n] as const;\n\nconst isViteInternalPath = (pathname: string): boolean =>\n VITE_INTERNAL_PREFIXES.some(\n (prefix) => pathname === prefix.replace(/\\/$/, \"\") || pathname.startsWith(prefix),\n );\n\nconst hasFileExtension = (pathname: string): boolean => extname(pathname) !== \"\";\n\nconst underRoute = (route: string, pathname: string): boolean =>\n route === \"/\" || pathname === route || pathname.startsWith(`${route}/`);\n\nconst childRoute = (route: string, child: string): string =>\n route === \"/\" ? `/${child}` : `${route}/${child}`;\n\nconst readBody = (req: IncomingMessage): Promise<string> =>\n new Promise((done, fail) => {\n let data = \"\";\n req.on(\"data\", (chunk) => {\n data += chunk;\n });\n req.on(\"end\", () => done(data));\n req.on(\"error\", fail);\n });\n\n/** One SSE connection watching one document. Honors write backpressure by coalescing to latest-change-wins (revisions make intermediate frames droppable). */\ninterface SseClient {\n readonly res: ServerResponse;\n readonly docId: string;\n readonly clientId: string | undefined;\n push(payload: Record<string, unknown>): void;\n}\n\nconst createSseClient = (\n res: ServerResponse,\n docId: string,\n clientId: string | undefined,\n): SseClient => {\n let pending: string | undefined;\n let draining = false;\n\n const flush = (): void => {\n if (pending === undefined) {\n draining = false;\n return;\n }\n const frame = pending;\n pending = undefined;\n if (res.write(frame)) flush();\n else draining = true;\n };\n res.on(\"drain\", flush);\n\n return {\n res,\n docId,\n clientId,\n push(payload) {\n const frame = `event: samva:change\\ndata: ${JSON.stringify(payload)}\\n\\n`;\n if (draining) {\n pending = frame; // latest-change-wins: an older queued frame is superseded\n return;\n }\n if (!res.write(frame)) draining = true;\n },\n };\n};\n\nexport const samvaEditor = (options: SamvaEditorPluginOptions = {}): Plugin => {\n const route = normalizeRoute(options.route ?? DEFAULT_ROUTE);\n const apiPrefix = childRoute(route, \"api\");\n let root = process.cwd();\n let templatesDir = resolve(root, options.templatesDir ?? \"emails\");\n let theme = new ThemeState(resolveThemePath(root, options.theme));\n let store = new EditorFileStore(templatesDir, {\n compile: () => theme.applyTo(options.compile),\n });\n let authenticated = false;\n\n const clients = new Set<SseClient>();\n const catalogClients = new Set<ServerResponse>();\n let refreshTail: Promise<unknown> = Promise.resolve();\n\n const queueStoreRefresh = (\n server: ViteDevServer,\n run: () => Promise<unknown>,\n ): Promise<unknown> => {\n refreshTail = refreshTail.then(run, run).catch((cause: unknown) => {\n server.config.logger.error(\n `samvaEditor refresh failed: ${cause instanceof Error ? cause.message : String(cause)}`,\n );\n });\n return refreshTail;\n };\n const queueRefresh = (server: ViteDevServer, documents: boolean): Promise<unknown> =>\n queueStoreRefresh(server, () => store.refreshAll({ documents }));\n\n return {\n name: \"samva-editor\",\n\n config() {\n return {\n oxc: { jsx: { runtime: \"automatic\", importSource: \"@samva/markup\" } },\n optimizeDeps: { exclude: [\"@samva/markup\"] },\n };\n },\n\n configResolved(resolved: ResolvedConfig) {\n root = resolved.root;\n templatesDir = isAbsolute(options.templatesDir ?? \"\")\n ? (options.templatesDir as string)\n : resolve(root, options.templatesDir ?? \"emails\");\n theme = new ThemeState(resolveThemePath(root, options.theme));\n store = new EditorFileStore(templatesDir, {\n compile: () => theme.applyTo(options.compile),\n });\n // API key stays server-side: `loadEnv(mode, root, \"\")` reads it from the\n // project's `.env` and from process.env, but — lacking a `VITE_` prefix — it\n // is NOT exposed to the client bundle. Presence lights up the authenticated\n // capability set.\n const env = loadEnv(resolved.mode, root, \"\");\n authenticated = Boolean(env.SAMVA_API_KEY);\n },\n\n async configureServer(server: ViteDevServer) {\n await theme.refresh();\n const evaluator = createServerModuleRunner(server.environments.ssr, { hmr: false });\n store = new EditorFileStore(templatesDir, {\n compile: () => theme.applyTo(options.compile),\n evaluateTsx: async (path, id, name, compile) => {\n const moduleExports = (await evaluator.import(path)) as Record<string, unknown>;\n return evaluateDefaultExport(id, name, moduleExports, compile);\n },\n });\n\n server.watcher.add(templatesDir);\n if (theme.themePath !== undefined) server.watcher.add(theme.themePath);\n\n const refreshTheme = (): void => {\n void theme.refresh().then(() => queueRefresh(server, false));\n };\n\n const refreshTsxDependents = (path: string, shapeChange: boolean): void => {\n const ssr = server.environments.ssr;\n if (ssr === undefined) return;\n const changedModules = ssr.moduleGraph.getModulesByFile(path) ?? new Set();\n const directTemplate = path.endsWith(\".tsx\") && store.idFor(path) !== undefined;\n const potentialModuleShapeChange =\n shapeChange && changedModules.size === 0 && MODULE_EXTENSIONS.has(extname(path));\n if (!directTemplate && changedModules.size === 0 && !potentialModuleShapeChange) return;\n\n void queueStoreRefresh(server, async () => {\n // Vite's SSR graph is the dependency oracle. Invalidate the changed\n // module and every importer so entry modules cannot serve a cached\n // emission. Add/unlink can change module resolution itself (including\n // restoring a dependency whose failed resolution has no stable graph\n // edge), so those rare shape changes invalidate the graph wholesale.\n const invalidated = new Set<EnvironmentModuleNode>();\n const collectImporters = (module: EnvironmentModuleNode): void => {\n if (invalidated.has(module)) return;\n invalidated.add(module);\n for (const importer of module.importers) collectImporters(importer);\n };\n for (const module of changedModules) collectImporters(module);\n if (shapeChange) ssr.moduleGraph.invalidateAll();\n else for (const module of invalidated) ssr.moduleGraph.invalidateModule(module);\n evaluator.evaluatedModules.clear();\n return store.refreshAll({ documents: true });\n });\n };\n\n const onChange = (path: string): void => {\n if (theme.themePath !== undefined && path === theme.themePath) {\n refreshTheme();\n return;\n }\n if (path.endsWith(\".samva\")) {\n void store.handleFileChange(path).then((change) => {\n if (change !== undefined) {\n void queueStoreRefresh(server, () => store.refreshSamva(change.id));\n }\n });\n return;\n }\n refreshTsxDependents(path, false);\n };\n const onCatalogShape = (path: string): void => {\n if (theme.themePath !== undefined && path === theme.themePath) {\n refreshTheme();\n return;\n }\n const id = store.idFor(path);\n if (id?.endsWith(\".samva\")) {\n void queueRefresh(server, true);\n return;\n }\n refreshTsxDependents(path, true);\n };\n server.watcher.on(\"change\", onChange);\n server.watcher.on(\"add\", onCatalogShape);\n server.watcher.on(\"unlink\", onCatalogShape);\n\n // Fan each store change out to the clients watching that document, attributing\n // origin per client: the client that saved sees `self`, everyone else `external`.\n const unsubscribe = store.subscribe((change: StoreChange) => {\n server.ws.send({ type: \"custom\", event: \"samva:document\", data: change });\n for (const client of clients) {\n if (client.docId !== change.id) continue;\n const origin =\n change.sourceClient !== undefined && change.sourceClient === client.clientId\n ? \"self\"\n : \"external\";\n client.push({\n id: change.id,\n rev: change.rev,\n source: change.source,\n authoredSource: change.authoredSource,\n origin,\n });\n }\n });\n const unsubscribeCatalog = store.subscribeCatalog(() => {\n for (const res of catalogClients) {\n res.write(\"event: samva:catalog\\ndata: refresh\\n\\n\");\n }\n server.ws.send({ type: \"custom\", event: \"samva:catalog\" });\n });\n\n let cleaned = false;\n const cleanup = (): void => {\n if (cleaned) return;\n cleaned = true;\n server.watcher.off(\"change\", onChange);\n server.watcher.off(\"add\", onCatalogShape);\n server.watcher.off(\"unlink\", onCatalogShape);\n server.watcher.off(\"close\", cleanup);\n server.httpServer?.off(\"close\", cleanup);\n unsubscribe();\n unsubscribeCatalog();\n void evaluator.close();\n for (const client of clients) client.res.end();\n clients.clear();\n for (const res of catalogClients) res.end();\n catalogClients.clear();\n };\n // In full mode this fires on server close; in middleware mode (no httpServer)\n // Vite closes the watcher with the server, which also covers middleware mode.\n server.httpServer?.once(\"close\", cleanup);\n server.watcher.once(\"close\", cleanup);\n\n const handler: Connect.NextHandleFunction = (req, res, next) => {\n const url = req.url ?? \"\";\n const pathname = url.split(\"?\")[0] || \"/\";\n const query = new URLSearchParams(url.split(\"?\")[1] ?? \"\");\n\n const apiRequest = pathname === apiPrefix || pathname.startsWith(`${apiPrefix}/`);\n if (!apiRequest && !underRoute(route, pathname)) return next();\n\n // A root-mounted editor is the default dedicated-server surface, but it\n // must never intercept Vite's own runtime/module URLs. Static requests\n // that are not editor bundle assets also fall through below.\n if (!apiRequest && route === \"/\" && isViteInternalPath(pathname)) return next();\n\n void (async () => {\n // ── JSON / SSE API ──\n if (pathname === `${apiPrefix}/capabilities`) {\n return sendJson(res, 200, { authenticated });\n }\n\n if (pathname === `${apiPrefix}/templates`) {\n const catalog = await store.catalog();\n const themeFile =\n theme.themePath === undefined ? undefined : relative(root, theme.themePath);\n const themeDiagnostics: ReadonlyArray<EditorCatalogDiagnostic> = theme\n .current()\n .diagnostics.map((item) => ({\n file: themeFile ?? \"theme.css\",\n severity: \"error\",\n code: item.code,\n message: item.message,\n span: item.span,\n }));\n return sendJson(res, 200, {\n templates: catalog.templates,\n diagnostics: [...catalog.diagnostics, ...themeDiagnostics],\n theme: {\n file: themeFile,\n tokens: theme.current().tokens,\n },\n compile: theme.applyTo(options.compile),\n });\n }\n\n if (pathname === `${apiPrefix}/document`) {\n const id = query.get(\"id\");\n if (!id) return sendJson(res, 400, { error: \"missing id\" });\n\n if (req.method === \"POST\") {\n const raw = await readBody(req);\n // Raw HTTP boundary: malformed JSON is a client error, not a server crash.\n let update: Parameters<EditorFileStore[\"save\"]>[1];\n // oxlint-disable-next-line samva/no-try-catch-or-throw -- raw HTTP JSON parsing boundary\n try {\n update = JSON.parse(raw) as Parameters<EditorFileStore[\"save\"]>[1];\n } catch {\n return sendJson(res, 400, { error: \"malformed JSON body\" });\n }\n if (!Predicate.isObject(update)) {\n return sendJson(res, 400, { error: \"malformed JSON body\" });\n }\n const outcome = await store.save(id, update, query.get(\"client\") ?? undefined);\n if (outcome.ok) return sendJson(res, 200, { rev: outcome.rev });\n if (\"conflict\" in outcome) return sendJson(res, 409, outcome.conflict);\n return sendJson(res, 422, { error: outcome.error });\n }\n\n const snapshot = await store.open(id);\n if (snapshot === undefined) return sendJson(res, 404, { error: \"template not found\" });\n return sendJson(res, 200, snapshot);\n }\n\n if (pathname === `${apiPrefix}/events`) {\n const id = query.get(\"id\");\n if (!id) return sendJson(res, 400, { error: \"missing id\" });\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"text/event-stream\");\n res.setHeader(\"cache-control\", \"no-cache\");\n res.setHeader(\"connection\", \"keep-alive\");\n res.write(\": connected\\n\\n\");\n const client = createSseClient(res, id, query.get(\"client\") ?? undefined);\n clients.add(client);\n req.on(\"close\", () => clients.delete(client));\n return;\n }\n\n if (pathname === `${apiPrefix}/catalog-events`) {\n res.statusCode = 200;\n res.setHeader(\"content-type\", \"text/event-stream\");\n res.setHeader(\"cache-control\", \"no-cache\");\n res.setHeader(\"connection\", \"keep-alive\");\n res.write(\": connected\\n\\n\");\n catalogClients.add(res);\n req.on(\"close\", () => catalogClients.delete(res));\n return;\n }\n\n if (apiRequest) return sendJson(res, 404, { error: \"not found\" });\n\n // ── Static editor SPA (mounted at `route`) ──\n // A page served at `route/` makes relative asset URLs resolve under it,\n // so a bare `route` must redirect to `route/` first.\n if (route !== \"/\" && pathname === route) {\n res.statusCode = 302;\n res.setHeader(\"location\", `${route}/`);\n res.end();\n return;\n }\n const rel =\n route === \"/\"\n ? pathname.replace(/^\\//, \"\")\n : pathname.slice(route.length).replace(/^\\//, \"\");\n const file = rel === \"\" ? \"index.html\" : rel;\n if (await serveStatic(res, join(EDITOR_DIR, file))) return;\n // Never answer a missing asset with HTML. Let Vite or later customer\n // middleware own file-like paths; only navigation paths get the SPA.\n const assetPrefix = childRoute(route, \"assets\");\n if (\n hasFileExtension(pathname) ||\n pathname === assetPrefix ||\n pathname.startsWith(`${assetPrefix}/`)\n ) {\n return next();\n }\n // SPA fallback: unknown navigation paths render the app shell.\n if (await serveStatic(res, join(EDITOR_DIR, \"index.html\"))) return;\n return next();\n })().catch((error: unknown) => {\n sendJson(res, 500, { error: error instanceof Error ? error.message : String(error) });\n });\n };\n\n server.middlewares.use(handler);\n },\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { Result } from "effect";
|
|
4
|
+
import { parseThemeCss } from "@samva/markup/theme-css";
|
|
5
|
+
//#region src/discovery.ts
|
|
6
|
+
const kindForFile = (name) => {
|
|
7
|
+
if (name.endsWith(".tsx")) return "tsx";
|
|
8
|
+
if (name.endsWith(".samva")) return "samva";
|
|
9
|
+
};
|
|
10
|
+
const toPosix = (value) => sep === "/" ? value : value.split(sep).join("/");
|
|
11
|
+
/**
|
|
12
|
+
* Recursively lists template files under `dir`. Hidden entries and
|
|
13
|
+
* `node_modules` are skipped. Results are sorted by id for deterministic
|
|
14
|
+
* ordering (the preview list and build output must be stable).
|
|
15
|
+
*/
|
|
16
|
+
const discoverTemplateFiles = async (dir) => {
|
|
17
|
+
const found = [];
|
|
18
|
+
const walk = async (current) => {
|
|
19
|
+
const visible = (await readdir(current, { withFileTypes: true }).catch(() => [])).filter((entry) => !entry.name.startsWith(".") && entry.name !== "node_modules");
|
|
20
|
+
await Promise.all(visible.map(async (entry) => {
|
|
21
|
+
const full = join(current, entry.name);
|
|
22
|
+
if (entry.isDirectory()) {
|
|
23
|
+
await walk(full);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const kind = kindForFile(entry.name);
|
|
27
|
+
if (kind !== void 0) found.push({
|
|
28
|
+
path: full,
|
|
29
|
+
id: toPosix(relative(dir, full)),
|
|
30
|
+
kind
|
|
31
|
+
});
|
|
32
|
+
}));
|
|
33
|
+
};
|
|
34
|
+
await walk(dir);
|
|
35
|
+
return found.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
36
|
+
};
|
|
37
|
+
const NO_THEME = {
|
|
38
|
+
tokens: void 0,
|
|
39
|
+
diagnostics: []
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Resolves the `theme` plugin option to an absolute path, or `undefined` when
|
|
43
|
+
* theming is explicitly disabled (`theme: false`). A string is taken relative to
|
|
44
|
+
* `root` (absolute strings pass through); the default is `<root>/theme.css`.
|
|
45
|
+
*/
|
|
46
|
+
const resolveThemePath = (root, option) => {
|
|
47
|
+
if (option === false) return void 0;
|
|
48
|
+
const file = option ?? "theme.css";
|
|
49
|
+
return isAbsolute(file) ? file : resolve(root, file);
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Reads and parses the project theme file. A missing file is not an error —
|
|
53
|
+
* previews render with base tokens only, no warning (per the brief). A present
|
|
54
|
+
* file with parse errors yields the diagnostics and no tokens; the caller decides
|
|
55
|
+
* whether to keep a previously-good theme.
|
|
56
|
+
*/
|
|
57
|
+
const loadProjectTheme = async (themePath) => {
|
|
58
|
+
if (themePath === void 0) return NO_THEME;
|
|
59
|
+
const css = await readFile(themePath, "utf8").catch((cause) => cause.code === "ENOENT" ? void 0 : cause);
|
|
60
|
+
if (css === void 0) return NO_THEME;
|
|
61
|
+
if (typeof css !== "string") return {
|
|
62
|
+
tokens: void 0,
|
|
63
|
+
diagnostics: [{
|
|
64
|
+
code: "theme-read-error",
|
|
65
|
+
message: `could not read theme file: ${css.message}`,
|
|
66
|
+
span: {
|
|
67
|
+
start: 0,
|
|
68
|
+
end: 0
|
|
69
|
+
}
|
|
70
|
+
}]
|
|
71
|
+
};
|
|
72
|
+
const parsed = parseThemeCss(css);
|
|
73
|
+
if (Result.isFailure(parsed)) return {
|
|
74
|
+
tokens: void 0,
|
|
75
|
+
diagnostics: parsed.failure
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
tokens: parsed.success,
|
|
79
|
+
diagnostics: []
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Folds a parsed theme into per-preview `CompileOptions` as the org-token layer,
|
|
84
|
+
* preserving any base/template layers the caller already configured. Returns the
|
|
85
|
+
* input unchanged when there is no theme to apply.
|
|
86
|
+
*/
|
|
87
|
+
const withThemeLayer = (compile, tokens) => {
|
|
88
|
+
if (tokens === void 0) return compile;
|
|
89
|
+
return {
|
|
90
|
+
...compile,
|
|
91
|
+
theme: {
|
|
92
|
+
...compile?.theme,
|
|
93
|
+
overrides: [tokens, ...compile?.theme?.overrides ?? []]
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Mutable holder for the dev server: parses `theme.css` on demand and keeps the
|
|
99
|
+
* LAST GOOD tokens when a re-parse fails, so an in-flight syntax error never
|
|
100
|
+
* blanks the preview — it renders the previous theme plus the parse diagnostics.
|
|
101
|
+
*/
|
|
102
|
+
var ThemeState = class {
|
|
103
|
+
themePath;
|
|
104
|
+
tokens = void 0;
|
|
105
|
+
diagnostics = [];
|
|
106
|
+
pending = Promise.resolve(NO_THEME);
|
|
107
|
+
constructor(themePath) {
|
|
108
|
+
this.themePath = themePath;
|
|
109
|
+
}
|
|
110
|
+
/** Re-reads + re-parses (serialized); on failure keeps the last good tokens. */
|
|
111
|
+
refresh() {
|
|
112
|
+
const run = async () => {
|
|
113
|
+
const next = await loadProjectTheme(this.themePath);
|
|
114
|
+
if (next.diagnostics.length === 0) {
|
|
115
|
+
this.tokens = next.tokens;
|
|
116
|
+
this.diagnostics = [];
|
|
117
|
+
} else this.diagnostics = next.diagnostics;
|
|
118
|
+
return this.current();
|
|
119
|
+
};
|
|
120
|
+
this.pending = this.pending.then(run, run);
|
|
121
|
+
return this.pending;
|
|
122
|
+
}
|
|
123
|
+
/** Current tokens (last good) + diagnostics (from the most recent parse). */
|
|
124
|
+
current() {
|
|
125
|
+
return {
|
|
126
|
+
tokens: this.tokens,
|
|
127
|
+
diagnostics: this.diagnostics
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Applies the current theme layer to a caller's compile options. */
|
|
131
|
+
applyTo(compile) {
|
|
132
|
+
return withThemeLayer(compile, this.tokens);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
export { discoverTemplateFiles as a, withThemeLayer as i, loadProjectTheme as n, resolveThemePath as r, ThemeState as t };
|
|
137
|
+
|
|
138
|
+
//# sourceMappingURL=theme-CfJe8s3k.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"theme-CfJe8s3k.js","names":[],"sources":["../src/discovery.ts","../src/theme.ts"],"sourcesContent":["import { readdir } from \"node:fs/promises\";\nimport { join, relative, sep } from \"node:path\";\n\n// Template file discovery: walk a templates directory for the two authoring\n// shapes the plugin serves — `.tsx` (evaluated through the emit chain) and\n// `.samva` (compiled directly). Kept dependency-free (node:fs only) so the\n// plugin's preview server and the CLI build share one discovery definition.\n\nexport type TemplateFileKind = \"tsx\" | \"samva\";\n\nexport interface TemplateFile {\n /** Absolute path on disk. */\n readonly path: string;\n /** Path relative to the templates dir, POSIX-normalized (stable id + display). */\n readonly id: string;\n readonly kind: TemplateFileKind;\n}\n\nconst kindForFile = (name: string): TemplateFileKind | undefined => {\n if (name.endsWith(\".tsx\")) return \"tsx\";\n if (name.endsWith(\".samva\")) return \"samva\";\n return undefined;\n};\n\nconst toPosix = (value: string): string => (sep === \"/\" ? value : value.split(sep).join(\"/\"));\n\n/**\n * Recursively lists template files under `dir`. Hidden entries and\n * `node_modules` are skipped. Results are sorted by id for deterministic\n * ordering (the preview list and build output must be stable).\n */\nexport const discoverTemplateFiles = async (dir: string): Promise<ReadonlyArray<TemplateFile>> => {\n const found: Array<TemplateFile> = [];\n\n const walk = async (current: string): Promise<void> => {\n const entries = await readdir(current, { withFileTypes: true }).catch(() => []);\n const visible = entries.filter(\n (entry) => !entry.name.startsWith(\".\") && entry.name !== \"node_modules\",\n );\n await Promise.all(\n visible.map(async (entry) => {\n const full = join(current, entry.name);\n if (entry.isDirectory()) {\n await walk(full);\n return;\n }\n const kind = kindForFile(entry.name);\n if (kind !== undefined) found.push({ path: full, id: toPosix(relative(dir, full)), kind });\n }),\n );\n };\n\n await walk(dir);\n return found.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n};\n","import { readFile } from \"node:fs/promises\";\nimport { isAbsolute, resolve } from \"node:path\";\n\nimport type { CompileOptions } from \"@samva/markup/compile\";\nimport type { Diagnostic } from \"@samva/markup/diagnostics\";\nimport { parseThemeCss, type ParsedTheme } from \"@samva/markup/theme-css\";\nimport { Result } from \"effect\";\n\n// Local preview theming: a project's `theme.css` (Tailwind-v4 `@theme` syntax,\n// design brief §7e) becomes the org-token layer of every preview compile. The\n// SML→HTML compiler takes an ordered `overrides` list of `TokenOverrides` layers;\n// `ParsedTheme` IS a `TokenOverrides`, so a parsed theme drops straight in as the\n// least-specific layer. Base tokens ← theme.css ← (nothing else here; template\n// overrides are authored per-template, not project-wide).\n//\n// SCOPE: local preview only. `samva theme push` / platform theme endpoints are a\n// separate surface that lands with the platform theme API — nothing here talks to\n// the platform (the emitted SML carries no theme; the platform recompiles with the\n// org's own theme layers server-side).\n\n/** Default theme file name, looked up at the project root. */\nexport const DEFAULT_THEME_FILE = \"theme.css\";\n\n/**\n * Result of parsing a project's `theme.css`. `tokens` is the parsed org layer to\n * merge into every compile (`undefined` when there is no usable theme); parse\n * diagnostics carry spans into the theme file, surfaced through the same channel\n * as template emit/compile diagnostics.\n */\nexport interface ProjectTheme {\n /** Parsed org-token layer, or `undefined` when the file is missing/unparsed. */\n readonly tokens: ParsedTheme | undefined;\n /** Parse diagnostics (spans into `theme.css`). Empty on success or no file. */\n readonly diagnostics: ReadonlyArray<Diagnostic>;\n}\n\nconst NO_THEME: ProjectTheme = { tokens: undefined, diagnostics: [] };\n\n/**\n * Resolves the `theme` plugin option to an absolute path, or `undefined` when\n * theming is explicitly disabled (`theme: false`). A string is taken relative to\n * `root` (absolute strings pass through); the default is `<root>/theme.css`.\n */\nexport const resolveThemePath = (\n root: string,\n option: string | false | undefined,\n): string | undefined => {\n if (option === false) return undefined;\n const file = option ?? DEFAULT_THEME_FILE;\n return isAbsolute(file) ? file : resolve(root, file);\n};\n\n/**\n * Reads and parses the project theme file. A missing file is not an error —\n * previews render with base tokens only, no warning (per the brief). A present\n * file with parse errors yields the diagnostics and no tokens; the caller decides\n * whether to keep a previously-good theme.\n */\nexport const loadProjectTheme = async (themePath: string | undefined): Promise<ProjectTheme> => {\n if (themePath === undefined) return NO_THEME;\n const css = await readFile(themePath, \"utf8\").catch((cause: NodeJS.ErrnoException) =>\n // Only a MISSING file is the silent opt-out. Any other read failure\n // (permissions, I/O) must surface as a diagnostic, not masquerade as\n // \"no theme.css\".\n cause.code === \"ENOENT\" ? undefined : cause,\n );\n if (css === undefined) return NO_THEME; // no theme.css → base tokens only, no warning.\n if (typeof css !== \"string\") {\n return {\n tokens: undefined,\n diagnostics: [\n {\n code: \"theme-read-error\",\n message: `could not read theme file: ${css.message}`,\n span: { start: 0, end: 0 },\n },\n ],\n };\n }\n const parsed = parseThemeCss(css);\n if (Result.isFailure(parsed)) return { tokens: undefined, diagnostics: parsed.failure };\n return { tokens: parsed.success, diagnostics: [] };\n};\n\n/**\n * Folds a parsed theme into per-preview `CompileOptions` as the org-token layer,\n * preserving any base/template layers the caller already configured. Returns the\n * input unchanged when there is no theme to apply.\n */\nexport const withThemeLayer = (\n compile: CompileOptions | undefined,\n tokens: ParsedTheme | undefined,\n): CompileOptions | undefined => {\n if (tokens === undefined) return compile;\n return {\n ...compile,\n theme: { ...compile?.theme, overrides: [tokens, ...(compile?.theme?.overrides ?? [])] },\n };\n};\n\n/**\n * Mutable holder for the dev server: parses `theme.css` on demand and keeps the\n * LAST GOOD tokens when a re-parse fails, so an in-flight syntax error never\n * blanks the preview — it renders the previous theme plus the parse diagnostics.\n */\nexport class ThemeState {\n private tokens: ParsedTheme | undefined = undefined;\n private diagnostics: ReadonlyArray<Diagnostic> = [];\n // Refreshes chain behind each other so two rapid saves can't interleave —\n // without this, the FIRST read could finish last and clobber the second\n // save's tokens while its diagnostics claim to describe the newer file.\n private pending: Promise<ProjectTheme> = Promise.resolve(NO_THEME);\n\n constructor(readonly themePath: string | undefined) {}\n\n /** Re-reads + re-parses (serialized); on failure keeps the last good tokens. */\n refresh(): Promise<ProjectTheme> {\n const run = async (): Promise<ProjectTheme> => {\n const next = await loadProjectTheme(this.themePath);\n if (next.diagnostics.length === 0) {\n // Success (or no file): adopt the new tokens (possibly undefined).\n this.tokens = next.tokens;\n this.diagnostics = [];\n } else {\n // Parse error: keep the last good tokens, surface the new diagnostics.\n this.diagnostics = next.diagnostics;\n }\n return this.current();\n };\n this.pending = this.pending.then(run, run);\n return this.pending;\n }\n\n /** Current tokens (last good) + diagnostics (from the most recent parse). */\n current(): ProjectTheme {\n return { tokens: this.tokens, diagnostics: this.diagnostics };\n }\n\n /** Applies the current theme layer to a caller's compile options. */\n applyTo(compile: CompileOptions | undefined): CompileOptions | undefined {\n return withThemeLayer(compile, this.tokens);\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@samva/vite",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unified Vite editor for Samva email, SMS, and WhatsApp templates",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"email",
|
|
7
|
+
"samva",
|
|
8
|
+
"sml",
|
|
9
|
+
"sms",
|
|
10
|
+
"tsx",
|
|
11
|
+
"vite",
|
|
12
|
+
"vite-plugin",
|
|
13
|
+
"whatsapp"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://samva.dev",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/AryaLabsHQ/samva/issues"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Arya Labs, Inc.",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/AryaLabsHQ/samva.git",
|
|
24
|
+
"directory": "packages/vite"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"package.json",
|
|
29
|
+
"README.md",
|
|
30
|
+
"AGENTS.md",
|
|
31
|
+
"LICENSE"
|
|
32
|
+
],
|
|
33
|
+
"type": "module",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./emit": {
|
|
40
|
+
"types": "./dist/emit.d.ts",
|
|
41
|
+
"import": "./dist/emit.js"
|
|
42
|
+
},
|
|
43
|
+
"./build": {
|
|
44
|
+
"types": "./dist/build.d.ts",
|
|
45
|
+
"import": "./dist/build.js"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "NODE_ENV=production tsdown && NODE_ENV=production vite build --config editor/vite.config.ts",
|
|
53
|
+
"build:editor": "NODE_ENV=production vite build --config editor/vite.config.ts",
|
|
54
|
+
"dev:editor": "vite --config editor/vite.config.ts",
|
|
55
|
+
"prepack": "bun run build",
|
|
56
|
+
"typecheck": "tsgo --noEmit && tsgo --noEmit -p editor/tsconfig.json",
|
|
57
|
+
"test": "vitest run --root ../.. --project packages packages/vite/tests"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@samva/markup": "0.0.0",
|
|
61
|
+
"effect": "4.0.0-beta.102"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@effect/vitest": "4.0.0-beta.102",
|
|
65
|
+
"@nucleo/ui": "0.0.1",
|
|
66
|
+
"@samva/editor": "0.0.0",
|
|
67
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
68
|
+
"@types/react": "19.2.18",
|
|
69
|
+
"@types/react-dom": "19.2.4",
|
|
70
|
+
"@vitejs/plugin-react": "^6.0.5",
|
|
71
|
+
"react": "19.2.8",
|
|
72
|
+
"react-dom": "19.2.8",
|
|
73
|
+
"tailwindcss": "^4.3.3",
|
|
74
|
+
"tsdown": "^0.22.13",
|
|
75
|
+
"typescript": "^6.0.3",
|
|
76
|
+
"vite": "~8.2.0",
|
|
77
|
+
"vitest": "^4.1.10"
|
|
78
|
+
},
|
|
79
|
+
"peerDependencies": {
|
|
80
|
+
"vite": "^7.0.0 || ^8.0.0"
|
|
81
|
+
},
|
|
82
|
+
"engines": {
|
|
83
|
+
"node": ">=22"
|
|
84
|
+
}
|
|
85
|
+
}
|