sdn-flow 0.2.0

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 (69) hide show
  1. package/.claude/SKILLS.md +7 -0
  2. package/.claude/skills/sdn-plugin-abi-compliance/SKILL.md +56 -0
  3. package/.claude/todo/001-js-host-startup-and-deno.md +85 -0
  4. package/LICENSE +21 -0
  5. package/README.md +223 -0
  6. package/bin/sdn-flow-host.js +169 -0
  7. package/docs/.nojekyll +0 -0
  8. package/docs/ARCHITECTURE.md +200 -0
  9. package/docs/HOST_CAPABILITY_MODEL.md +317 -0
  10. package/docs/PLUGIN_ARCHITECTURE.md +145 -0
  11. package/docs/PLUGIN_COMPATIBILITY.md +61 -0
  12. package/docs/PLUGIN_COMPLIANCE_CHECKS.md +82 -0
  13. package/docs/PLUGIN_MANIFEST.md +94 -0
  14. package/docs/css/style.css +465 -0
  15. package/docs/index.html +218 -0
  16. package/docs/js/app.mjs +751 -0
  17. package/docs/js/editor-panel.mjs +203 -0
  18. package/docs/js/flow-canvas.mjs +515 -0
  19. package/docs/js/flow-model.mjs +391 -0
  20. package/docs/js/workers/emception.worker.js +146 -0
  21. package/docs/js/workers/pyodide.worker.js +134 -0
  22. package/native/flow_source_generator.cpp +1958 -0
  23. package/package.json +67 -0
  24. package/schemas/FlowRuntimeAbi.fbs +91 -0
  25. package/src/auth/canonicalize.js +5 -0
  26. package/src/auth/index.js +11 -0
  27. package/src/auth/permissions.js +8 -0
  28. package/src/compiler/CppFlowSourceGenerator.js +475 -0
  29. package/src/compiler/EmceptionCompilerAdapter.js +244 -0
  30. package/src/compiler/SignedArtifactCatalog.js +152 -0
  31. package/src/compiler/index.js +8 -0
  32. package/src/compiler/nativeFlowSourceGeneratorTool.js +144 -0
  33. package/src/compliance/index.js +13 -0
  34. package/src/compliance/pluginCompliance.js +11 -0
  35. package/src/deploy/FlowDeploymentClient.js +532 -0
  36. package/src/deploy/index.js +8 -0
  37. package/src/designer/FlowDesignerSession.js +158 -0
  38. package/src/designer/index.js +2 -0
  39. package/src/designer/requirements.js +184 -0
  40. package/src/generated/runtimeAbiLayouts.js +544 -0
  41. package/src/host/appHost.js +105 -0
  42. package/src/host/autoHost.js +113 -0
  43. package/src/host/browserHostAdapters.js +108 -0
  44. package/src/host/compiledFlowRuntimeHost.js +703 -0
  45. package/src/host/constants.js +55 -0
  46. package/src/host/dependencyRuntime.js +227 -0
  47. package/src/host/descriptorAbi.js +351 -0
  48. package/src/host/fetchService.js +237 -0
  49. package/src/host/httpHostAdapters.js +280 -0
  50. package/src/host/index.js +91 -0
  51. package/src/host/installedFlowHost.js +885 -0
  52. package/src/host/invocationAbi.js +440 -0
  53. package/src/host/normalize.js +372 -0
  54. package/src/host/packageManagers.js +369 -0
  55. package/src/host/profile.js +134 -0
  56. package/src/host/runtimeAbi.js +106 -0
  57. package/src/host/workspace.js +895 -0
  58. package/src/index.js +8 -0
  59. package/src/runtime/FlowRuntime.js +273 -0
  60. package/src/runtime/MethodRegistry.js +295 -0
  61. package/src/runtime/constants.js +44 -0
  62. package/src/runtime/index.js +19 -0
  63. package/src/runtime/normalize.js +377 -0
  64. package/src/transport/index.js +7 -0
  65. package/src/transport/pki.js +7 -0
  66. package/src/utils/crypto.js +7 -0
  67. package/src/utils/encoding.js +65 -0
  68. package/src/utils/wasmCrypto.js +69 -0
  69. package/tools/run-plugin-compliance-check.mjs +153 -0
