orkestrator 2.25.0-alpha → 2.26.0-alpha
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/dist/client.js +1 -1
- package/dist/main.js +153 -26
- package/package.json +3 -3
package/dist/client.js
CHANGED
|
@@ -7444,7 +7444,7 @@ import path4 from "path";
|
|
|
7444
7444
|
// ../../apps/backend/package.json
|
|
7445
7445
|
var package_default = {
|
|
7446
7446
|
name: "@orkestrator/backend",
|
|
7447
|
-
version: "2.
|
|
7447
|
+
version: "2.26.0-alpha",
|
|
7448
7448
|
private: true,
|
|
7449
7449
|
type: "module",
|
|
7450
7450
|
exports: {
|
package/dist/main.js
CHANGED
|
@@ -815,7 +815,7 @@ var package_default;
|
|
|
815
815
|
var init_package = __esm(() => {
|
|
816
816
|
package_default = {
|
|
817
817
|
name: "@orkestrator/backend",
|
|
818
|
-
version: "2.
|
|
818
|
+
version: "2.26.0-alpha",
|
|
819
819
|
private: true,
|
|
820
820
|
type: "module",
|
|
821
821
|
exports: {
|
|
@@ -126665,12 +126665,12 @@ function toNodeProjectSummary(nodeId, raw, index) {
|
|
|
126665
126665
|
ref,
|
|
126666
126666
|
key: projectKey(ref),
|
|
126667
126667
|
name: boundedText(raw.name) || "Untitled project",
|
|
126668
|
-
order: typeof raw.order === "number" ? raw.order : index,
|
|
126668
|
+
order: typeof raw.order === "number" && Number.isFinite(raw.order) ? raw.order : index,
|
|
126669
126669
|
...typeof raw.folder === "string" && raw.folder ? { folder: boundedText(raw.folder, 64) } : {},
|
|
126670
126670
|
...hint ? { repositoryHint: hint } : {}
|
|
126671
126671
|
};
|
|
126672
126672
|
}
|
|
126673
|
-
function toNodeEnvironmentSummary(nodeId, raw) {
|
|
126673
|
+
function toNodeEnvironmentSummary(nodeId, raw, index = 0) {
|
|
126674
126674
|
if (typeof raw.id !== "string" || !raw.id || typeof raw.projectId !== "string")
|
|
126675
126675
|
return null;
|
|
126676
126676
|
const ref = environmentRef(nodeId, raw.id);
|
|
@@ -126679,6 +126679,7 @@ function toNodeEnvironmentSummary(nodeId, raw) {
|
|
|
126679
126679
|
key: environmentKey(ref),
|
|
126680
126680
|
projectId: raw.projectId,
|
|
126681
126681
|
name: boundedText(raw.name) || "Environment",
|
|
126682
|
+
order: typeof raw.order === "number" && Number.isFinite(raw.order) ? raw.order : index,
|
|
126682
126683
|
...typeof raw.branch === "string" ? { branch: boundedText(raw.branch) } : {},
|
|
126683
126684
|
status: boundedText(raw.status, 32) || "stopped",
|
|
126684
126685
|
activity: normalizeActivity(raw.agentActivityState),
|
|
@@ -128468,6 +128469,7 @@ class NodeCoordination {
|
|
|
128468
128469
|
}
|
|
128469
128470
|
|
|
128470
128471
|
// ../../apps/backend/src/core/nodes/node-portal.ts
|
|
128472
|
+
init_backend_profile();
|
|
128471
128473
|
init_federation();
|
|
128472
128474
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
128473
128475
|
|
|
@@ -128821,6 +128823,7 @@ class NodeReadModel {
|
|
|
128821
128823
|
changeTimer = null;
|
|
128822
128824
|
started = false;
|
|
128823
128825
|
listeners = new Set;
|
|
128826
|
+
self = { id: null, summary: null, stale: true, reading: null };
|
|
128824
128827
|
constructor(options2) {
|
|
128825
128828
|
this.options = options2;
|
|
128826
128829
|
}
|
|
@@ -128855,7 +128858,7 @@ class NodeReadModel {
|
|
|
128855
128858
|
this.listeners.add(listener);
|
|
128856
128859
|
return () => this.listeners.delete(listener);
|
|
128857
128860
|
}
|
|
128858
|
-
async summaries(nodeIds) {
|
|
128861
|
+
async summaries(nodeIds, options2 = {}) {
|
|
128859
128862
|
const list = await this.options.nodes.listNodes();
|
|
128860
128863
|
for (const view of list.nodes)
|
|
128861
128864
|
this.views.set(view.nodeId, view);
|
|
@@ -128878,7 +128881,73 @@ class NodeReadModel {
|
|
|
128878
128881
|
environments: state ? [...state.environmentsByProject.values()].flat() : []
|
|
128879
128882
|
});
|
|
128880
128883
|
}
|
|
128881
|
-
|
|
128884
|
+
const self = options2.includeSelf ? await this.readSelf() : null;
|
|
128885
|
+
return { selfId: await this.options.nodes.selfIdentity(), nodes, ...self ? { self } : {} };
|
|
128886
|
+
}
|
|
128887
|
+
noteLocalEvent(event, payload) {
|
|
128888
|
+
if (event === RESOURCE_CHANGED_EVENT) {
|
|
128889
|
+
const resource = payload?.resource;
|
|
128890
|
+
if (resource !== "project" && resource !== "environment")
|
|
128891
|
+
return;
|
|
128892
|
+
} else if (event !== BACKEND_PROFILE_CHANGED_EVENT) {
|
|
128893
|
+
return;
|
|
128894
|
+
}
|
|
128895
|
+
this.self.stale = true;
|
|
128896
|
+
if (this.self.id)
|
|
128897
|
+
this.noteChanged(this.self.id);
|
|
128898
|
+
}
|
|
128899
|
+
now() {
|
|
128900
|
+
return (this.options.now ?? Date.now)();
|
|
128901
|
+
}
|
|
128902
|
+
readSelf() {
|
|
128903
|
+
const local = this.options.local;
|
|
128904
|
+
if (!local)
|
|
128905
|
+
return Promise.resolve(null);
|
|
128906
|
+
if (this.self.summary && !this.self.stale)
|
|
128907
|
+
return Promise.resolve(this.self.summary);
|
|
128908
|
+
if (this.self.reading)
|
|
128909
|
+
return this.self.reading;
|
|
128910
|
+
this.self.stale = false;
|
|
128911
|
+
const run = (async () => {
|
|
128912
|
+
try {
|
|
128913
|
+
const [nodeId, profile] = await Promise.all([
|
|
128914
|
+
this.options.nodes.selfIdentity(),
|
|
128915
|
+
this.options.nodes.selfProfile()
|
|
128916
|
+
]);
|
|
128917
|
+
this.self.id = nodeId;
|
|
128918
|
+
const { projects, truncated: projectsTruncated } = await readProjects(nodeId, local.invoke);
|
|
128919
|
+
const environments = await this.readEnvironments(nodeId, local.invoke, projects.map((project) => project.ref.projectId));
|
|
128920
|
+
let truncated = projectsTruncated;
|
|
128921
|
+
const rows = [];
|
|
128922
|
+
for (const project of projects) {
|
|
128923
|
+
const result = environments.get(project.ref.projectId);
|
|
128924
|
+
if (!result) {
|
|
128925
|
+
this.self.stale = true;
|
|
128926
|
+
truncated = true;
|
|
128927
|
+
rows.push(...this.self.summary?.environments.filter((row) => row.projectId === project.ref.projectId) ?? []);
|
|
128928
|
+
continue;
|
|
128929
|
+
}
|
|
128930
|
+
truncated ||= result.truncated;
|
|
128931
|
+
rows.push(...result.environments);
|
|
128932
|
+
}
|
|
128933
|
+
this.self.summary = {
|
|
128934
|
+
nodeId,
|
|
128935
|
+
displayName: profile.displayName,
|
|
128936
|
+
...profile.colorToken ? { colorToken: profile.colorToken } : {},
|
|
128937
|
+
loadedAt: this.now(),
|
|
128938
|
+
truncated,
|
|
128939
|
+
projects,
|
|
128940
|
+
environments: rows
|
|
128941
|
+
};
|
|
128942
|
+
} catch {
|
|
128943
|
+
this.self.stale = true;
|
|
128944
|
+
}
|
|
128945
|
+
return this.self.summary;
|
|
128946
|
+
})().finally(() => {
|
|
128947
|
+
this.self.reading = null;
|
|
128948
|
+
});
|
|
128949
|
+
this.self.reading = run;
|
|
128950
|
+
return run;
|
|
128882
128951
|
}
|
|
128883
128952
|
async sync() {
|
|
128884
128953
|
if (!this.started)
|
|
@@ -128918,7 +128987,7 @@ class NodeReadModel {
|
|
|
128918
128987
|
prefixes: NODE_SUMMARY_EVENT_PREFIXES,
|
|
128919
128988
|
...this.options.fetchImpl ? { fetchImpl: this.options.fetchImpl } : {},
|
|
128920
128989
|
onOpen: () => void this.requestHydrate(view.nodeId),
|
|
128921
|
-
onEvent: (event, payload) => this.handleEvent(view.nodeId, epoch, event, payload)
|
|
128990
|
+
onEvent: (event, payload) => void this.handleEvent(view.nodeId, epoch, event, payload)
|
|
128922
128991
|
});
|
|
128923
128992
|
state.stream.start();
|
|
128924
128993
|
this.requestHydrate(view.nodeId);
|
|
@@ -128958,7 +129027,7 @@ class NodeReadModel {
|
|
|
128958
129027
|
}
|
|
128959
129028
|
return state;
|
|
128960
129029
|
}
|
|
128961
|
-
handleEvent(nodeId, epoch, event, payload) {
|
|
129030
|
+
async handleEvent(nodeId, epoch, event, payload) {
|
|
128962
129031
|
const state = this.states.get(nodeId);
|
|
128963
129032
|
if (!state || state.epoch !== epoch)
|
|
128964
129033
|
return;
|
|
@@ -128973,7 +129042,12 @@ class NodeReadModel {
|
|
|
128973
129042
|
return;
|
|
128974
129043
|
}
|
|
128975
129044
|
if (event === BACKEND_PROFILE_CHANGED_EVENT) {
|
|
128976
|
-
|
|
129045
|
+
try {
|
|
129046
|
+
await this.options.nodes.recordProfile(nodeId, parseBackendProfile(payload));
|
|
129047
|
+
if (this.started && this.states.get(nodeId) === state && state.epoch === epoch) {
|
|
129048
|
+
this.noteChanged(nodeId);
|
|
129049
|
+
}
|
|
129050
|
+
} catch {}
|
|
128977
129051
|
return;
|
|
128978
129052
|
}
|
|
128979
129053
|
if (event !== RESOURCE_CHANGED_EVENT || !payload || typeof payload !== "object")
|
|
@@ -129025,7 +129099,7 @@ class NodeReadModel {
|
|
|
129025
129099
|
if (!state || !link?.connected)
|
|
129026
129100
|
return;
|
|
129027
129101
|
const epoch = state.epoch;
|
|
129028
|
-
const updates = await this.readEnvironments(nodeId, link, projectIds);
|
|
129102
|
+
const updates = await this.readEnvironments(nodeId, (command, args) => link.invoke(command, args), projectIds);
|
|
129029
129103
|
if (this.states.get(nodeId) !== state || state.epoch !== epoch)
|
|
129030
129104
|
return;
|
|
129031
129105
|
for (const [projectId, result] of updates) {
|
|
@@ -129060,17 +129134,13 @@ class NodeReadModel {
|
|
|
129060
129134
|
return;
|
|
129061
129135
|
const epoch = state.epoch;
|
|
129062
129136
|
try {
|
|
129063
|
-
const
|
|
129064
|
-
const
|
|
129065
|
-
const
|
|
129066
|
-
const project = raw && typeof raw === "object" ? toNodeProjectSummary(state.nodeId, raw, index) : null;
|
|
129067
|
-
return project ? [project] : [];
|
|
129068
|
-
}).sort((a, b) => a.order - b.order);
|
|
129069
|
-
const environments = await this.readEnvironments(state.nodeId, link, projects.map((project) => project.ref.projectId));
|
|
129137
|
+
const invoke = (command, args) => link.invoke(command, args);
|
|
129138
|
+
const { projects, truncated: projectsTruncated } = await readProjects(state.nodeId, invoke);
|
|
129139
|
+
const environments = await this.readEnvironments(state.nodeId, invoke, projects.map((project) => project.ref.projectId));
|
|
129070
129140
|
if (this.states.get(state.nodeId) !== state || state.epoch !== epoch)
|
|
129071
129141
|
return;
|
|
129072
129142
|
const environmentsByProject = new Map;
|
|
129073
|
-
let truncated =
|
|
129143
|
+
let truncated = projectsTruncated;
|
|
129074
129144
|
let failed = false;
|
|
129075
129145
|
for (const project of projects) {
|
|
129076
129146
|
const result = environments.get(project.ref.projectId);
|
|
@@ -129098,19 +129168,19 @@ class NodeReadModel {
|
|
|
129098
129168
|
this.noteChanged(state.nodeId);
|
|
129099
129169
|
}
|
|
129100
129170
|
}
|
|
129101
|
-
async readEnvironments(nodeId,
|
|
129171
|
+
async readEnvironments(nodeId, invoke, projectIds) {
|
|
129102
129172
|
const results = new Map;
|
|
129103
129173
|
let next = 0;
|
|
129104
129174
|
const worker = async () => {
|
|
129105
129175
|
while (next < projectIds.length) {
|
|
129106
129176
|
const projectId = projectIds[next++];
|
|
129107
129177
|
try {
|
|
129108
|
-
const raw = await
|
|
129178
|
+
const raw = await invoke("get_environment_snapshots", { projectId });
|
|
129109
129179
|
const list = Array.isArray(raw) ? raw : [];
|
|
129110
|
-
const environments = list.
|
|
129111
|
-
const environment = entry && typeof entry === "object" ? toNodeEnvironmentSummary(nodeId, entry) : null;
|
|
129180
|
+
const environments = list.flatMap((entry, index) => {
|
|
129181
|
+
const environment = entry && typeof entry === "object" ? toNodeEnvironmentSummary(nodeId, entry, index) : null;
|
|
129112
129182
|
return environment ? [environment] : [];
|
|
129113
|
-
});
|
|
129183
|
+
}).sort((a, b) => a.order - b.order).slice(0, MAX_SUMMARY_ENVIRONMENTS_PER_PROJECT);
|
|
129114
129184
|
results.set(projectId, {
|
|
129115
129185
|
environments,
|
|
129116
129186
|
truncated: list.length > MAX_SUMMARY_ENVIRONMENTS_PER_PROJECT
|
|
@@ -129195,6 +129265,15 @@ class NodeReadModel {
|
|
|
129195
129265
|
}
|
|
129196
129266
|
}
|
|
129197
129267
|
}
|
|
129268
|
+
async function readProjects(nodeId, invoke) {
|
|
129269
|
+
const raw = await invoke("get_projects");
|
|
129270
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
129271
|
+
const projects = list.slice(0, MAX_SUMMARY_PROJECTS).flatMap((entry, index) => {
|
|
129272
|
+
const project = entry && typeof entry === "object" ? toNodeProjectSummary(nodeId, entry, index) : null;
|
|
129273
|
+
return project ? [project] : [];
|
|
129274
|
+
}).sort((a, b) => a.order - b.order);
|
|
129275
|
+
return { projects, truncated: list.length > MAX_SUMMARY_PROJECTS };
|
|
129276
|
+
}
|
|
129198
129277
|
function safeMessage(error) {
|
|
129199
129278
|
const message = error instanceof Error ? error.message : String(error);
|
|
129200
129279
|
return message.replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/Bearer\s+\S+/gi, "Bearer [redacted]").slice(0, 200);
|
|
@@ -129555,6 +129634,7 @@ class NodePortal {
|
|
|
129555
129634
|
nodes: options2.nodes,
|
|
129556
129635
|
emit: options2.emit,
|
|
129557
129636
|
...options2.summaries ? { persistence: options2.summaries } : {},
|
|
129637
|
+
...options2.local ? { local: options2.local } : {},
|
|
129558
129638
|
...options2.fetchImpl ? { fetchImpl: options2.fetchImpl } : {},
|
|
129559
129639
|
...options2.now ? { now: options2.now } : {}
|
|
129560
129640
|
});
|
|
@@ -129573,11 +129653,16 @@ class NodePortal {
|
|
|
129573
129653
|
this.grants.revokeAll();
|
|
129574
129654
|
this.workspaces.clear();
|
|
129575
129655
|
}
|
|
129576
|
-
summaries(nodeIds) {
|
|
129656
|
+
summaries(nodeIds, includeSelf) {
|
|
129577
129657
|
if (nodeIds !== undefined && (!Array.isArray(nodeIds) || !nodeIds.every(isBackendId))) {
|
|
129578
129658
|
throw new Error("Expected nodeIds to be node identities.");
|
|
129579
129659
|
}
|
|
129580
|
-
|
|
129660
|
+
if (includeSelf !== undefined && typeof includeSelf !== "boolean") {
|
|
129661
|
+
throw new Error("Expected includeSelf to be a boolean.");
|
|
129662
|
+
}
|
|
129663
|
+
return this.readModel.summaries(nodeIds, {
|
|
129664
|
+
includeSelf: includeSelf === true
|
|
129665
|
+
});
|
|
129581
129666
|
}
|
|
129582
129667
|
async invokeOnNode(args) {
|
|
129583
129668
|
const nodeId = args.nodeId;
|
|
@@ -129598,6 +129683,11 @@ class NodePortal {
|
|
|
129598
129683
|
await this.afterRouted?.(nodeId, args.command, commandArgs, result.result).catch((error) => {
|
|
129599
129684
|
console.warn("[nodes] Post-command step on a node failed:", error instanceof Error ? error.name : "unknown");
|
|
129600
129685
|
});
|
|
129686
|
+
if (args.command === "update_backend_profile") {
|
|
129687
|
+
try {
|
|
129688
|
+
await this.options.nodes.recordProfile(nodeId, parseBackendProfile(result.result));
|
|
129689
|
+
} catch {}
|
|
129690
|
+
}
|
|
129601
129691
|
this.readModel.requestHydrate(nodeId);
|
|
129602
129692
|
}
|
|
129603
129693
|
return result;
|
|
@@ -129850,6 +129940,9 @@ class NodeService {
|
|
|
129850
129940
|
selfIdentity() {
|
|
129851
129941
|
return this.self();
|
|
129852
129942
|
}
|
|
129943
|
+
selfProfile() {
|
|
129944
|
+
return this.storage.getBackendProfile();
|
|
129945
|
+
}
|
|
129853
129946
|
async status() {
|
|
129854
129947
|
const registry = await this.currentRegistry();
|
|
129855
129948
|
const mode = this.mode();
|
|
@@ -130490,6 +130583,37 @@ class NodeService {
|
|
|
130490
130583
|
console.warn("[nodes] Could not record a node verification:", errorCode(error));
|
|
130491
130584
|
}
|
|
130492
130585
|
}
|
|
130586
|
+
async recordProfile(nodeId, profile) {
|
|
130587
|
+
try {
|
|
130588
|
+
await this.mutateRegistry((registry) => {
|
|
130589
|
+
const node = registry.nodes.find((entry) => entry.nodeId === nodeId);
|
|
130590
|
+
const cached = node?.profileCache;
|
|
130591
|
+
if (!node || cached && cached.revision > profile.revision) {
|
|
130592
|
+
return { next: null, result: undefined };
|
|
130593
|
+
}
|
|
130594
|
+
if (cached && cached.revision === profile.revision && cached.displayName === profile.displayName && cached.colorToken === profile.colorToken) {
|
|
130595
|
+
return { next: null, result: undefined };
|
|
130596
|
+
}
|
|
130597
|
+
const updated = {
|
|
130598
|
+
...node,
|
|
130599
|
+
profileCache: {
|
|
130600
|
+
displayName: profile.displayName,
|
|
130601
|
+
colorToken: profile.colorToken,
|
|
130602
|
+
revision: profile.revision
|
|
130603
|
+
}
|
|
130604
|
+
};
|
|
130605
|
+
return {
|
|
130606
|
+
next: {
|
|
130607
|
+
...registry,
|
|
130608
|
+
nodes: registry.nodes.map((entry) => entry.nodeId === nodeId ? updated : entry)
|
|
130609
|
+
},
|
|
130610
|
+
result: undefined
|
|
130611
|
+
};
|
|
130612
|
+
});
|
|
130613
|
+
} catch (error) {
|
|
130614
|
+
console.warn("[nodes] Could not record a node profile:", errorCode(error));
|
|
130615
|
+
}
|
|
130616
|
+
}
|
|
130493
130617
|
async adoptIdentity(provisionalId, installationId) {
|
|
130494
130618
|
const selfId = await this.self();
|
|
130495
130619
|
if (installationId === selfId)
|
|
@@ -156958,6 +157082,7 @@ function registerBackendProfileCommands(register) {
|
|
|
156958
157082
|
register("update_backend_profile", async (args, context) => {
|
|
156959
157083
|
const profile = await context.storage.updateBackendProfile(parseBackendProfileUpdate(args));
|
|
156960
157084
|
context.emit(BACKEND_PROFILE_CHANGED_EVENT, profile);
|
|
157085
|
+
context.nodePortal?.readModel.noteLocalEvent(BACKEND_PROFILE_CHANGED_EVENT, profile);
|
|
156961
157086
|
return profile;
|
|
156962
157087
|
});
|
|
156963
157088
|
}
|
|
@@ -157062,8 +157187,8 @@ function registerNodeCommands(register) {
|
|
|
157062
157187
|
return nodes(context).trustHostKey(args.probeId);
|
|
157063
157188
|
});
|
|
157064
157189
|
register("get_node_summaries", async (args, context) => {
|
|
157065
|
-
assertOnlyKeys(args, ["nodeIds"], "arguments");
|
|
157066
|
-
return portal(context).summaries(args.nodeIds);
|
|
157190
|
+
assertOnlyKeys(args, ["nodeIds", "includeSelf"], "arguments");
|
|
157191
|
+
return portal(context).summaries(args.nodeIds, args.includeSelf);
|
|
157067
157192
|
});
|
|
157068
157193
|
register("invoke_on_node", async (args, context) => {
|
|
157069
157194
|
assertOnlyKeys(args, ["nodeId", "command", "args", "requestKey"], "arguments");
|
|
@@ -227349,6 +227474,7 @@ class OrkestratorBackend {
|
|
|
227349
227474
|
}
|
|
227350
227475
|
storage.setResourceChangeListener((change) => {
|
|
227351
227476
|
options2.emit(RESOURCE_CHANGED_EVENT, change);
|
|
227477
|
+
this.nodePortal?.readModel.noteLocalEvent(RESOURCE_CHANGED_EVENT, change);
|
|
227352
227478
|
});
|
|
227353
227479
|
const context = {
|
|
227354
227480
|
storage,
|
|
@@ -227416,6 +227542,7 @@ class OrkestratorBackend {
|
|
|
227416
227542
|
this.nodePortal = new NodePortal({
|
|
227417
227543
|
nodes: this.nodes,
|
|
227418
227544
|
emit: (event, payload) => options2.emit(event, payload),
|
|
227545
|
+
local: { invoke: (command, args = {}) => this.invoke(command, args) },
|
|
227419
227546
|
summaries: {
|
|
227420
227547
|
load: () => storage.loadNodeSummariesCache(),
|
|
227421
227548
|
save: (value) => storage.saveNodeSummariesCache(value)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "orkestrator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.26.0-alpha",
|
|
4
4
|
"description": "Run the Orkestrator backend service without the desktop application",
|
|
5
5
|
"homepage": "https://orkestrator.dev",
|
|
6
6
|
"repository": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"sharp": "0.35.5"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@orkestrator/protocol": "2.
|
|
43
|
-
"@orkestrator/toolchain": "2.
|
|
42
|
+
"@orkestrator/protocol": "2.26.0-alpha",
|
|
43
|
+
"@orkestrator/toolchain": "2.26.0-alpha"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"bun": ">=1.4.3"
|