@pipelab/shared 1.0.0-beta.1 → 1.0.0-beta.11

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.
@@ -8,6 +8,8 @@ import {
8
8
  number,
9
9
  array,
10
10
  GenericSchema,
11
+ optional,
12
+ looseObject,
11
13
  } from "valibot";
12
14
 
13
15
  export const createVersionSchema = <T extends GenericSchema<any, any>>(schema: T) => schema;
@@ -135,6 +137,72 @@ export const AppSettingsValidatorV7 = object({
135
137
  }),
136
138
  });
137
139
 
140
+ export const AppSettingsValidatorV8 = object({
141
+ theme: union([literal("light"), literal("dark")]),
142
+ version: literal("8.0.0"),
143
+ locale: union([
144
+ literal("en-US"),
145
+ literal("fr-FR"),
146
+ literal("pt-BR"),
147
+ literal("zh-CN"),
148
+ literal("es-ES"),
149
+ literal("de-DE"),
150
+ ]),
151
+ tours: object({
152
+ dashboard: object({
153
+ step: number(),
154
+ completed: boolean(),
155
+ }),
156
+ editor: object({
157
+ step: number(),
158
+ completed: boolean(),
159
+ }),
160
+ }),
161
+ autosave: boolean(),
162
+ agents: array(
163
+ object({
164
+ id: string(),
165
+ name: string(),
166
+ url: string(),
167
+ }),
168
+ ),
169
+ buildHistory: object({
170
+ retentionPolicy: object({
171
+ enabled: boolean(),
172
+ maxEntries: number(), // Maximum number of entries per pipeline
173
+ maxAge: number(), // Maximum age of entries in days
174
+ }),
175
+ }),
176
+ // Metadata list of plugins the user has enabled (official + community).
177
+ // No binaries are stored here — versions are resolved JIT at node-add time.
178
+ plugins: array(
179
+ object({
180
+ name: string(),
181
+ enabled: boolean(),
182
+ description: string(),
183
+ }),
184
+ ),
185
+ isInternalMigrationBannerClosed: optional(boolean(), false),
186
+ });
187
+
188
+ export const ConnectionValidator = looseObject({
189
+ id: string(),
190
+ pluginName: string(),
191
+ name: string(),
192
+ createdAt: string(),
193
+ isDefault: boolean(),
194
+ });
195
+
196
+ export const ConnectionsValidatorV1 = object({
197
+ version: literal("1.0.0"),
198
+ connections: array(ConnectionValidator),
199
+ });
200
+
201
+ export type Connection = InferInput<typeof ConnectionValidator>;
202
+ export type ConnectionsConfigV1 = InferInput<typeof ConnectionsValidatorV1>;
203
+ export type ConnectionsConfig = ConnectionsConfigV1;
204
+ export const ConnectionsValidator = ConnectionsValidatorV1;
205
+
138
206
  export type AppConfigV1 = InferInput<typeof AppSettingsValidatorV1>;
139
207
  export type AppConfigV2 = InferInput<typeof AppSettingsValidatorV2>;
140
208
  export type AppConfigV3 = InferInput<typeof AppSettingsValidatorV3>;
@@ -142,6 +210,7 @@ export type AppConfigV4 = InferInput<typeof AppSettingsValidatorV4>;
142
210
  export type AppConfigV5 = InferInput<typeof AppSettingsValidatorV5>;
143
211
  export type AppConfigV6 = InferInput<typeof AppSettingsValidatorV6>;
144
212
  export type AppConfigV7 = InferInput<typeof AppSettingsValidatorV7>;
213
+ export type AppConfigV8 = InferInput<typeof AppSettingsValidatorV8>;
145
214
 
146
- export type AppConfig = AppConfigV7;
147
- export const AppSettingsValidator = AppSettingsValidatorV7;
215
+ export type AppConfig = AppConfigV8;
216
+ export const AppSettingsValidator = AppSettingsValidatorV8;
package/src/graph.ts CHANGED
@@ -42,7 +42,7 @@ export const processGraph = async (options: {
42
42
  node: Block,
43
43
  params: Record<string, string>,
44
44
  steps: Steps,
45
- ) => Promise<End<"condition:execute"> | End<"action:execute">>;
45
+ ) => Promise<End<"action:execute">>;
46
46
  onNodeEnter: (node: Block) => void;
47
47
  onNodeExit: (node: Block) => void;
48
48
  abortSignal?: AbortSignal;
