@myelinbridge/cli 0.12.0 → 0.12.2
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 +9 -6
- package/bin/myelin.js +53 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,12 +45,15 @@ npx @myelinbridge/cli push ./run_042 --dataset onco1-wes --submit
|
|
|
45
45
|
|
|
46
46
|
- **Your client's own fields show up, read-only (0.12.0).** Clients can define
|
|
47
47
|
their own metadata fields on datasets (a sponsor study code, a work order, a
|
|
48
|
-
therapeutic area). `contract` prints them under CLIENT FIELDS
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`metadata
|
|
48
|
+
therapeutic area). `contract` prints them under CLIENT FIELDS — including a
|
|
49
|
+
required field nobody has filled in yet, shown as *"— required, not filled
|
|
50
|
+
in yet"* (0.12.2) — and `check` tells you the same thing via preflight;
|
|
51
|
+
informational only: their team fills them in Myelin, there is nothing to
|
|
52
|
+
change in your delivery, and metadata never moves the exit code. In
|
|
53
|
+
`--json`, datasets carry a `metadata` object
|
|
54
|
+
(`{key: {label, type, value, required?}}` — `required: true` and
|
|
55
|
+
`value: null` mark a required field with no value yet) and preflight
|
|
56
|
+
carries `metadata.missing_required`.
|
|
54
57
|
|
|
55
58
|
- **A missing path in `roles set` is refused, not guessed (0.11.1).** Flags are
|
|
56
59
|
not positional arguments: `roles set samplesheet --dataset onco1-wes` (path
|
package/bin/myelin.js
CHANGED
|
@@ -23,6 +23,23 @@
|
|
|
23
23
|
// client has not filled in yet — informational ONLY: metadata never moves the
|
|
24
24
|
// exit code (the client fills these in Myelin; nothing for a partner to fix,
|
|
25
25
|
// and a pipeline must never halt on it).
|
|
26
|
+
//
|
|
27
|
+
// 0.12.1 — three display-only fixes, no behavior change. `check`'s
|
|
28
|
+
// human-readable output now labels every rule by `name` instead of
|
|
29
|
+
// `check_type`: the type is the underlying evaluator (many-to-one with
|
|
30
|
+
// rules), so two rules sharing one used to print identical, contradictory
|
|
31
|
+
// lines. `check`/`push` now refuse an omitted <dir> the same way `roles set`
|
|
32
|
+
// already refused an omitted <path> (0.11.1) — before, the following
|
|
33
|
+
// --dataset flag was silently swallowed as the directory and the command
|
|
34
|
+
// died with a raw ENOENT instead of its usage line. `resolve` no longer
|
|
35
|
+
// doubles a project's code ("AML-CART-3B — AML-CART-3B — Phase 3…") when the
|
|
36
|
+
// title already starts with it.
|
|
37
|
+
//
|
|
38
|
+
// 0.12.2 — a required field with no value yet used to be invisible in
|
|
39
|
+
// `contract` (it only ever rendered keys the dataset already had a value
|
|
40
|
+
// for), even though `check` correctly warned about it via preflight. The API
|
|
41
|
+
// now includes required-but-empty fields in `metadata` too
|
|
42
|
+
// (`required: true`, `value: null`), and `contract` renders them.
|
|
26
43
|
|
|
27
44
|
import { readdirSync, statSync, createReadStream, readFileSync } from 'node:fs'
|
|
28
45
|
import { resolve, join, relative, sep, basename } from 'node:path'
|
|
@@ -582,7 +599,17 @@ async function cmdContract() {
|
|
|
582
599
|
const metaKeys = Object.keys(meta)
|
|
583
600
|
if (metaKeys.length > 0) {
|
|
584
601
|
out('CLIENT FIELDS — how your client describes this dataset (read-only)')
|
|
585
|
-
for (const k of metaKeys)
|
|
602
|
+
for (const k of metaKeys) {
|
|
603
|
+
const f = meta[k]
|
|
604
|
+
// A required field with no value yet is invisible unless we say so
|
|
605
|
+
// here too — `myelin check` already warns about it via preflight, and
|
|
606
|
+
// the two must agree on what's outstanding (0.12.0, cli-api-18).
|
|
607
|
+
out(
|
|
608
|
+
f.required
|
|
609
|
+
? ` · ${f.label} — required, not filled in yet`
|
|
610
|
+
: ` · ${f.label}: ${fmtFieldValue(f.value)}`,
|
|
611
|
+
)
|
|
612
|
+
}
|
|
586
613
|
out('')
|
|
587
614
|
}
|
|
588
615
|
out(`Run "myelin check <dir> --dataset ${dsRef}" to test ${s.checkable_before_upload} of these locally.`)
|
|
@@ -591,7 +618,10 @@ async function cmdContract() {
|
|
|
591
618
|
async function cmdCheck() {
|
|
592
619
|
const dir = args[1]
|
|
593
620
|
const dsRef = opt('dataset')
|
|
594
|
-
|
|
621
|
+
// Same trap as `roles set` (see the comment there): an omitted <dir> lets
|
|
622
|
+
// the following --dataset flag slide into its place, which used to reach
|
|
623
|
+
// resolve()/walkDir() and fail with a raw ENOENT instead of usage.
|
|
624
|
+
if (!dir || dir.startsWith('--') || !dsRef) die('Usage: myelin check <dir> --dataset <slug|id>')
|
|
595
625
|
const root = resolve(dir)
|
|
596
626
|
const files = walkDir(root)
|
|
597
627
|
if (files.length === 0) die(`No files under ${root}`)
|
|
@@ -608,10 +638,15 @@ async function cmdCheck() {
|
|
|
608
638
|
if (!JSON_MODE) {
|
|
609
639
|
// One width across all three lists so the verdicts line up, and the hint
|
|
610
640
|
// lines can still be interleaved under the check they belong to.
|
|
641
|
+
// Labelled by `name`, not `check_type` — the type is the underlying
|
|
642
|
+
// evaluator (e.g. "manifest_present") and is many-to-one with rules, so
|
|
643
|
+
// two rules of the same type used to print identical, contradictory
|
|
644
|
+
// lines. `name` is unique per rule and is what the manual list below
|
|
645
|
+
// already used.
|
|
611
646
|
const w = Math.max(
|
|
612
647
|
0,
|
|
613
|
-
...j.evaluated.map((c) => c.
|
|
614
|
-
...j.deferred.map((d) => d.
|
|
648
|
+
...j.evaluated.map((c) => c.name.length),
|
|
649
|
+
...j.deferred.map((d) => d.name.length),
|
|
615
650
|
...j.manual.map((m) => m.name.length),
|
|
616
651
|
)
|
|
617
652
|
for (const c of j.evaluated) {
|
|
@@ -620,7 +655,7 @@ async function cmdCheck() {
|
|
|
620
655
|
: c.severity === 'blocking' && c.verdict === 'not_evaluated' ? ' — BLOCKING, and nobody looked'
|
|
621
656
|
: c.severity === 'must_acknowledge' && c.verdict === 'failed' ? ' — your reviewer confirms this'
|
|
622
657
|
: ''
|
|
623
|
-
out(`${verdictMark(c.verdict)} ${c.
|
|
658
|
+
out(`${verdictMark(c.verdict)} ${c.name.padEnd(w)} ${verdictLabel(c.verdict)}${gate}`)
|
|
624
659
|
// Why we could not look, in the partner's terms. `abstained: 'rule'` is
|
|
625
660
|
// the one that matters most: it says the fault is in the client's rule,
|
|
626
661
|
// not in this delivery, and without the sentence the partner spends a
|
|
@@ -644,7 +679,7 @@ async function cmdCheck() {
|
|
|
644
679
|
(c.verdict === 'not_evaluated' && c.details?.abstained !== 'rule')
|
|
645
680
|
if (fixable && c.remediation) out(` hint: ${c.remediation}`)
|
|
646
681
|
}
|
|
647
|
-
for (const d of j.deferred) out(`… ${d.
|
|
682
|
+
for (const d of j.deferred) out(`… ${d.name.padEnd(w)} ${d.reason}`)
|
|
648
683
|
for (const m of j.manual) out(`○ ${m.name.padEnd(w)} ${m.reason}`)
|
|
649
684
|
}
|
|
650
685
|
|
|
@@ -716,7 +751,10 @@ async function cmdCheck() {
|
|
|
716
751
|
async function cmdPush() {
|
|
717
752
|
const dir = args[1]
|
|
718
753
|
const dsRef = opt('dataset')
|
|
719
|
-
|
|
754
|
+
// Same trap as `roles set` (see the comment there): an omitted <dir> lets
|
|
755
|
+
// the following --dataset flag slide into its place, which used to reach
|
|
756
|
+
// resolve()/walkDir() and fail with a raw ENOENT instead of usage.
|
|
757
|
+
if (!dir || dir.startsWith('--') || !dsRef) die('Usage: myelin push <dir> --dataset <slug|id> [--submit] [--replace]')
|
|
720
758
|
const root = resolve(dir)
|
|
721
759
|
const files = walkDir(root)
|
|
722
760
|
if (files.length === 0) die(`No files under ${root}`)
|
|
@@ -1077,7 +1115,14 @@ async function cmdResolve() {
|
|
|
1077
1115
|
const j = expectOk(await api('GET', `/deliveries/resolve?path=${encodeURIComponent(path)}`), 'resolve')
|
|
1078
1116
|
emit(j)
|
|
1079
1117
|
out(`${j.resolved} · ${j.query}`)
|
|
1080
|
-
|
|
1118
|
+
// Some seeded/client titles already embed the project code
|
|
1119
|
+
// ("AML-CART-3B — Phase 3 CAR-T…") — only prepend it when the title
|
|
1120
|
+
// doesn't already start with it, or the code prints twice.
|
|
1121
|
+
if (j.project) {
|
|
1122
|
+
const title =
|
|
1123
|
+
j.project.title && j.project.title.startsWith(j.project.code) ? j.project.title : `${j.project.code} — ${j.project.title}`
|
|
1124
|
+
out(` project ${title}`)
|
|
1125
|
+
}
|
|
1081
1126
|
if (j.dataset) out(` dataset ${j.dataset.name}${j.dataset.label ? ` (${j.dataset.label})` : ''}`)
|
|
1082
1127
|
if (j.delivery) {
|
|
1083
1128
|
out(` batch ${j.delivery.batch_name ?? j.delivery.batch_id} · #${j.delivery.sequence_number ?? '?'}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myelinbridge/cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Myelin Partner Ingestion CLI — push R&D data deliveries from a pipeline: preflight against the client's quality rules, resumable upload, submit, track review outcomes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|