@editframe/cli 0.10.0-beta.4 → 0.10.0-beta.6

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 (59) hide show
  1. package/dist/VERSION.d.ts +1 -1
  2. package/dist/VERSION.js +1 -1
  3. package/dist/commands/render.d.ts +1 -1
  4. package/dist/commands/render.js +77 -51
  5. package/dist/commands/sync.js +5 -2
  6. package/dist/operations/getRenderInfo.d.ts +40 -4
  7. package/dist/operations/getRenderInfo.js +13 -0
  8. package/dist/operations/processRenderInfo.js +3 -0
  9. package/dist/operations/syncAssetsDirectory/SubAssetSync.d.ts +20 -0
  10. package/dist/operations/syncAssetsDirectory/SubAssetSync.js +26 -0
  11. package/dist/operations/syncAssetsDirectory/SyncCaption.d.ts +19 -0
  12. package/dist/operations/syncAssetsDirectory/SyncCaption.js +66 -0
  13. package/dist/operations/syncAssetsDirectory/SyncCaption.test.d.ts +1 -0
  14. package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.d.ts +20 -0
  15. package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.js +79 -0
  16. package/dist/operations/syncAssetsDirectory/SyncFragmentIndex.test.d.ts +1 -0
  17. package/dist/operations/syncAssetsDirectory/SyncImage.d.ts +23 -0
  18. package/dist/operations/syncAssetsDirectory/SyncImage.js +95 -0
  19. package/dist/operations/syncAssetsDirectory/SyncImage.test.d.ts +1 -0
  20. package/dist/operations/syncAssetsDirectory/SyncStatus.d.ts +41 -0
  21. package/dist/operations/syncAssetsDirectory/SyncStatus.js +43 -0
  22. package/dist/operations/syncAssetsDirectory/SyncTrack.d.ts +70 -0
  23. package/dist/operations/syncAssetsDirectory/SyncTrack.js +138 -0
  24. package/dist/operations/syncAssetsDirectory/SyncTrack.test.d.ts +1 -0
  25. package/dist/operations/syncAssetsDirectory/doAssetSync.d.ts +5 -0
  26. package/dist/operations/syncAssetsDirectory/doAssetSync.js +48 -0
  27. package/dist/operations/syncAssetsDirectory/doAssetSync.test.d.ts +1 -0
  28. package/dist/operations/syncAssetsDirectory.d.ts +1 -1
  29. package/dist/operations/syncAssetsDirectory.js +20 -240
  30. package/dist/test-fixtures/fixture.d.ts +26 -0
  31. package/dist/utils/index.js +4 -1
  32. package/package.json +5 -5
  33. package/src/commands/process.ts +0 -1
  34. package/src/commands/render.ts +79 -58
  35. package/src/commands/sync.ts +5 -2
  36. package/src/operations/getRenderInfo.ts +14 -0
  37. package/src/operations/processRenderInfo.ts +3 -0
  38. package/src/operations/syncAssetsDirectory/SubAssetSync.ts +42 -0
  39. package/src/operations/syncAssetsDirectory/SyncCaption.test.ts +145 -0
  40. package/src/operations/syncAssetsDirectory/SyncCaption.ts +76 -0
  41. package/src/operations/syncAssetsDirectory/SyncFragmentIndex.test.ts +151 -0
  42. package/src/operations/syncAssetsDirectory/SyncFragmentIndex.ts +92 -0
  43. package/src/operations/syncAssetsDirectory/SyncImage.test.ts +131 -0
  44. package/src/operations/syncAssetsDirectory/SyncImage.ts +112 -0
  45. package/src/operations/syncAssetsDirectory/SyncStatus.ts +51 -0
  46. package/src/operations/syncAssetsDirectory/SyncTrack.test.ts +222 -0
  47. package/src/operations/syncAssetsDirectory/SyncTrack.ts +164 -0
  48. package/src/operations/syncAssetsDirectory/doAssetSync.test.ts +134 -0
  49. package/src/operations/syncAssetsDirectory/doAssetSync.ts +62 -0
  50. package/src/operations/syncAssetsDirectory.test.ts +482 -0
  51. package/src/operations/syncAssetsDirectory.ts +22 -283
  52. package/src/utils/index.ts +4 -1
  53. package/test-fixtures/fixture.ts +141 -0
  54. package/test-fixtures/network.ts +181 -0
  55. package/test-fixtures/test-captions.json +9 -0
  56. package/test-fixtures/test.mp4 +0 -0
  57. package/test-fixtures/test.png +0 -0
  58. package/src/commands/render.test.ts +0 -34
  59. /package/dist/{commands/render.test.d.ts → operations/syncAssetsDirectory.test.d.ts} +0 -0