@@ -68,47 +68,7 @@ export const processGraph = async (options: {
68
68
  options.definitions,
69
69
  );
70
70
 
71
- /* if (rawNode.type === 'condition') {
72
- options.onNodeEnter(rawNode)
73
-
74
- const newParams = await makeResolvedParams({
75
- params: rawNode.params,
76
- variables: options.variables,
77
- steps: options.steps,
78
- context: options.context
79
- })
80
-
81
- const result = await options.onExecuteItem(node, newParams, options.steps) as End<'condition:execute'>
82
-
83
- if ('result' in result) {
84
- logger().error(result.result)
85
- options.onNodeExit(rawNode)
86
- throw new Error('Condition error')
87
- } else {
88
- const { value, outputs } = result
89
- if (!options.steps[rawNode.uid]) {
90
- options.steps[rawNode.uid] = {
91
- outputs: {}
92
- }
93
- }
94
- options.steps[rawNode.uid].outputs = outputs
95
-
96
- if (value === true) {
97
- await processGraph({
98
- graph: rawNode.branchTrue,
99
- ...options,
100
- abortSignal: options.abortSignal
101
- })
102
- } else {
103
- await processGraph({
104
- graph: rawNode.branchFalse,
105
- ...options,
106
- abortSignal: options.abortSignal
107
- })
108
- }
109
- }
110
- options.onNodeExit(rawNode)
111
- } else */ if (rawNode.type === "action") {
71
+ if (rawNode.type === "action") {
112
72
  if (rawNode.disabled === true) {
113
73
  console.warn(
114
74
  `Node ${rawNode.uid} (${rawNode.origin.pluginId}::${rawNode.origin.nodeId}) is disabled`,
@@ -163,34 +123,6 @@ export const processGraph = async (options: {
163
123
  options.steps[rawNode.uid].outputs = result.result.outputs;
164
124
  }
165
125
  options.onNodeExit(rawNode);
166
- } else if (rawNode.type === "loop") {
167
- options.onNodeEnter(rawNode);
168
-
169
- // const context = {}
170
-
171
- // const arrayToLoopOn = await evaluate(rawNode.params.value, context)
172
-
173
- // element is the value of the element at loopindex
174
- // let loopindex = 0
175
- // for (const _element of arrayToLoopOn) {
176
- // await processGraph(rawNode.children, definitions, variables, steps, {
177
- // ...context,
178
- // loopindex
179
- // })
180
-
181
- // loopindex += 1
182
- // }
183
-
184
- // continue after loop
185
-
186
- // TODO: process loop
187
- // const result = await api.execute('node:execute', {
188
- // nodeId: rawNode.origin.nodeId,
189
- // pluginId: rawNode.origin.pluginId,
190
- // params: rawNode.params
191
- // })
192
- // console.log('result', result)
193
- options.onNodeExit(rawNode);
194
126
  } else if (rawNode.type === "comment") {
195
127
  // pass
196
128
  } else if (rawNode.type === "event") {
@@ -15,7 +15,11 @@
15
15
  "storage": "Storage",
16
16
  "integrations": "Integrations",
17
17
  "advanced": "Advanced",
18
- "billing": "Billing"
18
+ "billing": "Billing",
19
+ "plugins": "Plugins",
20
+ "core-plugins": "Core-Plugins",
21
+ "community-plugins": "Community-Plugins",
22
+ "versions": "Versionen"
19
23
  },
20
24
  "clearTempFolders": "Automatically clean up temporary files",
21
25
  "clearTempFoldersDescription": "Wenn aktiviert, werden alle temporären Dateien, die während der Pipeline-Ausführung erstellt wurden, automatisch gelöscht, nachdem die Pipeline abgeschlossen ist. Dies hilft, Speicherplatz auf der Festplatte zu sparen, aber möglicherweise müssen Sie selbst Artefakte kopieren, um sie zu bewahren.",
@@ -17,7 +17,11 @@
17
17
  "storage": "Storage",
18
18
  "integrations": "Integrations",
19
19
  "advanced": "Advanced",
20
- "billing": "Billing"
20
+ "billing": "Billing",
21
+ "plugins": "Plugins",
22
+ "core-plugins": "Core Plugins",
23
+ "community-plugins": "Community Plugins",
24
+ "versions": "Versions"
21
25
  },
22
26
  "pipeline-cache-folder": "Pipeline Cache Folder:",
23
27
  "enter-or-browse-for-a-folder": "Enter or browse for a folder",
@@ -83,7 +87,14 @@
83
87
  "your-project-has-be-saved-successfully": "Your project has be saved successfully",
84
88
  "view-history": "History",
85
89
  "add-plugin": "Add plugin",
86
- "display-advanced-nodes": "Display advanced nodes"
90
+ "display-advanced-nodes": "Display advanced nodes",
91
+ "project-settings": "Pipeline Settings",
92
+ "jit-loading-title": "Installing Required Plugins",
93
+ "jit-loading-subtitle": "Please wait while Pipelab fetches and prepares the necessary nodes for this pipeline.",
94
+ "jit-loading-status": "Downloading and building plugin packages…",
95
+ "validation-failed": "Validation Failed",
96
+ "validation-plugin-disabled-or-missing": "Plugin \"{pluginId}\" is disabled or not installed (required by \"{nodeName}\").",
97
+ "validation-missing-param": "Block \"{nodeName}\": missing required parameter \"{paramName}\"."
87
98
  },
88
99
  "home": {
89
100
  "invalid-preset": "Invalid preset",
@@ -129,7 +140,11 @@
129
140
  "confirm-migration-message": "Are you sure you want to migrate this pipeline to internal storage? This will create a copy in the internal storage managed by Pipelab.",
130
141
  "migrate-pipeline": "Migrate Pipeline",
131
142
  "migration-success": "Pipeline migrated successfully",
132
- "migrate-to-internal": "Migrate to Internal"
143
+ "migrate-to-internal": "Migrate to Internal",
144
+ "only-internal-supported-notice": "Pipelab now stores pipelines internally. External pipeline files are deprecated. You can still import external files as internal pipelines.",
145
+ "import-pipeline": "Import Pipeline",
146
+ "import-success": "Pipeline imported successfully",
147
+ "failed-to-read-file": "Failed to read the selected file"
133
148
  },
134
149
  "scenarios": {
135
150
  "scenarios": "Scenarios"
@@ -15,7 +15,11 @@
15
15
  "storage": "Storage",
16
16
  "integrations": "Integrations",
17
17
  "advanced": "Advanced",
18
- "billing": "Billing"
18
+ "billing": "Billing",
19
+ "plugins": "Plugins",
20
+ "core-plugins": "Complementos principales",
21
+ "community-plugins": "Complementos de la comunidad",
22
+ "versions": "Versiones"
19
23
  },
20
24
  "clearTempFolders": "Automatically clean up temporary files",
21
25
  "clearTempFoldersDescription": "Cuando está habilitado, todos los archivos temporales creados durante la ejecución del pipeline se eliminarán automáticamente después de que el pipeline se complete. Esto ayuda a ahorrar espacio en el disco, pero puede que necesite copiar los artefactos usted mismo para preservarlos.",
@@ -15,7 +15,11 @@
15
15
  "storage": "Stockage",
16
16
  "integrations": "Intégrations",
17
17
  "advanced": "Avancé",
18
- "billing": "Facturation"
18
+ "billing": "Facturation",
19
+ "plugins": "Plugins",
20
+ "core-plugins": "Plugins de base",
21
+ "community-plugins": "Plugins de la communauté",
22
+ "versions": "Versions"
19
23
  },
20
24
  "clearTempFolders": "Nettoyer automatiquement les fichiers temporaires",
21
25
  "clearTempFoldersDescription": "Lorsque cette option est activée, tous les fichiers temporaires créés lors de l'exécution du pipeline seront automatiquement supprimés une fois le pipeline terminé. Cela permet d'économiser de l'espace disque, mais vous devrez peut-être copier vous-même des artefacts pour les préserver.",
@@ -15,7 +15,11 @@
15
15
  "storage": "Armazenagem",
16
16
  "integrations": "Integrações",
17
17
  "advanced": "Avançado",
18
- "billing": "Pagamento"
18
+ "billing": "Pagamento",
19
+ "plugins": "Plugins",
20
+ "core-plugins": "Plugins nativos",
21
+ "community-plugins": "Plugins da comunidade",
22
+ "versions": "Versões"
19
23
  },
20
24
  "clearTempFolders": "Automatically clean up temporary files",
21
25
  "clearTempFoldersDescription": "When enabled, all temporary files created during pipeline execution will be automatically deleted after the pipeline completes. This helps save disk space but you may need to copy artifacts yourself to preserve them.",
@@ -15,7 +15,11 @@
15
15
  "storage": "Storage",
16
16
  "integrations": "Integrations",
17
17
  "advanced": "Advanced",
18
- "billing": "Billing"
18
+ "billing": "Billing",
19
+ "plugins": "Plugins",
20
+ "core-plugins": "核心插件",
21
+ "community-plugins": "社区插件",
22
+ "versions": "版本"
19
23
  },
