@editframe/cli 0.7.0-beta.9 → 0.8.0-beta.10

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.
Files changed (61) hide show
  1. package/dist/VERSION.d.ts +1 -0
  2. package/dist/VERSION.js +4 -0
  3. package/dist/commands/auth.d.ts +9 -0
  4. package/dist/commands/auth.js +33 -0
  5. package/dist/commands/check.d.ts +1 -0
  6. package/dist/commands/check.js +114 -0
  7. package/dist/commands/preview.d.ts +1 -0
  8. package/dist/commands/preview.js +5 -0
  9. package/dist/commands/process-file.d.ts +1 -0
  10. package/dist/commands/process-file.js +35 -0
  11. package/dist/commands/process.d.ts +1 -0
  12. package/dist/commands/process.js +35 -0
  13. package/dist/commands/render.d.ts +1 -0
  14. package/dist/commands/render.js +153 -0
  15. package/dist/commands/sync.d.ts +1 -0
  16. package/dist/commands/sync.js +5 -0
  17. package/dist/commands/webhook.d.ts +7 -0
  18. package/dist/commands/webhook.js +61 -0
  19. package/dist/index.d.ts +0 -0
  20. package/dist/index.js +18 -0
  21. package/dist/operations/getRenderInfo.d.ts +15 -0
  22. package/dist/operations/getRenderInfo.js +59 -0
  23. package/dist/operations/processRenderInfo.d.ts +2 -0
  24. package/dist/operations/processRenderInfo.js +30 -0
  25. package/dist/operations/syncAssetsDirectory.d.ts +1 -0
  26. package/dist/operations/syncAssetsDirectory.js +302 -0
  27. package/dist/utils/attachWorkbench.d.ts +2 -0
  28. package/dist/utils/getFolderSize.d.ts +1 -0
  29. package/dist/utils/getFolderSize.js +22 -0
  30. package/dist/utils/index.d.ts +2 -0
  31. package/dist/utils/index.js +14 -0
  32. package/dist/utils/launchBrowserAndWaitForSDK.d.ts +9 -0
  33. package/dist/utils/launchBrowserAndWaitForSDK.js +49 -0
  34. package/dist/utils/startDevServer.d.ts +7 -0
  35. package/dist/utils/startPreviewServer.d.ts +7 -0
  36. package/dist/utils/startPreviewServer.js +38 -0
  37. package/dist/utils/validateVideoResolution.d.ts +9 -0
  38. package/dist/utils/validateVideoResolution.js +29 -0
  39. package/dist/utils/withSpinner.d.ts +1 -0
  40. package/dist/utils/withSpinner.js +15 -0
  41. package/package.json +9 -8
  42. package/src/commands/auth.ts +1 -1
  43. package/src/commands/process-file.ts +54 -0
  44. package/src/commands/process.ts +5 -5
  45. package/src/commands/render.ts +19 -10
  46. package/src/commands/sync.ts +1 -1
  47. package/src/commands/webhook.ts +76 -0
  48. package/src/operations/processRenderInfo.ts +1 -1
  49. package/src/operations/syncAssetsDirectory.ts +123 -50
  50. package/src/utils/getFolderSize.ts +24 -0
  51. package/src/utils/launchBrowserAndWaitForSDK.ts +1 -1
  52. package/src/utils/startDevServer.ts +1 -1
  53. package/src/utils/startPreviewServer.ts +1 -1
  54. package/src/utils/validateVideoResolution.ts +36 -0
  55. package/.env +0 -1
  56. package/.env.example +0 -1
  57. package/CHANGELOG.md +0 -8
  58. package/src/VERSION.ts +0 -1
  59. package/src/index.ts +0 -29
  60. package/tsconfig.json +0 -5
  61. package/vite.config.ts +0 -22
@@ -1,5 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
+ import { inspect } from "node:util";
4
+ import { PassThrough } from "node:stream";
3
5
 
4
6
  import * as tar from "tar";
5
7
  import { program, Option } from "commander";
@@ -8,16 +10,16 @@ import { build } from "vite";
8
10
 
9
11
  import { md5Directory, md5FilePath } from "@editframe/assets";
10
12
 
