@ornncompute/cli 0.2.23 → 1.0.25
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/README.md +304 -420
- package/package.json +12 -6
- package/src/admin-capacity-reservations.mjs +280 -0
- package/src/admin-command-handlers.mjs +57 -0
- package/src/admin-enrollment.mjs +680 -0
- package/src/admin-mutations.mjs +187 -0
- package/src/admin-node-diagnostics.mjs +412 -0
- package/src/admin-node-lifecycle.mjs +908 -0
- package/src/admin-shell.mjs +322 -0
- package/src/api-client.mjs +81 -28
- package/src/atomic-output.mjs +112 -0
- package/src/auth-store.mjs +182 -11
- package/src/capability-catalog.mjs +7 -0
- package/src/catalog-shell.mjs +1350 -0
- package/src/cli.mjs +123 -10266
- package/src/connect-mode-handlers.mjs +170 -0
- package/src/device-auth.mjs +178 -61
- package/src/evidence-commands.mjs +1340 -0
- package/src/external-bucket-commands.mjs +1374 -0
- package/src/interaction-runtime.mjs +310 -0
- package/src/kubernetes-commands.mjs +2229 -0
- package/src/launch-mode-handlers.mjs +119 -0
- package/src/machine-reference.mjs +277 -0
- package/src/mount-commands.mjs +1146 -0
- package/src/session-commands.mjs +565 -0
- package/src/slurm-commands.mjs +263 -0
- package/src/startup.mjs +34 -11
- package/src/storage-commands.mjs +1144 -0
- package/src/update.mjs +172 -24
- package/src/vm-commands.mjs +4273 -0
- package/src/vpn-secret-output.mjs +150 -0
- package/src/wait-runtime.mjs +231 -0
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +126 -0
- package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
- package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
- package/vendor/capabilities/capabilities/admin.js +89 -0
- package/vendor/capabilities/capabilities/capacity-offers.js +368 -0
- package/vendor/capabilities/capabilities/cli.js +156 -0
- package/vendor/capabilities/capabilities/clusters.js +150 -1
- package/vendor/capabilities/capabilities/compute.js +522 -0
- package/vendor/capabilities/capabilities/evidence.js +145 -0
- package/vendor/capabilities/capabilities/external-buckets.js +191 -0
- package/vendor/capabilities/capabilities/fleet.js +21 -5
- package/vendor/capabilities/capabilities/identity.js +114 -3
- package/vendor/capabilities/capabilities/listings.js +14 -8
- package/vendor/capabilities/capabilities/mounts.js +176 -0
- package/vendor/capabilities/capabilities/networks.js +1257 -0
- package/vendor/capabilities/capabilities/observability.js +38 -8
- package/vendor/capabilities/capabilities/tokens.js +20 -8
- package/vendor/capabilities/capabilities/volumes.js +178 -0
- package/vendor/capabilities/capability.js +19 -13
- package/vendor/capabilities/cli-catalog.js +65 -0
- package/vendor/capabilities/cli-migrations.js +288 -0
- package/vendor/capabilities/cli.js +7 -0
- package/vendor/capabilities/software-catalog.js +36 -0
- package/vendor/capabilities/spec-catalog.js +2 -2
- package/src/catalog-dispatch.mjs +0 -605
- package/src/color.mjs +0 -71
- package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
- package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
- package/vendor/capabilities/capabilities/identity.d.ts +0 -3
- package/vendor/capabilities/capabilities/listings.d.ts +0 -51
- package/vendor/capabilities/capabilities/observability.d.ts +0 -5
- package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
- package/vendor/capabilities/capabilities/staff-access.js +0 -278
- package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
- package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
- package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
- package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
- package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
- package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
- package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
- package/vendor/capabilities/capabilities/staff-shared.js +0 -98
- package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
- package/vendor/capabilities/capabilities/users.d.ts +0 -2
- package/vendor/capabilities/capability.d.ts +0 -93
- package/vendor/capabilities/catalog.d.ts +0 -3
- package/vendor/capabilities/catalog.js +0 -26
- package/vendor/capabilities/index.d.ts +0 -16
- package/vendor/capabilities/index.js +0 -16
- package/vendor/capabilities/names.d.ts +0 -6
- package/vendor/capabilities/role.d.ts +0 -6
- package/vendor/capabilities/role.js +0 -17
- package/vendor/capabilities/spec-catalog.d.ts +0 -78
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { spawn as spawnProcessDefault } from "node:child_process";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
clusterModeConnectCandidates,
|
|
6
|
+
launchClusterMode,
|
|
7
|
+
readClusterModeConnectContext,
|
|
8
|
+
resolveClusterModeConnectTarget,
|
|
9
|
+
} from "./kubernetes-commands.mjs";
|
|
10
|
+
|
|
11
|
+
const SAFE_HOST = /^[A-Za-z0-9][A-Za-z0-9.:-]*$/;
|
|
12
|
+
const SAFE_USER = /^[A-Za-z_][A-Za-z0-9_.-]*[$]?$/;
|
|
13
|
+
const connectCache = new WeakMap();
|
|
14
|
+
|
|
15
|
+
function text(value) {
|
|
16
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function expandHome(path) {
|
|
20
|
+
if (path === "~") return homedir();
|
|
21
|
+
return path.startsWith("~/") ? `${homedir()}${path.slice(1)}` : path;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function shellQuote(value) {
|
|
25
|
+
const rendered = String(value);
|
|
26
|
+
return /^[A-Za-z0-9_./:@%+=,-]+$/.test(rendered)
|
|
27
|
+
? rendered
|
|
28
|
+
: `'${rendered.replaceAll("'", `'\\''`)}'`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function commandText(invocation) {
|
|
32
|
+
return [invocation.command, ...invocation.args].map(shellQuote).join(" ");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function safeLogin(runtime, credentials, input) {
|
|
36
|
+
const credentialType = text(credentials.credential_type);
|
|
37
|
+
if (credentialType && credentialType !== "login-ssh") {
|
|
38
|
+
runtime.error("The gateway returned a non-Slurm native-access credential.", {
|
|
39
|
+
code: "invalid_slurm_access",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const host = text(credentials.login_host);
|
|
43
|
+
if (!host) return null;
|
|
44
|
+
if (!SAFE_HOST.test(host)) {
|
|
45
|
+
runtime.error("The gateway returned an unsafe Slurm SSH host.", {
|
|
46
|
+
code: "invalid_slurm_access",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const user = text(input.user) ?? text(credentials.login_user);
|
|
50
|
+
if (user && !SAFE_USER.test(user)) {
|
|
51
|
+
runtime.usage("--user must be a valid SSH login name.", {
|
|
52
|
+
code: "invalid_ssh_user",
|
|
53
|
+
field: "user",
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const rawPort = credentials.login_port ?? 22;
|
|
57
|
+
const port = Number(rawPort);
|
|
58
|
+
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
59
|
+
runtime.error("The gateway returned an invalid Slurm SSH port.", {
|
|
60
|
+
code: "invalid_slurm_access",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const identityFile = text(input.identityFile);
|
|
64
|
+
if (identityFile && /[\u0000-\u001f\u007f]/.test(identityFile)) {
|
|
65
|
+
runtime.usage("--identity-file contains an invalid path.", {
|
|
66
|
+
code: "invalid_identity_file",
|
|
67
|
+
field: "identityFile",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const args = [];
|
|
71
|
+
if (identityFile) args.push("-i", expandHome(identityFile));
|
|
72
|
+
if (port !== 22) args.push("-p", String(port));
|
|
73
|
+
args.push("--", user ? `${user}@${host}` : host);
|
|
74
|
+
return {
|
|
75
|
+
args,
|
|
76
|
+
command: "ssh",
|
|
77
|
+
host,
|
|
78
|
+
port,
|
|
79
|
+
user,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function privateTransport(state) {
|
|
84
|
+
if (!state.privateNetwork) return null;
|
|
85
|
+
return {
|
|
86
|
+
private_network: {
|
|
87
|
+
id: text(state.privateNetwork.id),
|
|
88
|
+
name: text(state.privateNetwork.name) ?? text(state.privateNetwork.label),
|
|
89
|
+
},
|
|
90
|
+
vpn_connection: state.vpnConnection
|
|
91
|
+
? {
|
|
92
|
+
id: text(state.vpnConnection.id),
|
|
93
|
+
name: text(state.vpnConnection.name) ?? text(state.vpnConnection.label),
|
|
94
|
+
status: text(state.vpnConnection.lifecycle?.class) ?? text(state.vpnConnection.status),
|
|
95
|
+
}
|
|
96
|
+
: null,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function retryConnectAction(cluster) {
|
|
101
|
+
return {
|
|
102
|
+
command: `ornn connect ${cluster.id}`,
|
|
103
|
+
description: "Retry this exact Slurm cluster after its SSH login becomes ready.",
|
|
104
|
+
type: "command",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function spawnSsh(invocation, spawnProcess) {
|
|
109
|
+
return new Promise((resolve, reject) => {
|
|
110
|
+
const child = spawnProcess(invocation.command, invocation.args, { stdio: "inherit" });
|
|
111
|
+
child.on("error", reject);
|
|
112
|
+
child.on("exit", (code, signal) => {
|
|
113
|
+
resolve({ code: signal ? 130 : (code ?? 0), signal: signal ?? null });
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createSlurmConnectModeHandler({ spawnProcess = spawnProcessDefault } = {}) {
|
|
119
|
+
return {
|
|
120
|
+
mode: "slurm",
|
|
121
|
+
candidates(state) {
|
|
122
|
+
return clusterModeConnectCandidates(state, "slurm");
|
|
123
|
+
},
|
|
124
|
+
resolve(state) {
|
|
125
|
+
return resolveClusterModeConnectTarget(state, "slurm");
|
|
126
|
+
},
|
|
127
|
+
async matches(state) {
|
|
128
|
+
if (state.mode && state.mode !== "slurm" && state.cluster?.access_mode !== "slurm") {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
const prepared = await readClusterModeConnectContext(state, "slurm");
|
|
132
|
+
if (!prepared) return false;
|
|
133
|
+
connectCache.set(state, prepared);
|
|
134
|
+
return true;
|
|
135
|
+
},
|
|
136
|
+
async connect(state) {
|
|
137
|
+
const runtime = state.interaction;
|
|
138
|
+
const prepared =
|
|
139
|
+
connectCache.get(state) ?? (await readClusterModeConnectContext(state, "slurm"));
|
|
140
|
+
if (!prepared) {
|
|
141
|
+
runtime.error("The selected Slurm cluster could not be rediscovered.", {
|
|
142
|
+
code: "cluster_not_found",
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (state.input.output || state.input.overwrite) {
|
|
146
|
+
runtime.usage(
|
|
147
|
+
"Slurm connect uses native SSH and does not write a kubeconfig or other output file.",
|
|
148
|
+
{
|
|
149
|
+
code: "slurm_output_unsupported",
|
|
150
|
+
field: "output",
|
|
151
|
+
}
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const login = safeLogin(runtime, prepared.credentials, state.input);
|
|
155
|
+
const transport = privateTransport(state);
|
|
156
|
+
if (!login || ["partial", "failed", "ended"].includes(prepared.cluster.lifecycle.class)) {
|
|
157
|
+
const unavailable = prepared.credentialError === "unavailable";
|
|
158
|
+
return {
|
|
159
|
+
action: retryConnectAction(prepared.cluster),
|
|
160
|
+
data: {
|
|
161
|
+
capacity_reservation: prepared.capacityReservation,
|
|
162
|
+
cluster: prepared.cluster,
|
|
163
|
+
private_transport: transport,
|
|
164
|
+
ssh: null,
|
|
165
|
+
},
|
|
166
|
+
errors: [
|
|
167
|
+
{
|
|
168
|
+
code: unavailable ? "slurm_access_unavailable" : "slurm_access_not_ready",
|
|
169
|
+
message: unavailable
|
|
170
|
+
? "Slurm SSH access could not be loaded; the cluster remains unchanged."
|
|
171
|
+
: "The Slurm cluster exists, but its SSH login is not usable yet.",
|
|
172
|
+
retryable: true,
|
|
173
|
+
resource_ref: prepared.cluster.id,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
exitCode: 4,
|
|
177
|
+
outcome: {
|
|
178
|
+
class: unavailable ? "partial" : "working",
|
|
179
|
+
phase: unavailable ? "access_unavailable" : "ssh_pending",
|
|
180
|
+
},
|
|
181
|
+
presentation: {
|
|
182
|
+
lines: [
|
|
183
|
+
unavailable
|
|
184
|
+
? "Slurm SSH access is temporarily unavailable for the selected cluster."
|
|
185
|
+
: "The selected Slurm cluster is not SSH-ready yet.",
|
|
186
|
+
],
|
|
187
|
+
next: "ornn connect",
|
|
188
|
+
},
|
|
189
|
+
resume: prepared.resume,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const ssh = {
|
|
193
|
+
command: commandText(login),
|
|
194
|
+
host: login.host,
|
|
195
|
+
port: login.port,
|
|
196
|
+
transport: "ssh",
|
|
197
|
+
user: login.user,
|
|
198
|
+
};
|
|
199
|
+
const data = {
|
|
200
|
+
capacity_reservation: prepared.capacityReservation,
|
|
201
|
+
cluster: prepared.cluster,
|
|
202
|
+
private_transport: transport,
|
|
203
|
+
ssh,
|
|
204
|
+
};
|
|
205
|
+
if (runtime.eligibleHuman && !runtime.machine && state.input.print !== true) {
|
|
206
|
+
const launched = await spawnSsh(login, spawnProcess);
|
|
207
|
+
if (launched.code === 130) {
|
|
208
|
+
return {
|
|
209
|
+
data,
|
|
210
|
+
errors: [
|
|
211
|
+
{
|
|
212
|
+
code: "interrupted",
|
|
213
|
+
message:
|
|
214
|
+
"The local SSH process was interrupted; the Slurm cluster was not changed.",
|
|
215
|
+
retryable: true,
|
|
216
|
+
resource_ref: prepared.cluster.id,
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
exitCode: 130,
|
|
220
|
+
outcome: { class: "ended", phase: "interrupted" },
|
|
221
|
+
resume: prepared.resume,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
if (launched.code !== 0) {
|
|
225
|
+
return {
|
|
226
|
+
data,
|
|
227
|
+
errors: [
|
|
228
|
+
{
|
|
229
|
+
code: "ssh_failed",
|
|
230
|
+
message: `SSH exited with status ${launched.code}.`,
|
|
231
|
+
retryable: false,
|
|
232
|
+
resource_ref: prepared.cluster.id,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
exitCode: 1,
|
|
236
|
+
outcome: { class: "failed", phase: "ssh_failed" },
|
|
237
|
+
resume: prepared.resume,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
data,
|
|
243
|
+
outcome: { class: "ready", phase: "ssh_ready" },
|
|
244
|
+
presentation: {
|
|
245
|
+
lines: [
|
|
246
|
+
runtime.eligibleHuman && state.input.print !== true
|
|
247
|
+
? `Connected to Slurm cluster ${prepared.cluster.id}.`
|
|
248
|
+
: ssh.command,
|
|
249
|
+
"Use standard Slurm tools such as sinfo, srun, sbatch, and sacct after login.",
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
resume: prepared.resume,
|
|
253
|
+
};
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const slurmConnectModeHandler = createSlurmConnectModeHandler();
|
|
259
|
+
|
|
260
|
+
export const slurmLaunchModeHandler = {
|
|
261
|
+
mode: "slurm",
|
|
262
|
+
launch: (state) => launchClusterMode(state, "slurm"),
|
|
263
|
+
};
|
package/src/startup.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { pathToFileURL } from "node:url";
|
|
|
6
6
|
import { forwardSignals } from "./signals.mjs";
|
|
7
7
|
import {
|
|
8
8
|
acquireUpdateLock,
|
|
9
|
+
CLI_MIGRATION_GUIDE_URL,
|
|
9
10
|
compareVersions,
|
|
10
11
|
detectInstallContext,
|
|
11
12
|
installVersion,
|
|
@@ -31,6 +32,8 @@ process.exitCode = await entry.execute(process.argv.slice(3), {
|
|
|
31
32
|
function hasHumanOnlyArgument(argv) {
|
|
32
33
|
return argv.some(
|
|
33
34
|
(arg) =>
|
|
35
|
+
arg === "--format" ||
|
|
36
|
+
arg.startsWith("--format=") ||
|
|
34
37
|
arg === "--json" ||
|
|
35
38
|
arg.startsWith("--json=") ||
|
|
36
39
|
arg === "--raw" ||
|
|
@@ -39,7 +42,7 @@ function hasHumanOnlyArgument(argv) {
|
|
|
39
42
|
arg === "help" ||
|
|
40
43
|
arg === "--version" ||
|
|
41
44
|
arg === "-v" ||
|
|
42
|
-
arg === "version"
|
|
45
|
+
arg === "version"
|
|
43
46
|
);
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -73,7 +76,7 @@ function failedProbe(stderr) {
|
|
|
73
76
|
function unavailableLock(stderr) {
|
|
74
77
|
warning(
|
|
75
78
|
stderr,
|
|
76
|
-
"Automatic update unavailable; retry after any other ornn update finishes. If no updater or package installer is running, remove the stale update-install lock from your Ornn config directory or check that directory's permissions."
|
|
79
|
+
"Automatic update unavailable; retry after any other ornn update finishes. If no updater or package installer is running, remove the stale update-install lock from your Ornn config directory or check that directory's permissions."
|
|
77
80
|
);
|
|
78
81
|
return { kind: "exit", code: 1 };
|
|
79
82
|
}
|
|
@@ -88,7 +91,12 @@ function entryUrl(binPath) {
|
|
|
88
91
|
|
|
89
92
|
export async function probeInstalledCli(
|
|
90
93
|
binPath,
|
|
91
|
-
{
|
|
94
|
+
{
|
|
95
|
+
spawnProcess = spawn,
|
|
96
|
+
env = process.env,
|
|
97
|
+
signalSource = process,
|
|
98
|
+
timeoutMs = PROBE_TIMEOUT_MS,
|
|
99
|
+
} = {}
|
|
92
100
|
) {
|
|
93
101
|
return await new Promise((resolve) => {
|
|
94
102
|
let output = "";
|
|
@@ -96,11 +104,15 @@ export async function probeInstalledCli(
|
|
|
96
104
|
let timedOut = false;
|
|
97
105
|
let forceKillTimer;
|
|
98
106
|
let timeoutTimer;
|
|
99
|
-
const child = spawnProcess(
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
const child = spawnProcess(
|
|
108
|
+
process.execPath,
|
|
109
|
+
["--input-type=module", "--eval", PROBE_PROGRAM, entryUrl(binPath)],
|
|
110
|
+
{
|
|
111
|
+
env,
|
|
112
|
+
shell: false,
|
|
113
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
114
|
+
}
|
|
115
|
+
);
|
|
104
116
|
const scheduleForceKill = () => {
|
|
105
117
|
forceKillTimer ??= setTimeout(() => child.kill?.("SIGKILL"), PROBE_KILL_GRACE_MS);
|
|
106
118
|
};
|
|
@@ -136,14 +148,14 @@ export async function probeInstalledCli(
|
|
|
136
148
|
export async function restartCli(
|
|
137
149
|
binPath,
|
|
138
150
|
argv,
|
|
139
|
-
{ spawnProcess = spawn, env = process.env, signalSource = process } = {}
|
|
151
|
+
{ spawnProcess = spawn, env = process.env, signalSource = process } = {}
|
|
140
152
|
) {
|
|
141
153
|
return await new Promise((resolve, reject) => {
|
|
142
154
|
let parentSignal = null;
|
|
143
155
|
const child = spawnProcess(
|
|
144
156
|
process.execPath,
|
|
145
157
|
["--input-type=module", "--eval", RESTART_PROGRAM, entryUrl(binPath), binPath, ...argv],
|
|
146
|
-
{ env, shell: false, stdio: "inherit" }
|
|
158
|
+
{ env, shell: false, stdio: "inherit" }
|
|
147
159
|
);
|
|
148
160
|
const removeSignals = forwardSignals(signalSource, child, (signal) => {
|
|
149
161
|
parentSignal ??= signal;
|
|
@@ -165,7 +177,10 @@ function unexpectedVersion(stderr, version, currentVersion, target) {
|
|
|
165
177
|
? `The installer left ornn CLI at ${version}, expected ${target}.`
|
|
166
178
|
: "The installer left an invalid ornn CLI version.";
|
|
167
179
|
const downgrade = isValidReleaseVersion(version) && compareVersions(version, currentVersion) < 0;
|
|
168
|
-
warning(
|
|
180
|
+
warning(
|
|
181
|
+
stderr,
|
|
182
|
+
`${detail}${downgrade ? ` It is older than ${currentVersion}.` : ""} ${REINSTALL_GUIDANCE}`
|
|
183
|
+
);
|
|
169
184
|
return { kind: "exit", code: 1 };
|
|
170
185
|
}
|
|
171
186
|
|
|
@@ -220,6 +235,14 @@ export async function prepareStartup(argv, options = {}) {
|
|
|
220
235
|
return { kind: "continue" };
|
|
221
236
|
}
|
|
222
237
|
|
|
238
|
+
if (Number(target.split(".")[0]) > Number(options.currentVersion.split(".")[0])) {
|
|
239
|
+
warning(
|
|
240
|
+
stderr,
|
|
241
|
+
`Major update available: ${target}. Review ${CLI_MIGRATION_GUIDE_URL}, then run ornn update --major.`
|
|
242
|
+
);
|
|
243
|
+
return { kind: "continue" };
|
|
244
|
+
}
|
|
245
|
+
|
|
223
246
|
warning(stderr, `Updating ornn CLI ${options.currentVersion} -> ${target}...`);
|
|
224
247
|
let stableBin;
|
|
225
248
|
try {
|