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,237 @@
1
+ import { createInstalledFlowService } from "./installedFlowHost.js";
2
+
3
+ function normalizeString(value, fallback = null) {
4
+ if (typeof value !== "string") {
5
+ return fallback;
6
+ }
7
+ const normalized = value.trim();
8
+ return normalized.length > 0 ? normalized : fallback;
9
+ }
10
+
11
+ function isObject(value) {
12
+ return value !== null && typeof value === "object" && !Array.isArray(value);
13
+ }
14
+
15
+ function normalizeHeaderEntries(headers) {
16
+ if (!headers) {
17
+ return [];
18
+ }
19
+ if (typeof headers.forEach === "function") {
20
+ const entries = [];
21
+ headers.forEach((value, key) => {
22
+ entries.push([key, value]);
23
+ });
24
+ return entries;
25
+ }
26
+ if (typeof headers.entries === "function") {
27
+ return Array.from(headers.entries());
28
+ }
29
+ if (Array.isArray(headers)) {
30
+ return headers;
31
+ }
32
+ if (isObject(headers)) {
33
+ return Object.entries(headers);
34
+ }
35
+ return [];
36
+ }
37
+
38
+ function normalizeHeadersObject(headers) {
39
+ const normalized = {};
40
+ for (const [key, value] of normalizeHeaderEntries(headers)) {
41
+ const headerName = normalizeString(key, null);
42
+ if (!headerName) {
43
+ continue;
44
+ }
45
+ if (Array.isArray(value)) {
46
+ normalized[headerName.toLowerCase()] = value
47
+ .map((item) => String(item))
48
+ .join(", ");
49
+ continue;
50
+ }
51
+ normalized[headerName.toLowerCase()] = String(value);
52
+ }
53
+ return normalized;
54
+ }
55
+
56
+ function normalizeHeadersInit(headers) {
57
+ const normalized = new Headers();
58
+ for (const [key, value] of normalizeHeaderEntries(headers)) {
59
+ const headerName = normalizeString(key, null);
60
+ if (!headerName || value === null || value === undefined) {
61
+ continue;
62
+ }
63
+ if (Array.isArray(value)) {
64
+ for (const item of value) {
65
+ normalized.append(headerName, String(item));
66
+ }
67
+ continue;
68
+ }
69
+ normalized.set(headerName, String(value));
70
+ }
71
+ return normalized;
72
+ }
73
+
74
+ function normalizeUrlDetails(urlValue, baseUrl) {
75
+ if (!urlValue) {
76
+ return {
77
+ href: null,
78
+ origin: null,
79
+ path: "/",
80
+ query: {},
81
+ };
82
+ }
83
+
84
+ const url = baseUrl ? new URL(urlValue, baseUrl) : new URL(urlValue);
85
+ return {
86
+ href: url.href,
87
+ origin: url.origin,
88
+ path: url.pathname || "/",
89
+ query: Object.fromEntries(url.searchParams.entries()),
90
+ };
91
+ }
92
+
93
+ function normalizeBodyBytes(value) {
94
+ if (value === null || value === undefined) {
95
+ return null;
96
+ }
97
+ if (value instanceof Uint8Array) {
98
+ return value;
99
+ }
100
+ if (value instanceof ArrayBuffer) {
101
+ return new Uint8Array(value);
102
+ }
103
+ if (ArrayBuffer.isView(value)) {
104
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
105
+ }
106
+ if (typeof value === "string") {
107
+ return new TextEncoder().encode(value);
108
+ }
109
+ return value;
110
+ }
111
+
112
+ async function readRequestBodyBytes(request) {
113
+ if (!request || typeof request.arrayBuffer !== "function") {
114
+ return normalizeBodyBytes(request?.body ?? request?.payload ?? null);
115
+ }
116
+
117
+ if (request.bodyUsed) {
118
+ throw new Error("Fetch request body has already been consumed.");
119
+ }
120
+
121
+ const method = normalizeString(request.method, "GET");
122
+ if (method === "GET" || method === "HEAD") {
123
+ return null;
124
+ }
125
+
126
+ const bodyBuffer = await request.arrayBuffer();
127
+ return bodyBuffer.byteLength > 0 ? new Uint8Array(bodyBuffer) : null;
128
+ }
129
+
130
+ export async function normalizeFetchRequest(request, options = {}) {
131
+ const baseUrl = normalizeString(options.baseUrl, "http://localhost");
132
+ const urlValue =
133
+ normalizeString(request?.url, null) ??
134
+ normalizeString(request?.href, null) ??
135
+ normalizeString(request?.path, null);
136
+ const urlDetails = normalizeUrlDetails(urlValue, baseUrl);
137
+ const requestId =
138
+ normalizeString(request?.requestId, null) ??
139
+ normalizeString(request?.headers?.get?.("x-request-id"), null);
140
+
141
+ return {
142
+ triggerId: normalizeString(request?.triggerId, null),
143
+ requestId,
144
+ method: normalizeString(request?.method, "GET"),
145
+ path: urlDetails.path,
146
+ query: urlDetails.query,
147
+ headers: normalizeHeadersObject(request?.headers),
148
+ body: await readRequestBodyBytes(request),
149
+ metadata: {
150
+ url: urlDetails.href,
151
+ origin: urlDetails.origin,
152
+ ...((isObject(options.metadata) && options.metadata) || {}),
153
+ ...((isObject(request?.metadata) && request.metadata) || {}),
154
+ },
155
+ };
156
+ }
157
+
158
+ function resolveHttpResponseFrame(result) {
159
+ if (!Array.isArray(result?.outputs) || result.outputs.length === 0) {
160
+ return null;
161
+ }
162
+ return result.outputs[0]?.frame ?? null;
163
+ }
164
+
165
+ export function createFetchResponse(result, options = {}) {
166
+ const frame = resolveHttpResponseFrame(result);
167
+ const metadata = isObject(frame?.metadata) ? frame.metadata : {};
168
+ const headers = normalizeHeadersInit(
169
+ metadata.responseHeaders ?? metadata.headers ?? {},
170
+ );
171
+ const contentType = normalizeString(
172
+ metadata.contentType ?? metadata.content_type,
173
+ null,
174
+ );
175
+ if (contentType && !headers.has("content-type")) {
176
+ headers.set("content-type", contentType);
177
+ }
178
+
179
+ const statusCode = Number(
180
+ metadata.statusCode ?? metadata.status ?? metadata.status_code ?? 200,
181
+ );
182
+ const status = Number.isInteger(statusCode) && statusCode >= 100
183
+ ? statusCode
184
+ : 200;
185
+ const body =
186
+ frame?.payload !== undefined && frame?.payload !== null
187
+ ? normalizeBodyBytes(frame.payload)
188
+ : null;
189
+
190
+ if (frame === null) {
191
+ return new Response(null, {
192
+ status:
193
+ Number.isInteger(options.emptyStatus) && options.emptyStatus >= 100
194
+ ? options.emptyStatus
195
+ : 204,
196
+ headers,
197
+ });
198
+ }
199
+
200
+ return new Response(body, {
201
+ status,
202
+ headers,
203
+ });
204
+ }
205
+
206
+ export function createInstalledFlowFetchHandler(options = {}) {
207
+ const service = options.service ?? createInstalledFlowService(options);
208
+ const requestMapper = options.requestMapper ?? normalizeFetchRequest;
209
+ const responseMapper = options.responseMapper ?? createFetchResponse;
210
+
211
+ const handler = async (request, context = {}) => {
212
+ await service.start();
213
+ const mappedRequest = await requestMapper(request, {
214
+ ...options,
215
+ context,
216
+ });
217
+ const result = await service.handleHttpRequest(mappedRequest);
218
+ return responseMapper(result, {
219
+ request,
220
+ context,
221
+ service,
222
+ ...options,
223
+ });
224
+ };
225
+
226
+ handler.service = service;
227
+ handler.start = () => service.start();
228
+ handler.stop = () => service.stop();
229
+
230
+ return handler;
231
+ }
232
+
233
+ export default {
234
+ createFetchResponse,
235
+ createInstalledFlowFetchHandler,
236
+ normalizeFetchRequest,
237
+ };
@@ -0,0 +1,280 @@
1
+ import { startInstalledFlowAppHost } from "./appHost.js";
2
+
3
+ function normalizeString(value, fallback = null) {
4
+ if (typeof value !== "string") {
5
+ return fallback;
6
+ }
7
+ const normalized = value.trim();
8
+ return normalized.length > 0 ? normalized : fallback;
9
+ }
10
+
11
+ function resolveBindingUrl(binding = {}) {
12
+ const rawUrl = normalizeString(binding.url, null);
13
+ if (!rawUrl) {
14
+ throw new Error("HTTP binding is missing a url.");
15
+ }
16
+ return new URL(rawUrl);
17
+ }
18
+
19
+ function normalizeNodeHeaders(headers = {}) {
20
+ const normalized = {};
21
+ for (const [key, value] of Object.entries(headers)) {
22
+ if (value === undefined) {
23
+ continue;
24
+ }
25
+ if (Array.isArray(value)) {
26
+ normalized[key] = value.join(", ");
27
+ continue;
28
+ }
29
+ normalized[key] = String(value);
30
+ }
31
+ return normalized;
32
+ }
33
+
34
+ async function readNodeRequestBody(request) {
35
+ const chunks = [];
36
+ for await (const chunk of request) {
37
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
38
+ }
39
+ if (chunks.length === 0) {
40
+ return null;
41
+ }
42
+ return Buffer.concat(chunks);
43
+ }
44
+
45
+ async function createFetchRequestFromNodeIncomingMessage(request, binding) {
46
+ const bindingUrl = resolveBindingUrl(binding);
47
+ const requestUrl = new URL(request.url ?? bindingUrl.pathname, bindingUrl);
48
+ const method = normalizeString(request.method, "GET");
49
+ const headers = normalizeNodeHeaders(request.headers);
50
+ const body =
51
+ method === "GET" || method === "HEAD"
52
+ ? null
53
+ : await readNodeRequestBody(request);
54
+
55
+ return new Request(requestUrl, {
56
+ method,
57
+ headers,
58
+ body,
59
+ });
60
+ }
61
+
62
+ async function writeFetchResponseToNodeResponse(response, nodeResponse) {
63
+ nodeResponse.statusCode = response.status;
64
+ for (const [key, value] of response.headers.entries()) {
65
+ nodeResponse.setHeader(key, value);
66
+ }
67
+ if (response.body === null) {
68
+ nodeResponse.end();
69
+ return;
70
+ }
71
+ const bodyBuffer = Buffer.from(await response.arrayBuffer());
72
+ nodeResponse.end(bodyBuffer);
73
+ }
74
+
75
+ function closeHandle(handle) {
76
+ if (typeof handle === "function") {
77
+ return handle();
78
+ }
79
+ if (typeof handle?.stop === "function") {
80
+ return handle.stop();
81
+ }
82
+ if (typeof handle?.shutdown === "function") {
83
+ return handle.shutdown();
84
+ }
85
+ if (typeof handle?.abort === "function") {
86
+ return handle.abort();
87
+ }
88
+ if (typeof handle?.close === "function") {
89
+ return handle.close();
90
+ }
91
+ return undefined;
92
+ }
93
+
94
+ export function createDenoServeHttpAdapter(options = {}) {
95
+ const serve =
96
+ options.serve ??
97
+ (globalThis.Deno && typeof globalThis.Deno.serve === "function"
98
+ ? globalThis.Deno.serve.bind(globalThis.Deno)
99
+ : null);
100
+ if (typeof serve !== "function") {
101
+ throw new Error(
102
+ "createDenoServeHttpAdapter requires a Deno.serve-compatible function.",
103
+ );
104
+ }
105
+
106
+ return async function serveHttp({ binding, handler }) {
107
+ const url = resolveBindingUrl(binding);
108
+ const port = url.port ? Number(url.port) : 80;
109
+ const result = await serve(
110
+ {
111
+ hostname: url.hostname,
112
+ port,
113
+ },
114
+ handler,
115
+ );
116
+ return {
117
+ platform: "deno",
118
+ url: url.href,
119
+ hostname: url.hostname,
120
+ port,
121
+ server: result ?? null,
122
+ async close() {
123
+ await closeHandle(result);
124
+ },
125
+ };
126
+ };
127
+ }
128
+
129
+ export function createBunServeHttpAdapter(options = {}) {
130
+ const serve =
131
+ options.serve ??
132
+ (globalThis.Bun && typeof globalThis.Bun.serve === "function"
133
+ ? globalThis.Bun.serve.bind(globalThis.Bun)
134
+ : null);
135
+ if (typeof serve !== "function") {
136
+ throw new Error(
137
+ "createBunServeHttpAdapter requires a Bun.serve-compatible function.",
138
+ );
139
+ }
140
+
141
+ return async function serveHttp({ binding, handler }) {
142
+ const url = resolveBindingUrl(binding);
143
+ const port = url.port ? Number(url.port) : 80;
144
+ const result = await serve({
145
+ hostname: url.hostname,
146
+ port,
147
+ fetch: handler,
148
+ });
149
+ return {
150
+ platform: "bun",
151
+ url: url.href,
152
+ hostname: url.hostname,
153
+ port,
154
+ server: result ?? null,
155
+ async close() {
156
+ await closeHandle(result);
157
+ },
158
+ };
159
+ };
160
+ }
161
+
162
+ async function listenNodeServer(server, listenOptions) {
163
+ await new Promise((resolve, reject) => {
164
+ const onError = (error) => {
165
+ server.off("listening", onListening);
166
+ reject(error);
167
+ };
168
+ const onListening = () => {
169
+ server.off("error", onError);
170
+ resolve();
171
+ };
172
+ server.once("error", onError);
173
+ server.once("listening", onListening);
174
+ server.listen(listenOptions);
175
+ });
176
+ }
177
+
178
+ async function closeNodeServer(server) {
179
+ await new Promise((resolve, reject) => {
180
+ server.close((error) => {
181
+ if (error) {
182
+ reject(error);
183
+ return;
184
+ }
185
+ resolve();
186
+ });
187
+ });
188
+ }
189
+
190
+ export function createNodeServeHttpAdapter(options = {}) {
191
+ return async function serveHttp({ binding, handler }) {
192
+ const { createServer } = await import("node:http");
193
+ const url = resolveBindingUrl(binding);
194
+ if (url.protocol && url.protocol !== "http:") {
195
+ throw new Error(
196
+ `createNodeServeHttpAdapter only supports http bindings, received ${url.protocol}.`,
197
+ );
198
+ }
199
+
200
+ const server = (options.createServer ?? createServer)(async (request, response) => {
201
+ try {
202
+ const fetchRequest = await createFetchRequestFromNodeIncomingMessage(
203
+ request,
204
+ binding,
205
+ );
206
+ const fetchResponse = await handler(fetchRequest, {
207
+ nodeRequest: request,
208
+ nodeResponse: response,
209
+ binding,
210
+ });
211
+ await writeFetchResponseToNodeResponse(fetchResponse, response);
212
+ } catch (error) {
213
+ response.statusCode = 500;
214
+ response.end(
215
+ typeof error?.message === "string"
216
+ ? error.message
217
+ : "Internal Server Error",
218
+ );
219
+ }
220
+ });
221
+
222
+ await listenNodeServer(server, {
223
+ host: url.hostname,
224
+ port: url.port ? Number(url.port) : 80,
225
+ });
226
+ const address = server.address();
227
+ const actualPort =
228
+ address && typeof address === "object" && "port" in address
229
+ ? address.port
230
+ : url.port
231
+ ? Number(url.port)
232
+ : 80;
233
+ const actualUrl = new URL(url.href);
234
+ actualUrl.port = String(actualPort);
235
+
236
+ return {
237
+ platform: "node",
238
+ url: actualUrl.href,
239
+ hostname: url.hostname,
240
+ port: actualPort,
241
+ server,
242
+ async close() {
243
+ await closeNodeServer(server);
244
+ },
245
+ };
246
+ };
247
+ }
248
+
249
+ export async function startInstalledFlowDenoHttpHost(options = {}) {
250
+ return startInstalledFlowAppHost({
251
+ ...options,
252
+ serveHttp:
253
+ options.serveHttp ?? createDenoServeHttpAdapter(options),
254
+ });
255
+ }
256
+
257
+ export async function startInstalledFlowBunHttpHost(options = {}) {
258
+ return startInstalledFlowAppHost({
259
+ ...options,
260
+ serveHttp:
261
+ options.serveHttp ?? createBunServeHttpAdapter(options),
262
+ });
263
+ }
264
+
265
+ export async function startInstalledFlowNodeHttpHost(options = {}) {
266
+ return startInstalledFlowAppHost({
267
+ ...options,
268
+ serveHttp:
269
+ options.serveHttp ?? createNodeServeHttpAdapter(options),
270
+ });
271
+ }
272
+
273
+ export default {
274
+ createBunServeHttpAdapter,
275
+ createDenoServeHttpAdapter,
276
+ createNodeServeHttpAdapter,
277
+ startInstalledFlowBunHttpHost,
278
+ startInstalledFlowDenoHttpHost,
279
+ startInstalledFlowNodeHttpHost,
280
+ };
@@ -0,0 +1,91 @@
1
+ export {
2
+ HostedRuntimeAdapter,
3
+ HostedRuntimeAuthority,
4
+ HostedRuntimeBindingDirection,
5
+ HostedRuntimeEngine,
6
+ HostedRuntimeKind,
7
+ HostedRuntimeStartupPhase,
8
+ HostedRuntimeTransport,
9
+ } from "./constants.js";
10
+ export {
11
+ bindCompiledRuntimeAbi,
12
+ DefaultRequiredRuntimeExportRoles,
13
+ } from "./runtimeAbi.js";
14
+ export {
15
+ bindCompiledInvocationAbi,
16
+ DefaultRequiredInvocationExportRoles,
17
+ FlowFrameDescriptorLayout,
18
+ FlowInvocationDescriptorLayout,
19
+ } from "./invocationAbi.js";
20
+ export {
21
+ bindCompiledDescriptorAbi,
22
+ DefaultRequiredDescriptorExportRoles,
23
+ FlowNodeDispatchDescriptorLayout,
24
+ SignedArtifactDependencyDescriptorLayout,
25
+ } from "./descriptorAbi.js";
26
+ export { instantiateEmbeddedDependencies } from "./dependencyRuntime.js";
27
+ export { bindCompiledFlowRuntimeHost } from "./compiledFlowRuntimeHost.js";
28
+ export {
29
+ evaluateHostedCapabilitySupport,
30
+ listHostedRuntimeCapabilities,
31
+ normalizeHostedRuntimeEngine,
32
+ } from "./profile.js";
33
+ export {
34
+ createInstalledFlowBrowserFetchEventListener,
35
+ matchesInstalledFlowHttpBindingRequest,
36
+ startInstalledFlowBrowserFetchHost,
37
+ } from "./browserHostAdapters.js";
38
+ export {
39
+ resolveInstalledFlowAutoHostEngine,
40
+ startInstalledFlowAutoHost,
41
+ } from "./autoHost.js";
42
+ export {
43
+ listInstalledFlowHttpBindings,
44
+ startInstalledFlowAppHost,
45
+ } from "./appHost.js";
46
+ export {
47
+ createBunServeHttpAdapter,
48
+ createDenoServeHttpAdapter,
49
+ createNodeServeHttpAdapter,
50
+ startInstalledFlowBunHttpHost,
51
+ startInstalledFlowDenoHttpHost,
52
+ startInstalledFlowNodeHttpHost,
53
+ } from "./httpHostAdapters.js";
54
+ export {
55
+ createFetchResponse,
56
+ createInstalledFlowFetchHandler,
57
+ normalizeFetchRequest,
58
+ } from "./fetchService.js";
59
+ export {
60
+ createInstalledFlowApp,
61
+ installWorkspacePluginPackage,
62
+ installWorkspacePackageReference,
63
+ normalizeInstalledFlowWorkspace,
64
+ removeWorkspacePackageReference,
65
+ readInstalledFlowWorkspace,
66
+ resolveInstalledFlowWorkspace,
67
+ uninstallWorkspacePluginPackage,
68
+ updateWorkspacePackageReference,
69
+ writeInstalledFlowWorkspace,
70
+ } from "./workspace.js";
71
+ export {
72
+ createInstalledFlowHost,
73
+ createInstalledFlowService,
74
+ createInstalledFlowHostedRuntimePlan,
75
+ discoverInstalledPluginPackages,
76
+ loadInstalledPluginPackage,
77
+ normalizeInstalledPluginPackage,
78
+ registerInstalledPluginPackage,
79
+ registerInstalledPluginPackages,
80
+ } from "./installedFlowHost.js";
81
+ export {
82
+ createCommandPackageManager,
83
+ createNodeCommandRunner,
84
+ createNpmPackageManager,
85
+ } from "./packageManagers.js";
86
+ export {
87
+ normalizeHostedBinding,
88
+ normalizeHostedRuntime,
89
+ normalizeHostedRuntimePlan,
90
+ summarizeHostedRuntimePlan,
91
+ } from "./normalize.js";