omakit 0.1.8 → 0.1.9
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 +7 -2
- package/package.json +1 -1
- package/tools/marketplace/README.md +2 -1
- package/tools/marketplace/cli.mjs +2 -2
- package/tools/marketplace/completion.mjs +2 -1
- package/tools/marketplace/doctor.mjs +24 -14
- package/tools/marketplace/path-hint.mjs +1 -1
- package/tools/marketplace/paths.mjs +18 -0
- package/tools/marketplace/report.mjs +10 -3
- package/tools/marketplace/setup.mjs +7 -4
- package/tools/marketplace/upgrade.mjs +22 -4
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<img src="docs/media/banner.gif" alt="omakit" width="440">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
[](https://github.com/tcballard/omarchy-badges)
|
|
6
6
|
|
|
7
7
|
**Everything knowable about an Omarchy Quattro plugin submission, checked
|
|
8
8
|
before you post it:** the tree, the manifest, the form, the commit, and the
|
|
@@ -56,7 +56,7 @@ omakit setup
|
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
If `omakit` is not found afterwards, npm's global `bin` is not on your PATH
|
|
59
|
-
(measured:
|
|
59
|
+
(measured: an npm global prefix under `~/.local/share` whose `bin` no shell
|
|
60
60
|
searched). Run `"$(npm prefix --global)/bin/omakit" setup` once: it prints the
|
|
61
61
|
one line that puts that directory on PATH for the shell in `$SHELL`, and the
|
|
62
62
|
rc file to keep it in; `omakit doctor` reports the same as `omakit.path`.
|
|
@@ -192,6 +192,11 @@ the marketplace's current HEAD when the network is there, because the pin's
|
|
|
192
192
|
copy is stale within hours (4,201 of 4,293 commits in 30 days touched only
|
|
193
193
|
`registry.json`), and from the pin with `--offline`.
|
|
194
194
|
|
|
195
|
+
This is why the tool is Node: the marketplace's scanner, form parser and
|
|
196
|
+
catalog builder are Node modules, and omakit runs them verbatim from the
|
|
197
|
+
pinned commit instead of reimplementing their rules, where a different
|
|
198
|
+
language would mean a copy that can drift.
|
|
199
|
+
|
|
195
200
|
The security baseline is the marketplace's own code, imported unmodified and run
|
|
196
201
|
over a local snapshot with no network. Omakit adds no rule, renames no outcome,
|
|
197
202
|
and never restates the result as a safety claim: the baseline does no data-flow
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omakit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "The safe place to find out: everything knowable about an Omarchy Quattro plugin submission before you post it, on your own machine. Agent-first, read-only, posts nothing, zero dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Maarten Tolhuijs",
|
|
@@ -21,7 +21,7 @@ local commit through the transport seam the marketplace tests itself
|
|
|
21
21
|
| `issue.mjs` | Renders the issue the way the form would, then has the marketplace's own parser judge it. |
|
|
22
22
|
| `submit.mjs` | Assembles every check with its measured reason, and withholds the body when a blocking check fails. Three outcomes: `ready` (the body), `refused` (a blocking check failed) and `listed` (the plugin is already listed by its own repository: `identity.available` passes with the listing's record, the five body checks are omitted rather than drawn as waiting, no body exists on purpose, and `listing` carries the listed commit against the local one and the form to use for a newer commit). Decides the category and tags after the registry: a listed plugin, own or taken, is asked for neither; an unlisted one without them is asked through `ask.mjs` at a terminal, and is a usage error otherwise. Ends with `reproduce`, the command line that repeats the run without asking. Under `--offline` the validation-commit check is `skipped`, not passed: verdict `skipped`, listed under `skipped` and not `unknown`, never blocking, and the READY line says "1 check skipped (--offline)". |
|
|
23
23
|
| `ask.mjs` | The two questions `submit` asks a person at a terminal, and only there: category and tags, numbered from the pinned form, with the marketplace's own presentation for the manifest's kinds (read from the pinned catalog builder) as the default where it is on the list. Prompts on stderr, nothing persisted. |
|
|
24
|
-
| `doctor.mjs` | What is installed, what is pinned, and what has moved. `pin.freshness` compares each path in `PIN_PATHS` between the pin and the marketplace's HEAD by tree or blob id and splits what moved by the list `registry.mjs` reads live from: `registry.json` and `site/catalog.json` moving is `ok`, because those are read from HEAD anyway; anything else moving (`scripts/`, the two forms) is a `note` naming the paths, with an action a user can take, `omakit upgrade` and then an issue at the repository named in package.json. The maintainer's pin procedure stays in `docs/UPSTREAM_CONTRACT.md` and is never printed. HEAD unreadable is `unknown`. `--json` carries `changedPaths`, `readLive` and `pinned` under the check's evidence. |
|
|
24
|
+
| `doctor.mjs` | What is installed, what is pinned, and what has moved. `omakit.version` is one check for one question, is this the current omakit: `ok` with "0.1.8, the newest published version", a `note` with "0.1.8; 0.1.9 is published" and the upgrade command, `unknown` with the registry's failure code, `info` offline; its evidence carries `{ installed, latest, source }`. `pin.freshness` compares each path in `PIN_PATHS` between the pin and the marketplace's HEAD by tree or blob id and splits what moved by the list `registry.mjs` reads live from: `registry.json` and `site/catalog.json` moving is `ok`, because those are read from HEAD anyway; anything else moving (`scripts/`, the two forms) is a `note` naming the paths, with an action a user can take, `omakit upgrade` and then an issue at the repository named in package.json. The maintainer's pin procedure stays in `docs/UPSTREAM_CONTRACT.md` and is never printed. HEAD unreadable is `unknown`. `--json` carries `changedPaths`, `readLive` and `pinned` under the check's evidence. |
|
|
25
25
|
| `watch.mjs` | The validation watch: validated commit versus current default-branch HEAD, and the one action that refreshes it. |
|
|
26
26
|
| `github.mjs` | Read-only GitHub access. GET only. The credential is your `gh` login, read through one frozen `gh auth token` call, and is never written anywhere. |
|
|
27
27
|
| `style.mjs` | The visual system, defined once: the palette, the status vocabulary, the block ramp, the columns, the motion budgets, and the composition helpers every command draws with. Six states: `▁ ok`, `█ FAIL`, `▓ note`, `░ info`, `▒ ?` for a check that could not be made, and `▔ skip` for a check a flag said not to make, the floor's ink at the ceiling so it is never read as a pass. `docs/TUI.md` explains it. |
|
|
@@ -32,6 +32,7 @@ local commit through the transport seam the marketplace tests itself
|
|
|
32
32
|
| `banner.mjs` | The wordmark, on a bare `omakit` and in `setup` only. |
|
|
33
33
|
| `effect.mjs` | The one text effect: the wordmark through `ttfx` where it is drawn, with frozen arguments, a hard budget, no colour of its own, and nothing at all when `ttfx` is not there. |
|
|
34
34
|
| `progress.mjs` | The progress line, on stderr, only when a person is looking. |
|
|
35
|
+
| `paths.mjs` | Omakit's cache directory, following XDG, and `withHomeAbbreviated()`: a path under `$HOME` written as `~/...` for a person, applied where doctor, setup and pin render text and never where a result is built, so `--json` keeps every path absolute. |
|
|
35
36
|
| `cli.mjs` | The one entry point behind `bin/omakit`, and the one register every failure is reported in. `submit` exits on the outcome: 1 for `refused`, 0 for `ready` and `listed`. |
|
|
36
37
|
|
|
37
38
|
```text
|
|
@@ -29,7 +29,7 @@ import { progress } from "./progress.mjs"
|
|
|
29
29
|
import { banner, bannerEnabled } from "./banner.mjs"
|
|
30
30
|
import { COMMANDS, renderSummary, renderUsage, TAGLINE } from "./usage.mjs"
|
|
31
31
|
import { action, colourEnabled, GUTTER, labelled, mark, styler, wrap } from "./style.mjs"
|
|
32
|
-
import { omakitCacheDir } from "./paths.mjs"
|
|
32
|
+
import { omakitCacheDir, withHomeAbbreviated } from "./paths.mjs"
|
|
33
33
|
|
|
34
34
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..")
|
|
35
35
|
|
|
@@ -284,7 +284,7 @@ if (command === "setup") {
|
|
|
284
284
|
// ensurePin narrates: a state line to keep, then a fetch it is about to
|
|
285
285
|
// start. The fetch is the slow part, so it gets the progress line.
|
|
286
286
|
if (line.state === "fetching") spinner.phase(line.text)
|
|
287
|
-
else process.stdout.write(`${mark(line.state, c)}${wrap(line.text, { indent: GUTTER }, c).join("\n").trimStart()}\n`)
|
|
287
|
+
else process.stdout.write(`${mark(line.state, c)}${wrap(withHomeAbbreviated(line.text), { indent: GUTTER }, c).join("\n").trimStart()}\n`)
|
|
288
288
|
})
|
|
289
289
|
} catch (error) {
|
|
290
290
|
spinner.done()
|
|
@@ -13,6 +13,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"
|
|
|
13
13
|
import { basename, dirname, join } from "node:path"
|
|
14
14
|
import { tagSlug } from "./form.mjs"
|
|
15
15
|
import { COMMANDS, COMPLETION_SHELLS } from "./usage.mjs"
|
|
16
|
+
import { withHomeAbbreviated } from "./paths.mjs"
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* The completion model, read out of the help data. A subcommand is the word
|
|
@@ -221,7 +222,7 @@ export function completionInstall(env = process.env) {
|
|
|
221
222
|
const shell = basename(env.SHELL || "")
|
|
222
223
|
const home = env.HOME || ""
|
|
223
224
|
if (!home || !COMPLETION_SHELLS.includes(shell)) return null
|
|
224
|
-
const tilde = (dir) => (dir
|
|
225
|
+
const tilde = (dir) => withHomeAbbreviated(dir, env)
|
|
225
226
|
if (shell === "bash") {
|
|
226
227
|
const dir = join(env.XDG_DATA_HOME || join(home, ".local/share"), "bash-completion/completions")
|
|
227
228
|
return { shell, path: join(dir, "omakit"), display: `${tilde(dir)}/omakit`, note: null }
|
|
@@ -42,7 +42,7 @@ import { join } from "node:path"
|
|
|
42
42
|
import { MARKETPLACE_PIN, PIN_PATHS, marketplacePinDir, pinDiskUsage, pinIsSparse, requirePin } from "./pin.mjs"
|
|
43
43
|
import { LIVE_PATHS } from "./registry.mjs"
|
|
44
44
|
import { credential, defaultBranchHead, getJson, UNAUTHENTICATED_LIMIT, GitHubError } from "./github.mjs"
|
|
45
|
-
import {
|
|
45
|
+
import { NPM_REGISTRY, registryLatest, upgradeCommand } from "./upgrade.mjs"
|
|
46
46
|
import { pathHint } from "./path-hint.mjs"
|
|
47
47
|
|
|
48
48
|
/** "git+https://github.com/owner/name.git" in package.json -> "https://github.com/owner/name", or null. */
|
|
@@ -172,13 +172,13 @@ export function pinFreshness(identity, head, changedPaths = null, { issues = nul
|
|
|
172
172
|
|
|
173
173
|
/**
|
|
174
174
|
* @param {{ repoRoot: string, offline?: boolean, env?: object, npmPrefix?: () => string|null,
|
|
175
|
-
* resolveHead?: typeof defaultBranchHead, latest?: typeof
|
|
175
|
+
* resolveHead?: typeof defaultBranchHead, latest?: typeof registryLatest }} options
|
|
176
176
|
* `env` and `npmPrefix` are injectable for tests of the PATH check;
|
|
177
177
|
* `resolveHead` and `latest` for tests of the two checks that read the
|
|
178
178
|
* network, whose defaults are the tool's one HEAD resolver and its one
|
|
179
179
|
* registry read.
|
|
180
180
|
*/
|
|
181
|
-
export async function doctor({ repoRoot, offline = false, onPhase, env = process.env, npmPrefix, resolveHead = defaultBranchHead, latest: latestVersion =
|
|
181
|
+
export async function doctor({ repoRoot, offline = false, onPhase, env = process.env, npmPrefix, resolveHead = defaultBranchHead, latest: latestVersion = registryLatest }) {
|
|
182
182
|
// Optional: told what is being read while the network answers. Never
|
|
183
183
|
// affects the result.
|
|
184
184
|
const phase = onPhase || (() => {})
|
|
@@ -192,7 +192,27 @@ export async function doctor({ repoRoot, offline = false, onPhase, env = process
|
|
|
192
192
|
})
|
|
193
193
|
|
|
194
194
|
const self = tool(repoRoot)
|
|
195
|
-
|
|
195
|
+
// What is installed and what is published, one check: the two facts are
|
|
196
|
+
// one question, "is this the current omakit", and were two lines before.
|
|
197
|
+
// Offline, the first fact alone, as information. The evidence carries both
|
|
198
|
+
// and where the second came from.
|
|
199
|
+
const versionCheck = (state, detail, action = null, latest = null, source = null) =>
|
|
200
|
+
add("omakit.version", state, detail, action, { installed: self.version, latest, source })
|
|
201
|
+
if (offline) {
|
|
202
|
+
versionCheck("info", `${self.version}; the newest published version is not checked (--offline)`)
|
|
203
|
+
} else {
|
|
204
|
+
phase("asking the npm registry for the newest published version")
|
|
205
|
+
const published = await latestVersion(self.name)
|
|
206
|
+
if (published.version) {
|
|
207
|
+
const current = published.version === self.version
|
|
208
|
+
versionCheck(current ? "ok" : "advice",
|
|
209
|
+
current ? `${self.version}, the newest published version` : `${self.version}; ${published.version} is published`,
|
|
210
|
+
current ? null : `run \`${upgradeCommand(repoRoot, self.name)}\``,
|
|
211
|
+
published.version, NPM_REGISTRY)
|
|
212
|
+
} else {
|
|
213
|
+
versionCheck("unknown", `${self.version}; could not read the npm registry (${published.error?.code || "error"})`)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
196
216
|
|
|
197
217
|
// Reachable as a bare command, or the one line that makes it so for this
|
|
198
218
|
// install (path-hint.mjs). Measured: an npm prefix whose bin is not on PATH
|
|
@@ -241,16 +261,6 @@ export async function doctor({ repoRoot, offline = false, onPhase, env = process
|
|
|
241
261
|
{ pinCommit: identity.commit, marketplaceHead: null, branch: null })
|
|
242
262
|
}
|
|
243
263
|
|
|
244
|
-
phase("asking the npm registry for the newest published version")
|
|
245
|
-
const latest = await latestVersion(self.name)
|
|
246
|
-
if (latest) {
|
|
247
|
-
const current = latest === self.version
|
|
248
|
-
add("omakit.latest", current ? "ok" : "advice",
|
|
249
|
-
current ? `${latest} is the newest published version` : `${latest} is published, this is ${self.version}`,
|
|
250
|
-
current ? null : upgradeCommand(repoRoot, self.name))
|
|
251
|
-
} else {
|
|
252
|
-
add("omakit.latest", "unknown", "the npm registry did not answer, or this version is unpublished")
|
|
253
|
-
}
|
|
254
264
|
}
|
|
255
265
|
|
|
256
266
|
// Where the credential comes from, said out loud. Borrowing someone's `gh`
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// it so, for the install that is actually here.
|
|
3
3
|
//
|
|
4
4
|
// Measured: after `npm install --global omakit` on a machine whose npm prefix
|
|
5
|
-
// was ~/.local/share
|
|
5
|
+
// was a directory under ~/.local/share, the command was missing, because that
|
|
6
6
|
// prefix's `bin` was not on PATH. `omakit setup` then printed the `ln -s` hint
|
|
7
7
|
// written for a clone, which points at a bin/omakit that npm did not lay out
|
|
8
8
|
// where the hint assumes. An npm install needs the npm prefix's `bin` on PATH,
|
|
@@ -8,3 +8,21 @@ export function omakitCacheDir(name = "", env = process.env) {
|
|
|
8
8
|
: join(env.HOME || homedir(), ".cache")
|
|
9
9
|
return join(base, "omakit", name)
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Text for a person, with every path under the home directory written the
|
|
14
|
+
* way a shell would take it: `~/.cache/omakit/marketplace`. Only `$HOME`
|
|
15
|
+
* counts, because `~` is what the shell expands to `$HOME` and nothing else;
|
|
16
|
+
* with it unset, or for a path outside it, the text is returned as it came.
|
|
17
|
+
* Human output only: `--json` keeps every path absolute, so this is applied
|
|
18
|
+
* where text is rendered, never where a result is built.
|
|
19
|
+
*/
|
|
20
|
+
export function withHomeAbbreviated(text, env = process.env) {
|
|
21
|
+
const home = String(env.HOME || "").replace(/\/+$/, "")
|
|
22
|
+
if (!home || !home.startsWith("/")) return String(text)
|
|
23
|
+
// The home directory where a path starts: at the start of the text or after
|
|
24
|
+
// the characters a path follows in prose or a command, and followed by a
|
|
25
|
+
// separator or the end, so /tmp/home/me/x and /home/meh/x are left alone.
|
|
26
|
+
const literal = home.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
27
|
+
return String(text).replace(new RegExp(`(^|[\\s"'(=:])${literal}(?=/|$|[\\s"'),:])`, "g"), "$1~")
|
|
28
|
+
}
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
import {
|
|
21
21
|
action, colourEnabled, COLUMNS, continuation, field, GUTTER, labelled, mark, section, STEP, styler, verdict, width, wrap,
|
|
22
22
|
} from "./style.mjs"
|
|
23
|
+
import { withHomeAbbreviated } from "./paths.mjs"
|
|
23
24
|
|
|
24
25
|
const body = " ".repeat(GUTTER)
|
|
25
26
|
|
|
@@ -317,7 +318,13 @@ export function renderVerify(document, { colour = colourEnabled(), blockingRules
|
|
|
317
318
|
|
|
318
319
|
const DOCTOR_STATE = { ok: "pass", advice: "advisory", problem: "fail", info: "info", unknown: "unknown" }
|
|
319
320
|
|
|
320
|
-
|
|
321
|
+
/**
|
|
322
|
+
* @param {{ colour?: boolean, env?: object }} [options] `env` is where `$HOME`
|
|
323
|
+
* is read from: a path under it is printed as `~/...` for a person, the
|
|
324
|
+
* way a shell takes it, while the result itself, and so `--json`, keeps
|
|
325
|
+
* every path absolute.
|
|
326
|
+
*/
|
|
327
|
+
export function renderDoctor(result, { colour = colourEnabled(), env = process.env } = {}) {
|
|
321
328
|
const c = styler(colour)
|
|
322
329
|
const out = []
|
|
323
330
|
let previous = false
|
|
@@ -326,8 +333,8 @@ export function renderDoctor(result, { colour = colourEnabled() } = {}) {
|
|
|
326
333
|
const loud = state === "fail" || state === "advisory"
|
|
327
334
|
if (index > 0 && (loud || previous)) out.push("")
|
|
328
335
|
out.push(`${mark(state, c)}${c("name", check.id)}`)
|
|
329
|
-
out.push(...wrap(check.detail, { indent: GUTTER }, c))
|
|
330
|
-
if (check.action) out.push(...action(check.action, c))
|
|
336
|
+
out.push(...wrap(withHomeAbbreviated(check.detail, env), { indent: GUTTER }, c))
|
|
337
|
+
if (check.action) out.push(...action(withHomeAbbreviated(check.action, env), c))
|
|
331
338
|
previous = loud
|
|
332
339
|
}
|
|
333
340
|
out.push("")
|
|
@@ -20,6 +20,7 @@ import { TAGLINE } from "./usage.mjs"
|
|
|
20
20
|
import { installCompletion } from "./completion.mjs"
|
|
21
21
|
import { submissionContract } from "./form.mjs"
|
|
22
22
|
import { pathHint } from "./path-hint.mjs"
|
|
23
|
+
import { withHomeAbbreviated } from "./paths.mjs"
|
|
23
24
|
|
|
24
25
|
function version(command) {
|
|
25
26
|
try {
|
|
@@ -32,16 +33,18 @@ function version(command) {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* @param {{ repoRoot: string, entryPoint: string, stream?: NodeJS.WriteStream }} options
|
|
36
|
+
* @param {{ repoRoot: string, entryPoint: string, stream?: NodeJS.WriteStream, env?: object }} options
|
|
37
|
+
* `env` is where `$HOME` is read from: this is output for a person, so a
|
|
38
|
+
* path under it is printed as `~/...`.
|
|
36
39
|
*/
|
|
37
|
-
export async function setup({ repoRoot, entryPoint, stream = process.stdout }) {
|
|
40
|
+
export async function setup({ repoRoot, entryPoint, stream = process.stdout, env = process.env }) {
|
|
38
41
|
const c = styler(colourEnabled(stream))
|
|
39
42
|
const out = (line = "") => stream.write(`${line}\n`)
|
|
40
43
|
// A step is a status line: the mark, then the fact, wrapped under itself.
|
|
41
|
-
const step = (state, text) => out(`${mark(state, c)}${wrap(text, { indent: GUTTER }, c).join("\n").trimStart()}`)
|
|
44
|
+
const step = (state, text) => out(`${mark(state, c)}${wrap(withHomeAbbreviated(text, env), { indent: GUTTER }, c).join("\n").trimStart()}`)
|
|
42
45
|
// The one action under a step sits in the step's body; under a sentence it
|
|
43
46
|
// sits where the sentence does.
|
|
44
|
-
const fix = (text, indent = GUTTER) => { for (const line of action(text, c, { indent })) out(line) }
|
|
47
|
+
const fix = (text, indent = GUTTER) => { for (const line of action(withHomeAbbreviated(text, env), c, { indent })) out(line) }
|
|
45
48
|
|
|
46
49
|
// The one place the wordmark runs through `ttfx` (effect.mjs): a first run
|
|
47
50
|
// already spending seconds fetching the pin.
|
|
@@ -59,11 +59,29 @@ export const NPM_UPGRADE_ARGS = Object.freeze(["install", "--global", "--ignore-
|
|
|
59
59
|
|
|
60
60
|
/** The newest published version, or null when the registry did not answer. */
|
|
61
61
|
export async function latestOnRegistry(name) {
|
|
62
|
+
return (await registryLatest(name)).version
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The registry the newest version is read from, named in doctor's evidence. */
|
|
66
|
+
export const NPM_REGISTRY = "https://registry.npmjs.org"
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The newest published version, with the reason when there is none: the
|
|
70
|
+
* failure code from the one GET call site (network-unavailable,
|
|
71
|
+
* github-unavailable's npm sibling, not-found for an unpublished name), or
|
|
72
|
+
* `unpublished` when the registry answered without a version. `doctor` prints
|
|
73
|
+
* the code; `upgrade` only needs the version.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Promise<{ version: string|null, error: { code: string, message: string }|null }>}
|
|
76
|
+
*/
|
|
77
|
+
export async function registryLatest(name) {
|
|
62
78
|
try {
|
|
63
|
-
const meta = await getJson(
|
|
64
|
-
return meta?.version
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
const meta = await getJson(`${NPM_REGISTRY}/${encodeURIComponent(name)}/latest`)
|
|
80
|
+
return meta?.version
|
|
81
|
+
? { version: meta.version, error: null }
|
|
82
|
+
: { version: null, error: { code: "unpublished", message: "the registry answered without a version" } }
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return { version: null, error: { code: error?.code || "error", message: String(error?.message || error) } }
|
|
67
85
|
}
|
|
68
86
|
}
|
|
69
87
|
|