20
24
  "clearTempFolders": "Automatically clean up temporary files",
21
25
  "clearTempFoldersDescription": "When enabled, all temporary files created during pipeline execution will be automatically deleted after the pipeline completes. This helps save disk space but you may need to copy artifacts yourself to preserve them.",
package/src/index.ts CHANGED
@@ -6,6 +6,9 @@ import {
6
6
  defaultFileRepo as _defaultFileRepo,
7
7
  savedFileMigrator as _savedFileMigrator,
8
8
  configRegistry as _configRegistry,
9
+ normalizePipelineConfig as _normalizePipelineConfig,
10
+ connectionsMigrator as _connectionsMigrator,
11
+ defaultConnections as _defaultConnections,
9
12
  } from "./config/migrators";
10
13
 
11
14
  export const appSettingsMigrator = _appSettingsMigrator;
@@ -14,9 +17,24 @@ export const fileRepoMigrations = _fileRepoMigrations;
14
17
  export const defaultFileRepo = _defaultFileRepo;
15
18
  export const savedFileMigrator = _savedFileMigrator;
16
19
  export const configRegistry = _configRegistry;
20
+ export const normalizePipelineConfig = _normalizePipelineConfig;
21
+ export const connectionsMigrator = _connectionsMigrator;
22
+ export const defaultConnections = _defaultConnections;
17
23
 
