@pipelab/shared 1.0.0-beta.6 → 1.0.0-beta.8

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/src/model.test.ts CHANGED
@@ -1,5 +1,11 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { savedFileMigrator, SavedFileV1, SavedFileV2, SavedFileV3 } from "./model";
2
+ import { SavedFileV1, SavedFileV2, SavedFileV3, SavedFileV4, SavedFileV5 } from "./model";
3
+ import {
4
+ savedFileMigrator,
5
+ normalizePipelineConfig,
6
+ appSettingsMigrator,
7
+ } from "./config/migrators";
8
+ import { AppConfigV6, AppConfigV8 } from "./config.schema";
3
9
 
4
10
  describe("model", () => {
5
11
  it("should migrate 1.0.0 to 2.0.0", async () => {
@@ -211,4 +217,173 @@ describe("model", () => {
211
217
  version: "3.0.0",
212
218
  } satisfies SavedFileV3);
213
219
  });
220
+
221
+ it("should migrate 3.0.0 to 4.0.0", async () => {
222
+ const v3: SavedFileV3 = {
223
+ version: "3.0.0",
224
+ canvas: {
225
+ blocks: [],
226
+ triggers: [],
227
+ },
228
+ description: "desc",
229
+ name: "name",
230
+ variables: [],
231
+ };
232
+
233
+ const v4 = await savedFileMigrator.migrate(v3, {
234
+ debug: true,
235
+ target: "4.0.0",
236
+ });
237
+
238
+ expect(v4).toStrictEqual({
239
+ version: "4.0.0",
240
+ type: "default",
241
+ canvas: {
242
+ blocks: [],
243
+ triggers: [],
244
+ },
245
+ description: "desc",
246
+ name: "name",
247
+ variables: [],
248
+ } satisfies SavedFileV4);
249
+ });
250
+
251
+ it("should migrate 4.0.0 to 5.0.0", async () => {
252
+ const v4: SavedFileV4 = {
253
+ version: "4.0.0",
254
+ type: "default",
255
+ canvas: {
256
+ blocks: [
257
+ {
258
+ uid: "b1",
259
+ type: "action",
260
+ origin: {
261
+ pluginId: "electron",
262
+ nodeId: "open",
263
+ },
264
+ params: {},
265
+ },
266
+ {
267
+ uid: "b2",
268
+ type: "action",
269
+ origin: {
270
+ pluginId: "dicord",
271
+ nodeId: "send",
272
+ },
273
+ params: {},
274
+ },
275
+ ],
276
+ triggers: [],
277
+ },
278
+ description: "desc",
279
+ name: "name",
280
+ variables: [],
281
+ plugins: {
282
+ discord: "1.0.0",
283
+ },
284
+ };
285
+
286
+ const v5 = await savedFileMigrator.migrate(v4, {
287
+ debug: true,
288
+ target: "5.0.0",
289
+ });
290
+
291
+ expect(v5).toStrictEqual({
292
+ version: "5.0.0",
293
+ canvas: {
294
+ blocks: [
295
+ {
296
+ uid: "b1",
297
+ type: "action",
298
+ origin: {
299
+ pluginId: "@pipelab/plugin-electron",
300
+ nodeId: "open",
301
+ // "electron" wasn't in the old plugins map, so version falls back to "latest"
302
+ version: "latest",
303
+ },
304
+ params: {},
305
+ },
306
+ {
307
+ uid: "b2",
308
+ type: "action",
309
+ origin: {
310
+ pluginId: "@pipelab/plugin-discord",
311
+ nodeId: "send",
312
+ // "dicord" mapped to "@pipelab/plugin-discord" which had version "1.0.0" in the old map
313
+ version: "1.0.0",
314
+ },
315
+ params: {},
316
+ },
317
+ ],
318
+ triggers: [],
319
+ },
320
+ description: "desc",
321
+ name: "name",
322
+ variables: [],
323
+ // plugins top-level map is dropped in V5 for default pipelines
324
+ } satisfies SavedFileV5);
325
+ });
326
+
327
+ it("should migrate AppConfigV6 to AppConfigV8", async () => {
328
+ const v6: AppConfigV6 = {
329
+ version: "6.0.0",
330
+ theme: "dark",
331
+ cacheFolder: "/some/path",
332
+ clearTemporaryFoldersOnPipelineEnd: true,
333
+ locale: "fr-FR",
334
+ tours: {
335
+ dashboard: { step: 1, completed: true },
336
+ editor: { step: 2, completed: false },
337
+ },
338
+ autosave: false,
339
+ };
340
+
341
+ const v8 = await appSettingsMigrator.migrate(v6, { target: "8.0.0" });
342
+
343
+ expect(v8).toStrictEqual({
344
+ version: "8.0.0",
345
+ theme: "dark",
346
+ locale: "fr-FR",
347
+ tours: {
348
+ dashboard: { step: 1, completed: true },
349
+ editor: { step: 2, completed: false },
350
+ },
351
+ autosave: false,
352
+ agents: [],
353
+ buildHistory: {
354
+ retentionPolicy: {
355
+ enabled: false,
356
+ maxEntries: 50,
357
+ maxAge: 30,
358
+ },
359
+ },
360
+ plugins: expect.any(Array),
361
+ isInternalMigrationBannerClosed: false,
362
+ });
363
+ });
364
+
365
+ describe("normalizePipelineConfig", () => {
366
+ it("should normalize typo and legacy plugin IDs in block origins", () => {
367
+ const config = {
368
+ version: "5.0.0",
369
+ type: "default",
370
+ canvas: {
371
+ blocks: [
372
+ {
373
+ uid: "b1",
374
+ type: "action",
375
+ origin: {
376
+ pluginId: "dicord",
377
+ nodeId: "send",
378
+ },
379
+ },
380
+ ],
381
+ },
382
+ };
383
+
384
+ const changed = normalizePipelineConfig(config);
385
+ expect(changed).toBe(true);
386
+ expect(config.canvas.blocks[0].origin.pluginId).toBe("@pipelab/plugin-discord");
387
+ });
388
+ });
214
389
  });
