@emailshepherd/cli 0.1.26 → 0.1.35

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/dist/cli.js CHANGED
@@ -3,344 +3,68 @@
3
3
  // src/cli.ts
4
4
  import "dotenv/config";
5
5
  import { Command } from "commander";
6
+ import updateNotifier from "update-notifier";
6
7
 
7
8
  // package.json
8
- var version = "0.1.26";
9
-
10
- // src/commands/init.ts
11
- import { AxiosError } from "axios";
12
- import { existsSync, mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2, readFileSync, readdirSync, statSync } from "fs";
13
- import { resolve as resolve2, dirname, join } from "path";
14
- import { fileURLToPath } from "url";
15
- import { execSync } from "child_process";
16
- import { input, password, select } from "@inquirer/prompts";
17
-
18
- // src/api/axiosConfig.ts
19
- import axios from "axios";
20
- function configureAxios(config) {
21
- const baseUrl = config?.baseUrl ?? process.env.EMAILSHEPHERD_BASE_URL ?? "https://api.emailshepherd.com";
22
- const apiKey = config?.apiKey ?? process.env.EMAILSHEPHERD_API_KEY;
23
- axios.defaults.baseURL = baseUrl;
24
- axios.defaults.headers.common["Authorization"] = `Bearer ${apiKey}`;
25
- axios.defaults.headers.common["Content-Type"] = "application/json";
26
- }
27
-
28
- // src/api/generated/apiV1.ts
29
- import axios2 from "axios";
30
- var getEmailDesignSystems = (workspaceId, params, options) => {
31
- return axios2.get(
32
- `/api/v1/workspaces/${workspaceId}/email_design_systems`,
33
- {
34
- ...options,
35
- params: { ...params, ...options?.params }
36
- }
37
- );
38
- };
39
- var getEmailDesignSystem = (workspaceId, id, options) => {
40
- return axios2.get(
41
- `/api/v1/workspaces/${workspaceId}/email_design_systems/${id}`,
42
- options
43
- );
44
- };
45
- var updateEmailDesignSystem = (workspaceId, id, updateEmailDesignSystemBody, options) => {
46
- return axios2.patch(
47
- `/api/v1/workspaces/${workspaceId}/email_design_systems/${id}`,
48
- updateEmailDesignSystemBody,
49
- options
50
- );
51
- };
52
- var renderEmailDesignSystem = (workspaceId, emailDesignSystemId, renderEmailDesignSystemBody, options) => {
53
- return axios2.post(
54
- `/api/v1/workspaces/${workspaceId}/email_design_systems/${emailDesignSystemId}/renders`,
55
- renderEmailDesignSystemBody,
56
- options
57
- );
58
- };
59
- var getComponents = (workspaceId, emailDesignSystemId, params, options) => {
60
- return axios2.get(
61
- `/api/v1/workspaces/${workspaceId}/email_design_systems/${emailDesignSystemId}/components`,
62
- {
63
- ...options,
64
- params: { ...params, ...options?.params }
65
- }
66
- );
67
- };
68
- var createComponent = (workspaceId, emailDesignSystemId, createComponentBody, options) => {
69
- return axios2.post(
70
- `/api/v1/workspaces/${workspaceId}/email_design_systems/${emailDesignSystemId}/components`,
71
- createComponentBody,
72
- options
73
- );
74
- };
75
- var updateComponent = (workspaceId, emailDesignSystemId, id, updateComponentBody, options) => {
76
- return axios2.patch(
77
- `/api/v1/workspaces/${workspaceId}/email_design_systems/${emailDesignSystemId}/components/${id}`,
78
- updateComponentBody,
79
- options
80
- );
81
- };
82
- var getWorkspaces = (params, options) => {
83
- return axios2.get(
84
- `/api/v1/workspaces`,
85
- {
86
- ...options,
87
- params: { ...params, ...options?.params }
88
- }
89
- );
90
- };
91
-
92
- // src/utils/componentFiles.ts
93
- import { mkdirSync, writeFileSync } from "fs";
94
- import { resolve } from "path";
95
- var EXCLUDED_FIELDS = [
96
- "email_design_system_id",
97
- "last_updated_by",
98
- "updated_at",
99
- "created_at",
100
- "screenshot_url",
101
- "template",
102
- "container",
103
- "position"
104
- ];
105
- function cleanComponentForConfig(component) {
106
- const cleaned = {};
107
- for (const [key, value] of Object.entries(component)) {
108
- if (!EXCLUDED_FIELDS.includes(key)) {
109
- cleaned[key] = value;
110
- }
111
- }
112
- return cleaned;
113
- }
114
- function writeComponentFiles(component, directory) {
115
- writeFileSync(resolve(directory, "template.liquid"), component.template);
116
- const cleanedComponent = cleanComponentForConfig(component);
117
- const componentWithTemplate = { ...cleanedComponent, template: "TEMPLATE_PLACEHOLDER" };
118
- const jsonString = JSON.stringify(componentWithTemplate, null, 2).replace('"TEMPLATE_PLACEHOLDER"', "template");
119
- const componentContent = `import { defineComponent } from '@emailshepherd/cli/types';
120
- import template from './template.liquid?raw';
121
-
122
- export default defineComponent(${jsonString});
123
- `;
124
- writeFileSync(resolve(directory, "index.ts"), componentContent);
125
- }
126
- function buildComponentDirectory(component, targetDir) {
127
- const componentDirectory = resolve(targetDir, "src", "components", component.name);
128
- mkdirSync(componentDirectory, { recursive: true });
129
- writeComponentFiles(component, componentDirectory);
130
- }
131
-
132
- // src/commands/init.ts
133
- var __filename = fileURLToPath(import.meta.url);
134
- var __dirname = dirname(__filename);
135
- var TEMPLATES_DIR = join(__dirname, "..", "templates");
136
- var BASE_URL = "https://api.emailshepherd.com";
137
- function buildContainerComponentDirectory(component, targetDir) {
138
- const containerDir = resolve2(targetDir, "src", "container_component");
139
- writeComponentFiles(component, containerDir);
140
- }
141
- function copyTemplates(targetDir, replacements) {
142
- const templatesDir = TEMPLATES_DIR;
143
- const files = readdirSync(templatesDir);
144
- for (const file of files) {
145
- const sourcePath = join(templatesDir, file);
146
- const isDir = statSync(sourcePath).isDirectory();
147
- if (isDir && file === "claude") {
148
- const destDir = join(targetDir, ".claude");
149
- mkdirSync2(destDir, { recursive: true });
150
- for (const subFile of readdirSync(sourcePath)) {
151
- const subSource = join(sourcePath, subFile);
152
- const subDest = join(destDir, subFile);
153
- let content2 = readFileSync(subSource, "utf-8");
154
- for (const [placeholder, value] of Object.entries(replacements)) {
155
- content2 = content2.replace(new RegExp(`\\{\\{${placeholder}\\}\\}`, "g"), value);
156
- }
157
- writeFileSync2(subDest, content2);
158
- }
159
- continue;
160
- }
161
- if (isDir) {
162
- continue;
163
- }
164
- const destFile = file === "gitignore" ? ".gitignore" : file;
165
- const destPath = join(targetDir, destFile);
166
- let content = readFileSync(sourcePath, "utf-8");
167
- for (const [placeholder, value] of Object.entries(replacements)) {
168
- content = content.replace(new RegExp(`\\{\\{${placeholder}\\}\\}`, "g"), value);
9
+ var package_default = {
10
+ name: "@emailshepherd/cli",
11
+ version: "0.1.35",
12
+ type: "module",
13
+ publishConfig: {
14
+ registry: "https://registry.npmjs.org",
15
+ access: "public"
16
+ },
17
+ bin: {
18
+ emailshepherd: "./dist/cli.js"
19
+ },
20
+ exports: {
21
+ "./types": {
22
+ types: "./dist/types.d.ts",
23
+ default: "./dist/types.js"
169
24
  }
170
- writeFileSync2(destPath, content);
171
- }
172
- }
173
- async function bootstrapProject({ workspaceId, edsId, targetDir, projectName, apiKey }) {
174
- if (!existsSync(targetDir)) {
175
- mkdirSync2(targetDir, { recursive: true });
176
- }
177
- copyTemplates(targetDir, {
178
- PROJECT_NAME: projectName
179
- });
180
- const edsResponse = await getEmailDesignSystem(workspaceId, edsId);
181
- const edsData = edsResponse.data;
182
- const componentsResponse = await getComponents(workspaceId, edsId);
183
- const componentsData = componentsResponse.data;
184
- const containerComponent = componentsData.results.find((c) => c.container);
185
- const regularComponents = componentsData.results.filter((c) => !c.container);
186
- if (!containerComponent) {
187
- throw new Error("No container component found in the Email Design System");
188
- }
189
- if (existsSync(resolve2(targetDir, "src"))) {
190
- rmSync(resolve2(targetDir, "src"), { recursive: true });
191
- }
192
- mkdirSync2(resolve2(targetDir, "src"), { recursive: true });
193
- mkdirSync2(resolve2(targetDir, "src", "components"), { recursive: true });
194
- mkdirSync2(resolve2(targetDir, "src", "container_component"), { recursive: true });
195
- const viteEnvSource = join(TEMPLATES_DIR, "src", "vite-env.d.ts");
196
- const viteEnvDest = resolve2(targetDir, "src", "vite-env.d.ts");
197
- writeFileSync2(viteEnvDest, readFileSync(viteEnvSource, "utf-8"));
198
- buildContainerComponentDirectory(containerComponent, targetDir);
199
- for (const component of regularComponents) {
200
- buildComponentDirectory(component, targetDir);
201
- }
202
- const componentsIndexContent = `import type { ComponentDefinition } from '@emailshepherd/cli/types';
203
-
204
- const modules = import.meta.glob<{ default: ComponentDefinition }>('./*/index.ts', { eager: true });
205
-
206
- export default Object.values(modules).map((m) => m.default);
207
- `;
208
- writeFileSync2(resolve2(targetDir, "src", "components", "index.ts"), componentsIndexContent);
209
- const designTokensContent = `import { defineDesignTokens } from '@emailshepherd/cli/types';
210
-
211
- export default defineDesignTokens(${JSON.stringify(edsData.design_tokens || {}, null, 2)});
212
- `;
213
- writeFileSync2(resolve2(targetDir, "src", "design_tokens.ts"), designTokensContent);
214
- const customStylesContent = `import { defineCustomStyles } from '@emailshepherd/cli/types';
215
-
216
- export default defineCustomStyles(${JSON.stringify(edsData.custom_styles || [], null, 2)});
217
- `;
218
- writeFileSync2(resolve2(targetDir, "src", "custom_styles.ts"), customStylesContent);
219
- const edsContent = `import { defineEDS } from '@emailshepherd/cli/types';
220
- import designTokens from './design_tokens';
221
- import customStyles from './custom_styles';
222
- import containerComponent from './container_component';
223
- import components from './components';
224
-
225
- export default defineEDS({
226
- workspace_id: ${workspaceId},
227
- email_design_system_id: ${edsId},
228
- eds_metadata: {
229
- name: ${JSON.stringify(edsData.name)},
230
- design_tokens: designTokens,
231
- custom_styles: customStyles,
232
25
  },
233
- container_component: containerComponent,
234
- components,
235
- });
236
- `;
237
- writeFileSync2(resolve2(targetDir, "src", "eds.ts"), edsContent);
238
- const envContent = `EMAILSHEPHERD_API_KEY=${apiKey ?? "your_api_key_here"}
239
- EMAILSHEPHERD_BASE_URL=${BASE_URL}
240
- `;
241
- writeFileSync2(resolve2(targetDir, ".env"), envContent);
242
- console.log("Installing dependencies...");
243
- execSync("npm install", { cwd: targetDir, stdio: "inherit" });
244
- try {
245
- execSync("git init", { cwd: targetDir, stdio: "ignore" });
246
- execSync("git add .", { cwd: targetDir, stdio: "ignore" });
247
- execSync('git commit -m "Initial commit from EmailShepherd CLI"', { cwd: targetDir, stdio: "ignore" });
248
- } catch {
26
+ files: [
27
+ "dist"
28
+ ],
29
+ scripts: {
30
+ build: "tsup && cp -r src/templates dist/",
31
+ test: "vitest run",
32
+ "test:watch": "vitest",
33
+ prepublishOnly: "pnpm run build"
34
+ },
35
+ dependencies: {
36
+ "@emailshepherd/api-client": "workspace:*",
37
+ axios: "^1.13.2",
38
+ chokidar: "^4.0.3",
39
+ commander: "^14.0.2",
40
+ dotenv: "^17.2.3",
41
+ "update-notifier": "^7.3.1",
42
+ vite: "^7.2.6",
43
+ "vite-plugin-checker": "^0.12.0"
44
+ },
45
+ devDependencies: {
46
+ "@types/node": "^24.10.1",
47
+ "@types/update-notifier": "^6.0.8",
48
+ msw: "^2.12.7",
49
+ tsup: "^8.5.1",
50
+ tsx: "^4.21.0",
51
+ typescript: "^5.9.3",
52
+ vitest: "^4.0.16"
249
53
  }
250
- return { edsName: edsData.name };
251
- }
252
- function registerInitCommand(program2) {
253
- program2.command("init").description("Interactively initialize a new Email Design System project").action(async () => {
254
- try {
255
- console.log("\n\u{1F4E7} Welcome to EmailShepherd CLI\n");
256
- const apiKey = await password({
257
- message: "Enter your EmailShepherd API Key:",
258
- mask: "*"
259
- });
260
- configureAxios({ apiKey, baseUrl: BASE_URL });
261
- const workspacesResponse = await getWorkspaces();
262
- const workspaces = workspacesResponse.data.results;
263
- if (workspaces.length === 0) {
264
- console.error("No workspaces found for this API key.");
265
- process.exit(1);
266
- }
267
- const workspaceId = await select({
268
- message: "Select a workspace:",
269
- choices: workspaces.map((ws) => ({
270
- name: ws.name,
271
- value: ws.id,
272
- description: `ID: ${ws.id}`
273
- }))
274
- });
275
- console.log("\nFetching email design systems...");
276
- const edsResponse = await getEmailDesignSystems(workspaceId);
277
- const emailDesignSystems = edsResponse.data.results;
278
- if (emailDesignSystems.length === 0) {
279
- console.error("No email design systems found in this workspace.");
280
- process.exit(1);
281
- }
282
- const edsId = await select({
283
- message: "Select an Email Design System:",
284
- choices: emailDesignSystems.map((eds) => ({
285
- name: eds.name,
286
- value: eds.id,
287
- description: eds.description || `ID: ${eds.id}`
288
- }))
289
- });
290
- const selectedEds = emailDesignSystems.find((eds) => eds.id === edsId);
291
- const suggestedDirName = selectedEds?.name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
292
- const dirName = await input({
293
- message: "Enter the project directory name:",
294
- default: suggestedDirName,
295
- validate: (value) => {
296
- if (!value.trim()) return "Directory name is required";
297
- if (existsSync(resolve2(process.cwd(), value))) {
298
- return `Directory "${value}" already exists. Choose a different name.`;
299
- }
300
- return true;
301
- }
302
- });
303
- const targetDir = resolve2(process.cwd(), dirName);
304
- console.log("\nBootstrapping your project...");
305
- const { edsName } = await bootstrapProject({
306
- workspaceId,
307
- edsId,
308
- targetDir,
309
- projectName: dirName,
310
- apiKey
311
- });
312
- console.log(`
313
- \u2705 Project "${edsName}" initialized successfully!`);
314
- console.log(`
315
- Next steps:`);
316
- console.log(` cd ${dirName}`);
317
- console.log(` npx emailshepherd dev`);
318
- console.log("");
319
- } catch (error) {
320
- if (error instanceof AxiosError && error.response?.data) {
321
- console.error("\nAPI Error:", JSON.stringify(error.response.data, null, 2));
322
- } else if (error instanceof Error) {
323
- if (error.message.includes("User force closed")) {
324
- console.log("\n\nSetup cancelled.");
325
- process.exit(0);
326
- }
327
- console.error("\nError:", error.message);
328
- } else {
329
- console.error("\nError:", error);
330
- }
331
- process.exit(1);
332
- }
333
- });
334
- }
54
+ };
335
55
 
336
56
  // src/commands/validate.ts
337
- import { AxiosError as AxiosError3 } from "axios";
57
+ import { AxiosError as AxiosError2 } from "axios";
58
+ import { configureAxios as configureAxios2, renderEmailDesignSystem as renderEmailDesignSystem2 } from "@emailshepherd/api-client";
338
59
 
339
60
  // src/buildHtml.ts
340
- import { AxiosError as AxiosError2 } from "axios";
341
- import { existsSync as existsSync2, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
342
- import { resolve as resolve3 } from "path";
61
+ import { AxiosError } from "axios";
62
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
63
+ import { dirname, resolve } from "path";
64
+ import { fileURLToPath } from "url";
343
65
  import { createServer } from "vite";
66
+ import { configureAxios, renderEmailDesignSystem } from "@emailshepherd/api-client";
67
+ var __dirname = dirname(fileURLToPath(import.meta.url));
344
68
  var viteServer = null;
345
69
  async function getViteServer() {
346
70
  if (!viteServer) {
@@ -353,6 +77,12 @@ async function getViteServer() {
353
77
  }
354
78
  return viteServer;
355
79
  }
80
+ async function closeViteServer() {
81
+ if (viteServer) {
82
+ await viteServer.close();
83
+ viteServer = null;
84
+ }
85
+ }
356
86
  function renderErrorHtml(title, message, details) {
357
87
  return `<!DOCTYPE html>
358
88
  <html lang="en">
@@ -401,8 +131,8 @@ function renderErrorHtml(title, message, details) {
401
131
  </html>`;
402
132
  }
403
133
  var loadEDS = async () => {
404
- const edsPath = resolve3(process.cwd(), "src", "eds.ts");
405
- if (!existsSync2(edsPath)) {
134
+ const edsPath = resolve(process.cwd(), "src", "eds.ts");
135
+ if (!existsSync(edsPath)) {
406
136
  throw new Error(`Missing src/eds.ts - this file defines your Email Design System configuration.`);
407
137
  }
408
138
  const server = await getViteServer();
@@ -419,10 +149,17 @@ var buildEmailDesignSystemObject = async (eds) => {
419
149
  components: eds.components
420
150
  };
421
151
  };
422
- function writeHtml(html) {
423
- const distDir = resolve3(process.cwd(), "dist");
424
- mkdirSync3(distDir, { recursive: true });
425
- writeFileSync3(resolve3(distDir, "index.html"), html);
152
+ function writeRenderedHtml(html) {
153
+ const distDir = resolve(process.cwd(), "dist");
154
+ mkdirSync(distDir, { recursive: true });
155
+ writeFileSync(resolve(distDir, "rendered.html"), html);
156
+ }
157
+ function writeDevWrapper() {
158
+ const distDir = resolve(process.cwd(), "dist");
159
+ mkdirSync(distDir, { recursive: true });
160
+ const templatePath = resolve(__dirname, "templates", "devWrapper.html");
161
+ const devWrapperHtml = readFileSync(templatePath, "utf-8");
162
+ writeFileSync(resolve(distDir, "index.html"), devWrapperHtml);
426
163
  }
427
164
  async function buildHtml() {
428
165
  configureAxios();
@@ -441,7 +178,7 @@ async function buildHtml() {
441
178
  html = renderErrorHtml("API Error", "No HTML returned", response.data);
442
179
  }
443
180
  } catch (error) {
444
- if (error instanceof AxiosError2) {
181
+ if (error instanceof AxiosError) {
445
182
  const status = error.response?.status ?? "Unknown";
446
183
  const statusText = error.response?.statusText ?? error.message;
447
184
  html = renderErrorHtml("API Error", `Status: ${status} ${statusText}`, error.response?.data ?? error.message);
@@ -451,17 +188,17 @@ async function buildHtml() {
451
188
  html = renderErrorHtml("Error", "Unknown error", error);
452
189
  }
453
190
  }
454
- writeHtml(html);
191
+ writeRenderedHtml(html);
455
192
  return html;
456
193
  }
457
194
 
458
195
  // src/loadEDS.ts
459
196
  import { build } from "vite";
460
- import { existsSync as existsSync3 } from "fs";
461
- import { resolve as resolve4 } from "path";
197
+ import { existsSync as existsSync2 } from "fs";
198
+ import { resolve as resolve2 } from "path";
462
199
  async function loadEDS2() {
463
- const edsPath = resolve4(process.cwd(), "src", "eds.ts");
464
- if (!existsSync3(edsPath)) {
200
+ const edsPath = resolve2(process.cwd(), "src", "eds.ts");
201
+ if (!existsSync2(edsPath)) {
465
202
  throw new Error("Missing src/eds.ts - this file defines your Email Design System configuration.");
466
203
  }
467
204
  const result = await build({
@@ -485,26 +222,51 @@ async function loadEDS2() {
485
222
  return module.default;
486
223
  }
487
224
 
225
+ // src/typecheck.ts
226
+ import { execSync } from "child_process";
227
+ function runTypeCheck() {
228
+ try {
229
+ execSync("npx tsc --noEmit", {
230
+ cwd: process.cwd(),
231
+ encoding: "utf-8",
232
+ stdio: "pipe"
233
+ });
234
+ return { success: true, output: "" };
235
+ } catch (error) {
236
+ const execError = error;
237
+ const output = execError.stdout || execError.stderr || "Type check failed";
238
+ return { success: false, output };
239
+ }
240
+ }
241
+
488
242
  // src/commands/validate.ts
489
243
  function registerValidateCommand(program2) {
490
244
  program2.command("validate").description("Validate the Email Design System").action(async () => {
491
245
  try {
492
- configureAxios();
246
+ console.log("Checking types...");
247
+ const typeCheck = runTypeCheck();
248
+ if (!typeCheck.success) {
249
+ console.error("Type errors found:\n");
250
+ console.error(typeCheck.output);
251
+ process.exit(1);
252
+ }
253
+ configureAxios2();
493
254
  const eds = await loadEDS2();
494
255
  const objectRepresentation = await buildEmailDesignSystemObject(eds);
495
- const response = await renderEmailDesignSystem(
256
+ const response = await renderEmailDesignSystem2(
496
257
  eds.workspace_id,
497
258
  eds.email_design_system_id,
498
259
  objectRepresentation
499
260
  );
500
261
  if (response.data.html) {
501
262
  console.log("No validation errors");
263
+ process.exit(0);
502
264
  } else {
503
265
  console.log(JSON.stringify(response.data, null, 2));
504
266
  process.exit(1);
505
267
  }
506
268
  } catch (error) {
507
- if (error instanceof AxiosError3 && error.response?.data) {
269
+ if (error instanceof AxiosError2 && error.response?.data) {
508
270
  console.log(JSON.stringify(error.response.data, null, 2));
509
271
  } else if (error instanceof Error) {
510
272
  console.error(error.message);
@@ -516,63 +278,40 @@ function registerValidateCommand(program2) {
516
278
  });
517
279
  }
518
280
 
519
- // src/commands/save-component.ts
520
- import { AxiosError as AxiosError4 } from "axios";
521
- function registerSaveComponentCommand(program2) {
522
- program2.command("save-component").description("Save a component to the server").option("--by-name <name>", "Component name to save").option("--by-id <id>", "Component ID to save").action(async (options) => {
281
+ // src/commands/save.ts
282
+ import { AxiosError as AxiosError3 } from "axios";
283
+ import { configureAxios as configureAxios3, syncEmailDesignSystem } from "@emailshepherd/api-client";
284
+ function registerSaveAllCommand(program2) {
285
+ program2.command("save").description("Save the Email Design System and all components to the server").action(async () => {
523
286
  try {
524
- if (!options.byName && !options.byId) {
525
- console.error("Error: Either --by-name or --by-id is required");
526
- process.exit(1);
527
- }
528
- configureAxios();
529
- const eds = await loadEDS2();
530
- const component = eds.container_component.id === Number(options.byId) || eds.container_component.name === options.byName ? eds.container_component : eds.components.find(
531
- (c) => options.byId ? c.id === Number(options.byId) : c.name === options.byName
532
- );
533
- if (!component) {
534
- console.error(`Error: Component not found${options.byName ? ` with name "${options.byName}"` : ` with id ${options.byId}`}`);
287
+ console.log("Checking types...");
288
+ const typeCheck = runTypeCheck();
289
+ if (!typeCheck.success) {
290
+ console.error("Type errors found:\n");
291
+ console.error(typeCheck.output);
535
292
  process.exit(1);
536
293
  }
537
- const response = await updateComponent(
538
- eds.workspace_id,
539
- eds.email_design_system_id,
540
- component.id,
541
- component
542
- );
543
- console.log(`Component "${response.data.name}" saved successfully`);
544
- } catch (error) {
545
- if (error instanceof AxiosError4 && error.response?.data) {
546
- console.log(JSON.stringify(error.response.data, null, 2));
547
- } else if (error instanceof Error) {
548
- console.error(error.message);
549
- } else {
550
- console.error(error);
551
- }
552
- process.exit(1);
553
- }
554
- });
555
- }
556
-
557
- // src/commands/save-eds.ts
558
- import { AxiosError as AxiosError5 } from "axios";
559
- function registerSaveEDSCommand(program2) {
560
- program2.command("save-eds").description("Save the Email Design System metadata to the server").action(async () => {
561
- try {
562
- configureAxios();
294
+ configureAxios3();
563
295
  const eds = await loadEDS2();
564
- const response = await updateEmailDesignSystem(
296
+ console.log("Saving Email Design System...");
297
+ const edsResponse = await syncEmailDesignSystem(
565
298
  eds.workspace_id,
566
299
  eds.email_design_system_id,
567
300
  {
568
- name: eds.eds_metadata.name,
569
- design_tokens: eds.eds_metadata.design_tokens,
570
- custom_styles: eds.eds_metadata.custom_styles
301
+ email_design_system: {
302
+ name: eds.eds_metadata.name,
303
+ description: eds.eds_metadata.description,
304
+ design_tokens: eds.eds_metadata.design_tokens,
305
+ custom_styles: eds.eds_metadata.custom_styles || []
306
+ },
307
+ container_component: eds.container_component,
308
+ components: eds.components
571
309
  }
572
310
  );
573
- console.log(`Email Design System "${response.data.name}" saved successfully`);
311
+ console.log(`Email Design System saved successfully`);
312
+ process.exit(0);
574
313
  } catch (error) {
575
- if (error instanceof AxiosError5 && error.response?.data) {
314
+ if (error instanceof AxiosError3 && error.response?.data) {
576
315
  console.log(JSON.stringify(error.response.data, null, 2));
577
316
  } else if (error instanceof Error) {
578
317
  console.error(error.message);
@@ -584,72 +323,141 @@ function registerSaveEDSCommand(program2) {
584
323
  });
585
324
  }
586
325
 
587
- // src/commands/save-all.ts
588
- import { AxiosError as AxiosError6 } from "axios";
589
- function registerSaveAllCommand(program2) {
590
- program2.command("save-all").description("Save the Email Design System and all components to the server").action(async () => {
591
- try {
592
- configureAxios();
593
- const eds = await loadEDS2();
594
- console.log("Saving Email Design System...");
595
- const edsResponse = await updateEmailDesignSystem(
596
- eds.workspace_id,
597
- eds.email_design_system_id,
598
- {
599
- name: eds.eds_metadata.name,
600
- design_tokens: eds.eds_metadata.design_tokens,
601
- custom_styles: eds.eds_metadata.custom_styles
326
+ // src/commands/dev.ts
327
+ import { createServer as createServer2 } from "vite";
328
+ import chokidar from "chokidar";
329
+ import checker from "vite-plugin-checker";
330
+
331
+ // src/devServer.ts
332
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
333
+ import { resolve as resolve3 } from "path";
334
+ import { AxiosError as AxiosError4 } from "axios";
335
+ import { configureAxios as configureAxios4, renderEmailDesignSystem as renderEmailDesignSystem3 } from "@emailshepherd/api-client";
336
+ function extractFieldSchema(field) {
337
+ const base = {
338
+ type: field.type,
339
+ label: field.label,
340
+ liquid_variable: field.liquid_variable,
341
+ default_value: field.default_value
342
+ };
343
+ if (field.type === "enum") {
344
+ return {
345
+ ...base,
346
+ options: field.options.map((o) => ({ label: o.label, value: o.value }))
347
+ };
348
+ }
349
+ return base;
350
+ }
351
+ function extractComponentSchema(component) {
352
+ return {
353
+ name: component.name,
354
+ label: component.label,
355
+ fields: component.field_definitions.map(extractFieldSchema)
356
+ };
357
+ }
358
+ function writeFieldsManifest(eds) {
359
+ const distDir = resolve3(process.cwd(), "dist");
360
+ mkdirSync2(distDir, { recursive: true });
361
+ const manifest = {
362
+ container_component: extractComponentSchema(eds.container_component),
363
+ components: eds.components.map(extractComponentSchema)
364
+ };
365
+ writeFileSync2(resolve3(distDir, "fields.json"), JSON.stringify(manifest, null, 2));
366
+ }
367
+ function applyOverrides(eds, overrides) {
368
+ const applyToComponent = (component) => {
369
+ const componentOverrides = overrides[component.name];
370
+ if (!componentOverrides) return component;
371
+ return {
372
+ ...component,
373
+ field_definitions: component.field_definitions.map((field) => {
374
+ const override = componentOverrides[field.liquid_variable];
375
+ if (override !== void 0) {
376
+ return { ...field, default_value: override };
602
377
  }
603
- );
604
- console.log(`Email Design System "${edsResponse.data.name}" saved successfully`);
605
- console.log(`Saving container component "${eds.container_component.name}"...`);
606
- const containerResponse = await updateComponent(
607
- eds.workspace_id,
608
- eds.email_design_system_id,
609
- eds.container_component.id,
610
- eds.container_component
611
- );
612
- console.log(`Container component "${containerResponse.data.name}" saved successfully`);
613
- for (const component of eds.components) {
614
- console.log(`Saving component "${component.name}"...`);
615
- const response = await updateComponent(
616
- eds.workspace_id,
617
- eds.email_design_system_id,
618
- component.id,
619
- component
620
- );
621
- console.log(`Component "${response.data.name}" saved successfully`);
622
- }
623
- console.log("All saved successfully");
624
- } catch (error) {
625
- if (error instanceof AxiosError6 && error.response?.data) {
626
- console.log(JSON.stringify(error.response.data, null, 2));
627
- } else if (error instanceof Error) {
628
- console.error(error.message);
629
- } else {
630
- console.error(error);
631
- }
632
- process.exit(1);
378
+ return field;
379
+ })
380
+ };
381
+ };
382
+ return {
383
+ ...eds,
384
+ container_component: applyToComponent(eds.container_component),
385
+ components: eds.components.map(applyToComponent)
386
+ };
387
+ }
388
+ async function renderWithOverrides(overrides) {
389
+ configureAxios4();
390
+ const eds = await loadEDS();
391
+ const modifiedEds = applyOverrides(eds, overrides);
392
+ const objectRepresentation = await buildEmailDesignSystemObject(modifiedEds);
393
+ const response = await renderEmailDesignSystem3(
394
+ modifiedEds.workspace_id,
395
+ modifiedEds.email_design_system_id,
396
+ objectRepresentation
397
+ );
398
+ if (response.data.html) {
399
+ return response.data.html;
400
+ }
401
+ throw new Error("No HTML returned from API");
402
+ }
403
+ function renderApiPlugin() {
404
+ return {
405
+ name: "emailshepherd-render-api",
406
+ configureServer(server) {
407
+ server.middlewares.use(async (req, res, next) => {
408
+ if (req.url !== "/api/render" || req.method !== "POST") {
409
+ return next();
410
+ }
411
+ let body = "";
412
+ req.on("data", (chunk) => {
413
+ body += chunk.toString();
414
+ });
415
+ req.on("end", async () => {
416
+ try {
417
+ const overrides = JSON.parse(body);
418
+ const html = await renderWithOverrides(overrides);
419
+ res.setHeader("Content-Type", "application/json");
420
+ res.end(JSON.stringify({ html }));
421
+ } catch (error) {
422
+ res.statusCode = 500;
423
+ res.setHeader("Content-Type", "application/json");
424
+ if (error instanceof AxiosError4 && error.response?.data) {
425
+ res.end(JSON.stringify({ error: error.response.data }));
426
+ } else if (error instanceof Error) {
427
+ res.end(JSON.stringify({ error: error.message }));
428
+ } else {
429
+ res.end(JSON.stringify({ error: "Unknown error" }));
430
+ }
431
+ }
432
+ });
433
+ });
633
434
  }
634
- });
435
+ };
635
436
  }
636
437
 
637
438
  // src/commands/dev.ts
638
- import { createServer as createServer2 } from "vite";
639
- import chokidar from "chokidar";
640
439
  function registerDevCommand(program2) {
641
440
  program2.command("dev").description("Start the development server with hot reload").option("-p, --port <port>", "Port to run the server on", "5173").action(async (options) => {
642
441
  const port = parseInt(options.port, 10);
643
442
  console.log("Building...");
443
+ const eds = await loadEDS();
644
444
  await buildHtml();
445
+ writeDevWrapper();
446
+ writeFieldsManifest(eds);
645
447
  const server = await createServer2({
646
448
  root: "dist",
647
- server: { port }
449
+ server: { port },
450
+ plugins: [
451
+ checker({ typescript: true }),
452
+ renderApiPlugin()
453
+ ]
648
454
  });
649
455
  const watcher = chokidar.watch("src", { ignoreInitial: true, usePolling: true, interval: 300 });
650
456
  watcher.on("all", async (event, path) => {
651
457
  console.log(`[src] ${event}: ${path}, rebuilding...`);
458
+ const updatedEds = await loadEDS();
652
459
  await buildHtml();
460
+ writeFieldsManifest(updatedEds);
653
461
  server.ws.send({ type: "full-reload" });
654
462
  });
655
463
  await server.listen();
@@ -657,31 +465,26 @@ function registerDevCommand(program2) {
657
465
  });
658
466
  }
659
467
 
660
- // src/commands/create-component.ts
661
- import { AxiosError as AxiosError7 } from "axios";
662
- function registerCreateComponentCommand(program2) {
663
- program2.command("create-component").description("Create a new component in the Email Design System").requiredOption("--name <name>", "Component name (used as directory name and in code)").requiredOption("--label <label>", "Component label (displayed in the UI)").action(async (options) => {
468
+ // src/commands/build.ts
469
+ import { resolve as resolve4 } from "path";
470
+ function registerBuildCommand(program2) {
471
+ program2.command("build").description("Build the Email Design System HTML output").action(async () => {
664
472
  try {
665
- configureAxios();
666
- const eds = await loadEDS2();
667
- const response = await createComponent(
668
- eds.workspace_id,
669
- eds.email_design_system_id,
670
- {
671
- name: options.name,
672
- label: options.label,
673
- template: "<div>Hello, world!</div>",
674
- field_definitions: []
675
- }
676
- );
677
- const component = response.data;
678
- const targetDir = process.cwd();
679
- buildComponentDirectory(component, targetDir);
680
- console.log(`Component "${component.name}" created successfully`);
473
+ console.log("Checking types...");
474
+ const typeCheck = runTypeCheck();
475
+ if (!typeCheck.success) {
476
+ console.error("Type errors found:\n");
477
+ console.error(typeCheck.output);
478
+ process.exit(1);
479
+ }
480
+ console.log("Building...");
481
+ await buildHtml();
482
+ await closeViteServer();
483
+ const outputPath = resolve4(process.cwd(), "dist", "rendered.html");
484
+ console.log(`Built successfully: ${outputPath}`);
485
+ process.exit(0);
681
486
  } catch (error) {
682
- if (error instanceof AxiosError7 && error.response?.data) {
683
- console.log(JSON.stringify(error.response.data, null, 2));
684
- } else if (error instanceof Error) {
487
+ if (error instanceof Error) {
685
488
  console.error(error.message);
686
489
  } else {
687
490
  console.error(error);
@@ -692,13 +495,14 @@ function registerCreateComponentCommand(program2) {
692
495
  }
693
496
 
694
497
  // src/cli.ts
498
+ updateNotifier({ pkg: package_default }).notify({
499
+ message: `Update available: {currentVersion} \u2192 {latestVersion}
500
+ Run: npm update @emailshepherd/cli`
501
+ });
695
502
  var program = new Command();
696
- program.name("emailshepherd").description("EmailShepherd CLI").version(version);
697
- registerInitCommand(program);
503
+ program.name("emailshepherd").description("EmailShepherd CLI - run commands from within an EDS project").version(package_default.version);
698
504
  registerValidateCommand(program);
699
- registerSaveComponentCommand(program);
700
- registerCreateComponentCommand(program);
701
- registerSaveEDSCommand(program);
702
505
  registerSaveAllCommand(program);
703
506
  registerDevCommand(program);
507
+ registerBuildCommand(program);
704
508
  program.parse();