omakit 0.1.2 → 0.1.4
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 +39 -19
- package/package.json +1 -1
- package/skills/omarchy-plugin-submit/SKILL.md +17 -3
- package/skills/omarchy-plugin-validation-watch/SKILL.md +5 -0
- package/tools/marketplace/README.md +9 -6
- package/tools/marketplace/cli.mjs +29 -5
- package/tools/marketplace/doctor.mjs +98 -19
- package/tools/marketplace/path-hint.mjs +53 -0
- package/tools/marketplace/registry.mjs +211 -21
- package/tools/marketplace/report.mjs +13 -6
- package/tools/marketplace/setup.mjs +9 -10
- package/tools/marketplace/submit.mjs +103 -14
- package/tools/marketplace/upgrade.mjs +123 -13
- package/tools/marketplace/usage.mjs +7 -6
|
@@ -16,13 +16,16 @@
|
|
|
16
16
|
// against.
|
|
17
17
|
//
|
|
18
18
|
// It is not a self-updater in the sense this repository warns other people
|
|
19
|
-
// about. It does not fetch and execute arbitrary code: it
|
|
20
|
-
//
|
|
21
|
-
// it
|
|
19
|
+
// about. It does not fetch and execute arbitrary code: it hands the update to
|
|
20
|
+
// the installer that put the tool here. A Git checkout is fast-forwarded from
|
|
21
|
+
// the remote it was cloned from; an npm install is reinstalled by the `npm` on
|
|
22
|
+
// PATH, with frozen arguments, at the exact version the registry named, and
|
|
23
|
+
// only when that version is newer. It refuses if any of that is not true.
|
|
22
24
|
|
|
23
25
|
import { execFileSync } from "node:child_process"
|
|
24
|
-
import { existsSync } from "node:fs"
|
|
26
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
25
27
|
import { join, resolve, sep } from "node:path"
|
|
28
|
+
import { getJson } from "./github.mjs"
|
|
26
29
|
import { progress } from "./progress.mjs"
|
|
27
30
|
import { action, colourEnabled, GUTTER, mark, styler, verdict, wrap } from "./style.mjs"
|
|
28
31
|
|
|
@@ -40,11 +43,64 @@ export function installKind(repoRoot) {
|
|
|
40
43
|
export function upgradeCommand(repoRoot, name = "omakit") {
|
|
41
44
|
return {
|
|
42
45
|
git: "omakit upgrade",
|
|
43
|
-
npm:
|
|
46
|
+
npm: "omakit upgrade",
|
|
44
47
|
distro: `sudo pacman -Syu ${name}`,
|
|
45
48
|
}[installKind(repoRoot)]
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The frozen shape of the one `npm` invocation this tool makes. The package
|
|
53
|
+
* spec appended to it is `<name>@<version>` with the version the registry just
|
|
54
|
+
* reported, never `latest`, so what is printed is what is run. No sudo, no
|
|
55
|
+
* script execution (`--ignore-scripts`), and tests/unit/read-only.test.mjs
|
|
56
|
+
* asserts that npm is spawned nowhere else and with nothing else.
|
|
57
|
+
*/
|
|
58
|
+
export const NPM_UPGRADE_ARGS = Object.freeze(["install", "--global", "--ignore-scripts", "--no-fund", "--no-audit"])
|
|
59
|
+
|
|
60
|
+
/** The newest published version, or null when the registry did not answer. */
|
|
61
|
+
export async function latestOnRegistry(name) {
|
|
62
|
+
try {
|
|
63
|
+
const meta = await getJson(`https://registry.npmjs.org/${encodeURIComponent(name)}/latest`)
|
|
64
|
+
return meta?.version || null
|
|
65
|
+
} catch {
|
|
66
|
+
return null
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function installedVersion(repoRoot) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(readFileSync(join(repoRoot, "package.json"), "utf8")).version || null
|
|
73
|
+
} catch {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Where the `npm` on PATH installs global packages, or null when there is no npm. */
|
|
79
|
+
function npmGlobalRoot() {
|
|
80
|
+
try {
|
|
81
|
+
return resolve(execFileSync("npm", ["root", "--global"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim())
|
|
82
|
+
} catch {
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The frozen shape of the one other question this tool asks npm: where its
|
|
89
|
+
* global prefix is, whose `bin` is where `npm install --global` put the
|
|
90
|
+
* `omakit` command. Read-only; tests/unit/read-only.test.mjs holds npm to
|
|
91
|
+
* this shape, `root --global` and the install above, and to this file.
|
|
92
|
+
*/
|
|
93
|
+
export const NPM_PREFIX_ARGS = Object.freeze(["prefix", "--global"])
|
|
94
|
+
|
|
95
|
+
/** The `npm` on PATH's global prefix, or null when there is no npm. */
|
|
96
|
+
export function npmGlobalPrefix() {
|
|
97
|
+
try {
|
|
98
|
+
return resolve(execFileSync("npm", [...NPM_PREFIX_ARGS], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim())
|
|
99
|
+
} catch {
|
|
100
|
+
return null
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
48
104
|
function git(dir, args) {
|
|
49
105
|
return execFileSync("git", ["-C", dir, ...args], {
|
|
50
106
|
encoding: "utf8",
|
|
@@ -70,7 +126,7 @@ export function isExpectedRemote(url, expected = REPOSITORY) {
|
|
|
70
126
|
* a way to point the command at somebody else's repository, because nothing
|
|
71
127
|
* on the command line reaches it.
|
|
72
128
|
*/
|
|
73
|
-
export async function upgrade({ repoRoot, stream = process.stdout, dryRun = false, expectedRemote = REPOSITORY }) {
|
|
129
|
+
export async function upgrade({ repoRoot, stream = process.stdout, dryRun = false, expectedRemote = REPOSITORY, latest = latestOnRegistry, npmRoot = npmGlobalRoot, name = "omakit" }) {
|
|
74
130
|
const c = styler(colourEnabled(stream))
|
|
75
131
|
const out = (line = "") => stream.write(`${line}\n`)
|
|
76
132
|
const lines = (list) => { for (const line of list) out(line) }
|
|
@@ -83,13 +139,10 @@ export async function upgrade({ repoRoot, stream = process.stdout, dryRun = fals
|
|
|
83
139
|
const ok = (text) => out(`${mark("pass", c)}${wrap(text, { indent: GUTTER }, c).join("\n").trimStart()}`)
|
|
84
140
|
|
|
85
141
|
if (!existsSync(join(repoRoot, ".git"))) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
: "this is an npm package install, so omakit leaves upgrades to npm.",
|
|
91
|
-
upgradeCommand(repoRoot),
|
|
92
|
-
)
|
|
142
|
+
if (installKind(repoRoot) === "distro") {
|
|
143
|
+
return refuse("this is a distro package under /usr, so omakit leaves upgrades to the package manager.", upgradeCommand(repoRoot))
|
|
144
|
+
}
|
|
145
|
+
return upgradeNpm({ repoRoot, stream, dryRun, latest, npmRoot, name, refuse, note, ok, out, lines, c })
|
|
93
146
|
}
|
|
94
147
|
|
|
95
148
|
let remote
|
|
@@ -177,3 +230,60 @@ export async function upgrade({ repoRoot, stream = process.stdout, dryRun = fals
|
|
|
177
230
|
lines(wrap("The marketplace pin did not move: this updated the tool, not the commit its rules are read from. `omakit doctor` says whether that pin is behind, and docs/UPSTREAM_CONTRACT.md says what moving it involves.", {}, c))
|
|
178
231
|
return { ok: true, changed: true, from: before, to: after, commits: log.length }
|
|
179
232
|
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The npm route. `latest` and `npmRoot` are injectable for the tests only, the
|
|
236
|
+
* way `expectedRemote` is: nothing on the command line reaches them.
|
|
237
|
+
*/
|
|
238
|
+
async function upgradeNpm({ repoRoot, stream, dryRun, latest, npmRoot, name, refuse, note, ok, out, lines, c }) {
|
|
239
|
+
const root = resolve(repoRoot)
|
|
240
|
+
const globalRoot = npmRoot()
|
|
241
|
+
if (!globalRoot) {
|
|
242
|
+
return refuse("this is an npm package install, but no `npm` is on PATH to update it with.", `npm install --global ${name}@latest`)
|
|
243
|
+
}
|
|
244
|
+
if (root !== join(globalRoot, name)) {
|
|
245
|
+
return refuse(
|
|
246
|
+
`this omakit is installed at ${root}, but the npm on PATH installs global packages under ${globalRoot}. Updating with a different npm would leave this one where it is.`,
|
|
247
|
+
`npm install --global ${name}@latest`,
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
const current = installedVersion(root)
|
|
251
|
+
const spinner = progress({ stream: stream === process.stdout ? process.stderr : stream })
|
|
252
|
+
spinner.phase("asking the npm registry for the newest published version")
|
|
253
|
+
const newest = await latest(name)
|
|
254
|
+
spinner.done()
|
|
255
|
+
if (!newest) {
|
|
256
|
+
return refuse("the npm registry did not answer, so there is nothing to compare against.", "Connect to the network, then run `omakit upgrade` again.")
|
|
257
|
+
}
|
|
258
|
+
if (newest === current) {
|
|
259
|
+
ok(`already current at ${current}, the newest published version`)
|
|
260
|
+
out()
|
|
261
|
+
lines(wrap("The marketplace pin is a separate thing and is never touched here. `omakit doctor` says whether it is behind.", {}, c))
|
|
262
|
+
return { ok: true, changed: false, version: current }
|
|
263
|
+
}
|
|
264
|
+
const spec = `${name}@${newest}`
|
|
265
|
+
if (dryRun) {
|
|
266
|
+
note(`${newest} is published, this is ${current}; not applied (--dry-run)`)
|
|
267
|
+
out(`${" ".repeat(GUTTER)}${c("prose", `npm ${[...NPM_UPGRADE_ARGS, spec].join(" ")}`)}`)
|
|
268
|
+
out()
|
|
269
|
+
lines(action("omakit upgrade", c, { indent: 0 }))
|
|
270
|
+
return { ok: true, changed: false, version: current, available: newest }
|
|
271
|
+
}
|
|
272
|
+
spinner.phase(`npm install --global ${spec}`)
|
|
273
|
+
try {
|
|
274
|
+
execFileSync("npm", [...NPM_UPGRADE_ARGS, spec], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
|
|
275
|
+
} catch (error) {
|
|
276
|
+
spinner.done()
|
|
277
|
+
const reason = String(error?.stderr || "").trim().split("\n").filter((line) => /^npm (?:error|ERR!)/.test(line)).pop() || "npm install failed"
|
|
278
|
+
return refuse(`npm could not install ${spec}: ${reason.replace(/^npm (?:error|ERR!)\s*/, "")}`, `npm ${[...NPM_UPGRADE_ARGS, spec].join(" ")}`)
|
|
279
|
+
}
|
|
280
|
+
spinner.done()
|
|
281
|
+
const after = installedVersion(root)
|
|
282
|
+
if (after !== newest) {
|
|
283
|
+
return refuse(`npm finished, but ${root} reports ${after || "no version"} rather than ${newest}.`, `npm ${[...NPM_UPGRADE_ARGS, spec].join(" ")}`)
|
|
284
|
+
}
|
|
285
|
+
ok(`${current} to ${after}, through the npm that installed it`)
|
|
286
|
+
out()
|
|
287
|
+
lines(wrap("The marketplace pin did not move: this updated the tool, not the commit its rules are read from. `omakit doctor` says whether that pin is behind, and docs/UPSTREAM_CONTRACT.md says what moving it involves.", {}, c))
|
|
288
|
+
return { ok: true, changed: true, from: current, to: after }
|
|
289
|
+
}
|
|
@@ -72,9 +72,9 @@ export const COMMANDS = Object.freeze([
|
|
|
72
72
|
{
|
|
73
73
|
signature: "omakit upgrade [--dry-run]",
|
|
74
74
|
lines: [
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"the marketplace pin.",
|
|
75
|
+
"Update omakit through the installer that made it: npm, at the exact",
|
|
76
|
+
"version the registry names, or a fast-forward of a clone. Refuses",
|
|
77
|
+
"anything else, and never moves the marketplace pin.",
|
|
78
78
|
],
|
|
79
79
|
},
|
|
80
80
|
{
|
|
@@ -110,9 +110,10 @@ export const TARGET_NOTE = "<target> is a local Git repository path, or <https u
|
|
|
110
110
|
*/
|
|
111
111
|
export const AUTHENTICATION = Object.freeze([
|
|
112
112
|
"Read-only, and optional. omakit uses your `gh` login if you have one, and",
|
|
113
|
-
"otherwise goes unauthenticated. `
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
"otherwise goes unauthenticated. `verify` needs no network; `submit` reads",
|
|
114
|
+
"two things online and `--offline` turns both off; `watch` and `parity` are",
|
|
115
|
+
`capped at ${UNAUTHENTICATED_LIMIT} requests an hour without a login. omakit never writes a`,
|
|
116
|
+
"credential anywhere.",
|
|
116
117
|
])
|
|
117
118
|
|
|
118
119
|
/**
|