18
24
  // 2. Types
19
25
  export type { Migrator } from "./config/migrators";
26
+ export type { IpcDefinition } from "./apis";
27
+ export type { IpcDefinition as RendererIpcDefinition } from "./ipc.types";
28
+ export type { RequestId } from "./apis";
29
+ export type { RequestId as RendererRequestId } from "./ipc.types";
30
+ export type { NodeId } from "./model";
31
+ export type { FileRepo, FileRepoV1, FileRepoV2 } from "./config/projects-definition";
32
+ export {
33
+ FileRepoValidator,
34
+ FileRepoValidatorV1,
35
+ FileRepoValidatorV2,
36
+ FileRepoProjectValidatorV2,
37
+ } from "./config/projects-definition";
20
38
 
21
39
  // 3. Core Library exports (Systematic restoration)
22
40
  export * from "./apis";
@@ -45,6 +63,7 @@ export * from "./websocket.types";
45
63
  // 4. Configuration Sub-packages
46
64
  export * from "./config/projects-definition"; // <-- RE-ADDED
47
65
  export * from "./config/settings-definition"; // <-- RE-ADDED
66
+ export * from "./config/connections-definition";
48
67
  export * from "./config/projects-types";
49
68
 
50
69
  // NOTE: We avoid "export * from './config'" to prevent nested re-export circles.
package/src/ipc.types.ts CHANGED
@@ -43,6 +43,8 @@ export type IpcDefinition = {
43
43
  // input
44
44
  {
45
45
  status: UpdateStatus;
46
+ downloadUrl?: string;
47
+ version?: string;
46
48
  },
47
49
  EndEvent<void>,
48
50
  ];
package/src/model.test.ts CHANGED
@@ -1,5 +1,13 @@
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
+ fileRepoMigrations,
8
+ connectionsMigrator,
9
+ } from "./config/migrators";
10
+ import { AppConfigV6, AppConfigV8 } from "./config.schema";
3
11
 
