akm-cli 0.10.261014-beta → 0.10.261016-beta
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/CHANGELOG.md +118 -0
- package/README.md +7 -8
- package/SECURITY.md +3 -3
- package/STABILITY.md +2 -1
- package/dist/assets/hints/cli-hints-full.md +6 -4
- package/dist/assets/hints/cli-hints-short.md +4 -2
- package/dist/assets/opencode/akm-plugin.js +16 -8
- package/dist/assets/prompts/workflow-unit-preamble.md +3 -1
- package/dist/cli.js +2 -2
- package/dist/commands/health/plugin-staleness.js +86 -22
- package/dist/commands/health/semantic-search-check.js +2 -2
- package/dist/commands/read/search-cli.js +2 -2
- package/dist/commands/read/show.js +15 -3
- package/dist/core/asset/markdown.js +9 -0
- package/dist/core/config/config-walker.js +35 -32
- package/dist/core/config/schema/connections.js +6 -3
- package/dist/core/config/schema/harnesses.js +8 -1
- package/dist/core/config/schema/primitives.js +12 -1
- package/dist/core/config/schema/sources-bundles.js +2 -2
- package/dist/core/npm-package.js +124 -0
- package/dist/{integrations/harnesses/opencode/runtime → core}/tar.js +25 -16
- package/dist/integrations/harnesses/opencode/runtime/binary.js +8 -75
- package/dist/integrations/harnesses/opencode/runtime/index.js +5 -1
- package/dist/llm/embedders/local.js +23 -58
- package/dist/llm/embedders/onnx-runtime.js +121 -0
- package/dist/output/renderers.js +2 -2
- package/dist/output/shapes/helpers.js +2 -0
- package/dist/output/text/show-format.js +7 -0
- package/dist/registry/semver.js +12 -0
- package/dist/scripts/akm-migrate-node.js +21638 -5760
- package/dist/scripts/akm-migrate.js +21645 -5767
- package/dist/setup/semantic-assets.js +9 -6
- package/dist/setup/setup.js +56 -2
- package/dist/setup/steps/embeddings.js +21 -4
- package/dist/storage/database.js +17 -57
- package/dist/tasks/backends/cron.js +19 -0
- package/docs/architecture/akm-core-principles.md +3 -2
- package/docs/architecture/internals/health-advisories.md +2 -2
- package/docs/architecture/internals/storage-locations.md +1 -0
- package/docs/architecture/specs/ref.md +4 -3
- package/docs/architecture/testing/testing-workflow.md +28 -20
- package/docs/guides/discover-and-load.md +12 -4
- package/docs/guides/getting-started.md +23 -20
- package/docs/guides/recipes/headless-install.md +13 -8
- package/docs/guides/use-with-any-agent.md +4 -1
- package/docs/maintainers/release-checklist.md +15 -8
- package/docs/migration/v0.9-to-v0.10.md +17 -0
- package/docs/reference/cli.md +42 -12
- package/docs/reference/configuration.md +37 -15
- package/package.json +6 -9
- package/schemas/akm-config.json +535 -302
- package/dist/standalone/onnxruntime-library.js +0 -52
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
4
|
/**
|
|
5
|
-
* Local embedder: a BERT-family embedding model run
|
|
6
|
-
* its text encoded by @huggingface/tokenizers, mean-pooled
|
|
7
|
-
* mask and L2-normalised.
|
|
5
|
+
* Local embedder: a BERT-family embedding model run on onnxruntime-node's binding
|
|
6
|
+
* (`./onnx-runtime.ts`), its text encoded by @huggingface/tokenizers, mean-pooled
|
|
7
|
+
* over the attention mask and L2-normalised.
|
|
8
8
|
*
|
|
9
9
|
* Encapsulates the model's lifecycle as instance state on a `LocalEmbedder` so
|
|
10
10
|
* tests can construct fresh instances without leaking loaded models across
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import fs from "node:fs";
|
|
15
15
|
import path from "node:path";
|
|
16
|
-
import {
|
|
16
|
+
import { toErrorMessage } from "../../core/common.js";
|
|
17
17
|
import { ensureLocalModelFiles } from "./local-model-files.js";
|
|
18
|
+
import { hasLocalEmbeddings, loadOnnxRuntime, noLocalEmbeddingsReason, OnnxTensor } from "./onnx-runtime.js";
|
|
18
19
|
/**
|
|
19
20
|
* Default local model for embeddings.
|
|
20
21
|
* `bge-small-en-v1.5` scores higher on MTEB benchmarks than the previous
|
|
@@ -24,10 +25,10 @@ export const DEFAULT_LOCAL_MODEL = "Xenova/bge-small-en-v1.5";
|
|
|
24
25
|
/** The runtime the local embedder reports once its model has loaded. */
|
|
25
26
|
export const LOCAL_EMBEDDING_RUNTIME = "onnxruntime-node";
|
|
26
27
|
/**
|
|
27
|
-
* The embedding runtime is not usable on this machine. `platform`:
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* directory).
|
|
28
|
+
* The embedding runtime is not usable on this machine. `platform`: akm offers no
|
|
29
|
+
* local embeddings here, so keyword search and remote embeddings are what there
|
|
30
|
+
* is. `load`: it does, and the runtime could not be fetched or did not load (no
|
|
31
|
+
* network for the fetch, a `noexec` cache directory).
|
|
31
32
|
*/
|
|
32
33
|
export class LocalRuntimeUnavailableError extends Error {
|
|
33
34
|
kind;
|
|
@@ -55,50 +56,17 @@ const LOCAL_BATCH_SIZE = 32;
|
|
|
55
56
|
function resolveLocalModelName(overrideModel) {
|
|
56
57
|
return overrideModel || DEFAULT_LOCAL_MODEL;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* drops only the branch a build constant rules out, so the import sits in an
|
|
62
|
-
* `else`, not after the `throw`.
|
|
63
|
-
*/
|
|
64
|
-
async function importOnnxRuntime() {
|
|
65
|
-
if (typeof AKM_STANDALONE_NATIVE_MODULE !== "undefined" && AKM_STANDALONE_NATIVE_MODULE === "./native/none") {
|
|
66
|
-
throw new Error("no native library is available for this platform");
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
await preloadOnnxRuntimeLibrary();
|
|
70
|
-
return import("onnxruntime-node");
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Whether onnxruntime-node has a native library for this machine. A standalone
|
|
75
|
-
* binary knows from its build (`none`). The package has one for glibc Linux
|
|
76
|
-
* x64/arm64, Apple-silicon macOS and Windows x64/arm64, and none for musl Linux
|
|
77
|
-
* or Intel macOS.
|
|
78
|
-
*/
|
|
79
|
-
function hasNativeLibrary() {
|
|
80
|
-
if (typeof AKM_STANDALONE_NATIVE_MODULE !== "undefined")
|
|
81
|
-
return AKM_STANDALONE_NATIVE_MODULE !== "./native/none";
|
|
82
|
-
if (process.platform === "linux") {
|
|
83
|
-
const header = process.report?.getReport()?.header;
|
|
84
|
-
if (!header?.glibcVersionRuntime)
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
return ["linux-x64", "linux-arm64", "darwin-arm64", "win32-x64", "win32-arm64"].includes(`${process.platform}-${process.arch}`);
|
|
88
|
-
}
|
|
89
|
-
async function loadOnnxRuntime() {
|
|
59
|
+
async function loadRuntime() {
|
|
60
|
+
if (!hasLocalEmbeddings())
|
|
61
|
+
throw new LocalRuntimeUnavailableError(noLocalEmbeddingsReason(), "platform");
|
|
90
62
|
try {
|
|
91
|
-
return await
|
|
63
|
+
return await loadOnnxRuntime();
|
|
92
64
|
}
|
|
93
65
|
catch (error) {
|
|
94
|
-
const msg = error
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (kind === "load" && /Cannot find (?:module|package) ['"]onnxruntime-node['"]/.test(msg)) {
|
|
99
|
-
throw new LocalRuntimeUnavailableError("Semantic search requires onnxruntime-node. Reinstall akm-cli.", kind);
|
|
100
|
-
}
|
|
101
|
-
throw new LocalRuntimeUnavailableError(`Failed to load the embedding runtime (onnxruntime-node): ${msg}. Semantic search with the local model is unavailable; keyword search still works.`, kind);
|
|
66
|
+
const msg = toErrorMessage(error).replace(/\.$/, "");
|
|
67
|
+
// A `noexec` mount maps nothing executable, so the binding cannot load from the cache there.
|
|
68
|
+
const mount = /failed to map segment/.test(msg) ? " akm's cache directory must allow executing files." : "";
|
|
69
|
+
throw new LocalRuntimeUnavailableError(`Failed to load the embedding runtime (onnxruntime-node): ${msg}.${mount} Semantic search with the local model is unavailable; keyword search still works.`, "load");
|
|
102
70
|
}
|
|
103
71
|
}
|
|
104
72
|
/** The mean of one text's token vectors (padding excluded), scaled to unit length. */
|
|
@@ -116,17 +84,14 @@ function meanPooled(hidden, offset, tokens, dim) {
|
|
|
116
84
|
return vector.map((value) => value / Math.sqrt(norm));
|
|
117
85
|
}
|
|
118
86
|
async function loadOnnxPipeline(modelName) {
|
|
119
|
-
const ort = await
|
|
87
|
+
const ort = await loadRuntime();
|
|
120
88
|
const { Tokenizer } = await import("@huggingface/tokenizers");
|
|
121
89
|
const modelDir = await ensureLocalModelFiles(modelName);
|
|
122
90
|
const readJson = (file) => JSON.parse(fs.readFileSync(path.join(modelDir, file), "utf8"));
|
|
123
91
|
const tokenizerConfig = readJson("tokenizer_config.json");
|
|
124
92
|
const tokenizer = new Tokenizer(readJson("tokenizer.json"), tokenizerConfig);
|
|
125
93
|
const maxLength = typeof tokenizerConfig.model_max_length === "number" ? tokenizerConfig.model_max_length : Number.POSITIVE_INFINITY;
|
|
126
|
-
const session =
|
|
127
|
-
executionProviders: ["cpu"],
|
|
128
|
-
logSeverityLevel: 3,
|
|
129
|
-
});
|
|
94
|
+
const session = ort.createSession(path.join(modelDir, "onnx", "model.onnx"));
|
|
130
95
|
return async (texts) => {
|
|
131
96
|
// A text longer than the model's maximum is cut to it. Shorter ones are padded
|
|
132
97
|
// to the longest in the batch; the mask keeps padding out of the pooling.
|
|
@@ -142,13 +107,13 @@ async function loadOnnxPipeline(modelName) {
|
|
|
142
107
|
});
|
|
143
108
|
const dims = [texts.length, length];
|
|
144
109
|
const inputs = {
|
|
145
|
-
input_ids: new
|
|
146
|
-
attention_mask: new
|
|
147
|
-
token_type_ids: new
|
|
110
|
+
input_ids: new OnnxTensor("int64", ids, dims),
|
|
111
|
+
attention_mask: new OnnxTensor("int64", mask, dims),
|
|
112
|
+
token_type_ids: new OnnxTensor("int64", new BigInt64Array(texts.length * length), dims),
|
|
148
113
|
};
|
|
149
114
|
// Models that take fewer inputs (no token_type_ids) are fed only theirs.
|
|
150
115
|
const feeds = Object.fromEntries(session.inputNames.flatMap((name) => (inputs[name] ? [[name, inputs[name]]] : [])));
|
|
151
|
-
const outputs =
|
|
116
|
+
const outputs = session.run(feeds);
|
|
152
117
|
const hidden = (outputs.last_hidden_state ?? outputs.token_embeddings ?? outputs.logits)?.data;
|
|
153
118
|
const dim = hidden.length / (texts.length * length);
|
|
154
119
|
return encoded.map((row, i) => meanPooled(hidden, i * length * dim, row.length, dim));
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* ONNX Runtime for the local embedder: onnxruntime-node's N-API binding and the
|
|
6
|
+
* library it links, kept in akm's cache and loaded from there. `akm setup`
|
|
7
|
+
* fetches them when local embeddings are chosen, and the first local embedding
|
|
8
|
+
* fetches them when setup did not.
|
|
9
|
+
*
|
|
10
|
+
* akm does not depend on the onnxruntime-node package. Its JavaScript loads the
|
|
11
|
+
* binding from a path inside the package, which a compiled standalone binary can
|
|
12
|
+
* only satisfy by writing an embedded copy to the temp directory, and its install
|
|
13
|
+
* script downloads CUDA providers akm never uses. Here the binding is loaded by
|
|
14
|
+
* absolute path with the library beside it in the same folder, where the system
|
|
15
|
+
* loader finds it (`$ORIGIN` on Linux, `@loader_path` on macOS).
|
|
16
|
+
*/
|
|
17
|
+
import fs from "node:fs";
|
|
18
|
+
import { createRequire } from "node:module";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { toErrorMessage } from "../../core/common.js";
|
|
21
|
+
import { fetchNpmPackageFiles, NPM_REGISTRY } from "../../core/npm-package.js";
|
|
22
|
+
import { getCacheDir } from "../../core/paths.js";
|
|
23
|
+
import { info } from "../../core/warn.js";
|
|
24
|
+
/**
|
|
25
|
+
* The onnxruntime-node release whose binding akm loads, and the only one it supports. akm calls the binding through
|
|
26
|
+
* the contract between that package's own JavaScript and its addon, which no release documents or promises and which
|
|
27
|
+
* moves between releases (after 1.24.3, `loadModel` returns a promise and `initOrtOnce` takes a third argument). What
|
|
28
|
+
* akm relies on: `initOrtOnce(logLevel, Tensor)`; `new InferenceSession()`; `loadModel(path, options)`;
|
|
29
|
+
* `inputMetadata` and `outputMetadata` (`{ name }[]`); `run(feeds, fetches)`, which reads a tensor as
|
|
30
|
+
* `{ type, data, dims, location }` and makes each result with `new Tensor(type, data, dims)`. To move it: change this
|
|
31
|
+
* version, check that contract in the new release's `lib/binding.ts` and `lib/backend.ts`, check the library names in
|
|
32
|
+
* {@link LIBRARIES}, and run the reference-vector test in `tests/integration/semantic-search-e2e.test.ts`, the gate for
|
|
33
|
+
* any bump.
|
|
34
|
+
*/
|
|
35
|
+
export const ONNXRUNTIME_VERSION = "1.24.3";
|
|
36
|
+
/**
|
|
37
|
+
* The platforms akm offers local embeddings on, each with the library the binding links there. Every other platform
|
|
38
|
+
* has keyword search and remote embeddings. A platform is listed once a real run has verified it; Linux means glibc.
|
|
39
|
+
*/
|
|
40
|
+
const LIBRARIES = {
|
|
41
|
+
"linux-x64": "libonnxruntime.so.1",
|
|
42
|
+
"darwin-arm64": `libonnxruntime.${ONNXRUNTIME_VERSION}.dylib`,
|
|
43
|
+
};
|
|
44
|
+
const BINDING = "onnxruntime_binding.node";
|
|
45
|
+
/** This machine as `<platform>-<arch>`, Linux without glibc as `linux-musl-<arch>`. */
|
|
46
|
+
function thisPlatform() {
|
|
47
|
+
const header = process.report?.getReport()?.header;
|
|
48
|
+
const musl = process.platform === "linux" && !header?.glibcVersionRuntime;
|
|
49
|
+
return `${process.platform}${musl ? "-musl" : ""}-${process.arch}`;
|
|
50
|
+
}
|
|
51
|
+
/** Whether akm offers local embeddings on this machine. */
|
|
52
|
+
export function hasLocalEmbeddings() {
|
|
53
|
+
return thisPlatform() in LIBRARIES;
|
|
54
|
+
}
|
|
55
|
+
/** What a machine without local embeddings is told, wherever it is told. */
|
|
56
|
+
export function noLocalEmbeddingsReason() {
|
|
57
|
+
return `Local embeddings are not available on ${thisPlatform()}; choose a remote endpoint or keyword search.`;
|
|
58
|
+
}
|
|
59
|
+
/** A tensor as the binding reads one and makes its results. */
|
|
60
|
+
export class OnnxTensor {
|
|
61
|
+
type;
|
|
62
|
+
data;
|
|
63
|
+
dims;
|
|
64
|
+
location = "cpu";
|
|
65
|
+
constructor(type, data, dims) {
|
|
66
|
+
this.type = type;
|
|
67
|
+
this.data = data;
|
|
68
|
+
this.dims = dims;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Fetch the binding and its library into `directory`, naming the package, its URL and the folder when it cannot. */
|
|
72
|
+
async function fetchOnnxRuntime(directory) {
|
|
73
|
+
const library = LIBRARIES[thisPlatform()];
|
|
74
|
+
if (!library)
|
|
75
|
+
throw new Error(noLocalEmbeddingsReason());
|
|
76
|
+
const source = `bin/napi-v6/${process.platform}/${process.arch}`;
|
|
77
|
+
try {
|
|
78
|
+
info(`Fetching ONNX Runtime ${ONNXRUNTIME_VERSION} for local embeddings (about 86 MB, once) into ${directory}.`);
|
|
79
|
+
// The library first: a binding in the folder means its library is there too.
|
|
80
|
+
await fetchNpmPackageFiles("onnxruntime-node", ONNXRUNTIME_VERSION, directory, [
|
|
81
|
+
{ member: `package/${source}/${library}`, file: library },
|
|
82
|
+
{ member: `package/${source}/${BINDING}`, file: BINDING },
|
|
83
|
+
]);
|
|
84
|
+
return directory;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
throw new Error(`Could not fetch ONNX Runtime ${ONNXRUNTIME_VERSION} for local embeddings: package onnxruntime-node from ${NPM_REGISTRY}/onnxruntime-node/${ONNXRUNTIME_VERSION} into ${directory}: ${toErrorMessage(error)}. ` +
|
|
88
|
+
`Run \`akm setup --yes\` with network access, or copy ${BINDING} and ${library} from that package's ${source}/ into that folder.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The binding, loaded and initialised from `<cache>/onnxruntime/<version>-<platform>-<arch>/`, where it and its library
|
|
93
|
+
* are fetched first when the binding is not there.
|
|
94
|
+
*/
|
|
95
|
+
async function loadBinding() {
|
|
96
|
+
const directory = path.join(getCacheDir(), "onnxruntime", `${ONNXRUNTIME_VERSION}-${thisPlatform()}`);
|
|
97
|
+
if (!fs.existsSync(path.join(directory, BINDING)))
|
|
98
|
+
await fetchOnnxRuntime(directory);
|
|
99
|
+
const loaded = createRequire(import.meta.url)(path.join(directory, BINDING));
|
|
100
|
+
// 3: errors only.
|
|
101
|
+
loaded.initOrtOnce(3, OnnxTensor);
|
|
102
|
+
return loaded;
|
|
103
|
+
}
|
|
104
|
+
/** Once per process, however many callers ask meanwhile; a failure is not kept, so the next caller tries again. */
|
|
105
|
+
let loading;
|
|
106
|
+
/** The runtime, ready to load models: fetched first when it is not in the cache, then loaded. */
|
|
107
|
+
export async function loadOnnxRuntime() {
|
|
108
|
+
loading ??= loadBinding().catch((error) => {
|
|
109
|
+
loading = undefined;
|
|
110
|
+
throw error;
|
|
111
|
+
});
|
|
112
|
+
const loaded = await loading;
|
|
113
|
+
return {
|
|
114
|
+
createSession(modelPath) {
|
|
115
|
+
const session = new loaded.InferenceSession();
|
|
116
|
+
session.loadModel(modelPath, { executionProviders: ["cpu"], logSeverityLevel: 3 });
|
|
117
|
+
const outputs = Object.fromEntries(session.outputMetadata.map(({ name }) => [name, null]));
|
|
118
|
+
return { inputNames: session.inputMetadata.map(({ name }) => name), run: (feeds) => session.run(feeds, outputs) };
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
package/dist/output/renderers.js
CHANGED
|
@@ -217,8 +217,8 @@ const agentMdRenderer = {
|
|
|
217
217
|
},
|
|
218
218
|
};
|
|
219
219
|
// ── 4. knowledge-md ──────────────────────────────────────────────────────────
|
|
220
|
-
const KNOWLEDGE_ACTION = "Reference material - read the content below. For a large document,
|
|
221
|
-
"(
|
|
220
|
+
const KNOWLEDGE_ACTION = "Reference material - read the content below. For a large document, run `akm show <ref> --detail brief` for its " +
|
|
221
|
+
"outline (`toc`, no body), then `akm show <ref>#<slug>` for only the sections you need.";
|
|
222
222
|
const knowledgeMdRenderer = {
|
|
223
223
|
name: "knowledge-md",
|
|
224
224
|
buildShowResponse(ctx) {
|
|
@@ -348,6 +348,7 @@ export function shapeShowOutput(result, detail, shape = "human") {
|
|
|
348
348
|
"editHint",
|
|
349
349
|
"description",
|
|
350
350
|
"action",
|
|
351
|
+
"toc",
|
|
351
352
|
"content",
|
|
352
353
|
"template",
|
|
353
354
|
"prompt",
|
|
@@ -381,6 +382,7 @@ export function shapeShowOutput(result, detail, shape = "human") {
|
|
|
381
382
|
"action",
|
|
382
383
|
"description",
|
|
383
384
|
"tags",
|
|
385
|
+
"toc",
|
|
384
386
|
"content",
|
|
385
387
|
"template",
|
|
386
388
|
"prompt",
|
|
@@ -59,6 +59,13 @@ export function formatShowPlain(r, detail) {
|
|
|
59
59
|
if (r.schemaVersion !== undefined)
|
|
60
60
|
lines.push(`schemaVersion: ${String(r.schemaVersion)}`);
|
|
61
61
|
}
|
|
62
|
+
if (Array.isArray(r.toc) && r.toc.length > 0) {
|
|
63
|
+
lines.push("");
|
|
64
|
+
lines.push("toc:");
|
|
65
|
+
for (const heading of r.toc) {
|
|
66
|
+
lines.push(` ${"#".repeat(heading.level)} ${heading.text} (#${heading.slug}, line ${heading.startLine})`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
62
69
|
const links = typeof r.links === "object" && r.links !== null ? r.links : undefined;
|
|
63
70
|
if (links) {
|
|
64
71
|
lines.push("");
|
package/dist/registry/semver.js
CHANGED
|
@@ -41,6 +41,18 @@ export function isSemverRange(input) {
|
|
|
41
41
|
export function maxSatisfying(versions, range) {
|
|
42
42
|
return semver.maxSatisfying(versions, range) ?? undefined;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* Highest of `versions` in semver order, prereleases included (`0.10.1-beta` sorts above `0.9.30`). Invalid entries
|
|
46
|
+
* are ignored; `undefined` when none is valid. Use this to order known versions, not `maxSatisfying(versions, "*")`,
|
|
47
|
+
* which skips every prerelease.
|
|
48
|
+
*/
|
|
49
|
+
export function highestVersion(versions) {
|
|
50
|
+
return semver.rsort(versions.filter((version) => semver.valid(version) !== null))[0];
|
|
51
|
+
}
|
|
52
|
+
/** True when `version` is a valid semver prerelease (`1.2.3-beta`). */
|
|
53
|
+
export function isPrerelease(version) {
|
|
54
|
+
return semver.valid(version) !== null && semver.prerelease(version) !== null;
|
|
55
|
+
}
|
|
44
56
|
/** True when `version` satisfies `range` (both real semver forms). False for an invalid version or range. */
|
|
45
57
|
export function satisfiesRange(version, range) {
|
|
46
58
|
return semver.satisfies(version, range);
|