11
- import { withSpinner } from "../utils/withSpinner";
13
+ import { withSpinner } from "../utils/withSpinner.ts";
14
+ import { syncAssetDirectory } from "../operations/syncAssetsDirectory.ts";
12
15
  import { createRender, uploadRender } from "@editframe/api";
13
- import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK";
14
- import { PreviewServer } from "../utils/startPreviewServer";
15
- import { PassThrough } from "node:stream";
16
- import { syncAssetDirectory } from "../operations/syncAssetsDirectory";
17
- import { getClient } from "../utils";
18
- import { getRenderInfo } from "../operations/getRenderInfo";
19
- import { processRenderInfo } from "../operations/processRenderInfo";
20
- import { inspect } from "node:util";
16
+ import { launchBrowserAndWaitForSDK } from "../utils/launchBrowserAndWaitForSDK.ts";
17
+ import { PreviewServer } from "../utils/startPreviewServer.ts";
18
+ import { getClient } from "../utils/index.ts";
19
+ import { getRenderInfo } from "../operations/getRenderInfo.ts";
20
+ import { processRenderInfo } from "../operations/processRenderInfo.ts";
21
+ import { validateVideoResolution } from "../utils/validateVideoResolution.ts";
22
+ import { getFolderSize } from "../utils/getFolderSize.ts";
21
23
 
22
24
  interface StrategyBuilder {
23
25
  buildProductionUrl: (tagName: string, assetPath: string) => Promise<string>;
@@ -114,6 +116,12 @@ program
114
116
  },
115
117
  async (page) => {
116
118
  const renderInfo = await page.evaluate(getRenderInfo);
119
+
120
+ validateVideoResolution({
121
+ width: renderInfo.width,
122
+ height: renderInfo.height,
123
+ });
124
+
117
125
  await processRenderInfo(renderInfo);
118
126
 
119
127
  const doc = parseHTML(
@@ -172,7 +180,8 @@ program
172
180
  );
173
181
  const readable = new PassThrough();
174
182
  tarStream.pipe(readable);
175
- await uploadRender(getClient(), md5, readable);
183
+ const folderSize = await getFolderSize(distDir);
184
+ await uploadRender(getClient(), md5, readable, folderSize);
176
185
  process.stderr.write("Render assets uploaded\n");
177
186
  process.stderr.write(inspect(render));
178
187
  process.stderr.write("\n");
@@ -1,5 +1,5 @@
1
1
  import { program } from "commander";
2
- import { syncAssetDirectory } from "../operations/syncAssetsDirectory";
2
+ import { syncAssetDirectory } from "../operations/syncAssetsDirectory.ts";
3
3
 
4
4
  program
5
5
  .command("sync")
@@ -0,0 +1,76 @@
1
+ import { program, Option } from "commander";
2
+ import ora from "ora";
3
+ import chalk from "chalk";
4
+ import debug from "debug";
5
+ import { input, select } from "@inquirer/prompts";
6
+
7
+ import { getClient } from "../utils/index.ts";
8
+
9
+ const log = debug("ef:cli:auth");
10
+
11
+ export interface APITestWebhhokResult {
12
+ message: string;
13
+ }
14
+ const topics = [
15
+ "render.created",
16
+ "render.rendering",
17
+ "render.pending",
18
+ "render.failed",
19
+ "render.completed",
20
+ ];
21
+
22
+ export const testWebhookURL = async ({
23
+ webhookURL,
24
+ topic,
25
+ }: {
26
+ webhookURL: string;
27
+ topic: string;
28
+ }) => {
29
+ const response = await getClient().authenticatedFetch(
30
+ "/api/v1/test_webhook",
31
+ {
32
+ method: "POST",
33
+ body: JSON.stringify({
34
+ webhookURL,
35
+ topic,
36
+ }),
37
+ },
38
+ );
39
+ return response.json() as Promise<APITestWebhhokResult>;
40
+ };
41
+
42
+ const webhookCommand = program
43
+ .command("webhook")
44
+ .description("Test webhook URL with a topic")
45
+ .option("-u, --webhookURL <webhookURL>", "Webhook URL")
46
+ .addOption(new Option("-t, --topic <topic>", "Topic").choices(topics))
47
+ .action(async () => {
48
+ const options = webhookCommand.opts();
49
+ log("Options:", options);
50
+ let { webhookURL, topic } = options;
51
+
52
+ if (!webhookURL) {
53
+ const answer = await input({ message: "Enter a webhook URL:" });
54
+ webhookURL = answer;
55
+ }
56
+
57
+ if (!topic) {
58
+ const answer = await select({
59
+ message: "Select a topic:",
60
+ choices: [...topics.map((topic) => ({ title: topic, value: topic }))],
61
+ });
62
+ topic = answer;
63
+ }
64
+
65
+ const spinner = ora("Testing...").start();
66
+ try {
67
+ const apiData = await testWebhookURL({ webhookURL, topic });
68
+ spinner.succeed("Webhook URL is working! 🎉");
69
+ process.stderr.write(chalk.green(`${apiData.message}\n`));
70
+ } catch (error: any) {
71
+ spinner.fail("Webhook URL is not working!");
72
+ process.stderr.write(error?.message);
73
+ process.stderr.write("\n");
74
+ log("Error:", error);
75
+ }
76
+ });
@@ -3,7 +3,7 @@ import {
3
3
  cacheImage,
4
4
  findOrCreateCaptions,
5
5
  } from "@editframe/assets";
6
- import type { getRenderInfo } from "./getRenderInfo";
6
+ import type { getRenderInfo } from "./getRenderInfo.ts";
7
7
 
8
8
  export const processRenderInfo = async (
9
9
  renderInfo: Awaited<ReturnType<typeof getRenderInfo>>,
@@ -12,10 +12,11 @@ import {
12
12
  uploadISOBMFFTrack,
13
13
  type CreateISOBMFFTrackPayload,
14
14
  } from "@editframe/api";
15
+ import { statSync } from "node:fs";
15
16
 
16
17
  import { createReadStream } from "node:fs";
17
18
  import type { z } from "zod";
18
- import { getClient } from "../utils";
19
+ import { getClient } from "../utils/index.ts";
19
20
 
20
21
  const imageMatch = /\.(png|jpe?g|gif|webp)$/i;
21
22
  const trackMatch = /\.track-[\d]+.mp4$/i;
@@ -62,6 +63,7 @@ export const syncAssetDirectory = async (
62
63
  }
63
64
  throw error;
64
65
  });