4
12
  describe("model", () => {
5
13
  it("should migrate 1.0.0 to 2.0.0", async () => {
@@ -211,4 +219,308 @@ describe("model", () => {
211
219
  version: "3.0.0",
212
220
  } satisfies SavedFileV3);
213
221
  });
222
+
223
+ it("should migrate 3.0.0 to 4.0.0", async () => {
224
+ const v3: SavedFileV3 = {
225
+ version: "3.0.0",
226
+ canvas: {
227
+ blocks: [],
228
+ triggers: [],
229
+ },
230
+ description: "desc",
231
+ name: "name",
232
+ variables: [],
233
+ };
234
+
235
+ const v4 = await savedFileMigrator.migrate(v3, {
236
+ debug: true,
237
+ target: "4.0.0",
238
+ });
239
+
240
+ expect(v4).toStrictEqual({
241
+ version: "4.0.0",
242
+ type: "default",
243
+ canvas: {
244
+ blocks: [],
245
+ triggers: [],
246
+ },
247
+ description: "desc",
248
+ name: "name",
249
+ variables: [],
250
+ } satisfies SavedFileV4);
251
+ });
252
+
253
+ it("should migrate 4.0.0 to 5.0.0", async () => {
254
+ const v4: SavedFileV4 = {
255
+ version: "4.0.0",
256
+ type: "default",
257
+ canvas: {
258
+ blocks: [
259
+ {
260
+ uid: "b1",
261
+ type: "action",
262
+ origin: {
263
+ pluginId: "electron",
264
+ nodeId: "open",
265
+ },
266
+ params: {},
267
+ },
268
+ {
269
+ uid: "b2",
270
+ type: "action",
271
+ origin: {
272
+ pluginId: "dicord",
273
+ nodeId: "send",
274
+ },
275
+ params: {},
276
+ },
277
+ ],
278
+ triggers: [],
279
+ },
280
+ description: "desc",
281
+ name: "name",
282
+ variables: [],
283
+ plugins: {
284
+ discord: "1.0.0",
285
+ },
286
+ };
287
+
288
+ const v5 = await savedFileMigrator.migrate(v4, {
289
+ debug: true,
290
+ target: "5.0.0",
291
+ });
292
+
293
+ expect(v5).toStrictEqual({
294
+ version: "5.0.0",
295
+ canvas: {
296
+ blocks: [
297
+ {
298
+ uid: "b1",
299
+ type: "action",
300
+ origin: {
301
+ pluginId: "@pipelab/plugin-electron",
302
+ nodeId: "open",
303
+ // "electron" wasn't in the old plugins map, so version falls back to "latest"
304
+ version: "latest",
305
+ },
306
+ params: {},
307
+ },
308
+ {
309
+ uid: "b2",
310
+ type: "action",
311
+ origin: {
312
+ pluginId: "@pipelab/plugin-discord",
313
+ nodeId: "send",
314
+ // "dicord" mapped to "@pipelab/plugin-discord" which had version "1.0.0" in the old map
315
+ version: "1.0.0",
316
+ },
317
+ params: {},
318
+ },
319
+ ],
320
+ triggers: [],
321
+ },
322
+ description: "desc",
323
+ name: "name",
324
+ variables: [],
325
+ // plugins top-level map is dropped in V5 for default pipelines
326
+ } satisfies SavedFileV5);
327
+ });
328
+
329
+ it("should migrate AppConfigV6 to AppConfigV8", async () => {
330
+ const v6: AppConfigV6 = {
331
+ version: "6.0.0",
332
+ theme: "dark",
333
+ cacheFolder: "/some/path",
334
+ clearTemporaryFoldersOnPipelineEnd: true,
335
+ locale: "fr-FR",
336
+ tours: {
337
+ dashboard: { step: 1, completed: true },
338
+ editor: { step: 2, completed: false },
339
+ },
340
+ autosave: false,
341
+ };
342
+
343
+ const v8 = await appSettingsMigrator.migrate(v6, { target: "8.0.0" });
344
+
345
+ expect(v8).toStrictEqual({
346
+ version: "8.0.0",
347
+ theme: "dark",
348
+ locale: "fr-FR",
349
+ tours: {
350
+ dashboard: { step: 1, completed: true },
351
+ editor: { step: 2, completed: false },
352
+ },
353
+ autosave: false,
354
+ agents: [],
355
+ buildHistory: {
356
+ retentionPolicy: {
357
+ enabled: false,
358
+ maxEntries: 50,
359
+ maxAge: 30,
360
+ },
361
+ },
362
+ plugins: expect.any(Array),
363
+ isInternalMigrationBannerClosed: false,
364
+ });
365
+ });
366
+
367
+ describe("normalizePipelineConfig", () => {
368
+ it("should normalize typo and legacy plugin IDs in block origins", () => {
369
+ const config = {
370
+ version: "5.0.0",
371
+ type: "default",
372
+ canvas: {
373
+ blocks: [
374
+ {
375
+ uid: "b1",
376
+ type: "action",
377
+ origin: {
378
+ pluginId: "dicord",
379
+ nodeId: "send",
380
+ },
381
+ },
382
+ ],
383
+ },
384
+ };
385
+
386
+ const changed = normalizePipelineConfig(config);
387
+ expect(changed).toBe(true);
388
+ expect(config.canvas.blocks[0].origin.pluginId).toBe("@pipelab/plugin-discord");
389
+ });
390
+ });
391
+
392
+ describe("fileRepoMigrations", () => {
393
+ it("should migrate FileRepoV1 (data record) to FileRepoV2 (pipelines array)", async () => {
394
+ const v1 = {
395
+ version: "1.0.0" as const,
396
+ data: {
397
+ "pipeline-1": {
398
+ project: "main",
399
+ type: "internal" as const,
400
+ configName: "pipeline-1",
401
+ lastModified: "2026-06-04",
402
+ },
403
+ "pipeline-2": {
404
+ project: "main",
405
+ type: "external" as const,
406
+ path: "/path/to/pipeline-2.json",
407
+ lastModified: "2026-06-04",
408
+ summary: {
409
+ plugins: ["steam"],
410
+ name: "Test Pipeline 2",
411
+ description: "Legacy external pipeline",
412
+ },
413
+ },
414
+ },
415
+ };
416
+
417
+ const v2 = await fileRepoMigrations.migrate(v1, { target: "2.0.0" });
418
+
419
+ expect(v2).toStrictEqual({
420
+ version: "2.0.0",
421
+ data: v1.data,
422
+ projects: [
423
+ {
424
+ id: "main",
425
+ name: "Default project",
426
+ description: "The initial default project",
427
+ },
428
+ ],
429
+ pipelines: [
430
+ {
431
+ id: "pipeline-1",
432
+ project: "main",
433
+ type: "internal",
434
+ configName: "pipeline-1",
435
+ lastModified: "2026-06-04",
436
+ },
437
+ {
438
+ id: "pipeline-2",
439
+ project: "main",
440
+ type: "external",
441
+ path: "/path/to/pipeline-2.json",
442
+ lastModified: "2026-06-04",
443
+ summary: {
444
+ plugins: ["steam"],
445
+ name: "Test Pipeline 2",
446
+ description: "Legacy external pipeline",
447
+ },
448
+ },
449
+ ],
450
+ });
451
+ });
452
+
453
+ it("should fallback to default value for corrupted config", async () => {
454
+ const corrupted: any = {
455
+ version: "1.0.0",
456
+ data: null,
457
+ };
458
+
459
+ const result = await fileRepoMigrations.migrate(corrupted, { target: "2.0.0" });
460
+ expect(result.version).toBe("2.0.0");
461
+ expect(result.projects).toHaveLength(1);
462
+ expect(result.pipelines).toEqual([]);
463
+ });
464
+ });
465
+
466
+ describe("connectionsMigrator", () => {
467
+ it("should initialize default connections", async () => {
468
+ const result = await connectionsMigrator.migrate(undefined, { target: "1.0.0" });
469
+ expect(result).toStrictEqual({
470
+ version: "1.0.0",
471
+ connections: [],
472
+ });
473
+ });
474
+ });
475
+
476
+ describe("savedFileMigrator - edge cases", () => {
477
+ it("should normalize unmapped legacy plugin names and typos during migration to 5.0.0", async () => {
478
+ const v4: SavedFileV4 = {
479
+ version: "4.0.0",
480
+ type: "default",
481
+ canvas: {
482
+ blocks: [
483
+ {
484
+ uid: "b1",
485
+ type: "action",
486
+ origin: {
487
+ pluginId: "filesystem",
488
+ nodeId: "copy",
489
+ },
490
+ params: {},
491
+ },
492
+ {
493
+ uid: "b2",
494
+ type: "action",
495
+ origin: {
496
+ pluginId: "dicord",
497
+ nodeId: "send",
498
+ },
499
+ params: {},
500
+ },
501
+ {
502
+ uid: "b3",
503
+ type: "action",
504
+ origin: {
505
+ pluginId: "custom-cool",
506
+ nodeId: "run",
507
+ },
508
+ params: {},
509
+ },
510
+ ],
511
+ triggers: [],
512
+ },
513
+ description: "Edge case plugin names test",
514
+ name: "Edge Case",
515
+ variables: [],
516
+ plugins: {},
517
+ };
518
+
519
+ const v5 = await savedFileMigrator.migrate(v4, { target: "5.0.0" });
520
+
521
+ expect(v5.canvas.blocks[0].origin.pluginId).toBe("@pipelab/plugin-filesystem");
522
+ expect(v5.canvas.blocks[1].origin.pluginId).toBe("@pipelab/plugin-discord");
523
+ expect(v5.canvas.blocks[2].origin.pluginId).toBe("custom-cool");
524
+ });
525
+ });
214
526
  });