@@ -0,0 +1,895 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { normalizeProgram } from "../runtime/index.js";
5
+ import {
6
+ createInstalledFlowFetchHandler,
7
+ } from "./fetchService.js";
8
+ import {
9
+ createInstalledFlowService,
10
+ discoverInstalledPluginPackages,
11
+ normalizeInstalledPluginPackage,
12
+ } from "./installedFlowHost.js";
13
+ import { normalizeHostedRuntimePlan } from "./normalize.js";
14
+
15
+ function normalizeString(value, fallback = null) {
16
+ if (typeof value !== "string") {
17
+ return fallback;
18
+ }
19
+ const normalized = value.trim();
20
+ return normalized.length > 0 ? normalized : fallback;
21
+ }
22
+
23
+ function isObject(value) {
24
+ return value !== null && typeof value === "object" && !Array.isArray(value);
25
+ }
26
+
27
+ function normalizeStringArray(values) {
28
+ if (!Array.isArray(values)) {
29
+ return [];
30
+ }
31
+ return Array.from(
32
+ new Set(
33
+ values
34
+ .map((value) => normalizeString(value, null))
35
+ .filter((value) => value !== null),
36
+ ),
37
+ );
38
+ }
39
+
40
+ function resolvePathLike(value, baseDirectory) {
41
+ const normalized = normalizeString(value, null);
42
+ if (!normalized) {
43
+ return null;
44
+ }
45
+ return path.resolve(baseDirectory ?? process.cwd(), normalized);
46
+ }
47
+
48
+ function relativizePathLike(value, baseDirectory) {
49
+ if (!value) {
50
+ return null;
51
+ }
52
+ return path.relative(baseDirectory, value) || ".";
53
+ }
54
+
55
+ function normalizeMetadata(value) {
56
+ return isObject(value) ? { ...value } : {};
57
+ }
58
+
59
+ function normalizeWorkspacePluginPackage(pluginPackage = {}, baseDirectory = null) {
60
+ const normalizedPackage = normalizeInstalledPluginPackage(pluginPackage);
61
+ return {
62
+ ...normalizedPackage,
63
+ packageRoot: resolvePathLike(
64
+ pluginPackage.packageRoot ?? normalizedPackage.packageRoot,
65
+ baseDirectory,
66
+ ),
67
+ manifestPath: resolvePathLike(
68
+ pluginPackage.manifestPath ?? normalizedPackage.manifestPath,
69
+ baseDirectory,
70
+ ),
71
+ modulePath: resolvePathLike(
72
+ pluginPackage.modulePath ?? normalizedPackage.modulePath,
73
+ baseDirectory,
74
+ ),
75
+ };
76
+ }
77
+
78
+ function normalizeWorkspacePackageReference(reference = {}, baseDirectory = null) {
79
+ return {
80
+ packageId: normalizeString(reference.packageId, null),
81
+ pluginId: normalizeString(reference.pluginId, null),
82
+ version: normalizeString(reference.version, null),
83
+ sourceType:
84
+ normalizeString(reference.sourceType ?? reference.source_type, null) ??
85
+ "unknown",
86
+ sourceRef: normalizeString(
87
+ reference.sourceRef ?? reference.source_ref,
88
+ null,
89
+ ),
90
+ installPath: resolvePathLike(
91
+ reference.installPath ?? reference.install_path,
92
+ baseDirectory,
93
+ ),
94
+ status: normalizeString(reference.status, null) ?? "installed",
95
+ metadata: normalizeMetadata(reference.metadata),
96
+ };
97
+ }
98
+
99
+ function serializeWorkspacePluginPackage(pluginPackage, baseDirectory) {
100
+ return {
101
+ packageId: pluginPackage.packageId,
102
+ packageName: pluginPackage.packageName,
103
+ pluginId: pluginPackage.pluginId,
104
+ packageRoot: relativizePathLike(pluginPackage.packageRoot, baseDirectory),
105
+ manifestPath: relativizePathLike(pluginPackage.manifestPath, baseDirectory),
106
+ modulePath: relativizePathLike(pluginPackage.modulePath, baseDirectory),
107
+ runtimeTargets: pluginPackage.runtimeTargets,
108
+ capabilities: pluginPackage.capabilities,
109
+ startupPhase: pluginPackage.startupPhase,
110
+ autoStart: pluginPackage.autoStart,
111
+ metadata: pluginPackage.metadata,
112
+ };
113
+ }
114
+
115
+ function serializeWorkspacePackageReference(reference, baseDirectory) {
116
+ return {
117
+ packageId: reference.packageId,
118
+ pluginId: reference.pluginId,
119
+ version: reference.version,
120
+ sourceType: reference.sourceType,
121
+ sourceRef: reference.sourceRef,
122
+ installPath: relativizePathLike(reference.installPath, baseDirectory),
123
+ status: reference.status,
124
+ metadata: reference.metadata,
125
+ };
126
+ }
127
+
128
+ function packageReferenceKey(reference = {}) {
129
+ return (
130
+ reference.packageId ??
131
+ reference.pluginId ??
132
+ reference.sourceRef ??
133
+ reference.installPath
134
+ );
135
+ }
136
+
137
+ function pluginPackageKey(pluginPackage = {}) {
138
+ return (
139
+ pluginPackage.pluginId ??
140
+ pluginPackage.packageId ??
141
+ pluginPackage.manifestPath ??
142
+ pluginPackage.packageRoot
143
+ );
144
+ }
145
+
146
+ function removeWorkspacePluginPackageByKey(pluginPackages, key) {
147
+ return pluginPackages.filter((item) => pluginPackageKey(item) !== key);
148
+ }
149
+
150
+ async function resolveWorkspacePluginPackageInput(
151
+ pluginPackage,
152
+ baseDirectory,
153
+ ) {
154
+ const normalizedPackage = normalizeWorkspacePluginPackage(
155
+ pluginPackage,
156
+ baseDirectory,
157
+ );
158
+ if (
159
+ normalizedPackage.manifestPath ||
160
+ normalizedPackage.modulePath ||
161
+ normalizedPackage.manifest ||
162
+ normalizedPackage.module ||
163
+ normalizedPackage.handlers
164
+ ) {
165
+ return normalizedPackage;
166
+ }
167
+ if (!normalizedPackage.packageRoot) {
168
+ return normalizedPackage;
169
+ }
170
+
171
+ const discoveredPackages = await discoverInstalledPluginPackages({
172
+ rootDirectories: [normalizedPackage.packageRoot],
173
+ });
174
+ const discoveredPackage =
175
+ discoveredPackages.find(
176
+ (item) =>
177
+ item.pluginId === normalizedPackage.pluginId ||
178
+ item.packageId === normalizedPackage.packageId,
179
+ ) ??
180
+ (discoveredPackages.length === 1 ? discoveredPackages[0] : null);
181
+
182
+ if (!discoveredPackage) {
183
+ throw new Error(
184
+ `Could not resolve an installed plugin package from "${normalizedPackage.packageRoot}".`,
185
+ );
186
+ }
187
+
188
+ return normalizeWorkspacePluginPackage(
189
+ {
190
+ ...discoveredPackage,
191
+ ...pluginPackage,
192
+ packageRoot:
193
+ pluginPackage.packageRoot ?? discoveredPackage.packageRoot,
194
+ manifestPath:
195
+ pluginPackage.manifestPath ?? discoveredPackage.manifestPath,
196
+ modulePath: pluginPackage.modulePath ?? discoveredPackage.modulePath,
197
+ },
198
+ baseDirectory,
199
+ );
200
+ }
201
+
202
+ async function readJsonFile(filePath) {
203
+ return JSON.parse(await readFile(filePath, "utf8"));
204
+ }
205
+
206
+ export function normalizeInstalledFlowWorkspace(workspace = {}, options = {}) {
207
+ const baseDirectory = path.resolve(
208
+ normalizeString(
209
+ options.baseDirectory ??
210
+ workspace.baseDirectory ??
211
+ workspace.workspaceRoot ??
212
+ process.cwd(),
213
+ process.cwd(),
214
+ ),
215
+ );
216
+ const program =
217
+ workspace.program && isObject(workspace.program)
218
+ ? normalizeProgram(workspace.program)
219
+ : null;
220
+ const hostPlan =
221
+ workspace.hostPlan && isObject(workspace.hostPlan)
222
+ ? normalizeHostedRuntimePlan(workspace.hostPlan)
223
+ : null;
224
+ const flowPath = resolvePathLike(
225
+ workspace.flowPath ?? workspace.programPath,
226
+ baseDirectory,
227
+ );
228
+ const hostPlanPath = resolvePathLike(workspace.hostPlanPath, baseDirectory);
229
+ const pluginRootDirectories = normalizeStringArray(
230
+ workspace.pluginRootDirectories ?? workspace.rootDirectories,
231
+ ).map((directory) => resolvePathLike(directory, baseDirectory));
232
+ const pluginPackages = Array.isArray(workspace.pluginPackages)
233
+ ? workspace.pluginPackages.map((pluginPackage) =>
234
+ normalizeWorkspacePluginPackage(pluginPackage, baseDirectory),
235
+ )
236
+ : [];
237
+ const packageCatalog = Array.isArray(
238
+ workspace.packageCatalog ?? workspace.installedPackages,
239
+ )
240
+ ? (workspace.packageCatalog ?? workspace.installedPackages).map(
241
+ (reference) =>
242
+ normalizeWorkspacePackageReference(reference, baseDirectory),
243
+ )
244
+ : [];
245
+
246
+ return {
247
+ workspaceId:
248
+ normalizeString(
249
+ workspace.workspaceId ?? workspace.id ?? workspace.name,
250
+ null,
251
+ ) ?? "installed-flow-workspace",
252
+ description: normalizeString(workspace.description, null),
253
+ baseDirectory,
254
+ flowPath,
255
+ hostPlanPath,
256
+ pluginRootDirectories,
257
+ pluginPackages,
258
+ packageCatalog,
259
+ moduleCandidates: normalizeStringArray(workspace.moduleCandidates),
260
+ discover: workspace.discover !== false,
261
+ adapter: normalizeString(workspace.adapter ?? hostPlan?.adapter, null),
262
+ engine: normalizeString(workspace.engine ?? hostPlan?.engine, null),
263
+ program,
264
+ hostPlan,
265
+ fetch: normalizeMetadata(workspace.fetch),
266
+ service: normalizeMetadata(workspace.service),
267
+ metadata: normalizeMetadata(workspace.metadata),
268
+ };
269
+ }
270
+
271
+ export async function resolveInstalledFlowWorkspace(workspace = {}, options = {}) {
272
+ const normalizedWorkspace = normalizeInstalledFlowWorkspace(workspace, options);
273
+ let program = normalizedWorkspace.program;
274
+ let hostPlan = normalizedWorkspace.hostPlan;
275
+
276
+ if (!program && normalizedWorkspace.flowPath) {
277
+ program = normalizeProgram(await readJsonFile(normalizedWorkspace.flowPath));
278
+ }
279
+ if (!hostPlan && normalizedWorkspace.hostPlanPath) {
280
+ hostPlan = normalizeHostedRuntimePlan(
281
+ await readJsonFile(normalizedWorkspace.hostPlanPath),
282
+ );
283
+ }
284
+
285
+ return {
286
+ ...normalizedWorkspace,
287
+ adapter: normalizedWorkspace.adapter ?? hostPlan?.adapter ?? null,
288
+ engine: normalizedWorkspace.engine ?? hostPlan?.engine ?? null,
289
+ program,
290
+ hostPlan,
291
+ };
292
+ }
293
+
294
+ export async function readInstalledFlowWorkspace(workspacePath, options = {}) {
295
+ const resolvedWorkspacePath = path.resolve(workspacePath);
296
+ const workspace = await readJsonFile(resolvedWorkspacePath);
297
+ return resolveInstalledFlowWorkspace(workspace, {
298
+ ...options,
299
+ baseDirectory: path.dirname(resolvedWorkspacePath),
300
+ workspacePath: resolvedWorkspacePath,
301
+ });
302
+ }
303
+
304
+ export async function writeInstalledFlowWorkspace(workspacePath, workspace, options = {}) {
305
+ const resolvedWorkspacePath = path.resolve(workspacePath);
306
+ const baseDirectory = path.dirname(resolvedWorkspacePath);
307
+ const normalizedWorkspace = normalizeInstalledFlowWorkspace(workspace, {
308
+ ...options,
309
+ baseDirectory,
310
+ });
311
+ const payload = {
312
+ workspaceId: normalizedWorkspace.workspaceId,
313
+ description: normalizedWorkspace.description,
314
+ flowPath: relativizePathLike(normalizedWorkspace.flowPath, baseDirectory),
315
+ hostPlanPath: relativizePathLike(
316
+ normalizedWorkspace.hostPlanPath,
317
+ baseDirectory,
318
+ ),
319
+ pluginRootDirectories: normalizedWorkspace.pluginRootDirectories.map(
320
+ (directory) => relativizePathLike(directory, baseDirectory),
321
+ ),
322
+ pluginPackages: normalizedWorkspace.pluginPackages.map((pluginPackage) =>
323
+ serializeWorkspacePluginPackage(pluginPackage, baseDirectory),
324
+ ),
325
+ packageCatalog: normalizedWorkspace.packageCatalog.map((reference) =>
326
+ serializeWorkspacePackageReference(reference, baseDirectory),
327
+ ),
328
+ moduleCandidates: normalizedWorkspace.moduleCandidates,
329
+ discover: normalizedWorkspace.discover,
330
+ adapter: normalizedWorkspace.adapter,
331
+ engine: normalizedWorkspace.engine,
332
+ fetch: normalizedWorkspace.fetch,
333
+ service: normalizedWorkspace.service,
334
+ metadata: normalizedWorkspace.metadata,
335
+ };
336
+
337
+ if (normalizedWorkspace.program) {
338
+ payload.program = normalizedWorkspace.program;
339
+ }
340
+ if (normalizedWorkspace.hostPlan) {
341
+ payload.hostPlan = normalizedWorkspace.hostPlan;
342
+ }
343
+
344
+ await mkdir(baseDirectory, { recursive: true });
345
+ await writeFile(
346
+ resolvedWorkspacePath,
347
+ `${JSON.stringify(payload, null, 2)}\n`,
348
+ "utf8",
349
+ );
350
+ return resolvedWorkspacePath;
351
+ }
352
+
353
+ export async function installWorkspacePluginPackage(
354
+ workspace,
355
+ pluginPackage,
356
+ options = {},
357
+ ) {
358
+ const normalizedWorkspace = await resolveInstalledFlowWorkspace(
359
+ workspace,
360
+ options,
361
+ );
362
+ const resolvedPackage = await resolveWorkspacePluginPackageInput(
363
+ pluginPackage,
364
+ normalizedWorkspace.baseDirectory,
365
+ );
366
+ const packageKey =
367
+ resolvedPackage.pluginId ??
368
+ resolvedPackage.packageId ??
369
+ resolvedPackage.manifestPath ??
370
+ resolvedPackage.packageRoot;
371
+ const pluginPackages = normalizedWorkspace.pluginPackages.filter((item) => {
372
+ const itemKey =
373
+ item.pluginId ?? item.packageId ?? item.manifestPath ?? item.packageRoot;
374
+ return itemKey !== packageKey;
375
+ });
376
+ pluginPackages.push(resolvedPackage);
377
+ pluginPackages.sort((left, right) =>
378
+ `${left.packageName ?? ""}:${left.pluginId ?? left.packageId}`.localeCompare(
379
+ `${right.packageName ?? ""}:${right.pluginId ?? right.packageId}`,
380
+ ),
381
+ );
382
+
383
+ return {
384
+ ...normalizedWorkspace,
385
+ pluginPackages,
386
+ };
387
+ }
388
+
389
+ export async function uninstallWorkspacePluginPackage(
390
+ workspace,
391
+ selector,
392
+ options = {},
393
+ ) {
394
+ const normalizedWorkspace = await resolveInstalledFlowWorkspace(
395
+ workspace,
396
+ options,
397
+ );
398
+ const normalizedSelector = isObject(selector)
399
+ ? {
400
+ pluginId: normalizeString(selector.pluginId, null),
401
+ packageId: normalizeString(selector.packageId, null),
402
+ packageRoot: resolvePathLike(selector.packageRoot, normalizedWorkspace.baseDirectory),
403
+ manifestPath: resolvePathLike(selector.manifestPath, normalizedWorkspace.baseDirectory),
404
+ modulePath: resolvePathLike(selector.modulePath, normalizedWorkspace.baseDirectory),
405
+ }
406
+ : {
407
+ pluginId: normalizeString(selector, null),
408
+ packageId: normalizeString(selector, null),
409
+ packageRoot: null,
410
+ manifestPath: null,
411
+ modulePath: null,
412
+ };
413
+ const pluginPackages = normalizedWorkspace.pluginPackages.filter((item) => {
414
+ if (normalizedSelector.pluginId && item.pluginId === normalizedSelector.pluginId) {
415
+ return false;
416
+ }
417
+ if (normalizedSelector.packageId && item.packageId === normalizedSelector.packageId) {
418
+ return false;
419
+ }
420
+ if (
421
+ normalizedSelector.packageRoot &&
422
+ item.packageRoot === normalizedSelector.packageRoot
423
+ ) {
424
+ return false;
425
+ }
426
+ if (
427
+ normalizedSelector.manifestPath &&
428
+ item.manifestPath === normalizedSelector.manifestPath
429
+ ) {
430
+ return false;
431
+ }
432
+ if (
433
+ normalizedSelector.modulePath &&
434
+ item.modulePath === normalizedSelector.modulePath
435
+ ) {
436
+ return false;
437
+ }
438
+ return true;
439
+ });
440
+
441
+ return {
442
+ ...normalizedWorkspace,
443
+ pluginPackages,
444
+ };
445
+ }
446
+
447
+ function requirePackageManager(options = {}) {
448
+ const packageManager = options.packageManager ?? null;
449
+ if (!packageManager) {
450
+ throw new Error(
451
+ "Workspace package-reference operations require a packageManager with install/update/remove methods.",
452
+ );
453
+ }
454
+ return packageManager;
455
+ }
456
+
457
+ function removeWorkspacePackageReferenceBySelector(
458
+ packageCatalog,
459
+ selector,
460
+ baseDirectory,
461
+ ) {
462
+ const normalizedSelector = isObject(selector)
463
+ ? normalizeWorkspacePackageReference(selector, baseDirectory)
464
+ : {
465
+ packageId: normalizeString(selector, null),
466
+ pluginId: normalizeString(selector, null),
467
+ version: null,
468
+ sourceType: null,
469
+ sourceRef: normalizeString(selector, null),
470
+ installPath: null,
471
+ status: null,
472
+ metadata: {},
473
+ };
474
+ return packageCatalog.filter((reference) => {
475
+ if (
476
+ normalizedSelector.packageId &&
477
+ reference.packageId === normalizedSelector.packageId
478
+ ) {
479
+ return false;
480
+ }
481
+ if (
482
+ normalizedSelector.pluginId &&
483
+ reference.pluginId === normalizedSelector.pluginId
484
+ ) {
485
+ return false;
486
+ }
487
+ if (
488
+ normalizedSelector.sourceRef &&
489
+ reference.sourceRef === normalizedSelector.sourceRef
490
+ ) {
491
+ return false;
492
+ }
493
+ if (
494
+ normalizedSelector.installPath &&
495
+ reference.installPath === normalizedSelector.installPath
496
+ ) {
497
+ return false;
498
+ }
499
+ return true;
500
+ });
501
+ }
502
+
503
+ function findWorkspacePackageReference(packageCatalog, selector, baseDirectory) {
504
+ return packageCatalog.find((reference) =>
505
+ removeWorkspacePackageReferenceBySelector(
506
+ [reference],
507
+ selector,
508
+ baseDirectory,
509
+ ).length === 0,
510
+ );
511
+ }
512
+
513
+ function sortWorkspacePackageReferences(packageCatalog) {
514
+ packageCatalog.sort((left, right) =>
515
+ `${left.packageId ?? ""}:${left.pluginId ?? ""}:${left.sourceRef ?? ""}`.localeCompare(
516
+ `${right.packageId ?? ""}:${right.pluginId ?? ""}:${right.sourceRef ?? ""}`,
517
+ ),
518
+ );
519
+ return packageCatalog;
520
+ }
521
+
522
+ async function resolveInstalledPackageManagerRecord(record, baseDirectory) {
523
+ const normalizedReference = normalizeWorkspacePackageReference(
524
+ record,
525
+ baseDirectory,
526
+ );
527
+ const pluginPackage = await resolveWorkspacePluginPackageInput(
528
+ {
529
+ ...(record.pluginPackage ?? {}),
530
+ packageId:
531
+ record.pluginPackage?.packageId ?? normalizedReference.packageId,
532
+ pluginId: record.pluginPackage?.pluginId ?? normalizedReference.pluginId,
533
+ packageRoot:
534
+ record.pluginPackage?.packageRoot ?? normalizedReference.installPath,
535
+ },
536
+ baseDirectory,
537
+ );
538
+
539
+ return {
540
+ packageReference: {
541
+ ...normalizedReference,
542
+ packageId: normalizedReference.packageId ?? pluginPackage.packageId,
543
+ pluginId: normalizedReference.pluginId ?? pluginPackage.pluginId,
544
+ },
545
+ pluginPackage,
546
+ };
547
+ }
548
+
549
+ export async function installWorkspacePackageReference(
550
+ workspace,
551
+ packageReference,
552
+ options = {},
553
+ ) {
554
+ const normalizedWorkspace = await resolveInstalledFlowWorkspace(
555
+ workspace,
556
+ options,
557
+ );
558
+ const packageManager = requirePackageManager(options);
559
+ const installRecord = await packageManager.install(
560
+ packageReference,
561
+ normalizedWorkspace,
562
+ options,
563
+ );
564
+ const resolvedRecord = await resolveInstalledPackageManagerRecord(
565
+ installRecord,
566
+ normalizedWorkspace.baseDirectory,
567
+ );
568
+ const referenceKey = packageReferenceKey(resolvedRecord.packageReference);
569
+ const packageCatalog = removeWorkspacePackageReferenceBySelector(
570
+ normalizedWorkspace.packageCatalog,
571
+ resolvedRecord.packageReference,
572
+ normalizedWorkspace.baseDirectory,
573
+ );
574
+ packageCatalog.push(resolvedRecord.packageReference);
575
+ sortWorkspacePackageReferences(packageCatalog);
576
+
577
+ const pluginPackages = removeWorkspacePluginPackageByKey(
578
+ normalizedWorkspace.pluginPackages,
579
+ referenceKey,
580
+ );
581
+ pluginPackages.push(resolvedRecord.pluginPackage);
582
+ pluginPackages.sort((left, right) =>
583
+ `${left.packageName ?? ""}:${left.pluginId ?? left.packageId}`.localeCompare(
584
+ `${right.packageName ?? ""}:${right.pluginId ?? right.packageId}`,
585
+ ),
586
+ );
587
+
588
+ return {
589
+ ...normalizedWorkspace,
590
+ pluginPackages,
591
+ packageCatalog,
592
+ };
593
+ }
594
+
595
+ export async function updateWorkspacePackageReference(
596
+ workspace,
597
+ selector,
598
+ options = {},
599
+ ) {
600
+ const normalizedWorkspace = await resolveInstalledFlowWorkspace(
601
+ workspace,
602
+ options,
603
+ );
604
+ const packageManager = requirePackageManager(options);
605
+ const existingReference = findWorkspacePackageReference(
606
+ normalizedWorkspace.packageCatalog,
607
+ selector,
608
+ normalizedWorkspace.baseDirectory,
609
+ );
610
+ if (!existingReference) {
611
+ throw new Error("Workspace package reference was not found for update.");
612
+ }
613
+ const updateRecord = await packageManager.update(
614
+ existingReference,
615
+ normalizedWorkspace,
616
+ options,
617
+ );
618
+
619
+ return installWorkspacePackageReference(
620
+ {
621
+ ...normalizedWorkspace,
622
+ packageCatalog: removeWorkspacePackageReferenceBySelector(
623
+ normalizedWorkspace.packageCatalog,
624
+ selector,
625
+ normalizedWorkspace.baseDirectory,
626
+ ),
627
+ pluginPackages: removeWorkspacePluginPackageByKey(
628
+ normalizedWorkspace.pluginPackages,
629
+ existingReference.pluginId ??
630
+ existingReference.packageId ??
631
+ existingReference.installPath,
632
+ ),
633
+ },
634
+ updateRecord,
635
+ {
636
+ ...options,
637
+ packageManager: {
638
+ install(record) {
639
+ return record;
640
+ },
641
+ },
642
+ },
643
+ );
644
+ }
645
+
646
+ export async function removeWorkspacePackageReference(
647
+ workspace,
648
+ selector,
649
+ options = {},
650
+ ) {
651
+ const normalizedWorkspace = await resolveInstalledFlowWorkspace(
652
+ workspace,
653
+ options,
654
+ );
655
+ const packageManager = requirePackageManager(options);
656
+ const existingReference = findWorkspacePackageReference(
657
+ normalizedWorkspace.packageCatalog,
658
+ selector,
659
+ normalizedWorkspace.baseDirectory,
660
+ );
661
+ if (!existingReference) {
662
+ return normalizedWorkspace;
663
+ }
664
+ if (typeof packageManager.remove === "function") {
665
+ await packageManager.remove(existingReference, normalizedWorkspace, options);
666
+ }
667
+
668
+ return {
669
+ ...normalizedWorkspace,
670
+ packageCatalog: removeWorkspacePackageReferenceBySelector(
671
+ normalizedWorkspace.packageCatalog,
672
+ selector,
673
+ normalizedWorkspace.baseDirectory,
674
+ ),
675
+ pluginPackages: removeWorkspacePluginPackageByKey(
676
+ normalizedWorkspace.pluginPackages,
677
+ existingReference.pluginId ??
678
+ existingReference.packageId ??
679
+ existingReference.installPath,
680
+ ),
681
+ };
682
+ }
683
+
684
+ export async function createInstalledFlowApp(options = {}) {
685
+ const workspacePath = normalizeString(options.workspacePath, null)
686
+ ? path.resolve(options.workspacePath)
687
+ : null;
688
+ let workspace =
689
+ options.workspace !== undefined
690
+ ? await resolveInstalledFlowWorkspace(options.workspace, options)
691
+ : workspacePath
692
+ ? await readInstalledFlowWorkspace(workspacePath, options)
693
+ : await resolveInstalledFlowWorkspace({}, options);
694
+
695
+ const service = createInstalledFlowService({
696
+ program: workspace.program,
697
+ pluginRootDirectories: workspace.pluginRootDirectories,
698
+ pluginPackages: workspace.pluginPackages,
699
+ discover: workspace.discover,
700
+ moduleCandidates:
701
+ workspace.moduleCandidates.length > 0
702
+ ? workspace.moduleCandidates
703
+ : undefined,
704
+ ...(workspace.service ?? {}),
705
+ ...(options.serviceOptions ?? {}),
706
+ importModule: options.importModule,
707
+ context: options.context ?? null,
708
+ runtimeOptions: options.runtimeOptions,
709
+ onSinkOutput: options.onSinkOutput,
710
+ });
711
+ const fetchHandler = async (request, context = {}) =>
712
+ createInstalledFlowFetchHandler({
713
+ service,
714
+ ...(workspace.fetch ?? {}),
715
+ ...(options.fetchOptions ?? {}),
716
+ })(request, context);
717
+ fetchHandler.service = service;
718
+ fetchHandler.start = () => service.start();
719
+ fetchHandler.stop = () => service.stop();
720
+
721
+ async function reloadWorkspace(reloadOptions = {}) {
722
+ if (!workspacePath) {
723
+ return workspace;
724
+ }
725
+ workspace = await readInstalledFlowWorkspace(workspacePath, {
726
+ ...options,
727
+ ...reloadOptions,
728
+ });
729
+ return workspace;
730
+ }
731
+
732
+ return {
733
+ workspacePath,
734
+ host: service.host,
735
+ service,
736
+ fetchHandler,
737
+ getWorkspace() {
738
+ return workspace;
739
+ },
740
+ getSummary() {
741
+ return {
742
+ workspaceId: workspace.workspaceId,
743
+ programId: workspace.program?.programId ?? null,
744
+ adapter: workspace.adapter,
745
+ engine: workspace.engine,
746
+ pluginRootDirectories: workspace.pluginRootDirectories,
747
+ packageCatalogCount: workspace.packageCatalog.length,
748
+ hostId: workspace.hostPlan?.hostId ?? null,
749
+ };
750
+ },
751
+ async start() {
752
+ const startup = await service.start();
753
+ return {
754
+ ...startup,
755
+ workspace: this.getSummary(),
756
+ };
757
+ },
758
+ stop() {
759
+ return service.stop();
760
+ },
761
+ async refresh(refreshOptions = {}) {
762
+ if (refreshOptions.reloadWorkspace !== false && workspacePath) {
763
+ await reloadWorkspace(refreshOptions);
764
+ }
765
+ const refreshResult = await service.refresh({
766
+ ...refreshOptions,
767
+ program: workspace.program,
768
+ pluginRootDirectories: workspace.pluginRootDirectories,
769
+ pluginPackages: workspace.pluginPackages,
770
+ discover: workspace.discover,
771
+ moduleCandidates:
772
+ workspace.moduleCandidates.length > 0
773
+ ? workspace.moduleCandidates
774
+ : undefined,
775
+ });
776
+ return {
777
+ ...refreshResult,
778
+ workspace: this.getSummary(),
779
+ };
780
+ },
781
+ async reloadWorkspace(reloadOptions = {}) {
782
+ return reloadWorkspace(reloadOptions);
783
+ },
784
+ async save(saveOptions = {}) {
785
+ if (!workspacePath) {
786
+ throw new Error(
787
+ "Installed flow app cannot save workspace state without workspacePath.",
788
+ );
789
+ }
790
+ return writeInstalledFlowWorkspace(workspacePath, workspace, saveOptions);
791
+ },
792
+ async installPluginPackage(pluginPackage, installOptions = {}) {
793
+ workspace = await installWorkspacePluginPackage(
794
+ workspace,
795
+ pluginPackage,
796
+ installOptions,
797
+ );
798
+ if (workspacePath && installOptions.save !== false) {
799
+ await writeInstalledFlowWorkspace(workspacePath, workspace, installOptions);
800
+ }
801
+ if (installOptions.refresh === false) {
802
+ return workspace;
803
+ }
804
+ return this.refresh({
805
+ ...installOptions,
806
+ reloadWorkspace: workspacePath ? installOptions.save !== false : false,
807
+ });
808
+ },
809
+ async uninstallPluginPackage(selector, uninstallOptions = {}) {
810
+ workspace = await uninstallWorkspacePluginPackage(
811
+ workspace,
812
+ selector,
813
+ uninstallOptions,
814
+ );
815
+ if (workspacePath && uninstallOptions.save !== false) {
816
+ await writeInstalledFlowWorkspace(
817
+ workspacePath,
818
+ workspace,
819
+ uninstallOptions,
820
+ );
821
+ }
822
+ if (uninstallOptions.refresh === false) {
823
+ return workspace;
824
+ }
825
+ return this.refresh({
826
+ ...uninstallOptions,
827
+ reloadWorkspace: workspacePath ? uninstallOptions.save !== false : false,
828
+ });
829
+ },
830
+ async installPackageReference(packageReference, installOptions = {}) {
831
+ workspace = await installWorkspacePackageReference(
832
+ workspace,
833
+ packageReference,
834
+ installOptions,
835
+ );
836
+ if (workspacePath && installOptions.save !== false) {
837
+ await writeInstalledFlowWorkspace(workspacePath, workspace, installOptions);
838
+ }
839
+ if (installOptions.refresh === false) {
840
+ return workspace;
841
+ }
842
+ return this.refresh({
843
+ ...installOptions,
844
+ reloadWorkspace: workspacePath ? installOptions.save !== false : false,
845
+ });
846
+ },
847
+ async updatePackageReference(selector, updateOptions = {}) {
848
+ workspace = await updateWorkspacePackageReference(
849
+ workspace,
850
+ selector,
851
+ updateOptions,
852
+ );
853
+ if (workspacePath && updateOptions.save !== false) {
854
+ await writeInstalledFlowWorkspace(workspacePath, workspace, updateOptions);
855
+ }
856
+ if (updateOptions.refresh === false) {
857
+ return workspace;
858
+ }
859
+ return this.refresh({
860
+ ...updateOptions,
861
+ reloadWorkspace: workspacePath ? updateOptions.save !== false : false,
862
+ });
863
+ },
864
+ async removePackageReference(selector, removeOptions = {}) {
865
+ workspace = await removeWorkspacePackageReference(
866
+ workspace,
867
+ selector,
868
+ removeOptions,
869
+ );
870
+ if (workspacePath && removeOptions.save !== false) {
871
+ await writeInstalledFlowWorkspace(workspacePath, workspace, removeOptions);
872
+ }
873
+ if (removeOptions.refresh === false) {
874
+ return workspace;
875
+ }
876
+ return this.refresh({
877
+ ...removeOptions,
878
+ reloadWorkspace: workspacePath ? removeOptions.save !== false : false,
879
+ });
880
+ },
881
+ };
882
+ }
883
+
884
+ export default {
885
+ createInstalledFlowApp,
886
+ installWorkspacePluginPackage,
887
+ installWorkspacePackageReference,
888
+ normalizeInstalledFlowWorkspace,
889
+ readInstalledFlowWorkspace,
890
+ removeWorkspacePackageReference,
891
+ resolveInstalledFlowWorkspace,
892
+ uninstallWorkspacePluginPackage,
893
+ updateWorkspacePackageReference,
894
+ writeInstalledFlowWorkspace,
895
+ };