@@ -0,0 +1,222 @@
1
+ import { describe, expect, test } from "vitest";
2
+ import { fixture, withFixtures } from "../../../test-fixtures/fixture.ts";
3
+ import {
4
+ mockCreateIsobmffFile,
5
+ mockCreateIsobmffTrack,
6
+ mockGetIsobmffTrackUpload,
7
+ useMSW,
8
+ } from "../../../test-fixtures/network.ts";
9
+ import { SyncTrack } from "./SyncTrack.ts";
10
+
11
+ describe("SyncTrack", async () => {
12
+ const server = useMSW();
13
+ await withFixtures(
14
+ [fixture("test.mp4", "test.mp4")],
15
+ async ({ files: [video], generateTrack }) => {
16
+ test("Reads byte size", async () => {
17
+ const syncTrack = new SyncTrack(
18
+ await generateTrack(video!, 1),
19
+ video!.md5,
20
+ );
21
+ await expect(syncTrack.byteSize()).resolves.toEqual(26434);
22
+ });
23
+ describe("prepare()", () => {
24
+ test("Creates ISOBMFF file", async () => {
25
+ server.use(
26
+ mockCreateIsobmffFile({
27
+ complete: true,
28
+ id: "123",
29
+ filename: "test.mp4",
30
+ fixture: video!,
31
+ }),
32
+ );
33
+ const syncTrack = new SyncTrack(
34
+ await generateTrack(video!, 1),
35
+ video!.md5,
36
+ );
37
+ await syncTrack.prepare();
38
+ expect(syncTrack.isoFile).toBeDefined();
39
+ });
40
+
41
+ test("Probes track", async () => {
42
+ server.use(
43
+ mockCreateIsobmffFile({
44
+ complete: true,
45
+ id: "123",
46
+ filename: "test.mp4",
47
+ fixture: video!,
48
+ }),
49
+ );
50
+ const syncTrack = new SyncTrack(
51
+ await generateTrack(video!, 1),
52
+ video!.md5,
53
+ );
54
+ await syncTrack.prepare();
55
+ expect(syncTrack.probeResult).toBeDefined();
56
+ });
57
+ });
58
+
59
+ describe("validate()", () => {
60
+ test("throws when prepare() is not called", async () => {
61
+ const syncTrack = new SyncTrack(
62
+ await generateTrack(video!, 1),
63
+ video!.md5,
64
+ );
65
+ await expect(syncTrack.validate()).rejects.toThrow();
66
+ });
67
+
68
+ test.skip("throws when track has bad duration", async () => {});
69
+ test.skip("throws when track has bad codec_type", async () => {});
70
+ test.skip("throws when no isoFile exists", async () => {});
71
+ });
72
+
73
+ describe(".create()", () => {
74
+ test("throws if prepare() is not called", async () => {
75
+ const syncTrack = new SyncTrack(
76
+ await generateTrack(video!, 1),
77
+ video!.md5,
78
+ );
79
+ await expect(syncTrack.create()).rejects.toThrow();
80
+ });
81
+
82
+ test("isComplete() returns false when not complete", async () => {
83
+ server.use(
84
+ mockCreateIsobmffFile({
85
+ complete: true,
86
+ id: "123",
87
+ filename: "test.mp4",
88
+ fixture: video!,
89
+ }),
90
+ mockCreateIsobmffTrack({
91
+ complete: false,
92
+ id: "track-1",
93
+ fileId: "123",
94
+ fixture: video!,
95
+ }),
96
+ );
97
+
98
+ const syncTrack = new SyncTrack(
99
+ await generateTrack(video!, 1),
100
+ video!.md5,
101
+ );
102
+ await syncTrack.prepare();
103
+ await syncTrack.create();
104
+ expect(syncTrack.isComplete()).toBe(false);
105
+ });
106
+
107
+ test("isComplete() returns true when complete", async () => {
108
+ server.use(
109
+ mockCreateIsobmffFile({
110
+ complete: true,
111
+ id: "123",
112
+ filename: "test.mp4",
113
+ fixture: video!,
114
+ }),
115
+ mockCreateIsobmffTrack({
116
+ complete: true,
117
+ id: "track-1",
118
+ fileId: "123",
119
+ fixture: video!,
120
+ }),
121
+ );
122
+
123
+ const syncTrack = new SyncTrack(
124
+ await generateTrack(video!, 1),
125
+ video!.md5,
126
+ );
127
+ await syncTrack.prepare();
128
+ await syncTrack.create();
129
+ expect(syncTrack.isComplete()).toBe(true);
130
+ });
131
+ });
132
+
133
+ describe(".upload()", () => {
134
+ test("throws when not created", async () => {
135
+ const syncTrack = new SyncTrack(
136
+ await generateTrack(video!, 1),
137
+ video!.md5,
138
+ );
139
+ await expect(syncTrack.upload()).rejects.toThrow();
140
+ });
141
+ test("uploads track", async () => {
142
+ server.use(
143
+ mockCreateIsobmffFile({
144
+ complete: true,
145
+ id: "123",
146
+ filename: "test.mp4",
147
+ fixture: video!,
148
+ }),
149
+ mockCreateIsobmffTrack({
150
+ complete: true,
151
+ id: "track-1",
152
+ fileId: "123",
153
+ fixture: video!,
154
+ }),
155
+ mockGetIsobmffTrackUpload({
156
+ complete: true,
157
+ id: "track-1",
158
+ trackId: 1,
159
+ fileId: "123",
160
+ }),
161
+ );
162
+ const syncTrack = new SyncTrack(
163
+ await generateTrack(video!, 1),
164
+ video!.md5,
165
+ );
166
+ await syncTrack.prepare();
167
+ await syncTrack.create();
168
+ await expect(syncTrack.upload()).resolves.toBeUndefined();
169
+ });
170
+ });
171
+ describe(".markSynced()", () => {
172
+ test("throws when not created", async () => {
173
+ const syncTrack = new SyncTrack(
174
+ await generateTrack(video!, 1),
175
+ video!.md5,
176
+ );
177
+ await expect(syncTrack.markSynced()).rejects.toThrow();
178
+ });
179
+ test("marks synced", async () => {
180
+ server.use(
181
+ mockCreateIsobmffFile({
182
+ complete: true,
183
+ id: "123",
184
+ fixture: video!,
185
+ }),
186
+ mockCreateIsobmffTrack({
187
+ complete: true,
188
+ id: "track-1",
189
+ fileId: "123",
190
+ fixture: video!,
191
+ }),
192
+ );
193
+ const syncTrack = new SyncTrack(
194
+ await generateTrack(video!, 1),
195
+ video!.md5,
196
+ );
197
+ await syncTrack.prepare();
198
+ await syncTrack.create();
199
+ await syncTrack.markSynced();
200
+ await expect(syncTrack.syncStatus.isSynced()).resolves.toBe(true);
201
+ await expect(syncTrack.syncStatus.readInfo()).resolves.toEqual({
202
+ version: "1",
203
+ complete: true,
204
+ id: "123:track-1",
205
+ md5: video!.md5,
206
+ asset_id: `${video!.md5}:test.mp4`,
207
+ byte_size: 26434,
208
+ });
209
+
210
+ await expect(syncTrack.fileSyncStatus.readInfo()).resolves.toEqual({
211
+ version: "1",
212
+ complete: true,
213
+ id: "123",
214
+ md5: video!.md5,
215
+ asset_id: `${video!.md5}:test.mp4`,
216
+ byte_size: 26434,
217
+ });
218
+ });
219
+ });
220
+ },
221
+ );
222
+ });
@@ -0,0 +1,164 @@
1
+ import { createReadStream } from "node:fs";
2
+ import fs from "node:fs/promises";
3
+ import { dirname, join } from "node:path";
4
+
5
+ import type { z } from "zod";
6
+
7
+ import {
8
+ type CreateISOBMFFFileResult,
9
+ type CreateISOBMFFTrackPayload,
10
+ type CreateISOBMFFTrackResult,
11
+ createISOBMFFFile,
12
+ createISOBMFFTrack,
13
+ uploadISOBMFFTrack,
14
+ } from "@editframe/api";
15
+ import { Probe } from "@editframe/assets";
16
+
17
+ import { getClient } from "../../utils/index.ts";
18
+ import type { SubAssetSync } from "./SubAssetSync.ts";
19
+ import { SyncStatus } from "./SyncStatus.ts";
20
+
21
+ export class SyncTrack implements SubAssetSync<CreateISOBMFFTrackResult> {
22
+ icon = "📼";
23
+ label = "track";
24
+ syncStatus: SyncStatus = new SyncStatus(this.path);
25
+ fileSyncStatus: SyncStatus = new SyncStatus(
26
+ join(dirname(this.path), "isobmff"),
27
+ );
28
+ created: CreateISOBMFFTrackResult | null = null;
29
+
30
+ constructor(
31
+ public path: string,
32
+ public md5: string,
33
+ ) {}
34
+
35
+ private _isoFile: CreateISOBMFFFileResult | null = null;
36
+
37
+ get isoFile() {
38
+ if (this._isoFile) {
39
+ return this._isoFile;
40
+ }
41
+ throw new Error("ISOBMFF file not found. Call prepare() first.");
42
+ }
43
+
44
+ async byteSize() {
45
+ return (await fs.stat(this.path)).size;
46
+ }
47
+
48
+ private _probeResult: Probe | null = null;
49
+ get probeResult() {
50
+ if (this._probeResult) {
51
+ return this._probeResult;
52
+ }
53
+ throw new Error("Probe result not found. Call prepare() first.");
54
+ }
55
+
56
+ get track() {
57
+ const [track] = this.probeResult.streams;
58
+ if (track) {
59
+ return track;
60
+ }
61
+ throw new Error(`No track found in track: ${this.path}`);
62
+ }
63
+
64
+ async prepare() {
65
+ this._isoFile = await createISOBMFFFile(getClient(), {
66
+ md5: this.md5,
67
+ filename: this.path.replace(/\.track-[\d]+.mp4$/, ""),
68
+ });
69
+ this._probeResult = await Probe.probePath(this.path);
70
+ }
71
+
72
+ get trackId() {
73
+ const trackId = this.path.match(/track-([\d]+).mp4/)?.[1];
74
+ if (!trackId) {
75
+ throw new Error(`No track ID found for track: ${this.path}`);
76
+ }
77
+ return trackId;
78
+ }
79
+
80
+ get trackDuration() {
81
+ const track = this.track;
82
+ if (!track.duration) {
83
+ throw new Error(`No duration found in track: ${this.path}`);
84
+ }
85
+ return track.duration;
86
+ }
87
+
88
+ async validate() {
89
+ this.trackId;
90
+ this.isoFile;
91
+ this.trackDuration;
92
+ }
93
+
94
+ async create(): Promise<void> {
95
+ const track = this.track;
96
+ const isoFile = this.isoFile;
97
+
98
+ const createPayload: z.infer<typeof CreateISOBMFFTrackPayload> =
99
+ track.codec_type === "audio"
100
+ ? {
101
+ type: track.codec_type,
102
+ file_id: isoFile.id,
103
+ track_id: Number(this.trackId),
104
+ probe_info: track,
105
+ duration_ms: Math.round(this.trackDuration * 1000),
106
+ codec_name: track.codec_name,
107
+ byte_size: await this.byteSize(),
108
+ }
109
+ : {
110
+ type: track.codec_type,
111
+ file_id: isoFile.id,
112
+ track_id: Number(this.trackId),
113
+ probe_info: track,
114
+ duration_ms: Math.round(this.trackDuration * 1000),
115
+ codec_name: track.codec_name,
116
+ byte_size: await this.byteSize(),
117
+ };
118
+
119
+ this.created = await createISOBMFFTrack(getClient(), createPayload);
120
+ }
121
+ isComplete() {
122
+ return !!this.created?.complete;
123
+ }
124
+ async upload() {
125
+ if (!this.created) {
126
+ throw new Error(
127
+ "Track not created. Should have been prevented by .isComplete()",
128
+ );
129
+ }
130
+ return uploadISOBMFFTrack(
131
+ getClient(),
132
+ this.isoFile.id,
133
+ Number(this.trackId),
134
+ createReadStream(this.path),
135
+ this.created?.byte_size,
136
+ );
137
+ }
138
+ async markSynced() {
139
+ if (!this.created) {
140
+ throw new Error(
141
+ "Track not created. Should have been prevented by .isComplete()",
142
+ );
143
+ }
144
+ const byteSize = await this.byteSize();
145
+ await Promise.all([
146
+ this.syncStatus.markSynced({
147
+ version: "1",
148
+ complete: true,
149
+ id: `${this.created.file_id}:${this.created.track_id}`,
150
+ md5: this.md5,
151
+ asset_id: this.created.asset_id,
152
+ byte_size: byteSize,
153
+ }),
154
+ this.fileSyncStatus.markSynced({
155
+ version: "1",
156
+ complete: true,
157
+ id: this.created.file_id,
158
+ md5: this.md5,
159
+ asset_id: this.created.asset_id,
160
+ byte_size: byteSize,
161
+ }),
162
+ ]);
163
+ }
164
+ }
@@ -0,0 +1,134 @@
1
+ import { describe, expect, test, vi } from "vitest";
2
+ import type { SubAssetSync } from "./SubAssetSync.ts";
3
+ import type { SyncStatusInfo } from "./SyncStatus.ts";
4
+ import { doAssetSync } from "./doAssetSync.ts";
5
+
6
+ const collectAsyncGenerator = async (
7
+ generator: SubAssetSync<unknown>,
8
+ ): Promise<
9
+ {
10
+ status: "info" | "success";
11
+ message: string;
12
+ }[]
13
+ > => {
14
+ const result = [];
15
+ for await (const item of doAssetSync(generator)) {
16
+ result.push(item);
17
+ }
18
+ return result;
19
+ };
20
+
21
+ const buildFakeSync = (): SubAssetSync<unknown> => {
22
+ const fakeSyncStatus = {
23
+ isSynced: vi.fn().mockReturnValue(false),
24
+ } as unknown as SyncStatusInfo;
25
+ const fakeSync = {
26
+ label: "TEST_LABEL",
27
+ icon: "🧪",
28
+ path: "TEST_PATH",
29
+ readInfo: vi.fn().mockReturnValue(Promise.resolve(null)),
30
+ syncStatus: fakeSyncStatus,
31
+ markSynced: vi.fn().mockReturnValue(Promise.resolve()),
32
+ isComplete: vi.fn().mockReturnValue(false),
33
+ prepare: vi.fn().mockReturnValue(Promise.resolve()),
34
+ validate: vi.fn().mockReturnValue(Promise.resolve()),
35
+ create: vi.fn().mockReturnValue(Promise.resolve()),
36
+ upload: vi.fn().mockReturnValue(Promise.resolve()),
37
+ } as unknown as SubAssetSync<unknown>;
38
+ return fakeSync;
39
+ };
40
+
41
+ describe("doAssetSync", () => {
42
+ test("Succeeds if all steps are executed without error", async () => {
43
+ const fakeSync = buildFakeSync();
44
+ const messages = await collectAsyncGenerator(fakeSync);
45
+ expect(messages).toEqual([
46
+ {
47
+ status: "info",
48
+ message: "🧪 Syncing TEST_LABEL: TEST_PATH",
49
+ },
50
+ {
51
+ status: "success",
52
+ message: "Synced TEST_LABEL: TEST_PATH",
53
+ },
54
+ ]);
55
+ });
56
+
57
+ test("Succeeds if asset is already synced", async () => {
58
+ const fakeSync = buildFakeSync();
59
+ fakeSync.syncStatus.isSynced = vi.fn().mockReturnValue(true);
60
+ const messages = await collectAsyncGenerator(fakeSync);
61
+ expect(messages).toEqual([
62
+ {
63
+ status: "info",
64
+ message: "Sub-asset has already been synced: TEST_PATH",
65
+ },
66
+ ]);
67
+ });
68
+
69
+ test("Succeeds if asset is already uploaded", async () => {
70
+ const fakeSync = buildFakeSync();
71
+ fakeSync.isComplete = vi.fn().mockReturnValue(true);
72
+ const messages = await collectAsyncGenerator(fakeSync);
73
+ expect(messages).toEqual([
74
+ {
75
+ status: "info",
76
+ message: "🧪 Syncing TEST_LABEL: TEST_PATH",
77
+ },
78
+ {
79
+ status: "success",
80
+ message: "Synced TEST_LABEL: TEST_PATH",
81
+ },
82
+ ]);
83
+ });
84
+
85
+ test("Throws if prepare fails", async () => {
86
+ const fakeSync = buildFakeSync();
87
+ fakeSync.prepare = vi
88
+ .fn()
89
+ .mockReturnValue(Promise.reject(new Error("TEST_ERROR")));
90
+ await expect(collectAsyncGenerator(fakeSync)).rejects.toThrow(
91
+ "Error validating TEST_LABEL: TEST_ERROR",
92
+ );
93
+ });
94
+
95
+ test("Throws if validate fails", async () => {
96
+ const fakeSync = buildFakeSync();
97
+ fakeSync.validate = vi
98
+ .fn()
99
+ .mockReturnValue(Promise.reject(new Error("TEST_ERROR")));
100
+ await expect(collectAsyncGenerator(fakeSync)).rejects.toThrow(
101
+ "Error validating TEST_LABEL: TEST_ERROR",
102
+ );
103
+ });
104
+
105
+ test("Throws if create fails", async () => {
106
+ const fakeSync = buildFakeSync();
107
+ fakeSync.create = vi
108
+ .fn()
109
+ .mockReturnValue(Promise.reject(new Error("TEST_ERROR")));
110
+ await expect(collectAsyncGenerator(fakeSync)).rejects.toThrow(
111
+ "Error creating TEST_LABEL: TEST_ERROR",
112
+ );
113
+ });
114
+
115
+ test("Throws if upload fails", async () => {
116
+ const fakeSync = buildFakeSync();
117
+ fakeSync.upload = vi
118
+ .fn()
119
+ .mockReturnValue(Promise.reject(new Error("TEST_ERROR")));
120
+ await expect(collectAsyncGenerator(fakeSync)).rejects.toThrow(
121
+ "Error uploading TEST_LABEL: TEST_ERROR",
122
+ );
123
+ });
124
+
125
+ test("Throws if markSynced fails", async () => {
126
+ const fakeSync = buildFakeSync();
127
+ fakeSync.markSynced = vi
128
+ .fn()
129
+ .mockReturnValue(Promise.reject(new Error("TEST_ERROR")));
130
+ await expect(collectAsyncGenerator(fakeSync)).rejects.toThrow(
131
+ "Error marking TEST_LABEL as synced: TEST_ERROR",
132
+ );
133
+ });
134
+ });
@@ -0,0 +1,62 @@
1
+ import type { SubAssetSync } from "./SubAssetSync.ts";
2
+
3
+ export const doAssetSync = async function* (
4
+ assetSync: SubAssetSync<unknown>,
5
+ ): AsyncGenerator<{
6
+ status: "info" | "success";
7
+ message: string;
8
+ }> {
9
+ if (await assetSync.syncStatus.isSynced()) {
10
+ yield {
11
+ status: "info",
12
+ message: `Sub-asset has already been synced: ${assetSync.path}`,
13
+ };
14
+ return;
15
+ }
16
+
17
+ try {
18
+ await assetSync.prepare();
19
+ await assetSync.validate();
20
+ } catch (error) {
21
+ const message = error instanceof Error ? error.message : "Unknown error";
22
+
23
+ throw new Error(`Error validating ${assetSync.label}: ${message}`);
24
+ }
25
+
26
+ yield {
27
+ status: "info",
28
+ message: `${assetSync.icon} Syncing ${assetSync.label}: ${assetSync.path}`,
29
+ };
30
+
31
+ try {
32
+ await assetSync.create();
33
+ } catch (error) {
34
+ const message = error instanceof Error ? error.message : "Unknown error";
35
+
36
+ throw new Error(`Error creating ${assetSync.label}: ${message}`);
37
+ }
38
+
39
+ if (!assetSync.isComplete()) {
40
+ try {
41
+ await assetSync.upload();
42
+ } catch (error) {
43
+ const message = error instanceof Error ? error.message : "Unknown error";
44
+
45
+ throw new Error(`Error uploading ${assetSync.label}: ${message}`);
46
+ }
47
+ }
48
+
49
+ try {
50
+ await assetSync.markSynced();
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : "Unknown error";
53
+
54
+ throw new Error(`Error marking ${assetSync.label} as synced: ${message}`);
55
+ }
56
+
57
+ yield {
58
+ status: "success",
59
+ message: `Synced ${assetSync.label}: ${assetSync.path}`,
60
+ };
61
+ return;
62
+ };