package/src/model.ts CHANGED
@@ -32,6 +32,10 @@ export type Position = {
32
32
  export const OriginValidator = object({
33
33
  pluginId: string(),
34
34
  nodeId: string(),
35
+ version: pipe(
36
+ optional(string()),
37
+ description('Pinned version of the plugin for this block. Falls back to "latest" when absent.'),
38
+ ),
35
39
  });
36
40
 
37
41
  export type Origin = InferOutput<typeof OriginValidator>;
@@ -61,39 +65,6 @@ const BlockActionValidatorV3 = object({
61
65
  origin: OriginValidator,
62
66
  });
63
67
 
64
- export type BlockCondition = {
65
- type: "condition";
66
- uid: string;
67
- origin: Origin;
68
- params: Record<string, any>;
69
- branchTrue: Array<Block>;
70
- branchFalse: Array<Block>;
71
- };
72
-
73
- const BlockConditionValidator: GenericSchema<BlockCondition> = object({
74
- type: literal("condition"),
75
- uid: string(),
76
- origin: OriginValidator,
77
- params: record(string(), any()),
78
- branchTrue: lazy(() => array(BlockValidator)),
79
- branchFalse: lazy(() => array(BlockValidator)),
80
- });
81
-
82
- export type BlockLoop = {
83
- type: "loop";
84
- uid: string;
85
- origin: Origin;
86
- params: Record<string, any>;
87
- children: Array<Block>;
88
- };
89
- const BlockLoopValidator: GenericSchema<BlockLoop> = object({
90
- type: literal("loop"),
91
- uid: string(),
92
- origin: OriginValidator,
93
- params: record(string(), any()),
94
- children: lazy(() => array(BlockValidator)),
95
- });
96
-
97
68
  const BlockEventValidator = object({
98
69
  type: literal("event"),
99
70
  uid: string(),
@@ -180,20 +151,22 @@ export const SavedFileValidatorV3 = object({
180
151
  variables: array(VariableValidatorV1),
181
152
  });
182
153
 
183
- export const SavedFileDefaultValidator = object({
154
+ export const SavedFileDefaultValidatorV4 = object({
184
155
  version: literal("4.0.0"),
185
156
  type: literal("default"),
186
157
  name: string(),
187
158
  description: string(),
159
+ plugins: optional(record(string(), string())),
188
160
  canvas: CanvasValidatorV3,
189
161
  variables: array(VariableValidatorV1),
190
162
  });
191
163
 
192
- export const SavedFileSimpleValidator = object({
164
+ export const SavedFileSimpleValidatorV4 = object({
193
165
  version: literal("4.0.0"),
194
166
  type: literal("simple"),
195
167
  name: string(),
196
168
  description: string(),
169
+ plugins: optional(record(string(), string())),
197
170
  source: object({
198
171
  type: union([literal("c3-html"), literal("c3-nwjs"), literal("godot"), literal("html")]),
199
172
  path: string(),
@@ -208,19 +181,53 @@ export const SavedFileSimpleValidator = object({
208
181
  }),
209
182
  });
210
183
 
211
- export type SavedFileDefault = InferOutput<typeof SavedFileDefaultValidator>;
212
- export type SavedFileSimple = InferOutput<typeof SavedFileSimpleValidator>;
213
- export const SavedFileValidatorV4 = union([SavedFileDefaultValidator, SavedFileSimpleValidator]);
184
+ export const SavedFileDefaultValidatorV5 = object({
185
+ version: literal("5.0.0"),
186
+ name: string(),
187
+ description: string(),
188
+ canvas: CanvasValidatorV3,
189
+ variables: array(VariableValidatorV1),
190
+ });
191
+
192
+ export const SavedFileSimpleValidatorV5 = object({
193
+ version: literal("5.0.0"),
194
+ type: literal("simple"),
195
+ name: string(),
196
+ description: string(),
197
+ plugins: record(string(), string()),
198
+ source: object({
199
+ type: union([literal("c3-html"), literal("c3-nwjs"), literal("godot"), literal("html")]),
200
+ path: string(),
201
+ }),
202
+ packaging: object({
203
+ enabled: boolean(),
204
+ }),
205
+ publishing: object({
206
+ steam: object({ enabled: boolean(), appId: optional(string()) }),
207
+ itch: object({ enabled: boolean(), project: optional(string()) }),
208
+ poki: object({ enabled: boolean(), gameId: optional(string()) }),
209
+ }),
210
+ });
211
+
212
+ export type SavedFileDefault = InferOutput<typeof SavedFileDefaultValidatorV5>;
213
+ export type SavedFileSimple = InferOutput<typeof SavedFileSimpleValidatorV5>;
214
+
215
+ export const SavedFileValidatorV4 = union([
216
+ SavedFileDefaultValidatorV4,
217
+ SavedFileSimpleValidatorV4,
218
+ ]);
219
+ export const SavedFileValidatorV5 = SavedFileDefaultValidatorV5;
214
220
 
215
221
  export type SavedFileV1 = InferOutput<typeof SavedFileValidatorV1>;
216
222
  export type SavedFileV2 = InferOutput<typeof SavedFileValidatorV2>;
217
223
  export type SavedFileV3 = InferOutput<typeof SavedFileValidatorV3>;
218
224
  export type SavedFileV4 = InferOutput<typeof SavedFileValidatorV4>;
219
- export type SavedFile = SavedFileV4;
220
- export const SavedFileValidator = SavedFileValidatorV4;
225
+ export type SavedFileV5 = InferOutput<typeof SavedFileValidatorV5>;
226
+ export type SavedFile = SavedFileV5;
227
+ export const SavedFileValidator = SavedFileValidatorV5;
221
228
 
222
229
  export type Preset = SavedFile;
223
- export type PresetResult = { data: SavedFile; hightlight?: boolean; disabled?: boolean };
230
+ export type PresetResult = { data: Preset; hightlight?: boolean; disabled?: boolean };
224
231
  export type PresetFn = () => Promise<PresetResult>;
225
232
 
226
233
  export type Steps = Record<
@@ -230,4 +237,4 @@ export type Steps = Record<
230
237
  }
231
238
  >;
232
239
 
233
- export type EnhancedFile<T extends SavedFile = SavedFile> = WithId<SaveLocation> & { content: T };
240
+ export type EnhancedFile<T = SavedFile> = WithId<SaveLocation> & { content: T };
@@ -154,10 +154,8 @@ export type IconType =
154
154
  icon: string;
155
155
  };
156
156
  export interface PluginDefinition {
157
- id: string;
158
- name: string;
159
- icon: IconType;
160
- description: string;
157
+ packageName?: string;
158
+ version?: string;
161
159
  }
162
160
 
163
161
  export type RendererNodeDefinition = {
@@ -165,6 +163,13 @@ export type RendererNodeDefinition = {
165
163
  };
166
164
 
167
165
  export interface RendererPluginDefinition extends PluginDefinition {
166
+ id: string;
167
+ name: string;
168
+ icon: IconType;
169
+ description: string;
170
+ isOfficial: boolean;
171
+ packageName: string;
172
+ version: string;
168
173
  nodes: Array<RendererNodeDefinition>;
169
174
  }
170
175
 
@@ -203,10 +208,7 @@ export type SetOutputActionFn<T extends Action> = (
203
208
  key: keyof T["outputs"],
204
209
  value: T["outputs"][typeof key]["value"],
205
210
  ) => void;
206
- export type SetOutputLoopFn<T extends Loop> = (
207
- key: keyof T["outputs"],
208
- value: T["outputs"][typeof key]["value"],
209
- ) => void;
211
+
210
212
  export type SetOutputExpressionFn<T extends Expression> = (
211
213
  key: keyof T["outputs"],
212
214
  value: T["outputs"][typeof key]["value"],
@@ -244,12 +246,6 @@ export type ExtractInputsFromAction<ACTION extends Action> = {
244
246
  [index in keyof ACTION["params"]]: ACTION["params"][index]["value"];
245
247
  };
246
248
 
247
- export type ExtractInputsFromCondition<CONDITION extends Condition> = {
248
- [index in keyof CONDITION["params"]]: CONDITION["params"][index]["value"];
249
- };
250
- export type ExtractInputsFromLoop<LOOP extends Loop> = {
251
- [index in keyof LOOP["params"]]: LOOP["params"][index]["value"];
252
- };
253
249
  export type ExtractInputsFromEvent<EVENT extends Event> = {
254
250
  [index in keyof EVENT["params"]]: EVENT["params"][index]["value"];
255
251
  };
@@ -257,32 +253,6 @@ export type ExtractInputsFromExpression<EXPRESSION extends Expression> = {
257
253
  [index in keyof EXPRESSION["params"]]: EXPRESSION["params"][index]["value"];
258
254
  };
259
255
 
260
- export interface Condition extends BaseNode {
261
- id: string;
262
- type: "condition";
263
- version?: number;
264
- displayString: string;
265
- icon: string;
266
- name: string;
267
- description: string;
268
- params: InputsDefinition;
269
- meta?: Meta;
270
- platforms?: NodeJS.Platform[];
271
- }
272
-
273
- export interface Loop extends BaseNode {
274
- id: string;
275
- type: "loop";
276
- version?: number;
277
- displayString: string;
278
- icon: string;
279
- name: string;
280
- description: string;
281
- params: InputsDefinition;
282
- meta?: Meta;
283
- outputs: OutputsDefinition;
284
- }
285
-
286
256
  export interface Expression extends BaseNode {
287
257
  id: string;
288
258
  type: "expression";
@@ -309,7 +279,7 @@ export interface Event extends BaseNode {
309
279
  platforms?: NodeJS.Platform[];
310
280
  }
311
281
 
312
- export type PipelabNode = Event | Condition | Expression | Action | Loop;
282
+ export type PipelabNode = Event | Expression | Action;
313
283
 
314
284
  export const createDefinition = <T extends MainPluginDefinition>(definition: T) => {
315
285
  return definition satisfies T;
@@ -450,20 +420,6 @@ export const createExpression = <T extends Omit<Expression, "type">>(expression:
450
420
  } satisfies Expression;
451
421
  };
452
422
 
453
- export const createCondition = <T extends Omit<Condition, "type">>(condition: T) => {
454
- return {
455
- ...condition,
456
- type: "condition",
457
- } satisfies Condition;
458
- };
459
-
460
- export const createLoop = <T extends Omit<Loop, "type">>(loop: T) => {
461
- return {
462
- ...loop,
463
- type: "loop",
464
- } satisfies Loop;
465
- };
466
-
467
423
  export const createEvent = <T extends Omit<Event, "type">>(event: T) => {
468
424
  return {
469
425
  ...event,
package/src/plugins.ts CHANGED
@@ -9,7 +9,16 @@ export const usePlugins = () => {
9
9
  const load = () => {};
10
10
 
11
11
  const registerPlugins = (newPlugins: Plugin[]) => {
12
- plugins.value.push(...newPlugins);
12
+ const current = [...plugins.value];
13
+ for (const np of newPlugins) {
14
+ const idx = current.findIndex((p) => p.id === np.id);
15
+ if (idx !== -1) {
16
+ current[idx] = np;
17
+ } else {
18
+ current.push(np);
19
+ }
20
+ }
21
+ plugins.value = current;
13
22
  };
14
23
 
15
24
  return {
package/src/utils.ts CHANGED
@@ -1,3 +1,29 @@
1
1
  export const foo = "bar";
2
2
 
3
3
  export type WithId<T> = T extends string | number ? never : T & { id: string };
4
+
5
+ export const transformUrl = (url: string | undefined | null): string => {
6
+ if (url && typeof url === "string") {
7
+ const getHost = (): string => {
8
+ if (typeof window !== "undefined") {
9
+ const isDev = window.location.port === "5173";
10
+ if (isDev) {
11
+ return `http://${window.location.hostname}:33753`;
12
+ } else {
13
+ return `${window.location.protocol}//${window.location.host}`;
14
+ }
15
+ }
16
+ return "http://localhost:33753";
17
+ };
18
+
19
+ if (url.startsWith("file://")) {
20
+ const filePath = url.substring("file://".length);
21
+ return `${getHost()}/media-file/${encodeURIComponent(filePath)}`;
22
+ }
23
+ if (url.startsWith("media://")) {
24
+ const filePath = url.replace(/^media:\/\/+/, "/");
25
+ return `${getHost()}/media-file/${encodeURIComponent(filePath)}`;
26
+ }
27
+ }
28
+ return url || "";
29
+ };