omakit 0.1.6 → 0.1.8
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 +31 -11
- package/package.json +1 -1
- package/skills/omarchy-plugin-submit/SKILL.md +34 -7
- package/skills/omarchy-plugin-validation-watch/SKILL.md +5 -1
- package/tests/parity/run.mjs +145 -113
- package/tools/marketplace/README.md +19 -5
- package/tools/marketplace/cli.mjs +36 -19
- package/tools/marketplace/doctor.mjs +68 -25
- package/tools/marketplace/form.mjs +36 -4
- package/tools/marketplace/registry.mjs +76 -7
- package/tools/marketplace/report.mjs +32 -7
- package/tools/marketplace/style.mjs +13 -3
- package/tools/marketplace/submit.mjs +68 -32
- package/tools/marketplace/usage.mjs +12 -8
- package/tools/marketplace/watch.mjs +21 -11
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
import { resolveSubject, SubjectError } from "../subject/resolve.mjs"
|
|
15
15
|
import { requirePin } from "./pin.mjs"
|
|
16
|
-
import { submissionContract, resolveCategory, resolveTags, tagSlug } from "./form.mjs"
|
|
17
|
-
import { idUniverse, checkIdentity, baselineFigures, figure, liveRegistry, registrySourceDetail, catalogPresentation, defaultPresentation } from "./registry.mjs"
|
|
16
|
+
import { submissionContract, newerCommitChoice, resolveCategory, resolveTags, tagSlug } from "./form.mjs"
|
|
17
|
+
import { idUniverse, checkIdentity, listingOf, baselineFigures, figure, liveRegistry, registrySourceDetail, catalogPresentation, defaultPresentation } from "./registry.mjs"
|
|
18
18
|
import { readTree } from "./tree.mjs"
|
|
19
19
|
import { inspectTree } from "./plugin.mjs"
|
|
20
20
|
import { findAgentControl, REMEDY as AGENT_CONTROL_REMEDY } from "./agent-control.mjs"
|
|
@@ -23,26 +23,16 @@ import { renderIssue, verifyAgainstOfficialParser } from "./issue.mjs"
|
|
|
23
23
|
import { defaultBranchHead } from "./github.mjs"
|
|
24
24
|
import { REFRESH_ACTION } from "./watch.mjs"
|
|
25
25
|
import { omakitCacheDir } from "./paths.mjs"
|
|
26
|
-
import { join } from "node:path"
|
|
27
|
-
import { pathToFileURL } from "node:url"
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* The marketplace's own name for the verification action that lists a newer
|
|
31
|
-
* commit of an already listed plugin, read from the pin rather than typed:
|
|
32
|
-
* a form option retyped here would drift by a word, and a person would be
|
|
33
|
-
* sent to choose something the form no longer offers.
|
|
34
|
-
*/
|
|
35
|
-
async function newerCommitAction(pinDir) {
|
|
36
|
-
const verification = await import(pathToFileURL(join(pinDir, "scripts/plugin-verification-request.mjs")).href)
|
|
37
|
-
return verification.upstreamUpdateVerificationAction
|
|
38
|
-
}
|
|
39
26
|
|
|
40
27
|
/**
|
|
41
28
|
* One arrow per cause, in this order, so a person fixes the thing that is
|
|
42
29
|
* actually wrong. Measured before this: an id listed by its own repository
|
|
43
30
|
* was told to "choose an unused plugin id outside the reserved namespace",
|
|
44
31
|
* which is the remedy for a different failure, and an author who followed
|
|
45
|
-
* it would have renamed a plugin the marketplace already lists.
|
|
32
|
+
* it would have renamed a plugin the marketplace already lists. The last
|
|
33
|
+
* arrow is for a listed repository whose manifest carries another id, or
|
|
34
|
+
* one with a second cause beside it; a plugin listed by its own repository
|
|
35
|
+
* with nothing else wrong is not a failure at all (see `listing` below).
|
|
46
36
|
*/
|
|
47
37
|
function identityRemedies(identity, universe, newerCommit) {
|
|
48
38
|
const remedies = []
|
|
@@ -66,7 +56,7 @@ export class SubmitError extends Error {
|
|
|
66
56
|
}
|
|
67
57
|
|
|
68
58
|
/**
|
|
69
|
-
* A check has
|
|
59
|
+
* A check has four verdicts. `pass` and `fail` are its own. `unknown` is
|
|
70
60
|
* for a check that could not run because one it depends on failed: it is
|
|
71
61
|
* rendered as a question, its detail names what it waited on, and it counts
|
|
72
62
|
* in neither `blocking` nor `advisory`, so a refusal lists root causes only.
|
|
@@ -75,19 +65,28 @@ export class SubmitError extends Error {
|
|
|
75
65
|
* checklist and official-parser each failed for want of a body nobody could
|
|
76
66
|
* render yet, and the closing refusal listed them with "not rendered" where
|
|
77
67
|
* a remedy goes.
|
|
68
|
+
*
|
|
69
|
+
* `skipped` is for a check that was not made because a flag said not to,
|
|
70
|
+
* independent of `waitedOn`: it never blocks, it counts in `skipped` and
|
|
71
|
+
* not in `unknown`, and its detail says which flag. Measured on 0.1.6:
|
|
72
|
+
* `--offline` handed `verdict: true` to the validation-commit check, so
|
|
73
|
+
* `--json` said `"verdict": "pass"` and the report drew `▁ ok` for a
|
|
74
|
+
* comparison that never happened, and an agent reading `checks` rather than
|
|
75
|
+
* the prose could tell the owner every check passed.
|
|
78
76
|
*/
|
|
79
77
|
function check(id, fields) {
|
|
80
78
|
const waitedOn = (fields.waitedOn || []).filter(Boolean)
|
|
79
|
+
const skipped = fields.skipped === true
|
|
81
80
|
return {
|
|
82
81
|
id,
|
|
83
82
|
source: fields.source,
|
|
84
83
|
why: fields.why,
|
|
85
84
|
severity: fields.severity || "blocking",
|
|
86
|
-
verdict: waitedOn.length ? "unknown" : fields.verdict ? "pass" : "fail",
|
|
87
|
-
detail: waitedOn.length ? `not checked: it needs ${waitedOn.join(" and ")} to pass first` : fields.detail || "",
|
|
85
|
+
verdict: skipped ? "skipped" : waitedOn.length ? "unknown" : fields.verdict ? "pass" : "fail",
|
|
86
|
+
detail: !skipped && waitedOn.length ? `not checked: it needs ${waitedOn.join(" and ")} to pass first` : fields.detail || "",
|
|
88
87
|
paths: fields.paths || [],
|
|
89
88
|
// One arrow, or one per cause in the order they should be read.
|
|
90
|
-
remedy: waitedOn.length ? null : Array.isArray(fields.remedy) ? (fields.remedy.length ? fields.remedy : null) : fields.remedy || null,
|
|
89
|
+
remedy: skipped || waitedOn.length ? null : Array.isArray(fields.remedy) ? (fields.remedy.length ? fields.remedy : null) : fields.remedy || null,
|
|
91
90
|
}
|
|
92
91
|
}
|
|
93
92
|
|
|
@@ -241,9 +240,30 @@ export async function submitPreflight(options) {
|
|
|
241
240
|
// --- identity -------------------------------------------------------------
|
|
242
241
|
|
|
243
242
|
const identity = checkIdentity(universe, { id: tree.pluginId, repositoryUrl: subject.repository.url })
|
|
244
|
-
const
|
|
243
|
+
const update = await newerCommitChoice({ pinDir })
|
|
244
|
+
const identityRemedy = identity.ok || identity.own ? null : identityRemedies(identity, universe, update.choice)
|
|
245
245
|
const listed = identity.problems.some((problem) => problem.code === "plugin-id-listed" || problem.code === "submission-repository-listed")
|
|
246
246
|
|
|
247
|
+
// The subject's own listing is the third outcome of a run, not a failed
|
|
248
|
+
// check. Measured on 0.1.6: `omakit submit` on the author's own listed
|
|
249
|
+
// plugin printed FAIL identity.available and REFUSED, closed with "Fix it,
|
|
250
|
+
// then run submit again", and the remedy under it said there was nothing to
|
|
251
|
+
// submit. A healthy state was drawn as a failure and the closing line
|
|
252
|
+
// contradicted the remedy. Now the check passes with what the marketplace
|
|
253
|
+
// records about the listing, and the run ends in LISTED.
|
|
254
|
+
const listing = identity.own
|
|
255
|
+
? (() => {
|
|
256
|
+
const record = listingOf(live, tree.pluginId) || { repository: subject.repository.url, id: tree.pluginId, addedAt: null, verificationCommit: null, verificationStatus: null, verificationCheckedAt: null }
|
|
257
|
+
return {
|
|
258
|
+
...record,
|
|
259
|
+
localCommit: subject.commit,
|
|
260
|
+
sameCommit: Boolean(record.verificationCommit) && record.verificationCommit === subject.commit.toLowerCase(),
|
|
261
|
+
source: live.source,
|
|
262
|
+
updateRoute: { form: update.name, choice: update.choice },
|
|
263
|
+
}
|
|
264
|
+
})()
|
|
265
|
+
: null
|
|
266
|
+
|
|
247
267
|
// The category and the tags are decided here, after the registry. Measured
|
|
248
268
|
// before this: a listed plugin was asked for both and then told there was
|
|
249
269
|
// nothing to submit. A listed plugin needs neither; an unlisted plugin with
|
|
@@ -268,11 +288,13 @@ export async function submitPreflight(options) {
|
|
|
268
288
|
}
|
|
269
289
|
checks.push(check("identity.available", {
|
|
270
290
|
source: "marketplace-pin",
|
|
271
|
-
why: `The marketplace refuses \`plugin-id-listed\`, \`plugin-id-retired\`, \`reserved-plugin-id\` and \`submission-repository-listed\`. Checked here against ${figure(universe.counts.listedIds)} listed ids, ${figure(universe.counts.retiredIds)} retired ids and ${figure(universe.counts.listedRepositories)} listed repositories from the registry and catalog at the commit the detail names, and the reserved namespace from the pinned catalog builder. The registry is read from the marketplace's current HEAD when the network is there because the pin's copy is stale within hours: registry.json changed in 4,201 of the marketplace's 4,293 commits in the 30 days to 2026-09-13, about 140 a day (docs/MEASUREMENTS.md M7). Code and the form are only ever read from the pin.`,
|
|
272
|
-
verdict: identity.ok,
|
|
291
|
+
why: `The marketplace refuses \`plugin-id-listed\`, \`plugin-id-retired\`, \`reserved-plugin-id\` and \`submission-repository-listed\`. Checked here against ${figure(universe.counts.listedIds)} listed ids, ${figure(universe.counts.retiredIds)} retired ids and ${figure(universe.counts.listedRepositories)} listed repositories from the registry and catalog at the commit the detail names, and the reserved namespace from the pinned catalog builder. The registry is read from the marketplace's current HEAD when the network is there because the pin's copy is stale within hours: registry.json changed in 4,201 of the marketplace's 4,293 commits in the 30 days to 2026-09-13, about 140 a day (docs/MEASUREMENTS.md M7). Code and the form are only ever read from the pin. A plugin listed by its own repository (owner and name, case-insensitively, a trailing .git ignored) is not refused: it is listed, and this check says since when and at which commit.`,
|
|
292
|
+
verdict: identity.ok || identity.own,
|
|
273
293
|
detail: `${identity.ok
|
|
274
294
|
? `id "${tree.pluginId}" is unused, outside the reserved ${universe.reservedPrefix}* namespace, and the repository is not listed`
|
|
275
|
-
:
|
|
295
|
+
: listing
|
|
296
|
+
? `listed by this repository since ${listing.addedAt || "an unrecorded date"}, verification commit ${listing.verificationCommit || "unrecorded"} (${listing.verificationStatus || "status unrecorded"}, checked ${listing.verificationCheckedAt || "at an unrecorded time"})`
|
|
297
|
+
: identity.problems.map((problem) => `${problem.code}: ${problem.detail}`).join("; ")}; ${registrySourceDetail(live)}`,
|
|
276
298
|
remedy: identityRemedy,
|
|
277
299
|
}))
|
|
278
300
|
|
|
@@ -295,7 +317,11 @@ export async function submitPreflight(options) {
|
|
|
295
317
|
remedy: "Give the plugin a name in manifest.json, or pass --name.",
|
|
296
318
|
}))
|
|
297
319
|
|
|
298
|
-
|
|
320
|
+
// On the subject's own listing no body is rendered, on purpose: the
|
|
321
|
+
// submission form is not the route. The five checks that exist only for the
|
|
322
|
+
// body are omitted rather than drawn as questions waiting on identity,
|
|
323
|
+
// because identity did not fail.
|
|
324
|
+
if (!listing) checks.push(check("submission.category", {
|
|
299
325
|
source: "marketplace-pin",
|
|
300
326
|
why: `Exactly one category from the form's controlled list; the marketplace refuses \`submission-category-invalid\` otherwise. The list (${contract.categories.length} options) is read from ${contract.formPath} at the pin.`,
|
|
301
327
|
verdict: category.ok,
|
|
@@ -304,7 +330,7 @@ export async function submitPreflight(options) {
|
|
|
304
330
|
waitedOn: mootWaitsOn,
|
|
305
331
|
}))
|
|
306
332
|
|
|
307
|
-
checks.push(check("submission.tags", {
|
|
333
|
+
if (!listing) checks.push(check("submission.tags", {
|
|
308
334
|
source: "marketplace-pin",
|
|
309
335
|
why: `1 to ${contract.maximumTags} tags from the form's controlled list; the marketplace refuses \`submission-tag-count-invalid\` and \`submission-tags-invalid\` otherwise. The list (${contract.tagLabels.length} options) and the maximum are read from the pin.`,
|
|
310
336
|
verdict: tags.ok,
|
|
@@ -323,7 +349,7 @@ export async function submitPreflight(options) {
|
|
|
323
349
|
].filter(Boolean)
|
|
324
350
|
let issue = null
|
|
325
351
|
let parsed = null
|
|
326
|
-
if (!bodyWaitsOn.length) {
|
|
352
|
+
if (!bodyWaitsOn.length && !listing) {
|
|
327
353
|
issue = renderIssue(contract, {
|
|
328
354
|
pluginName,
|
|
329
355
|
repositoryUrl: subject.repository.url,
|
|
@@ -343,7 +369,7 @@ export async function submitPreflight(options) {
|
|
|
343
369
|
remedy: subject.repository.url ? null : "Give the repository a github.com origin remote.",
|
|
344
370
|
}))
|
|
345
371
|
|
|
346
|
-
checks.push(check("submission.headings", {
|
|
372
|
+
if (!listing) checks.push(check("submission.headings", {
|
|
347
373
|
source: "marketplace-pin",
|
|
348
374
|
why: `The six form headings must appear in exact order: ${contract.headings.join(", ")}. 11 open submissions are malformed in the body and receive "The validation result could not be published to the issue. A maintainer must review the workflow.", which blames the maintainer for the author's mistake; one of them differs from a valid submission by the single word "Suggested" instead of "Suggest". The headings are rendered from the form at the pin, never typed.`,
|
|
349
375
|
verdict: Boolean(issue),
|
|
@@ -351,7 +377,7 @@ export async function submitPreflight(options) {
|
|
|
351
377
|
waitedOn: bodyWaitsOn,
|
|
352
378
|
}))
|
|
353
379
|
|
|
354
|
-
checks.push(check("submission.checklist", {
|
|
380
|
+
if (!listing) checks.push(check("submission.checklist", {
|
|
355
381
|
source: "marketplace-pin",
|
|
356
382
|
why: `All ${contract.checklist.length} checklist items must be present with their exact text and checked; the marketplace refuses \`submission-checklist-unconfirmed\` otherwise. The text is read from the form at the pin, character for character.`,
|
|
357
383
|
verdict: Boolean(issue),
|
|
@@ -359,7 +385,7 @@ export async function submitPreflight(options) {
|
|
|
359
385
|
waitedOn: bodyWaitsOn,
|
|
360
386
|
}))
|
|
361
387
|
|
|
362
|
-
checks.push(check("submission.official-parser", {
|
|
388
|
+
if (!listing) checks.push(check("submission.official-parser", {
|
|
363
389
|
source: "marketplace-pin",
|
|
364
390
|
why: "The strongest available proof that the body is well formed: the marketplace's own `parseCurrentSubmission` from the pinned commit is run over the rendered title and body. If it accepts them here it accepts them there, and 0 of the marketplace's heading, tag and checklist rules are duplicated in Omakit, so none of them can drift. This is the check that closes all 50 measured title-and-body failures at once: 39 on the title prefix plus 11 malformed bodies.",
|
|
365
391
|
verdict: Boolean(parsed?.ok),
|
|
@@ -388,7 +414,8 @@ export async function submitPreflight(options) {
|
|
|
388
414
|
source: "omakit",
|
|
389
415
|
why: "The marketplace validates the default-branch HEAD it resolves when the issue is opened or edited, not the commit checked here. 73% of the 464 submissions parked in the author's court have a HEAD ahead of their validated commit, so a preflight against a commit that is not the pushed HEAD describes a tree nobody will review. Not a marketplace rule; an Omakit refusal to report on the wrong tree.",
|
|
390
416
|
severity: options.offline ? "advisory" : "blocking",
|
|
391
|
-
|
|
417
|
+
skipped: options.offline === true,
|
|
418
|
+
verdict: validationMatches === true,
|
|
392
419
|
detail: options.offline
|
|
393
420
|
? `not checked (--offline). Local commit ${subject.commit}.`
|
|
394
421
|
: head
|
|
@@ -437,7 +464,13 @@ export async function submitPreflight(options) {
|
|
|
437
464
|
const blocking = checks.filter((entry) => entry.severity === "blocking" && entry.verdict === "fail")
|
|
438
465
|
const advisory = checks.filter((entry) => entry.severity === "advisory" && entry.verdict === "fail")
|
|
439
466
|
const unknown = checks.filter((entry) => entry.verdict === "unknown")
|
|
440
|
-
const
|
|
467
|
+
const skipped = checks.filter((entry) => entry.verdict === "skipped")
|
|
468
|
+
// Three outcomes. `refused`: a blocking check failed and no body exists.
|
|
469
|
+
// `listed`: nothing failed and the plugin is already listed by this
|
|
470
|
+
// repository, so there is no body either, and nothing is wrong. `ready`:
|
|
471
|
+
// the body. `ready` the boolean stays what it was, true for the third only.
|
|
472
|
+
const outcome = blocking.length ? "refused" : listing ? "listed" : "ready"
|
|
473
|
+
const ready = outcome === "ready"
|
|
441
474
|
|
|
442
475
|
return {
|
|
443
476
|
pin: {
|
|
@@ -478,10 +511,13 @@ export async function submitPreflight(options) {
|
|
|
478
511
|
offline: options.offline === true,
|
|
479
512
|
}),
|
|
480
513
|
checks,
|
|
514
|
+
outcome,
|
|
481
515
|
ready,
|
|
516
|
+
listing,
|
|
482
517
|
blocking: blocking.map((entry) => entry.id),
|
|
483
518
|
advisory: advisory.map((entry) => entry.id),
|
|
484
519
|
unknown: unknown.map((entry) => entry.id),
|
|
520
|
+
skipped: skipped.map((entry) => entry.id),
|
|
485
521
|
issue: ready ? issue : null,
|
|
486
522
|
baseline: preflight.invoked
|
|
487
523
|
? {
|
|
@@ -45,10 +45,13 @@ export const COMMANDS = Object.freeze([
|
|
|
45
45
|
lines: [
|
|
46
46
|
"Every check that is knowable before submitting, the resolved commit, and",
|
|
47
47
|
"the exact issue title and body. Prints them. Never posts anything.",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
48
|
+
"Three outcomes: READY (exit 0, the body), REFUSED (exit 1, no body), and",
|
|
49
|
+
"LISTED (exit 0): the plugin is already listed by its own repository, so",
|
|
50
|
+
"the submission form is not the route and nothing is asked. An id taken",
|
|
51
|
+
"by another repository is refused. An unlisted plugin without a category",
|
|
52
|
+
"and tags is asked at a terminal, with the form's lists numbered; in a",
|
|
53
|
+
"pipe or with --json that is a usage error, exit 2. A READY or REFUSED",
|
|
54
|
+
"report ends with the command line that repeats the run unasked.",
|
|
52
55
|
],
|
|
53
56
|
},
|
|
54
57
|
{
|
|
@@ -115,10 +118,11 @@ export const TARGET_NOTE = "<target> is a local Git repository path, or <https u
|
|
|
115
118
|
*/
|
|
116
119
|
export const AUTHENTICATION = Object.freeze([
|
|
117
120
|
"Read-only, and optional. omakit uses your `gh` login if you have one, and",
|
|
118
|
-
"otherwise goes unauthenticated. `verify` needs no network
|
|
119
|
-
"
|
|
120
|
-
`
|
|
121
|
-
|
|
121
|
+
"otherwise goes unauthenticated. `verify` needs no network once the pin",
|
|
122
|
+
"exists, except to fetch a reviewer-mode <https url>@<sha> target, once;",
|
|
123
|
+
"`submit` reads two things online and `--offline` turns both off; `watch` and",
|
|
124
|
+
`\`parity\` are capped at ${UNAUTHENTICATED_LIMIT} requests an hour without a login. omakit never`,
|
|
125
|
+
"writes a credential anywhere.",
|
|
122
126
|
])
|
|
123
127
|
|
|
124
128
|
/**
|
|
@@ -106,12 +106,17 @@ export function validationCommentCommit(comments) {
|
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
|
-
* @param {{ repoRoot: string, issueUrl: string
|
|
109
|
+
* @param {{ repoRoot: string, issueUrl: string, onPhase?: (name: string) => void,
|
|
110
|
+
* github?: { issue?: typeof issue, issueComments?: typeof issueComments, defaultBranchHead?: typeof defaultBranchHead } }} options
|
|
111
|
+
* `github` is injectable for tests, so the whole path from an issue body to
|
|
112
|
+
* a verdict can be run on data that never left the machine; the default is
|
|
113
|
+
* the read-only GitHub access in `github.mjs`.
|
|
110
114
|
*/
|
|
111
|
-
export async function validationWatch({ repoRoot, issueUrl, onPhase }) {
|
|
115
|
+
export async function validationWatch({ repoRoot, issueUrl, onPhase, github = {} }) {
|
|
112
116
|
// Optional: told the name of the step about to run, so a terminal can say
|
|
113
117
|
// what is happening while the network answers. Never affects the result.
|
|
114
118
|
const phase = onPhase || (() => {})
|
|
119
|
+
const read = { issue, issueComments, defaultBranchHead, ...github }
|
|
115
120
|
const { dir: pinDir } = requirePin(repoRoot)
|
|
116
121
|
const target = parseIssueUrl(issueUrl)
|
|
117
122
|
if (`${target.owner}/${target.repository}`.toLowerCase() !== MARKETPLACE_SLUG) {
|
|
@@ -124,14 +129,14 @@ export async function validationWatch({ repoRoot, issueUrl, onPhase }) {
|
|
|
124
129
|
const record = await loadRecord(pinDir)
|
|
125
130
|
|
|
126
131
|
phase(`reading issue #${target.number}`)
|
|
127
|
-
const subject = await issue(target.owner, target.repository, target.number)
|
|
132
|
+
const subject = await read.issue(target.owner, target.repository, target.number)
|
|
128
133
|
phase(`reading the comments on issue #${target.number}`)
|
|
129
|
-
const comments = await issueComments(target.owner, target.repository, target.number)
|
|
134
|
+
const comments = await read.issueComments(target.owner, target.repository, target.number)
|
|
130
135
|
|
|
131
|
-
const
|
|
132
|
-
const repositoryUrl =
|
|
133
|
-
const repositoryError =
|
|
134
|
-
const issueKind =
|
|
136
|
+
const repository = await repositoryFor(pinDir, subject)
|
|
137
|
+
const repositoryUrl = repository.url
|
|
138
|
+
const repositoryError = repository.error
|
|
139
|
+
const issueKind = repository.kind
|
|
135
140
|
|
|
136
141
|
let validated = null
|
|
137
142
|
let baselineError = null
|
|
@@ -164,7 +169,7 @@ export async function validationWatch({ repoRoot, issueUrl, onPhase }) {
|
|
|
164
169
|
if (repositoryUrl) {
|
|
165
170
|
phase("reading the plugin repository's default-branch HEAD")
|
|
166
171
|
try {
|
|
167
|
-
head = await defaultBranchHead(repositoryUrl)
|
|
172
|
+
head = await read.defaultBranchHead(repositoryUrl)
|
|
168
173
|
} catch (error) {
|
|
169
174
|
headError = { code: error.code || "head-unreadable", message: error.message }
|
|
170
175
|
}
|
|
@@ -202,11 +207,16 @@ export async function validationWatch({ repoRoot, issueUrl, onPhase }) {
|
|
|
202
207
|
baselineError,
|
|
203
208
|
head,
|
|
204
209
|
headError,
|
|
205
|
-
verdict: validationVerdict({ comparable, stale, validated, head, fallback, baselineError, headError, pushedAfterReview }),
|
|
210
|
+
verdict: validationVerdict({ comparable, stale, validated, head, fallback, baselineError, headError, pushedAfterReview, repositoryUrl }),
|
|
206
211
|
}
|
|
207
212
|
}
|
|
208
213
|
|
|
209
|
-
|
|
214
|
+
// `repositoryUrl` has no default on purpose. Measured on 0.1.6: the command
|
|
215
|
+
// computed it and then did not pass it, and the parameter defaulted to the
|
|
216
|
+
// truthy string "unknown", so an issue whose body named no repository was
|
|
217
|
+
// reported as a HEAD that could not be read, and the branch below that names
|
|
218
|
+
// the real cause was reachable from the unit test alone.
|
|
219
|
+
export function validationVerdict({ comparable, stale, validated, head, fallback, baselineError, headError, pushedAfterReview, repositoryUrl }) {
|
|
210
220
|
if (baselineError) {
|
|
211
221
|
return {
|
|
212
222
|
state: "unknown",
|