@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.
- package/dist/VERSION.d.ts +1 -0
- package/dist/VERSION.js +4 -0
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +33 -0
- package/dist/commands/check.d.ts +1 -0
- package/dist/commands/check.js +114 -0
- package/dist/commands/preview.d.ts +1 -0
- package/dist/commands/preview.js +5 -0
- package/dist/commands/process-file.d.ts +1 -0
- package/dist/commands/process-file.js +35 -0
- package/dist/commands/process.d.ts +1 -0
- package/dist/commands/process.js +35 -0
- package/dist/commands/render.d.ts +1 -0
- package/dist/commands/render.js +153 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +5 -0
- package/dist/commands/webhook.d.ts +7 -0
- package/dist/commands/webhook.js +61 -0
- package/dist/index.d.ts +0 -0
- package/dist/index.js +18 -0
- package/dist/operations/getRenderInfo.d.ts +15 -0
- package/dist/operations/getRenderInfo.js +59 -0
- package/dist/operations/processRenderInfo.d.ts +2 -0
- package/dist/operations/processRenderInfo.js +30 -0
- package/dist/operations/syncAssetsDirectory.d.ts +1 -0
- package/dist/operations/syncAssetsDirectory.js +302 -0
- package/dist/utils/attachWorkbench.d.ts +2 -0
- package/dist/utils/getFolderSize.d.ts +1 -0
- package/dist/utils/getFolderSize.js +22 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +14 -0
- package/dist/utils/launchBrowserAndWaitForSDK.d.ts +9 -0
- package/dist/utils/launchBrowserAndWaitForSDK.js +49 -0
- package/dist/utils/startDevServer.d.ts +7 -0
- package/dist/utils/startPreviewServer.d.ts +7 -0
- package/dist/utils/startPreviewServer.js +38 -0
- package/dist/utils/validateVideoResolution.d.ts +9 -0
- package/dist/utils/validateVideoResolution.js +29 -0
- package/dist/utils/withSpinner.d.ts +1 -0
- package/dist/utils/withSpinner.js +15 -0
- package/package.json +9 -8
- package/src/commands/auth.ts +1 -1
- package/src/commands/process-file.ts +54 -0
- package/src/commands/process.ts +5 -5
- package/src/commands/render.ts +19 -10
- package/src/commands/sync.ts +1 -1
- package/src/commands/webhook.ts +76 -0
- package/src/operations/processRenderInfo.ts +1 -1
- package/src/operations/syncAssetsDirectory.ts +123 -50
- package/src/utils/getFolderSize.ts +24 -0
- package/src/utils/launchBrowserAndWaitForSDK.ts +1 -1
- package/src/utils/startDevServer.ts +1 -1
- package/src/utils/startPreviewServer.ts +1 -1
- package/src/utils/validateVideoResolution.ts +36 -0
- package/.env +0 -1
- package/.env.example +0 -1
- package/CHANGELOG.md +0 -8
- package/src/VERSION.ts +0 -1
- package/src/index.ts +0 -29
- package/tsconfig.json +0 -5
- package/vite.config.ts +0 -22
package/src/commands/render.ts
CHANGED
|
@@ -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 {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
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
|
|
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");
|
package/src/commands/sync.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
+
reportError(subAsset, `Invalid image format: ${subAsset}`);
|
|
116
134
|
break;
|
|
117
135
|
}
|
|
118
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
233
|
-
|
|
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(
|
|
238
|
-
|
|
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
|
-
|
|
243
|
-
|
|
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
|
-
|
|
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
|
-
|
|
305
|
+
reportInfo(subAsset, "✔ Captions have already been synced.");
|
|
306
|
+
await syncStatus.markSynced();
|
|
258
307
|
} else {
|
|
259
308
|
const readStream = createReadStream(subAssetPath);
|
|
260
|
-
|
|
261
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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
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
|
-
});
|