66
+
65
67
  if (!stat?.isDirectory()) {
66
68
  console.error(`No assets cache directory found at ${fullPath}`);
67
69
  return;
@@ -70,12 +72,28 @@ export const syncAssetDirectory = async (
70
72
 
71
73
  process.stderr.write(`Syncing asset dir: ${fullPath}\n`);
72
74
 
75
+ const errors: Record<string, string[]> = {};
76
+
77
+ const reportError = (path: string, message: string) => {
78
+ errors[path] ||= [];
79
+ errors[path].push(message);
80
+ process.stderr.write(` 🚫 ${message}\n`);
81
+ };
82
+
83
+ const reportSuccess = (_path: string, message: string) => {
84
+ process.stderr.write(` ✅ ${message}\n`);
85
+ };
86
+
87
+ const reportInfo = (_path: string, message: string) => {
88
+ process.stderr.write(` ${message}\n`);
89
+ };
90
+
73
91
  for (const asset of assets) {
74
- process.stderr.write(`Syncing asset: ${asset}\n`);
92
+ reportInfo(asset, `Syncing asset: ${asset}`);
75
93
  const assetDir = path.join(fullPath, asset);
76
94
  const stat = await fs.stat(assetDir);
77
95
  if (!stat.isDirectory()) {
78
- process.stderr.write("Invalid asset. Did not find asset directory.\n");
96
+ reportError(asset, "Invalid asset. Did not find asset directory.");
79
97
  return;
80
98
  }
81
99
  const subAssets = await fs.readdir(assetDir);
@@ -88,8 +106,9 @@ export const syncAssetDirectory = async (
88
106
  const subAssetPath = path.join(assetDir, subAsset);
89
107
  const syncStatus = new SyncStatus(subAssetPath);
90
108
  if (await syncStatus.isSynced()) {
91
- process.stderr.write(
92
- ` ✔ Sub-asset has already been synced: ${subAsset}\n`,
109
+ reportInfo(
110
+ subAsset,
111
+ `✔ Sub-asset has already been synced: ${subAsset}`,
93
112
  );
94
113
  continue;
95
114
  }
@@ -97,10 +116,9 @@ export const syncAssetDirectory = async (
97
116
  case imageMatch.test(subAsset): {
98
117
  const probeResult = await Probe.probePath(subAssetPath);
99
118
  const [videoProbe] = probeResult.videoStreams;
119
+ const { format } = probeResult;
100
120
  if (!videoProbe) {
101
- process.stderr.write(
102
- `🚫 No video stream found in image: ${subAsset}\n`,
103
- );
121
+ reportError(subAsset, `No media info found in image: ${subAsset}`);
104
122
  break;
105
123
  }
106
124
  const ext = path.extname(subAsset).slice(1);
@@ -112,34 +130,45 @@ export const syncAssetDirectory = async (
112
130
  ext === "webp"
113
131
  )
114
132
  ) {
115
- process.stderr.write(`🚫 Invalid image format: ${subAsset}\n`);
133
+ reportError(subAsset, `Invalid image format: ${subAsset}`);
116
134
  break;
117
135
  }
118
- process.stderr.write(`🖼️ Syncing image: ${subAsset}\n`);
136
+ reportInfo(subAsset, `🖼️ Syncing image: ${subAsset}`);
119
137
  const created = await createImageFile(getClient(), {
120
138
  id: asset,
121
139
  filename: subAsset,
122
140
  width: videoProbe.width,
123
141
  height: videoProbe.height,
124
142
  mime_type: `image/${ext}`,
143
+ byte_size: (await fs.stat(subAssetPath)).size,
125
144
  });
126
145
  if (created) {
127
146
  if (created.complete) {
128
- process.stderr.write(" ✔ Image has already been synced.\n");
147
+ reportInfo(subAsset, " ✔ Image has already been synced");
148
+ await syncStatus.markSynced();
129
149
  } else {
130
150
  await uploadImageFile(
131
151
  getClient(),
132
152
  created.id,
133
153
  createReadStream(subAssetPath),
134
- );
135
- process.stderr.write(" ✅ Image has been synced.\n");
154
+ Number.parseInt(format.size || "0"),
155
+ )
156
+ .then(() => {
157
+ reportSuccess(subAsset, "Image has been synced.");
158
+ return syncStatus.markSynced();
159
+ })
160
+ .catch((error) => {
161
+ reportError(
162
+ subAsset,
163
+ `Error uploading image: ${error.message}`,
164
+ );
165
+ });
136
166
  }
137
- await syncStatus.markSynced();
138
167
  }
139
168
  break;
140
169
  }
141
170
  case trackMatch.test(subAsset): {
142
- process.stderr.write(`📼 Syncing a/v track: ${subAsset}\n`);
171
+ reportInfo(subAsset, `📼 Syncing a/v track: ${subAsset}`);
143
172
  const createdFile = await createISOBMFFFile(getClient(), {
144
173
  id: asset,
145
174
  filename: subAsset.replace(/\.track-[\d]+.mp4$/, ""),
@@ -148,29 +177,26 @@ export const syncAssetDirectory = async (
148
177
  const probe = await Probe.probePath(subAssetPath);
149
178
  const trackId = subAsset.match(/track-([\d]+).mp4/)?.[1];
150
179
  if (!trackId) {
151
- process.stderr.write(
152
- `🚫 No track ID found for track: ${subAsset}\n`,
153
- );
180
+ reportError(subAsset, `No track ID found for track: ${subAsset}`);
154
181
  break;
155
182
  }
156
183
  const [track] = probe.streams;
157
184
  if (!track) {
158
- process.stderr.write(
159
- `🚫 No track stream found in track: ${subAsset}\n`,
185
+ reportError(
186
+ subAsset,
187
+ `No track stream found in track: ${subAsset}`,
160
188
  );
161
189
  break;
162
190
  }
163
191
  if (track.duration === undefined) {
164
- process.stderr.write(
165
- `🚫 No duration found in track: ${subAsset}\n`,
166
- );
192
+ reportError(subAsset, `No duration found in track: ${subAsset}`);
167
193
  break;
168
194
  }
169
195
 
170
196
  const stat = await fs.stat(subAssetPath);
171
197
 
172
198
  /**
173
- * Because the payload is a discriminated union, we nede to create these objects
199
+ * Because the payload is a discriminated union, we need to create these objects
174
200
  * only after the value for type has been narrowed, otherwise the object will
175
201
  * have type: "audio" | "video" which doesnt' match the payload type that
176
202
  * looks more like { type: "audio", ... } | { type: "video", ... }
@@ -201,79 +227,126 @@ export const syncAssetDirectory = async (
201
227
  );
202
228
 
203
229
  if (createdTrack) {
204
- if (
205
- createdTrack.last_received_byte ===
206
- createdTrack.byte_size - 1
207
- ) {
208
- process.stderr.write(" ✔ Track has already been synced.\n");
230
+ if (createdTrack.next_byte === createdTrack.byte_size) {
231
+ reportInfo(subAsset, "✔ Track has already been synced.");
232
+ await syncStatus.markSynced();
209
233
  } else {
210
234
  await uploadISOBMFFTrack(
211
235
  getClient(),
212
236
  createdFile.id,
213
237
  Number(trackId),
214
238
  createReadStream(subAssetPath),
215
- );
216
- process.stderr.write(" ✅ Track has been synced.\n");
239
+ createdTrack.byte_size,
240
+ )
241
+ .then(() => {
242
+ reportSuccess(subAsset, "Track has been synced.");
243
+ return syncStatus.markSynced();
244
+ })
245
+ .catch((error) => {
246
+ reportError(
247
+ subAsset,
248
+ `Error uploading track: ${error.message}`,
249
+ );
250
+ });
217
251
  }
218
- await syncStatus.markSynced();
219
252
  }
220
253
  }
221
254
 
222
255
  break;
223
256
  }
224
257
  case fragmentIndexMatch.test(subAsset): {
225
- process.stderr.write(`📋 Syncing fragment index: ${subAsset}\n`);
258
+ reportInfo(subAsset, `📋 Syncing fragment index: ${subAsset}`);
226
259
  const createdFile = await createISOBMFFFile(getClient(), {
227
260
  id: asset,
228
261
  filename: subAsset.replace(/\.tracks.json$/, ""),
229
262
  });
230
263
  if (createdFile) {
231
264
  if (createdFile.fragment_index_complete) {
232
- process.stderr.write(
233
- " ✔ Fragment index has already been synced.\n",
234
- );
265
+ reportInfo(subAsset, "✔ Fragment index has already been synced.");
266
+ await syncStatus.markSynced();
235
267
  } else {
268
+ const stats = statSync(subAssetPath);
236
269
  const readStream = createReadStream(subAssetPath);
237
- await uploadFragmentIndex(getClient(), asset, readStream);
238
- process.stderr.write(" ✅ Fragment index has been synced.\n");
270
+ await uploadFragmentIndex(
271
+ getClient(),
272
+ asset,
273
+ readStream,
274
+ stats.size,
275
+ )
276
+ .then(() => {
277
+ reportSuccess(subAsset, "Fragment index has been synced.");
278
+ return syncStatus.markSynced();
279
+ })
280
+ .catch((error) => {
281
+ reportError(
282
+ subAsset,
283
+ `Error uploading fragment index: ${error.message}`,
284
+ );
285
+ });
239
286
  }
240
- await syncStatus.markSynced();
241
287
  } else {
242
- process.stderr.write(
243
- `🚫 No file found for fragment index: ${subAsset}\n`,
288
+ reportError(
289
+ subAsset,
290
+ `No file found for fragment index: ${subAsset}`,
244
291
  );
245
292
  break;
246
293
  }
247
294
  break;
248
295
  }
249
296
  case captionsMatch.test(subAsset): {
250
- process.stderr.write(`📝 Syncing captions: ${subAsset}\n`);
297
+ reportInfo(subAsset, `📝 Syncing captions: ${subAsset}`);
251
298
  const createdFile = await createCaptionFile(getClient(), {
252
299
  id: asset,
253
300
  filename: subAsset.replace(/\.captions.json$/, ""),
301
+ byte_size: (await fs.stat(subAsset)).size,
254
302
  });
255
303
  if (createdFile) {
256
304
  if (createdFile.complete) {
257
- process.stderr.write(" ✔ Captions have already been synced.\n");
305
+ reportInfo(subAsset, "✔ Captions have already been synced.");
306
+ await syncStatus.markSynced();
258
307
  } else {
259
308
  const readStream = createReadStream(subAssetPath);
260
- await uploadCaptionFile(getClient(), asset, readStream);
261
- process.stderr.write(" ✅ Captions have been synced.\n");
309
+ const stats = statSync(subAssetPath);
310
+ await uploadCaptionFile(
311
+ getClient(),
312
+ asset,
313
+ readStream,
314
+ stats.size,
315
+ )
316
+ .then(() => {
317
+ reportSuccess(subAsset, "Captions have been synced.");
318
+ return syncStatus.markSynced();
319
+ })
320
+ .catch((error) => {
321
+ reportError(
322
+ subAsset,
323
+ `Error uploading captions: ${error.message}`,
324
+ );
325
+ });
262
326
  }
263
- await syncStatus.markSynced();
264
327
  } else {
265
- process.stderr.write(
266
- `🚫 No file found for captions: ${subAsset}\n`,
267
- );
328
+ reportError(subAsset, `No file found for captions: ${subAsset}`);
268
329
  break;
269
330
  }
270
331
  break;
271
332
  }
272
333
  default: {
273
- process.stderr.write(`🚫 Unknown sub-asset: ${subAsset}\n`);
334
+ reportError(subAsset, `Unknown sub-asset: ${subAsset}`);
274
335
  break;
275
336
  }
276
337
  }
277
338
  }
278
339
  }
340
+
341
+ if (Object.keys(errors).length) {
342
+ process.stderr.write("\n\n❌ Encountered errors while syncing assets:\n");
343
+ for (const [asset, messages] of Object.entries(errors)) {
344
+ process.stderr.write(`\n🚫 ${asset}\n`);
345
+ for (const message of messages) {
346
+ process.stderr.write(` - ${message}\n`);
347
+ }
348
+ }
349
+
350
+ throw new Error("Failed to sync assets");
351
+ }
279
352
  };
@@ -0,0 +1,24 @@
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export const getFolderSize = async (folderPath: string): Promise<number> => {
5
+ let totalSize = 0;
6
+
7
+ async function calculateSize(dir: string): Promise<void> {
8
+ const files = await fs.readdir(dir);
9
+
10
+ for (const file of files) {
11
+ const filePath = path.join(dir, file);
12
+ const stats = await fs.stat(filePath);
13
+
14
+ if (stats.isDirectory()) {
15
+ await calculateSize(filePath);
16
+ } else {
17
+ totalSize += stats.size;
18
+ }
19
+ }
20
+ }
21
+
22
+ await calculateSize(folderPath);
23
+ return totalSize;
24
+ };
@@ -2,7 +2,7 @@ import chalk from "chalk";
2
2
  import { type Browser, type Page, chromium } from "playwright";
3
3
  import debug from "debug";
4
4
 
5
- import { withSpinner } from "./withSpinner";
5
+ import { withSpinner } from "./withSpinner.ts";
6
6
 
7
7
  const browserLog = debug("ef:cli::browser");
8
8
 
@@ -5,7 +5,7 @@ import tailwindcss from "tailwindcss";
5
5
 
6
6
  import { vitePluginEditframe as editframe } from "@editframe/vite-plugin";
7
7
 
8
- import { withSpinner } from "./withSpinner";
8
+ import { withSpinner } from "./withSpinner.ts";
9
9
 
10
10
  export class DevServer {
11
11
  static async start(directory: string) {
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
 
4
4
  import { vitePluginEditframe as editframe } from "@editframe/vite-plugin";
5
5
 
6
- import { withSpinner } from "./withSpinner";
6
+ import { withSpinner } from "./withSpinner.ts";
7
7
 
8
8
  export class PreviewServer {
9
9
  static async start(directory: string) {
@@ -0,0 +1,36 @@
1
+ import { z } from "zod";
2
+ import ora from "ora";
3
+ import debug from "debug";
4
+
5
+ const log = debug("ef:cli:validateVideoResolution");
6
+
7
+ type VideoPayload = {
8
+ width: number;
9
+ height: number;
10
+ };
11
+
12
+ const schema = z
13
+ .object({
14
+ width: z.number().int(),
15
+ height: z.number().int(),
16
+ })
17
+ .refine((data) => data.width % 2 === 0, {
18
+ message: "Width must be divisible by 2",
19
+ path: ["width"],
20
+ })
21
+ .refine((data) => data.height % 2 === 0, {
22
+ message: "Height must be divisible by 2",
23
+ });
24
+ export const validateVideoResolution = async (rawPayload: VideoPayload) => {
25
+ const spinner = ora("Validating video resolution").start();
26
+ const result = schema.safeParse(rawPayload);
27
+ if (result.success) {
28
+ spinner.succeed("Video resolution is valid");
29
+ return result.data;
30
+ }
31
+ spinner.fail("Invalid video resolution");
32
+ process.stderr.write(result.error?.errors.map((e) => e.message).join("\n"));
33
+ process.stderr.write("\n");
34
+ log("Error:", result.error?.errors.map((e) => e.message).join("\n"));
35
+ process.exit(1);
36
+ };
package/.env DELETED
@@ -1 +0,0 @@
1
- EF_HOST=http://localhost:3000
package/.env.example DELETED
@@ -1 +0,0 @@
1
- EF_HOST=http://localhost:3000
package/CHANGELOG.md DELETED
@@ -1,8 +0,0 @@
1
- # @editframe/cli
2
-
3
- ## 0.7.0-beta.9
4
-
5
- ### Patch Changes
6
-
7
- - Updated dependencies [[`17c4452`](https://github.com/editframe/elements/commit/17c4452f679b042ac0accd9bf520728455e8cc2c)]:
8
- - @editframe/elements@0.7.0-beta.9
package/src/VERSION.ts DELETED
@@ -1 +0,0 @@
1
- export const VERSION = "0.7.0-beta.8";
package/src/index.ts DELETED
@@ -1,29 +0,0 @@
1
- #!/usr/bin/env node
2
- import "dotenv/config";
3
- import { program, Option } from "commander";
4
-
5
- import { VERSION } from "./VERSION";
6
-
7
- program
8
- .name("editframe")
9
- .addOption(new Option("-t, --token <token>", "API Token").env("EF_TOKEN"))
10
- .addOption(
11
- new Option("--ef-host <host>", "Editframe Host")
12
- .env("EF_HOST")
13
- .default("https://editframe.dev"),
14
- )
15
- .addOption(
16
- new Option("--ef-render-host <host>", "Editframe Render Host")
17
- .env("EF_RENDER_HOST")
18
- .default("https://editframe.dev"),
19
- )
20
- .version(VERSION);
21
-
22
- import "./commands/auth";
23
- import "./commands/sync";
24
- import "./commands/render";
25
- import "./commands/preview";
26
- import "./commands/process";
27
- import "./commands/check";
28
-
29
- program.parse(process.argv);
package/tsconfig.json DELETED
@@ -1,5 +0,0 @@
1
- {
2
- "include": ["src/**/*.ts", "../api/src/util/authenticatedFetch.ts"],
3
- "exclude": [""],
4
- "extends": "../../tsconfig.json"
5
- }
package/vite.config.ts DELETED
@@ -1,22 +0,0 @@
1
- import { execPromise } from "../../lib/util/execPromise";
2
-
3
- import path from "node:path";
4
-
5
- import { defineViteBuildConfig } from "../defineViteBuildConfig";
6
-
7
- const root = path.dirname(new URL(import.meta.url).pathname);
8
-
9
- export default defineViteBuildConfig({
10
- root: root,
11
- name: "editframe-cli",
12
- plugins: [
13
- {
14
- name: "chmod-executables",
15
- async closeBundle() {
16
- const cliExecPath = path.join(__dirname, "dist/index.js");
17
- process.stderr.write(`marking ${cliExecPath} as executable\n`);
18
- await execPromise(`chmod +x ${cliExecPath}`);
19
- },
20
- },
21
- ],
22
- });